import type { AppProduct, AppDomain, ApiEnvelope } from '~/app/types' import { $fetch } from 'ofetch' type RuntimeConfig = ReturnType /** * 从统一响应封装中取出 data * 后端可能返回 { code, data } 也可能直接返回对象 * * 关键:未找到时上游返回的是「无 data 字段的信封」, * 例如 { "code": 0, "message": "操作成功" }。 * 旧实现在无 data 字段时会把整个信封当结果返回(truthy), * 导致调用方误判为「命中」,进而跳过了未授权 403 拦截。 * 因此:无 data 字段的信封一律视为「未找到」返回 null。 */ function unwrap(res: ApiEnvelope | T): T | null { if (!res || typeof res !== 'object') return null const obj = res as Record // 标准信封:存在 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 { const appApiBase = config.public.appApiBase as string if (!productId) return null try { const res = await $fetch | AppProduct>( `/api/app/product/detail/${productId}`, { baseURL: appApiBase, timeout: 5000, retry: 1 } ) return sanitize(unwrap(res)) } catch { return null } } /** * 按域名查询应用产品信息 * 后端: GET {appApiBase}/api/app/product/getByDomain?domain={domain} * 用于生产环境(根据当前访问域名解析应用) * 后端已改造为直接返回完整 AppProduct(含 tenantId / templateId) */ export async function getAppProductByDomain( domain: string, config: RuntimeConfig ): Promise { const appApiBase = config.public.appApiBase as string if (!domain) return null try { const res = await $fetch | AppProduct>( '/api/app/product/getByDomain', { baseURL: appApiBase, query: { domain }, timeout: 5000, retry: 1 } ) return sanitize(unwrap(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 { const appApiBase = config.public.appApiBase as string if (!domain) return null try { const res = await $fetch | AppDomain>( '/api/app/domain/getByDomain', { baseURL: appApiBase, query: { domain }, timeout: 5000, retry: 1 } ) return unwrap(res) } catch { return null } }