// server/utils/newApiTokens.ts - NewAPI Token 工具:准备 AIArtStudio Token 并在服务端读取完整 key。 import type { H3Event } from "h3"; import { createError } from "h3"; import type { IUserReadyData } from "#shared/types"; import { newApiAuthedFetch } from "~~/server/utils/fetch"; const AI_ART_STUDIO_TOKEN_NAME = "AIArtStudio"; const TOKEN_PAGE_SIZE = 100; interface INewApiTokenItem { /** NewAPI Token ID,后续获取完整 key 时会用到 */ id: number; /** Token 所属用户 ID */ user_id: number; /** 列表接口返回的脱敏 key,不对前端透出 */ key: string; /** Token 状态:1 为启用,2 为禁用 */ status: number; /** Token 名称 */ name: string; /** 软删除时间,null 表示未删除 */ DeletedAt: unknown; } interface INewApiTokenPageData { /** 当前页码 */ page: number; /** 每页数量 */ page_size: number; /** Token 总数 */ total: number; /** 当前页 Token 列表 */ items: INewApiTokenItem[]; } interface INewApiWrappedResponse { /** 上游业务数据 */ data?: T | null; /** 上游提示信息 */ message?: string; /** 上游业务成功状态 */ success?: boolean; } interface INewApiTokenKeyData { /** NewAPI 返回的完整 Token Key,仅允许服务端内部使用 */ key: string; } const AI_ART_STUDIO_TOKEN_PAYLOAD = { remain_quota: 0, remain_amount: 0, expired_time: -1, unlimited_quota: true, model_limits_enabled: false, model_limits: "", cross_group_retry: false, name: AI_ART_STUDIO_TOKEN_NAME, group: "", allow_ips: "" }; /** 检查当前登录用户是否已有可用 AIArtStudio Key,没有则创建一个 */ export const ensureAiArtStudioToken = async ( event: H3Event ): Promise => { const token = await findActiveAiArtStudioToken(event); if (token) { return { ready: true, created: false }; } await createAiArtStudioToken(event); return { ready: true, created: true }; }; /** 确保 AIArtStudio Token 可用,并获取完整 key,完整 key 不允许透出到前端 */ export const getAiArtStudioTokenKey = async ( event: H3Event ): Promise => { let token = await findActiveAiArtStudioToken(event); if (!token) { await createAiArtStudioToken(event); token = await findActiveAiArtStudioToken(event); } if (!token) { throw createError({ statusCode: 502, statusMessage: "环境初始化失败" }); } const response = await newApiAuthedFetch< INewApiWrappedResponse >(event, `/api/token/${token.id}/key`, { method: "POST" }); const data = unwrapNewApiResponse(response, "环境读取失败"); if (typeof data.key !== "string" || !data.key) { throw createError({ statusCode: 502, statusMessage: "环境读取失败" }); } return data.key; }; /** 分页读取 Token 列表,只认名称为 AIArtStudio 且状态启用的未删除 Token */ const findActiveAiArtStudioToken = async ( event: H3Event ): Promise => { let page = 1; let total = Number.POSITIVE_INFINITY; while ((page - 1) * TOKEN_PAGE_SIZE < total) { const response = await newApiAuthedFetch< INewApiWrappedResponse >(event, `/api/token/?p=${page}&size=${TOKEN_PAGE_SIZE}`, { method: "GET" }); const data = unwrapNewApiResponse(response, "环境检查失败"); const token = data.items.find(isActiveAiArtStudioToken); if (token) { return token; } total = data.total; page += 1; } return null; }; /** 创建默认 AIArtStudio Token,参数固定,避免 handler 内重复维护 */ const createAiArtStudioToken = async (event: H3Event) => { const response = await newApiAuthedFetch>( event, "/api/token/", { method: "POST", body: AI_ART_STUDIO_TOKEN_PAYLOAD, headers: { "Content-Type": "application/json" } } ); unwrapNewApiResponse(response, "环境初始化失败", true); }; /** 判断 Token 是否满足本项目 ready 条件 */ const isActiveAiArtStudioToken = (token: INewApiTokenItem): boolean => { return ( token.name === AI_ART_STUDIO_TOKEN_NAME && token.status === 1 && token.DeletedAt === null ); }; /** 统一处理 NewAPI 的 { success, message, data } 外壳 */ const unwrapNewApiResponse = ( response: INewApiWrappedResponse, fallbackMessage: string, allowNullData: boolean = false ): T => { if (response.success !== true) { throw createError({ statusCode: 502, statusMessage: response.message || fallbackMessage, data: response }); } if (response.data === null || response.data === undefined) { if (allowNullData) return response.data as T; throw createError({ statusCode: 502, statusMessage: fallbackMessage, data: response }); } return response.data; };