85 lines
3.0 KiB
TypeScript
85 lines
3.0 KiB
TypeScript
import { defineMiddleware } from "astro:middleware";
|
||
import { COOKIE_LANG, localePath, resolvePreferredLocale } from "@app/i18n";
|
||
import { COOKIE_SESSION } from "@app/types";
|
||
import { authService } from "@app/trpc";
|
||
|
||
const SKIP_PREFIX = ["/api", "/_astro", "/favicon", "/robots", "/sitemap", "/llms"];
|
||
|
||
/** 需登录的基础路径(无 locale 前缀)。按需增删。 */
|
||
const PROTECTED_BASES = ["/dashboard", "/settings"] as const;
|
||
|
||
const LOCALE_SEGMENTS = new Set(["zh-CN", "en"]);
|
||
|
||
function shouldSkip(pathname: string): boolean {
|
||
if (pathname !== "/" && pathname.includes(".")) return true;
|
||
return SKIP_PREFIX.some((p) => pathname.startsWith(p));
|
||
}
|
||
|
||
function basePathOf(pathname: string): string {
|
||
const segs = pathname.split("/").filter(Boolean);
|
||
const head = segs[0];
|
||
if (head && LOCALE_SEGMENTS.has(head)) {
|
||
return `/${segs.slice(1).join("/")}`;
|
||
}
|
||
// 规范化前的 zh-cn 等在后续 redirect 处理,这里先按原样判断
|
||
if (head && head.toLowerCase() === "zh-cn") {
|
||
return `/${segs.slice(1).join("/")}`;
|
||
}
|
||
return `/${segs.join("/")}`;
|
||
}
|
||
|
||
function isProtectedPath(pathname: string): boolean {
|
||
const base = basePathOf(pathname);
|
||
const normalized = base === "/" ? "/" : base.replace(/\/$/, "");
|
||
return PROTECTED_BASES.some((p) => normalized === p || normalized.startsWith(`${p}/`));
|
||
}
|
||
|
||
function localeOf(pathname: string): "zh-CN" | "en" {
|
||
const head = pathname.split("/").filter(Boolean)[0];
|
||
if (head === "en") return "en";
|
||
return "zh-CN";
|
||
}
|
||
|
||
export const onRequest = defineMiddleware(async (context, next) => {
|
||
const { request, url, cookies } = context;
|
||
const path = url.pathname;
|
||
|
||
if (shouldSkip(path)) {
|
||
return next();
|
||
}
|
||
|
||
// 裸 `/`:cookie → Accept-Language → /zh-CN 或 /en
|
||
if (path === "/") {
|
||
if (request.method !== "GET") return next();
|
||
const preferred = resolvePreferredLocale(
|
||
cookies.get(COOKIE_LANG)?.value,
|
||
request.headers.get("accept-language"),
|
||
);
|
||
return context.redirect(localePath(preferred, "/"));
|
||
}
|
||
|
||
// 小写 zh-cn → 规范 zh-CN(保留 rest)
|
||
const seg = path.split("/").filter(Boolean)[0];
|
||
if (seg && seg.toLowerCase() === "zh-cn" && seg !== "zh-CN") {
|
||
const rest = path.slice(seg.length) || "/";
|
||
return context.redirect(`/zh-CN${rest === "/" ? "" : rest}`);
|
||
}
|
||
|
||
// 指定路由鉴权:无有效 session 则跳登录
|
||
if (request.method === "GET" && isProtectedPath(path)) {
|
||
const token = cookies.get(COOKIE_SESSION)?.value;
|
||
const user = await authService.getSessionUser(token);
|
||
if (!user) {
|
||
const locale = localeOf(path);
|
||
const nextParam = encodeURIComponent(path + url.search);
|
||
return context.redirect(`${localePath(locale, "/auth/login")}?next=${nextParam}`);
|
||
}
|
||
}
|
||
|
||
const response = await next();
|
||
if (request.method === "GET") {
|
||
response.headers.set("Vary", "Accept-Language");
|
||
}
|
||
return response;
|
||
});
|