106 lines
3.5 KiB
TypeScript
106 lines
3.5 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
||
import { getDb } from '@/lib/db'
|
||
import { initDatabase } from '@/lib/db-schema'
|
||
import { verifyApiKey } from '@/lib/auth'
|
||
import { writeAuditLog, getClientIP } from '@/lib/audit'
|
||
|
||
function verifyEnvApiKey(key: string): boolean {
|
||
const allowed = process.env.ALLOWED_API_KEYS || ''
|
||
if (!allowed) return false
|
||
return allowed.split(',').map(k => k.trim()).includes(key)
|
||
}
|
||
|
||
function validateTicketNo(ticketNo: string): string | null {
|
||
if (!/^\d{14}$/.test(ticketNo)) return '工单号必须为 14 位纯数字'
|
||
const y = parseInt(ticketNo.slice(0, 4))
|
||
const m = parseInt(ticketNo.slice(4, 6))
|
||
const d = parseInt(ticketNo.slice(6, 8))
|
||
const dt = new Date(y, m - 1, d)
|
||
if (dt.getFullYear() !== y || dt.getMonth() !== m - 1 || dt.getDate() !== d) {
|
||
return '工单号前 8 位必须为合法日期(YYYYMMDD)'
|
||
}
|
||
return null
|
||
}
|
||
|
||
export async function POST(request: NextRequest) {
|
||
try {
|
||
initDatabase()
|
||
|
||
const authHeader = request.headers.get('authorization')
|
||
let authenticated = false
|
||
let apiKeyInfo: { id: number; name: string; permissions: string[] } | null = null
|
||
|
||
if (authHeader?.startsWith('Bearer ak_')) {
|
||
const key = authHeader.slice(7)
|
||
if (verifyEnvApiKey(key)) {
|
||
authenticated = true
|
||
} else {
|
||
const keyInfo = verifyApiKey(key)
|
||
if (keyInfo) {
|
||
authenticated = true
|
||
apiKeyInfo = keyInfo
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!authenticated) {
|
||
return NextResponse.json({ error: '未授权:需要有效的 API Key' }, { status: 401 })
|
||
}
|
||
|
||
const body = await request.json()
|
||
const ticketNo = String(body.ticket_no || '').trim()
|
||
if (!ticketNo) {
|
||
return NextResponse.json({ error: '缺少必填字段: ticket_no' }, { status: 400 })
|
||
}
|
||
|
||
const validationError = validateTicketNo(ticketNo)
|
||
if (validationError) {
|
||
return NextResponse.json({ error: validationError }, { status: 400 })
|
||
}
|
||
|
||
const db = getDb()
|
||
const ticketId = parseInt(ticketNo)
|
||
|
||
const existing = db.prepare('SELECT * FROM tickets WHERE id = ?').get(ticketId) as Record<string, unknown> | undefined
|
||
if (existing) {
|
||
return NextResponse.json({ ticket: existing, created: false })
|
||
}
|
||
|
||
db.prepare(`
|
||
INSERT INTO tickets (id, device_ip, device_sn, device_name, content, assign_time,
|
||
ticket_type, fault_category, fault_subcategory, responsibility, current_status, counted_in_sla)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
`).run(
|
||
ticketId,
|
||
body.device_ip || null,
|
||
body.device_sn || null,
|
||
body.device_name || null,
|
||
body.content || null,
|
||
body.assign_time || new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString().slice(0, 19),
|
||
body.ticket_type || null,
|
||
body.fault_category || null,
|
||
body.fault_subcategory || null,
|
||
body.responsibility || null,
|
||
'open',
|
||
body.counted_in_sla ?? 1,
|
||
)
|
||
|
||
const ticket = db.prepare('SELECT * FROM tickets WHERE id = ?').get(ticketId)
|
||
|
||
writeAuditLog({
|
||
userId: null,
|
||
apiKeyId: apiKeyInfo?.id ?? null,
|
||
action: 'create',
|
||
entityType: 'ticket',
|
||
entityId: ticketId,
|
||
details: { created: { ticket_no: ticketNo, device_ip: body.device_ip || null } },
|
||
ipAddress: getClientIP(request)
|
||
})
|
||
|
||
return NextResponse.json({ ticket, created: true }, { status: 201 })
|
||
} catch (e) {
|
||
const msg = e instanceof Error ? e.message : '创建失败'
|
||
return NextResponse.json({ error: msg }, { status: 500 })
|
||
}
|
||
}
|