@ -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 ?? []
|
||||
}
|
||||
Reference in New Issue
Block a user