import { addAudit, ensureDatabase } from "@/db/store"; export const dynamic = "force-dynamic"; type RouteContext = { params: Promise<{ path: string[] }> }; function ok(data: unknown, requestId = crypto.randomUUID()) { return Response.json({ code: 0, message: "success", requestId, serverTime: Date.now(), data, }); } function fail(code: number, message: string, status = 400) { return Response.json( { code, message, requestId: crypto.randomUUID(), serverTime: Date.now(), data: null }, { status }, ); } export async function GET(request: Request, context: RouteContext) { const db = await ensureDatabase(); const path = (await context.params).path; const url = new URL(request.url); if (path.length === 4 && path[0] === "devices" && path[2] === "people") { const cursor = Number(url.searchParams.get("cursor") ?? 0); const limit = Math.min(Number(url.searchParams.get("limit") ?? 200), 500); const rows = await db .prepare("SELECT id AS personId,name,workshop,role,active,face_status AS faceStatus,face_version AS faceVersion,updated_at AS updatedAt FROM people WHERE updated_at>? ORDER BY updated_at LIMIT ?") .bind(cursor, limit) .all(); const nextCursor = rows.results.reduce((value, row) => Math.max(value, Number(row.updatedAt ?? 0)), cursor); return ok({ items: rows.results, nextCursor, hasMore: rows.results.length === limit }); } if (path.length === 4 && path[0] === "devices" && path[2] === "faces") { const person = await db .prepare("SELECT id AS personId,name,face_status AS faceStatus,face_version AS faceVersion,updated_at AS updatedAt FROM people WHERE id=? AND active=1") .bind(path[3]) .first(); if (!person) return fail(4002, "PERSON_DELETED", 404); return ok({ ...person, featureEncoding: "server-managed", feature: null }); } if (path.length === 2 && path[0] === "deliveries") { const row = await db.prepare("SELECT * FROM deliveries WHERE record_id=?").bind(path[1]).first(); if (!row) return fail(3004, "DELIVERY_NOT_FOUND", 404); return ok(row); } return fail(1001, "接口不存在", 404); } export async function POST(request: Request, context: RouteContext) { const db = await ensureDatabase(); const path = (await context.params).path; const body = (await request.json().catch(() => ({}))) as Record; if (path.join("/") === "auth/tokens") { const deviceId = String(body.deviceId ?? body.deviceNo ?? ""); if (!deviceId) return fail(1001, "deviceId 必填"); return ok({ accessToken: `local-${deviceId}-${Date.now()}`, tokenType: "Bearer", expiresIn: 28_800 }); } if (path.length === 3 && path[0] === "devices" && path[2] === "heartbeat") { const deviceId = path[1]; const ip = String(body.ip ?? "0.0.0.0"); await db .prepare("INSERT INTO devices(id,type,ip,status,last_seen,heartbeat_seconds,fill_percent,category) VALUES(?,?,?,'在线',?,30,0,'终端') ON CONFLICT(id) DO UPDATE SET status='在线',last_seen=excluded.last_seen,ip=excluded.ip") .bind(deviceId, String(body.deviceType ?? "回收终端"), ip, Date.now()) .run(); return ok({ accepted: true, nextHeartbeatIn: 30 }); } if (path.length === 3 && path[0] === "devices" && path[2] === "status") { const deviceId = path[1]; const fillPercent = Number(body.fillPercent ?? body.fullRate ?? 0); await db.prepare("UPDATE devices SET status=?,last_seen=?,fill_percent=? WHERE id=?").bind(String(body.status ?? "在线"), Date.now(), fillPercent, deviceId).run(); return ok({ accepted: true }); } if (path.length === 3 && path[0] === "devices" && path[2] === "alerts") { const deviceId = path[1]; const alerts = Array.isArray(body.alerts) ? body.alerts : [body]; for (const item of alerts as Array>) { await db .prepare("INSERT OR IGNORE INTO alerts(id,created_at,type,severity,source,reference,description,status) VALUES(?,?,?,?,?,?,?,'待处理')") .bind(String(item.alarmId ?? crypto.randomUUID()), Number(item.reportedAt ?? Date.now()), String(item.type ?? "设备告警"), String(item.severity ?? "警告"), "设备上报", deviceId, String(item.message ?? "设备异常")) .run(); } return ok({ accepted: alerts.length }); } if (path.length === 3 && path[0] === "devices" && path[2] === "deliveries") { const deviceId = path[1]; const recordId = String(body.recordId ?? ""); if (!recordId) return fail(1001, "recordId 必填"); const weight = Number(body.weight ?? body.afterWeight ?? 0); await db .prepare("INSERT OR IGNORE INTO deliveries(record_id,device_id,created_at,employee_name,employee_no,machine_no,production_batch,garbage_type,weight,payload,status) VALUES(?,?,?,?,?,?,?,?,?,?,?)") .bind(recordId, deviceId, Number(body.createdAt ?? Date.now()), String(body.employeeName ?? body.person ?? ""), String(body.employeeNo ?? ""), String(body.machineNo ?? ""), String(body.productionBatch ?? ""), Number(body.garbageType ?? body.typeCode ?? 0), weight, JSON.stringify(body), "已接收") .run(); await db .prepare("INSERT OR IGNORE INTO weighings(id,created_at,operator,machine,category,source,weight,event_id,status) VALUES(?,?,?,?,?,?,?,?,?)") .bind(recordId, Number(body.createdAt ?? Date.now()), String(body.employeeName ?? body.person ?? "未知"), String(body.machineNo ?? "—"), `类型 ${String(body.garbageType ?? body.typeCode ?? 0)}`, deviceId, weight, String(body.eventId ?? recordId), "已确认") .run(); await addAudit(deviceId, "投放数据上报", `${recordId} · ${weight}kg`); return ok({ accepted: true, duplicate: false }); } if (path.length === 3 && path[0] === "devices" && path[2] === "sync-acks") { await addAudit(path[1], "同步结果上报", JSON.stringify(body).slice(0, 500)); return ok({ accepted: true }); } return fail(1001, "接口不存在", 404); }