Files
hjc-web/server/utils/app.ts
T
gxwebsoft 2b69686795 feat(app): 添加多模板关于我们页面及相关路由和404页面
- 新增404页面,优化未找到页面体验,避免被搜索引擎索引
- 增加文件代理接口,隐藏真实文件服务器地址,支持文件请求代理
- 实现/article、/case、/product及/page动态路由兼容列表与详情展示
- 添加动态CMS页面兼容入口处理旧式路径,统一路由与SEO设置
- 新增模板1、模板7、模板2、模板3关于我们页面,实现多模板支持
- 模板增强支持CMS单页内容加载及SEO信息动态设置
- 配置环境变量及Git忽略文件规则辅助开发和构建环境管理
2026-09-08 12:13:44 +08:00

130 lines
3.8 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 type { AppProduct, AppDomain, ApiEnvelope } from '~/app/types'
import { $fetch } from 'ofetch'
type RuntimeConfig = ReturnType<typeof useRuntimeConfig>
/**
* 从统一响应封装中取出 data
* 后端可能返回 { code, data } 也可能直接返回对象
*
* 关键:未找到时上游返回的是「无 data 字段的信封」,
* 例如 { "code": 0, "message": "操作成功" }。
* 旧实现在无 data 字段时会把整个信封当结果返回(truthy),
* 导致调用方误判为「命中」,进而跳过了未授权 403 拦截。
* 因此:无 data 字段的信封一律视为「未找到」返回 null。
*/
function unwrap<T>(res: ApiEnvelope<T> | T): T | null {
if (!res || typeof res !== 'object') return null
const obj = res as Record<string, unknown>
// 标准信封:存在 data 字段则取 data(未找到时 data 为 null
if ('data' in obj) {
return (obj.data as T) ?? null
}
// 信封但无 data(如 { code, message })→ 视为无结果
if ('code' in obj || 'message' in obj) {
return null
}
// 直接返回对象(无信封包裹)的情况
return (res as T) ?? null
}
/** 不应暴露到前端 / SSR payload 的敏感字段 */
const SENSITIVE_KEYS = ['productSecret', 'apiUrl', 'reviewerId', 'rejectReason']
/**
* 脱敏:移除应用密钥等敏感字段
*/
function sanitize(app: AppProduct | null): AppProduct | null {
if (!app) return null
const clean = { ...app }
for (const key of SENSITIVE_KEYS) {
delete clean[key]
}
return clean
}
/**
* 按 productId 查询应用产品信息
* 后端: GET {appApiBase}/api/app/product/detail/{productId}
* 用于本地开发环境(.env 指定 NUXT_PUBLIC_APP_ID
*/
export async function getAppProductById(
productId: string | number,
config: RuntimeConfig
): Promise<AppProduct | null> {
const appApiBase = config.public.appApiBase as string
if (!productId) return null
try {
const res = await $fetch<ApiEnvelope<AppProduct> | AppProduct>(
`/api/app/product/detail/${productId}`,
{
baseURL: appApiBase,
timeout: 5000,
retry: 1
}
)
return sanitize(unwrap<AppProduct>(res))
} catch {
return null
}
}
/**
* 按域名查询应用产品信息
* 后端: GET {appApiBase}/api/app/product/getByDomain?domain={domain}
* 用于生产环境(根据当前访问域名解析应用)
* 后端已改造为直接返回完整 AppProduct(含 tenantId / templateId
*/
export async function getAppProductByDomain(
domain: string,
config: RuntimeConfig
): Promise<AppProduct | null> {
const appApiBase = config.public.appApiBase as string
if (!domain) return null
try {
const res = await $fetch<ApiEnvelope<AppProduct> | AppProduct>(
'/api/app/product/getByDomain',
{
baseURL: appApiBase,
query: { domain },
timeout: 5000,
retry: 1
}
)
return sanitize(unwrap<AppProduct>(res))
} catch {
return null
}
}
/**
* 按域名查询应用域名绑定(appDomain 表)
* 后端: GET {appApiBase}/api/app/domain/getByDomain?domain={domain}
* 用于生产环境兜底解析(主解析 app_product.domain 未命中时回退到此表)
* 返回绑定关系(含 tenantId / productId),用于取出租户与回查完整应用信息
*/
export async function getAppDomainByDomain(
domain: string,
config: RuntimeConfig
): Promise<AppDomain | null> {
const appApiBase = config.public.appApiBase as string
if (!domain) return null
try {
const res = await $fetch<ApiEnvelope<AppDomain> | AppDomain>(
'/api/app/domain/getByDomain',
{
baseURL: appApiBase,
query: { domain },
timeout: 5000,
retry: 1
}
)
return unwrap<AppDomain>(res)
} catch {
return null
}
}