feat: 新增用户状态检查和 api key 准备功能
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
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<T> {
|
||||
/** 上游业务数据 */
|
||||
data?: T | null;
|
||||
/** 上游提示信息 */
|
||||
message?: string;
|
||||
/** 上游业务成功状态 */
|
||||
success?: boolean;
|
||||
}
|
||||
|
||||
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<IUserReadyData> => {
|
||||
const hasToken = await hasActiveAiArtStudioToken(event);
|
||||
if (hasToken) {
|
||||
return {
|
||||
ready: true,
|
||||
created: false
|
||||
};
|
||||
}
|
||||
|
||||
await createAiArtStudioToken(event);
|
||||
|
||||
return {
|
||||
ready: true,
|
||||
created: true
|
||||
};
|
||||
};
|
||||
|
||||
/** 分页读取 Token 列表,只认名称为 AIArtStudio 且状态启用的未删除 Token */
|
||||
const hasActiveAiArtStudioToken = async (event: H3Event): Promise<boolean> => {
|
||||
let page = 1;
|
||||
let total = Number.POSITIVE_INFINITY;
|
||||
|
||||
while ((page - 1) * TOKEN_PAGE_SIZE < total) {
|
||||
const response = await newApiAuthedFetch<
|
||||
INewApiWrappedResponse<INewApiTokenPageData>
|
||||
>(event, `/api/token/?p=${page}&size=${TOKEN_PAGE_SIZE}`, {
|
||||
method: "GET"
|
||||
});
|
||||
const data = unwrapNewApiResponse(response, "环境检查失败");
|
||||
|
||||
if (data.items.some(isActiveAiArtStudioToken)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
total = data.total;
|
||||
page += 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/** 创建默认 AIArtStudio Token,参数固定,避免 handler 内重复维护 */
|
||||
const createAiArtStudioToken = async (event: H3Event) => {
|
||||
const response = await newApiAuthedFetch<INewApiWrappedResponse<null>>(
|
||||
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 = <T>(
|
||||
response: INewApiWrappedResponse<T>,
|
||||
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;
|
||||
};
|
||||
Reference in New Issue
Block a user