import { handleDeleteCookie, handleLoginData, handleRefreshToken, } from "@/components/login/libs/cookie" import { getSessionCacheTtlMs } from "@/components/login/libs/session-cache" import { getClientIpFromRequest } from "@/components/login/libs/session-common" import { genAccessToken } from "@/components/login/libs/session" import { // NextRequest, NextResponse, } from "next/server" const isParamsValid = (params: {} | null) => params !== null && // 排除 null typeof params === "object" && // 确保是对象类型 !Array.isArray(params) && // 排除数组(可选) Object.keys(params).length > 0 // 检查是否有自身可枚举属性 const formatDurationMs = (durationMs: number) => Number.isFinite(durationMs) ? durationMs.toFixed(2) : "0.00" const buildServerTimingHeader = (props: { sessionLookupMs: number redisLookupMs: number sessionSource: "memory" | "shared" | "redis" | "skip" upstreamFetchMs: number totalMs: number }) => { const { sessionLookupMs, redisLookupMs, sessionSource, upstreamFetchMs, totalMs, } = props const cacheTtlMs = getSessionCacheTtlMs() return [ `session-lookup;dur=${formatDurationMs(sessionLookupMs)};desc="${sessionSource}"`, `redis;dur=${formatDurationMs(redisLookupMs)};desc="${sessionSource === "redis" ? "get" : sessionSource}"`, `upstream;dur=${formatDurationMs(upstreamFetchMs)}`, `total;dur=${formatDurationMs(totalMs)}`, `session-cache;dur=${sessionSource === "memory" ? "1" : "0"};desc="ttl=${cacheTtlMs}ms"`, ].join(", ") } const attachServerTiming = ( response: Response | NextResponse, props: { sessionLookupMs: number redisLookupMs: number sessionSource: "memory" | "shared" | "redis" | "skip" upstreamFetchMs: number totalMs: number } ) => { const nextResponse = response instanceof NextResponse ? response : new NextResponse(response.body, { status: response.status, headers: response.headers, }) nextResponse.headers.set("Server-Timing", buildServerTimingHeader(props)) return nextResponse } export async function POST(req: any) { const requestStart = performance.now() const timing = { sessionLookupMs: 0, redisLookupMs: 0, sessionSource: "skip" as "memory" | "shared" | "redis" | "skip", upstreamFetchMs: 0, } try { const body = await req.json() let { url, data, options = {}, method = "GET", isDownload = false, isLogin = false, isRefresh = false, isDeleteCookie = false, notJson = false, } = body if (isDeleteCookie) { await handleDeleteCookie() return attachServerTiming( new NextResponse( JSON.stringify({ message: "Successfully delete cookie!", code: 200, status: true, }), { headers: { "Content-Type": "application/json", }, } ), { ...timing, totalMs: performance.now() - requestStart, } ) } let userData: any = null if (!isLogin) { const sessionResult = await genAccessToken(req) timing.sessionLookupMs = sessionResult.timing.sessionLookupMs timing.redisLookupMs = sessionResult.timing.redisLookupMs timing.sessionSource = sessionResult.timing.sessionSource if (!sessionResult.ok) { return attachServerTiming( NextResponse.json( { message: sessionResult.message || "未获取到用户信息", code: 406 }, { status: 406 } ), { ...timing, totalMs: performance.now() - requestStart, } ) } userData = sessionResult.userData } let fetchOptions: any = {} if (isRefresh) { if (!userData?.refresh_token) { return attachServerTiming( NextResponse.json( { message: "Refresh token is empty", code: 406 }, { status: 200 } ), { ...timing, totalMs: performance.now() - requestStart, } ) } fetchOptions = { method, headers: { "content-type": "application/json", ["Token"]: userData?.refresh_token, ["Refresh-Token"]: userData?.refresh_token, }, // body: JSON.stringify({ // token: userData?.refresh_token, // }), } } else { const requestHeaders: Record = { "content-type": "application/json", } if (!isLogin) { requestHeaders.Token = `${userData?.access_token || ""}` requestHeaders["Refresh-Token"] = `${userData?.refresh_token || ""}` } fetchOptions = { method, headers: requestHeaders, } if (method === "POST" || method === "PUT" || method === "DELETE") { fetchOptions.body = data } else { if (isParamsValid(data)) { const queryString = new URLSearchParams( Object.entries(data).map(([key, value]) => [key, String(value)]) ).toString() if (queryString) { url += url.includes("?") ? `&${queryString}` : `?${queryString}` } } } } const isPrefixUrl = url.startsWith("http://") || url.startsWith("https://") const fetchUrl = isPrefixUrl ? url : `${process.env.NEXT_PUBLIC_URL}${url}` const upstreamFetchStart = performance.now() const res = await fetch(fetchUrl, fetchOptions) timing.upstreamFetchMs = performance.now() - upstreamFetchStart if (res?.status !== 200) { let error = "" if (res.headers.get("Content-Type")?.includes("application/json")) { const data = await res.json() error = data?.message || "" } else { error = await res.text() } return attachServerTiming( NextResponse.json({ message: error }, { status: res?.status }), { ...timing, totalMs: performance.now() - requestStart, } ) } if ( options?.responseType === "blob" || options?.responseType === "arraybuffer" || isDownload ) { return attachServerTiming( new NextResponse(res.body, { status: res.status, headers: { "Content-Type": res.headers.get("Content-Type") || "application/octet-stream", "x-real-name": res?.headers?.get("content-disposition") || "", }, }), { ...timing, totalMs: performance.now() - requestStart, } ) } else if (!notJson) { if (!isLogin && !isRefresh) { return attachServerTiming( new NextResponse(res.body, { status: res.status, headers: { "Content-Type": res.headers.get("Content-Type") || "application/json", }, }), { ...timing, totalMs: performance.now() - requestStart, } ) } try { const data = await (res.headers .get("Content-Type") ?.includes("application/json") ? res.json() : res.text()) if (isLogin) { const newData = await handleLoginData({ clientIP: getClientIpFromRequest(req), fingerprint: body.fingerprint, data, }) return attachServerTiming( new NextResponse(JSON.stringify(newData), { headers: { "Content-Type": "application/json", }, }), { ...timing, totalMs: performance.now() - requestStart, } ) } if (isRefresh) { const newData = await handleRefreshToken({ userData, data, }) return attachServerTiming( new NextResponse(JSON.stringify(newData), { headers: { "Content-Type": "application/json", }, }), { ...timing, totalMs: performance.now() - requestStart, } ) } return attachServerTiming( new NextResponse(JSON.stringify(data), { headers: { "Content-Type": "application/json", }, }), { ...timing, totalMs: performance.now() - requestStart, } ) } catch (err) { return attachServerTiming( NextResponse.json({ error: err }, { status: 500 }), { ...timing, totalMs: performance.now() - requestStart, } ) } } else { return attachServerTiming(res, { ...timing, totalMs: performance.now() - requestStart, }) } } catch (err) { return attachServerTiming( NextResponse.json({ error: err }, { status: 500 }), { ...timing, totalMs: performance.now() - requestStart, } ) } }