feat: 支持批量生图

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-04-27 01:40:46 +08:00
parent 9a715dede8
commit 7f59b555c6
22 changed files with 1731 additions and 394 deletions
+67 -9
View File
@@ -1,5 +1,8 @@
// server/api/images/history/[id]/public.post.ts - 生图历史公开接口:将当前用户单条生图记录发布到广场。
import type { IImagePublicStateData } from "#shared/types/openai";
// server/api/images/history/[id]/public.post.ts - 生图历史公开接口:将当前用户单个批次中选定的图片发布到广场。
import type {
IImagePublicStateData,
IImagePublicStateRequest
} from "#shared/types/openai";
import {
clearNewApiAuthCookies,
createApiLogger,
@@ -17,9 +20,10 @@ import {
*
* 流程:
* 1. 从 httpOnly cookie 读取当前用户 ID。
* 2. 只允许公开当前用户、未软删除的生图记录。
* 3. 允许图片尚未归档时先创建公开记录;广场列表会等 hostedImageUrl 可用后再展示
* 4. 不返回上游响应、图床响应或内部错误详情
* 2. 只允许公开当前用户、未软删除的生图批次记录。
* 3. 请求体可指定 imageIds 和 coverImageId;空 body 时兼容旧前端,默认公开全部成功图
* 4. 允许图片尚未归档时先创建公开记录;广场列表会等封面 hostedImageUrl 可用后再展示
* 5. 不返回上游响应、图床响应或内部错误详情。
*/
export default defineEventHandler(async (event) => {
const logger = createApiLogger("images.history.public.publish");
@@ -28,13 +32,32 @@ export default defineEventHandler(async (event) => {
try {
const userId = getNewApiUserIdFromCookie(event);
const recordId = parseRecordId(getRouterParam(event, "id"));
const requestBody = await readBody<IImagePublicStateRequest | null>(event);
if (
requestBody === null ||
typeof requestBody !== "object" ||
Array.isArray(requestBody)
) {
return createErrorResponse(400, "请求体必须是 JSON 对象");
}
const imageIds = parseImageIds(requestBody.imageIds);
const coverImageId = parseRecordId(requestBody.coverImageId);
logger.info("公开参数", {
userId,
recordId: recordId.toString()
recordId: recordId.toString(),
imageCount: imageIds.length,
coverImageId: coverImageId.toString()
});
const result = await publishImageGenerationToPlaza(userId, recordId);
const result = await publishImageGenerationToPlaza({
userId,
recordId,
imageIds,
coverImageId
});
if (!result) {
logger.warn("记录不存在", {
@@ -52,9 +75,19 @@ export default defineEventHandler(async (event) => {
return createErrorResponse(403, "图片已下架,无法公开");
}
if (result === "INVALID_IMAGES") {
logger.warn("公开图片参数无效", {
userId,
recordId: recordId.toString()
});
return createErrorResponse(400, "请选择可公开的图片");
}
logger.done("成功", {
userId,
recordId: recordId.toString()
recordId: recordId.toString(),
imageCount: result.imageIds?.length ?? 0,
coverImageId: result.coverImageId
});
return createSuccessResponse<IImagePublicStateData>(result, "图片已公开");
@@ -68,7 +101,7 @@ export default defineEventHandler(async (event) => {
}
if (isBadRequestError(error)) {
return createErrorResponse(400, "生图记录 ID 不正确");
return createErrorResponse(400, getBadRequestMessage(error));
}
logger.error("失败", {
@@ -90,6 +123,17 @@ const parseRecordId = (value: string | undefined): bigint => {
return BigInt(value);
};
const parseImageIds = (value: unknown): bigint[] => {
if (!Array.isArray(value) || value.length === 0) {
throw createError({
statusCode: 400,
statusMessage: "请选择可公开的图片"
});
}
return Array.from(new Set(value.map((item) => parseRecordId(String(item)))));
};
const isBadRequestError = (error: unknown): boolean => {
return (
typeof error === "object" &&
@@ -98,3 +142,17 @@ const isBadRequestError = (error: unknown): boolean => {
error.statusCode === 400
);
};
const getBadRequestMessage = (error: unknown) => {
if (
typeof error === "object" &&
error !== null &&
"statusMessage" in error &&
typeof error.statusMessage === "string" &&
error.statusMessage
) {
return error.statusMessage;
}
return "生图记录 ID 不正确";
};