import { randomUUID } from 'crypto' import { NextRequest, NextResponse } from 'next/server' import { getApiKeyFromRequest, getClientIp, logApiCall, successResponse, validateApiKey, } from '@/lib/external-api' import { prisma } from '@/lib/prisma' import { MATERIAL_TYPE_OPTIONS } from '@/lib/material-type-options' type TraceContext = { traceId: string startTime: number ipAddress?: string apiKeyName?: string } function getElapsedMs(startTime: number) { return Math.max(0, Date.now() - startTime) } function buildErrorResponse(status: number, code: string, message: string) { return NextResponse.json( { success: false, error: { code, message, }, }, { status }, ) } function finalizeResponse( response: NextResponse, trace: TraceContext, statusCode: number, outcome: string, ) { const duration = getElapsedMs(trace.startTime) response.headers.set('X-Trace-Id', trace.traceId) response.headers.set('X-Server-Duration-Ms', String(duration)) response.headers.set('Server-Timing', `app;dur=${duration}`) console.info( '[external/dingtalk-material-options]', JSON.stringify({ traceId: trace.traceId, outcome, statusCode, duration, ipAddress: trace.ipAddress || null, apiKeyName: trace.apiKeyName || null, }), ) return response } export async function GET(request: NextRequest) { const trace: TraceContext = { traceId: randomUUID(), startTime: Date.now(), ipAddress: getClientIp(request), } const apiKey = getApiKeyFromRequest(request, { allowQuery: true }) const keyRecord = await validateApiKey(apiKey) if (!keyRecord) { return finalizeResponse( buildErrorResponse(401, 'INVALID_API_KEY', 'API Key 无效或缺失'), trace, 401, 'INVALID_API_KEY', ) } trace.apiKeyName = keyRecord.name try { const [brands, sceneCategories, productCategories] = await Promise.all([ prisma.category.findMany({ orderBy: { name: 'asc' }, select: { name: true }, }), prisma.scene.findMany({ orderBy: { name: 'asc' }, select: { name: true }, }), prisma.productCategory.findMany({ orderBy: { name: 'asc' }, select: { name: true }, }), ]) const duration = getElapsedMs(trace.startTime) await logApiCall({ apiKeyId: keyRecord.id, endpoint: '/api/external/dingtalk-material-options', method: 'GET', queryType: 'options', queryValue: 'all', statusCode: 200, duration, ipAddress: trace.ipAddress, userAgent: request.headers.get('user-agent') || undefined, }) return finalizeResponse( successResponse({ requirementTypes: [...MATERIAL_TYPE_OPTIONS], brands: brands.map((item) => item.name), sceneCategories: sceneCategories.map((item) => item.name), productCategories: productCategories.map((item) => item.name), }), trace, 200, 'OPTIONS_OK', ) } catch (error) { console.error('[external/dingtalk-material-options] error:', error) const duration = getElapsedMs(trace.startTime) await logApiCall({ apiKeyId: keyRecord.id, endpoint: '/api/external/dingtalk-material-options', method: 'GET', queryType: 'options', queryValue: 'all', statusCode: 500, duration, ipAddress: trace.ipAddress, userAgent: request.headers.get('user-agent') || undefined, }) return finalizeResponse( buildErrorResponse(500, 'SERVER_ERROR', '服务器内部错误'), trace, 500, 'SERVER_ERROR', ) } }