feat: 多功能更新

This commit is contained in:
2026-04-26 00:39:28 +08:00
parent abd1b03529
commit e26e2f5491
26 changed files with 992 additions and 330 deletions
-22
View File
@@ -1,22 +0,0 @@
import { consola } from "consola";
import { prisma } from "~~/server/utils";
export default defineEventHandler(async () => {
const startedAt = Date.now();
try {
await prisma.$queryRaw`SELECT 1`;
return createSuccessResponse(
{
ok: true,
elapsedMs: Date.now() - startedAt
},
"数据库连接正常"
);
} catch (error) {
consola.error("[images.db-health] 数据库连接失败", error);
return createUpstreamErrorResponse(error, "数据库连接失败");
}
});
+109 -70
View File
@@ -1,20 +1,29 @@
import { randomUUID } from "node:crypto";
import { consola } from "consola";
import type {
IImageGenerateData,
IImageGenerateRequest
} from "#shared/types/openai";
import {
askImgStream,
clearNewApiAuthCookies,
createApiLogger,
createErrorResponse,
createRunningImageGeneration,
downloadImageAsBase64,
createSuccessResponse,
createUpstreamErrorResponse,
finishImageGenerationFailed,
finishImageGenerationSuccess,
getAiArtStudioTokenKey,
getNewApiUserIdFromCookie,
isUnauthorizedError
getUserArchiveIdentity,
isUnauthorizedError,
toSafeLogError,
uploadImageFromUrl
} from "~~/server/utils";
type ApiLogger = ReturnType<typeof createApiLogger>;
export default defineEventHandler(async (event) => {
const requestId = randomUUID();
const logger = createApiLogger("images.generate");
let stage = "read_body";
const requestBody = await readBody<Partial<IImageGenerateRequest> | null>(
event
@@ -25,87 +34,95 @@ export default defineEventHandler(async (event) => {
requestBody !== null &&
(typeof requestBody !== "object" || Array.isArray(requestBody))
) {
logger.warn("请求体格式错误", {
bodyType: Array.isArray(requestBody) ? "array" : typeof requestBody
});
return createErrorResponse(400, "请求体必须是 JSON 对象");
}
const prompt = requestBody?.prompt?.trim();
if (!prompt) {
logger.warn("缺少提示词");
return createErrorResponse(400, "请输入图片描述");
}
logger.info("开始", {
promptLength: prompt.length
});
let record: { id: bigint; startedAt: Date } | null = null;
try {
consola.info("[images.generate] 开始", {
requestId,
promptLength: prompt.length
});
stage = "read_user_id";
const userId = getNewApiUserIdFromCookie(event);
consola.info("[images.generate] 读取用户成功", {
requestId,
logger.info("读取用户成功", {
userId
});
stage = "create_running_record";
record = await createRunningImageGeneration(userId, prompt);
consola.info("[images.generate] 创建生图记录成功", {
requestId,
logger.info("创建生图记录成功", {
recordId: record.id.toString()
});
stage = "get_api_key";
const apiKey = await getAiArtStudioTokenKey(event);
consola.info("[images.generate] 获取服务端 key 成功", {
requestId,
logger.info("获取服务端 key 成功", {
recordId: record.id.toString()
});
stage = "call_image_api";
const result = await askImg({
stage = "call_image_stream_api";
const result = await askImgStream({
apiKey,
prompt
});
consola.info("[images.generate] 上游生图成功", {
requestId,
logger.info("上游流式生图成功", {
recordId: record.id.toString(),
hasImageUrl: Boolean(result.imageUrl)
hasImageUrl: Boolean(result.imageUrl),
contentLength: getStreamContentLength(result.upstreamResponse),
hasUsage: hasStreamUsage(result.upstreamResponse)
});
stage = "download_image_base64";
const downloadedImage = await downloadGeneratedImage(result.imageUrl);
consola.info("[images.generate] 图片归档下载完成", {
requestId,
stage = "upload_lsky";
const archiveResult = await archiveGeneratedImage({
imageUrl: result.imageUrl,
userId,
recordId: record.id,
createdAt: record.startedAt,
logger
});
logger.info("图床归档完成", {
recordId: record.id.toString(),
hasBase64: Boolean(downloadedImage.base64),
mimeType: downloadedImage.mimeType
hosted: Boolean(archiveResult.hostedImageUrl),
mimeType: archiveResult.imageMimeType
});
stage = "finish_success_record";
await finishImageGenerationSuccess(record.id, record.startedAt, {
imageUrl: result.imageUrl,
imageBase64: downloadedImage.base64,
imageMimeType: downloadedImage.mimeType,
hostedImageUrl: archiveResult.hostedImageUrl,
imageMimeType: archiveResult.imageMimeType,
revisedPrompt: result.revisedPrompt,
upstreamResponse: result.upstreamResponse,
errorMessage: downloadedImage.errorMessage
hostedResponse: archiveResult.hostedResponse,
errorMessage: archiveResult.errorMessage
});
consola.info("[images.generate] 完成", {
requestId,
recordId: record.id.toString()
logger.done("成", {
recordId: record.id.toString(),
hosted: Boolean(archiveResult.hostedImageUrl)
});
return createSuccessResponse<IImageGenerateData>(
{
imageUrl: result.imageUrl,
hostedImageUrl: archiveResult.hostedImageUrl,
revisedPrompt: result.revisedPrompt
},
"图片生成成功"
);
} catch (error) {
consola.error("[images.generate] 失败", {
requestId,
logger.error("失败", {
stage,
recordId: record?.id.toString() ?? null,
error: toSafeLogError(error)
@@ -114,8 +131,7 @@ export default defineEventHandler(async (event) => {
if (record) {
await finishImageGenerationFailed(record.id, record.startedAt, error).catch(
(recordError) => {
consola.error("[images.generate] 更新失败记录失败", {
requestId,
logger.error("更新失败记录失败", {
recordId: record?.id.toString() ?? null,
error: toSafeLogError(recordError)
});
@@ -132,52 +148,75 @@ export default defineEventHandler(async (event) => {
}
});
/** 图片 URL 仍然可用时,base64 保存失败不阻断本次生成结果 */
const downloadGeneratedImage = async (
imageUrl: string
): Promise<{
base64: string | null;
mimeType: string | null;
const archiveGeneratedImage = async ({
imageUrl,
userId,
recordId,
createdAt,
logger
}: {
imageUrl: string;
userId: number;
recordId: bigint;
createdAt: Date;
logger: ApiLogger;
}): Promise<{
hostedImageUrl: string | null;
imageMimeType: string | null;
hostedResponse: unknown;
errorMessage: string | null;
}> => {
try {
const image = await downloadImageAsBase64(imageUrl);
// 图床归档失败不能阻断本次生成结果,前端仍可使用上游图片 URL。
const identity = await getUserArchiveIdentity(userId);
const uploaded = await uploadImageFromUrl({
imageUrl,
userId,
username: identity.username,
recordId,
createdAt
});
return {
base64: image.base64,
mimeType: image.mimeType,
hostedImageUrl: uploaded.publicUrl,
imageMimeType: uploaded.mimetype,
hostedResponse: uploaded.response,
errorMessage: null
};
} catch (error) {
consola.error("[images.generate] 保存生成图片 base64 失败", {
logger.error("图床归档失败", {
recordId: recordId.toString(),
error: toSafeLogError(error)
});
return {
base64: null,
mimeType: null,
errorMessage: "图片生成成功,但 base64 保存失败"
hostedImageUrl: null,
imageMimeType: null,
hostedResponse: null,
errorMessage: "图片生成成功,但图床归档失败"
};
}
};
const toSafeLogError = (error: unknown) => {
const maybeError = error as {
data?: unknown;
message?: string;
name?: string;
response?: {
_data?: unknown;
status?: number;
};
stack?: string;
status?: number;
statusCode?: number;
};
// 日志只读取流式聚合结果的元信息,不记录完整提示词、key 或图片内容。
const getStreamContentLength = (upstreamResponse: unknown) => {
if (
upstreamResponse &&
typeof upstreamResponse === "object" &&
"content" in upstreamResponse &&
typeof upstreamResponse.content === "string"
) {
return upstreamResponse.content.length;
}
return {
name: maybeError.name,
message: maybeError.message,
status: maybeError.status ?? maybeError.statusCode ?? maybeError.response?.status,
data: maybeError.data ?? maybeError.response?._data ?? null,
stack: maybeError.stack
};
return 0;
};
const hasStreamUsage = (upstreamResponse: unknown) => {
return (
upstreamResponse !== null &&
typeof upstreamResponse === "object" &&
"usage" in upstreamResponse &&
Boolean(upstreamResponse.usage)
);
};
+30 -1
View File
@@ -1,9 +1,14 @@
import type { IImageHistoryListData } from "#shared/types/openai";
import {
clearNewApiAuthCookies,
createApiLogger,
createErrorResponse,
createSuccessResponse,
createUpstreamErrorResponse,
getNewApiUserIdFromCookie,
isUnauthorizedError,
listImageGenerationHistory
listImageGenerationHistory,
toSafeLogError
} from "~~/server/utils";
const DEFAULT_PAGE = 1;
@@ -11,6 +16,9 @@ const DEFAULT_PAGE_SIZE = 10;
const MAX_PAGE_SIZE = 50;
export default defineEventHandler(async (event) => {
const logger = createApiLogger("images.history.list");
logger.info("开始");
try {
const userId = getNewApiUserIdFromCookie(event);
const query = getQuery(event);
@@ -19,12 +27,27 @@ export default defineEventHandler(async (event) => {
normalizePositiveInt(query.size, DEFAULT_PAGE_SIZE),
MAX_PAGE_SIZE
);
logger.info("查询参数", {
userId,
page,
pageSize
});
const result = await listImageGenerationHistory({
userId,
page,
pageSize
});
logger.done("成功", {
userId,
page,
pageSize,
total: result.total,
itemCount: result.items.length
});
return createSuccessResponse<IImageHistoryListData>(
result,
"获取生图历史成功"
@@ -32,9 +55,15 @@ export default defineEventHandler(async (event) => {
} catch (error) {
if (isUnauthorizedError(error)) {
clearNewApiAuthCookies(event);
logger.warn("未登录或登录态失效", {
error: toSafeLogError(error)
});
return createErrorResponse(401, "未登录");
}
logger.error("失败", {
error: toSafeLogError(error)
});
return createUpstreamErrorResponse(error, "获取生图历史失败");
}
});
+30 -1
View File
@@ -1,27 +1,56 @@
import {
clearNewApiAuthCookies,
createApiLogger,
createErrorResponse,
createSuccessResponse,
createUpstreamErrorResponse,
getNewApiUserIdFromCookie,
isUnauthorizedError,
softDeleteImageGeneration
softDeleteImageGeneration,
toSafeLogError
} from "~~/server/utils";
export default defineEventHandler(async (event) => {
const logger = createApiLogger("images.history.delete");
logger.info("开始");
try {
const userId = getNewApiUserIdFromCookie(event);
const recordId = parseRecordId(getRouterParam(event, "id"));
logger.info("删除参数", {
userId,
recordId: recordId.toString()
});
const deleted = await softDeleteImageGeneration(userId, recordId);
if (!deleted) {
logger.warn("记录不存在", {
userId,
recordId: recordId.toString()
});
return createErrorResponse(404, "生图记录不存在");
}
logger.done("成功", {
userId,
recordId: recordId.toString()
});
return createSuccessResponse(null, "删除生图记录成功");
} catch (error) {
if (isUnauthorizedError(error)) {
clearNewApiAuthCookies(event);
logger.warn("未登录或登录态失效", {
error: toSafeLogError(error)
});
return createErrorResponse(401, "未登录");
}
logger.error("失败", {
error: toSafeLogError(error)
});
return createUpstreamErrorResponse(error, "删除生图记录失败");
}
});
+32 -1
View File
@@ -1,21 +1,46 @@
import type { IImageHistoryDetail } from "#shared/types/openai";
import {
clearNewApiAuthCookies,
createApiLogger,
createErrorResponse,
createSuccessResponse,
createUpstreamErrorResponse,
getImageGenerationDetail,
getNewApiUserIdFromCookie,
isUnauthorizedError
isUnauthorizedError,
toSafeLogError
} from "~~/server/utils";
export default defineEventHandler(async (event) => {
const logger = createApiLogger("images.history.detail");
logger.info("开始");
try {
const userId = getNewApiUserIdFromCookie(event);
const recordId = parseRecordId(getRouterParam(event, "id"));
logger.info("查询参数", {
userId,
recordId: recordId.toString()
});
const result = await getImageGenerationDetail(userId, recordId);
if (!result) {
logger.warn("记录不存在", {
userId,
recordId: recordId.toString()
});
return createErrorResponse(404, "生图记录不存在");
}
logger.done("成功", {
userId,
recordId: recordId.toString(),
status: result.status,
hasHostedImageUrl: Boolean(result.hostedImageUrl)
});
return createSuccessResponse<IImageHistoryDetail>(
result,
"获取生图详情成功"
@@ -23,9 +48,15 @@ export default defineEventHandler(async (event) => {
} catch (error) {
if (isUnauthorizedError(error)) {
clearNewApiAuthCookies(event);
logger.warn("未登录或登录态失效", {
error: toSafeLogError(error)
});
return createErrorResponse(401, "未登录");
}
logger.error("失败", {
error: toSafeLogError(error)
});
return createUpstreamErrorResponse(error, "获取生图详情失败");
}
});
+21 -1
View File
@@ -1,15 +1,35 @@
import type { IImageGenerationStatsData } from "#shared/types/openai";
import { getGenerationStats } from "~~/server/utils";
import {
createApiLogger,
createSuccessResponse,
createUpstreamErrorResponse,
getGenerationStats,
toSafeLogError
} from "~~/server/utils";
export default defineEventHandler(async () => {
const logger = createApiLogger("images.stats");
logger.info("开始");
try {
const result = await getGenerationStats();
logger.done("成功", {
totalRequests: result.totalRequests,
successRequests: result.successRequests,
failedRequests: result.failedRequests,
runningRequests: result.runningRequests,
totalImages: result.totalImages
});
return createSuccessResponse<IImageGenerationStatsData>(
result,
"获取生图统计成功"
);
} catch (error) {
logger.error("失败", {
error: toSafeLogError(error)
});
return createUpstreamErrorResponse(error, "获取生图统计失败");
}
});