import { $fetch } from 'ofetch' import { useStorage } from 'nitropack/runtime' import type { ApiEnvelope, CmsSiteInfo } from '~/app/types' type RuntimeConfig = ReturnType /** * 直接调用 CMS 获取站点信息 * CMS 接口:/cms/cms-website/getSiteInfo * * 供 SSR 中间件(tenant.ts)预取使用:结果写入 event.context.siteInfo, * 再由 site-init.server.ts 注入 useState('site-info'),保证 SSR 首屏 * 渲染模板时 siteTemplateId 已可用(网站数据库优先于应用绑定/环境默认)。 * * 与 /api/site/info handler 各自独立调用 CMS,互不依赖: * handler 面向直接 API 调用方(返回原始响应),本函数面向 SSR 预取(返回解包数据)。 * * @returns 解包后的 CmsSiteInfo;失败或 tenantId 为空返回 null(调用方回退默认模板) */ export async function getCmsSiteInfo( tenantId: string, config: RuntimeConfig, query: Record = {} ): Promise { if (!tenantId) return null const modulesApiBase = config.public.modulesApiBase as string try { const res = await $fetch | CmsSiteInfo>( '/cms/cms-website/getSiteInfo', { baseURL: modulesApiBase, headers: { TenantId: tenantId }, query, timeout: 6000, retry: 1 } ) const envelope = res as ApiEnvelope return envelope?.data ?? (res as CmsSiteInfo) } catch { return null } } /** * 在线留言是否要求滑块验证码。 * 来源优先级(从高到低): * 1) CMS 后台「网站设置」setting.requireCaptcha(权威,后台配置即生效) * 2) 运行时配置 public.contactCaptcha(本地调试/部署级覆盖,NUXT_PUBLIC_CONTACT_CAPTCHA=true|false) * 3) 默认 false(未配置则不显示滑块、不强制验证) * * 结果按租户缓存 5 分钟(仅缓存 CMS 解析出的 raw 值),避免每次提交都回源 CMS。 */ const siteKv = useStorage('site-cache') const CAPTCHA_CONFIG_TTL = 5 * 60 * 1000 type CaptchaReqCache = { raw: boolean | null; ts: number } function resolveCaptchaRequired(raw: boolean | null, envOverride: boolean | null): boolean { if (raw !== null) return raw if (envOverride !== null) return envOverride return false } export async function getContactCaptchaRequired( tenantId: string, config: RuntimeConfig ): Promise { const envOverride = typeof config.public.contactCaptcha === 'boolean' ? config.public.contactCaptcha : null const cacheKey = `captcha-req:${tenantId}` if (tenantId) { try { const cached = await siteKv.getItem(cacheKey) if (cached && Date.now() - cached.ts < CAPTCHA_CONFIG_TTL) { return resolveCaptchaRequired(cached.raw, envOverride) } } catch { // 缓存读取失败则回源 } } try { const info = await getCmsSiteInfo(tenantId, config) const raw = (info?.setting?.requireCaptcha as boolean | undefined) ?? null if (tenantId) { await siteKv.setItem(cacheKey, { raw, ts: Date.now() }) } return resolveCaptchaRequired(raw, envOverride) } catch { // CMS 不可达时,回退到环境变量覆盖(若也未设置则默认 false) return envOverride ?? false } }