初始化项目
This commit is contained in:
0
src/app/api/auth/loayout.tsx
Normal file
0
src/app/api/auth/loayout.tsx
Normal file
84
src/app/api/auth/login/route.ts
Normal file
84
src/app/api/auth/login/route.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { prisma } from '@/lib/prisma' // 使用统一的prisma实例
|
||||
import { compare } from 'bcryptjs'
|
||||
import { z } from 'zod'
|
||||
|
||||
const loginSchema = z.object({
|
||||
phone: z.string()
|
||||
.min(11, '手机号必须是11位')
|
||||
.max(11, '手机号必须是11位')
|
||||
.regex(/^1[3-9]\d{9}$/, '请输入有效的手机号'),
|
||||
password: z.string().min(6, '密码至少需要6个字符'),
|
||||
})
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { phone, password } = loginSchema.parse(body)
|
||||
|
||||
console.log('登录尝试:', phone) // 添加日志
|
||||
|
||||
// 查找用户 - 使用正确的查询方式
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
phone: phone.trim() // 去除空格
|
||||
},
|
||||
})
|
||||
|
||||
console.log('找到用户:', user) // 添加日志
|
||||
|
||||
if (!user) {
|
||||
console.log('用户不存在:', phone)
|
||||
return NextResponse.json(
|
||||
{ success: false, error: '用户不存在' },
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
const passwordMatch = await compare(password, user.password || '')
|
||||
console.log('密码验证结果:', passwordMatch)
|
||||
|
||||
if (!passwordMatch) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '密码错误'
|
||||
}, { status: 401 })
|
||||
}
|
||||
|
||||
// 创建会话
|
||||
const sessionToken = crypto.randomUUID()
|
||||
await prisma.session.create({
|
||||
data: {
|
||||
id: sessionToken,
|
||||
userId: user.id,
|
||||
expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
|
||||
}
|
||||
})
|
||||
|
||||
// 设置cookie
|
||||
const response = NextResponse.json({
|
||||
success: true,
|
||||
user: {
|
||||
id: user.id,
|
||||
phone: user.phone,
|
||||
name: user.name
|
||||
}
|
||||
})
|
||||
|
||||
response.cookies.set('session', sessionToken, {
|
||||
httpOnly: true,
|
||||
// secure: process.env.NODE_ENV === 'production',
|
||||
maxAge: 60 * 60 * 24 * 7
|
||||
})
|
||||
|
||||
return response
|
||||
|
||||
} catch (error) {
|
||||
console.error('登录错误:', error)
|
||||
return NextResponse.json(
|
||||
{ success: false, error: '服务器错误,请稍后重试' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
38
src/app/api/auth/logout/route.ts
Normal file
38
src/app/api/auth/logout/route.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { cookies } from 'next/headers'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function POST() {
|
||||
try {
|
||||
const cookieStore = await cookies()
|
||||
const sessionToken = cookieStore.get('session')?.value
|
||||
|
||||
// 删除数据库中的session(如果存在)
|
||||
if (sessionToken) {
|
||||
await prisma.session.deleteMany({
|
||||
where: { id: sessionToken }
|
||||
}).catch(() => {
|
||||
// 忽略删除错误,确保cookie被清除
|
||||
})
|
||||
}
|
||||
|
||||
// 清除cookie
|
||||
const response = NextResponse.json({ success: true })
|
||||
response.cookies.set('session', '', {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
maxAge: 0, // 立即过期
|
||||
path: '/',
|
||||
})
|
||||
|
||||
return response
|
||||
|
||||
} catch (error) {
|
||||
console.error('退出错误:', error)
|
||||
return NextResponse.json(
|
||||
{ success: false, error: '退出失败' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
174
src/app/api/stats/route.ts
Normal file
174
src/app/api/stats/route.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
// 处理 BigInt 序列化
|
||||
function safeSerialize(data: unknown) {
|
||||
return JSON.parse(JSON.stringify(data, (key, value) =>
|
||||
typeof value === 'bigint' ? value.toString() : value
|
||||
))
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const reportType = searchParams.get('type')
|
||||
|
||||
switch (reportType) {
|
||||
case 'gateway_info':
|
||||
return await getGatewayInfo()
|
||||
case 'gateway_config':
|
||||
return await getGatewayConfig(request)
|
||||
case 'city_config_count':
|
||||
return await getCityConfigCount()
|
||||
case 'city_node_count':
|
||||
return await getCityNodeCount()
|
||||
case 'allocation_status':
|
||||
return await getAllocationStatus()
|
||||
case 'edge_nodes':
|
||||
return await getEdgeNodes(request)
|
||||
default:
|
||||
return NextResponse.json({ error: 'Invalid report type' }, { status: 400 })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('API Error:', error)
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
// 获取网关基本信息
|
||||
async function getGatewayInfo() {
|
||||
try {
|
||||
const result = await prisma.$queryRaw`
|
||||
SELECT macaddr, inner_ip, setid, enable
|
||||
FROM token
|
||||
ORDER BY macaddr
|
||||
`
|
||||
return NextResponse.json(safeSerialize(result))
|
||||
} catch (error) {
|
||||
console.error('Gateway info query error:', error)
|
||||
return NextResponse.json({ error: '查询网关信息失败' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
// 网关配置
|
||||
async function getGatewayConfig(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const macAddress = searchParams.get('mac') || '000C29DF1647'
|
||||
|
||||
// 使用参数化查询防止SQL注入
|
||||
const result = await prisma.$queryRaw`
|
||||
SELECT edge, city, user, public, inner_ip, ischange, isonline
|
||||
FROM gateway
|
||||
LEFT JOIN cityhash ON cityhash.hash = gateway.cityhash
|
||||
LEFT JOIN edge ON edge.macaddr = gateway.edge
|
||||
WHERE gateway.macaddr = ${macAddress};
|
||||
`
|
||||
return NextResponse.json(safeSerialize(result))
|
||||
} catch (error) {
|
||||
console.error('Gateway config query error:', error)
|
||||
return NextResponse.json({ error: '查询网关配置失败' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
// 城市节点配置数量统计
|
||||
async function getCityConfigCount() {
|
||||
try {
|
||||
const result = await prisma.$queryRaw`
|
||||
SELECT c.city, COUNT(e.id) as node_count
|
||||
FROM cityhash c
|
||||
LEFT JOIN edge e ON c.id = e.city_id
|
||||
GROUP BY c.city
|
||||
`
|
||||
return NextResponse.json(safeSerialize(result))
|
||||
} catch (error) {
|
||||
console.error('City config count query error:', error)
|
||||
return NextResponse.json({ error: '查询城市配置失败' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
// 城市节点数量分布
|
||||
async function getCityNodeCount() {
|
||||
try {
|
||||
const result = await prisma.$queryRaw`
|
||||
SELECT c.city, c.hash, c.label, COUNT(e.id) as count, c.offset
|
||||
FROM cityhash c
|
||||
LEFT JOIN edge e ON c.id = e.city_id
|
||||
GROUP BY c.hash, c.city, c.label, c.offset
|
||||
ORDER BY count DESC
|
||||
`
|
||||
return NextResponse.json(safeSerialize(result))
|
||||
} catch (error) {
|
||||
console.error('City node count query error:', error)
|
||||
return NextResponse.json({ error: '查询城市节点失败' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
// 城市分配状态
|
||||
async function getAllocationStatus() {
|
||||
try {
|
||||
// 使用参数化查询防止SQL注入
|
||||
const result = await prisma.$queryRaw`
|
||||
SELECT
|
||||
city,
|
||||
c1.count AS count,
|
||||
c2.assigned AS assigned
|
||||
FROM
|
||||
cityhash
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
city_id,
|
||||
COUNT(*) AS count
|
||||
FROM
|
||||
edge
|
||||
WHERE
|
||||
active = 1
|
||||
GROUP BY
|
||||
city_id
|
||||
) c1 ON c1.city_id = cityhash.id
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
city AS city_id,
|
||||
COUNT(*) AS assigned
|
||||
FROM
|
||||
\`change\`
|
||||
WHERE
|
||||
time > NOW() - INTERVAL 1 DAY
|
||||
GROUP BY
|
||||
city
|
||||
) c2 ON c2.city_id = cityhash.id
|
||||
WHERE
|
||||
cityhash.macaddr IS NOT NULL;
|
||||
`
|
||||
return NextResponse.json(safeSerialize(result))
|
||||
} catch (error) {
|
||||
console.error('Allocation status query error:', error)
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
||||
return NextResponse.json(
|
||||
{ error: '查询分配状态失败: ' + errorMessage },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取节点信息
|
||||
async function getEdgeNodes(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const threshold = searchParams.get('threshold') || '20'
|
||||
const limit = searchParams.get('limit') || '100'
|
||||
|
||||
// 使用参数化查询防止SQL注入
|
||||
const result = await prisma.$queryRaw`
|
||||
SELECT edge.id, edge.macaddr, city, public, isp, single, sole, arch, online
|
||||
FROM edge
|
||||
LEFT JOIN cityhash ON cityhash.id = edge.city_id
|
||||
WHERE edge.id > ${threshold} AND active = true
|
||||
LIMIT ${limit}
|
||||
`
|
||||
return NextResponse.json(safeSerialize(result))
|
||||
} catch (error) {
|
||||
console.error('Edge nodes query error:', error)
|
||||
return NextResponse.json({ error: '查询边缘节点失败' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
28
src/app/api/test/route.ts
Normal file
28
src/app/api/test/route.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
// src/app/api/test/route.ts
|
||||
import { db } from '@/lib/db'
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
// src/app/api/test/route.ts
|
||||
export async function GET() {
|
||||
try {
|
||||
// 测试数据库连接
|
||||
await db.$queryRaw`SELECT 1`
|
||||
|
||||
// 暂时注释掉用户查询,因为表可能还不存在
|
||||
// const users = await db.user.findMany()
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '数据库连接成功',
|
||||
// userCount: users.length,
|
||||
// users: users
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('数据库测试错误:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
message: '数据库连接失败'
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user