初始化项目

This commit is contained in:
wmp
2025-09-13 14:00:56 +08:00
commit f1fa28401e
61 changed files with 4710 additions and 0 deletions

View File

View 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 }
)
}
}

View 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
View 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
View 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 })
}
}