333 lines
9.0 KiB
TypeScript
333 lines
9.0 KiB
TypeScript
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: 401 },
|
|
{ status: 401 }
|
|
),
|
|
{
|
|
...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<string, string> = {
|
|
"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,
|
|
}
|
|
)
|
|
}
|
|
}
|