app-template/app/web/src/middleware.ts

85 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
});