feat(api): 添加安全上传 Skill 和 COS 图片归一化

This commit is contained in:
yuzhe
2026-07-22 18:12:23 +08:00
parent e7e268d4eb
commit b8b4d7a11c
17 changed files with 997 additions and 18 deletions

View File

@@ -4,7 +4,7 @@ import { notesRepository } from '../repositories/notesRepository.js';
import { imagesRepository } from '../repositories/imagesRepository.js';
import { annotationsRepository } from '../repositories/annotationsRepository.js';
import { database, databaseDialect, withTransaction, type QueryContext } from '../database.js';
import { storeUploadedFile } from '../storage.js';
import { storeExternalImageUrl, storeUploadedFile } from '../storage.js';
import { recalculateCollectionStatus } from './collectionsService.js';
import { ensureProjectCompatibilityCollection, recalculateProjectReviewStatus } from './projectsService.js';
@@ -24,8 +24,10 @@ async function prepareFiles(files: UploadedFile[]): Promise<StoredImage[]> {
return Promise.all(files.map(async (file) => ({ ...(await readImageSize(file.path)), ...(await storeUploadedFile(file)) })));
}
function externalImages(images: string[]): StoredImage[] {
return images.map((url) => ({ url, width: 0, height: 0, storageProvider: 'external', storageKey: '' }));
async function prepareExternalImages(images: string[]): Promise<StoredImage[]> {
const prepared: StoredImage[] = [];
for (const image of images) prepared.push(await storeExternalImageUrl(image));
return prepared;
}
async function createRoundInTransaction(
@@ -138,10 +140,11 @@ export const notesService = {
},
async createInProjectFromUrls(projectId: number, title: string, description: string, imageUrls: string[], tags: string[], externalId: string | null = null): Promise<Note> {
const prepared = await prepareExternalImages(imageUrls);
return withTransaction(async (tx) => {
const collectionId = await ensureProjectCompatibilityCollection(projectId, tx);
const id = await tx.insertId("INSERT INTO notes (project_id, collection_id, external_id, title, description, tags, review_status) VALUES (?, ?, ?, ?, ?, ?, 'pending')", [projectId, collectionId, externalId, title, description, JSON.stringify(tags)]);
await createRoundInTransaction(tx, id, projectId, collectionId, { title, description, tags, images: externalImages(imageUrls) }, undefined, 'draft');
await createRoundInTransaction(tx, id, projectId, collectionId, { title, description, tags, images: prepared }, undefined, 'draft');
return (await notesRepository.findById(id))!;
});
},
@@ -162,9 +165,10 @@ export const notesService = {
const collection = await database.one<{ project_id: number }>('SELECT project_id FROM collections WHERE id = ?', [collectionId]);
if (!collection) throw new Error('作品交付集不存在');
const projectId = Number(collection.project_id);
const prepared = await prepareExternalImages(imageUrls);
return withTransaction(async (tx) => {
const id = await tx.insertId("INSERT INTO notes (project_id, collection_id, external_id, title, description, tags, review_status) VALUES (?, ?, ?, ?, ?, ?, 'pending')", [projectId, collectionId, externalId, title, description, JSON.stringify(tags)]);
await createRoundInTransaction(tx, id, projectId, collectionId, { title, description, tags, images: externalImages(imageUrls) }, undefined, 'draft');
await createRoundInTransaction(tx, id, projectId, collectionId, { title, description, tags, images: prepared }, undefined, 'draft');
return (await notesRepository.findById(id))!;
});
},
@@ -183,7 +187,7 @@ export const notesService = {
async createRoundFromUrls(id: number, round: UrlRound, createdBy?: number): Promise<Note> {
const current = await notesRepository.findById(id);
if (!current) throw new Error('作品不存在');
const prepared = { ...round, images: externalImages(round.images) };
const prepared = { ...round, images: await prepareExternalImages(round.images) };
await withTransaction((tx) => createRoundInTransaction(tx, id, current.project_id, current.collection_id, prepared, createdBy, current.review_status));
return (await notesRepository.findById(id))!;
},