update
Some checks failed
deploy / deploy (push) Has been cancelled

This commit is contained in:
toom1996
2026-08-23 00:58:33 +08:00
parent c8d9735751
commit b274ccdfec
21 changed files with 612 additions and 380 deletions

View File

@ -1,96 +0,0 @@
// src/lib/ssg.ts — SSG 构建专用接口客户端(集中封装 /api/ssg/*)
//
// SSG 接口只在「构建 / 预渲染期」由 Astro 后端(Node)fetch 调用,
// 永远不进入浏览器 bundle。它们位于独立的内部端口(默认 8091),不对外暴露、
// nginx 绝不反代。本文件与运行期公共 API 客户端(api.ts)完全分离:
// - api.ts → 运行期浏览器调用 /api/public/*,base = PUBLIC_API_BASE(同源 /api)
// - ssg.ts → 构建期 Node 调用 /api/ssg/*,base = SSG_API(独立内部端口 8091)
//
// base 解析优先级:
// 1) SSG_API —— 构建专用内部地址(部署由 build arg 注入,如 http://localhost:8091)
// 2) API_SSR —— 兼容旧命名
// 3) http://localhost:8091 —— 本地回落(SSG 引擎默认端口,绝不是 8090)
//
// token:SSG_TOKEN(server-only,绝不加 PUBLIC_ 前缀,不会进入 bundle);
// 为空则后端不校验(依赖回环绑定即可)。
// 同时读 import.meta.env 与 process.env:
// 构建/预渲染在 Node 中运行,.env 里的 server-only 变量两者皆可能承载,合并最稳妥。
function env(name: string): string | undefined {
const e = import.meta.env as any
if (e[name] != null && e[name] !== "") return e[name] as string
if (typeof process !== "undefined" && process.env[name]) return process.env[name] as string
return undefined
}
export function ssgBase(): string {
return (env("SSG_API") || env("API_SSR") || "http://localhost:8091").replace(/\/$/, "")
}
function ssgToken(): string {
return (env("SSG_TOKEN") || "").toString().trim()
}
// 若后端 SSG 端口启用了 SSG_TOKEN,则给每个请求附上 ?token=。
function withToken(path: string): string {
const t = ssgToken()
if (!t) return path
return path + (path.includes("?") ? "&" : "?") + "token=" + encodeURIComponent(t)
}
async function ssgRequest<T>(path: string): Promise<T | null> {
const url = ssgBase() + withToken(path)
try {
const res = await fetch(url)
if (!res.ok) return null
return (await res.json()) as T
} catch {
// 后端不可达 / 超时:返回 null,由各 getter 回落到空数组(构建期不会崩)。
return null
}
}
// ---------- 类型 ----------
// 注意:id / brand_id 是后端 hashid 编码串(string),不是自增数字主键。
export interface SsgPopularBrand {
id: string
article_id: string
brand: string
cover: string
title: string
}
export interface SsgBrand {
id: string
name: string
show_name: string
article_count: number
}
// ---------- getter ----------
// 首页「热门品牌」区块:按图片数排名前 limit 个品牌,每个附带代表走秀的封面与标题。
export async function getSsgPopularBrands(limit = 20): Promise<SsgPopularBrand[]> {
const j = await ssgRequest<{ data?: SsgPopularBrand[] }>(`/api/ssg/brands/popular?limit=${limit}`)
return j?.data ?? []
}
// 品牌索引页全量:一次拉齐精选集合,前端按首字母分桶渲染。
export async function getSsgBrands(opts?: {
size?: number
only_with_articles?: number
featured?: number
}): Promise<SsgBrand[]> {
const qs = new URLSearchParams()
if (opts?.size) qs.set("size", String(opts.size))
if (opts?.only_with_articles != null) qs.set("only_with_articles", String(opts.only_with_articles))
if (opts?.featured != null) qs.set("featured", String(opts.featured))
const j = await ssgRequest<{ data?: SsgBrand[] }>(`/api/ssg/brands?${qs.toString()}`)
return j?.data ?? []
}
// getStaticPaths 用的轻量枚举:只回文章编码 uid,不带回 cover/description,省带宽。
export async function getSsgArticleIds(): Promise<string[]> {
const j = await ssgRequest<{ data?: string[] }>(`/api/ssg/articles/ids`)
return j?.data ?? []
}