feat: SSR 安全防卫与缓存作用域化(branding/limits/pages/auth/session)

theme/useTheme 服务端防卫 window/document;branding/limits/pages 缓存读请求作用域、seed 只写作用域;homeBoot 拆出 seedHomeBoot 与帖子详情快照构建器供 prefetchRoute 复用;getHomeStoreState 改返回 state 修复命令式访问
This commit is contained in:
2026-09-09 01:05:12 +08:00
parent 86a317c862
commit 27132e89c2
10 changed files with 144 additions and 61 deletions

View File

@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
import { api } from '../api/client';
import type { SiteBranding } from '../api/types';
import { getScope } from '../ssr/scope';
export const DEFAULT_BRANDING: SiteBranding = {
name: '姜十三论坛',
@@ -30,8 +31,8 @@ export function siteMetaDescription(brand: SiteBranding): string {
return brand.slogan?.trim() ?? '';
}
/** 从服务端注入的 boot 数据同步初始化,避免首屏闪默认站名 */
function readBootBranding(): SiteBranding | null {
if (typeof window === 'undefined') return null;
try {
const boot = window.__J13_BRANDING__;
if (!boot || typeof boot !== 'object') return null;
@@ -50,6 +51,11 @@ let cacheEpoch = 0;
let preferCacheOnce = false;
const listeners = new Set<() => void>();
/** 当前生效的品牌配置:SSR 读请求作用域,客户端读模块缓存 */
function currentBranding(): SiteBranding | null {
return getScope()?.branding ?? cached;
}
function fetchBranding(): Promise<SiteBranding> {
if (preferCacheOnce && cached) {
preferCacheOnce = false;
@@ -96,13 +102,13 @@ function applyDocumentBrand(brand: SiteBranding) {
/** 同步读取已缓存的品牌配置(供 SEO 等非 hook 场景) */
export function getCachedSiteBranding(): SiteBranding {
return cached ?? DEFAULT_BRANDING;
return currentBranding() ?? DEFAULT_BRANDING;
}
/** 获取站点品牌配置(名称、Logo 等) */
export function useSiteBranding() {
const [branding, setBranding] = useState<SiteBranding>(cached ?? DEFAULT_BRANDING);
const [loading, setLoading] = useState(!cached);
const [branding, setBranding] = useState<SiteBranding>(currentBranding() ?? DEFAULT_BRANDING);
const [loading, setLoading] = useState(!currentBranding());
const [epoch, setEpoch] = useState(cacheEpoch);
useEffect(() => {
@@ -160,9 +166,15 @@ export function invalidateSiteBrandingCache() {
listeners.forEach(fn => fn());
}
/** 用管理端刚保存的值立即更新缓存与文档标题 */
/** 用管理端刚保存的值立即更新缓存与文档标题(SSR 时只写请求作用域) */
export function seedSiteBrandingCache(brand: SiteBranding) {
cached = { ...DEFAULT_BRANDING, ...brand };
const merged: SiteBranding = { ...DEFAULT_BRANDING, ...brand };
const scope = getScope();
if (scope) {
scope.branding = merged;
return;
}
cached = merged;
applyDocumentBrand(cached);
cacheEpoch += 1;
listeners.forEach(fn => fn());