feat(api-watermark): 添加水印管理相关接口

This commit is contained in:
R524809
2026-09-04 12:45:27 +08:00
parent 70d9d5b92a
commit c2523fe349
10 changed files with 424 additions and 7 deletions
+1
View File
@@ -1,3 +1,4 @@
export * from './collection';
export * from './auth';
export * from './stores';
export * from './watermarks';
+70
View File
@@ -0,0 +1,70 @@
import { z } from 'zod';
const uuid = z.uuid();
const opacity = z
.string()
.regex(/^(0|1)(\.\d{1,4})?$|^0\.\d{1,4}$/, '不透明度必须是 0 到 1 的小数');
const scaleRatio = z
.string()
.regex(/^(0\.([1-9]|[1-9]\d{1,3})|1(\.\d{1,4})?|2(\.0{1,4})?)$/, '缩放比例必须在 0.1 到 2 之间');
export const watermarkPositionSchema = z.enum([
'top_left', 'top_center', 'top_right', 'middle_left', 'center',
'middle_right', 'bottom_left', 'bottom_center', 'bottom_right',
]);
const textConfig = z.object({
text_content: z.string().trim().min(1).max(200),
font_code: z.string().trim().min(1).max(120),
font_size_px: z.number().int().min(12).max(60),
opacity,
position: watermarkPositionSchema,
}).strict();
const imageConfig = z.object({
image_asset_id: uuid,
scale_ratio: scaleRatio,
opacity,
position: watermarkPositionSchema,
}).strict();
export const createWatermarkTemplateRequestSchema = z.object({
name: z.string().trim().min(1).max(120),
kind: z.enum(['text', 'image']),
config: z.union([textConfig, imageConfig]),
}).strict().superRefine((value, context) => {
if (value.kind === 'text' && !('text_content' in value.config)) {
context.addIssue({ code: 'custom', path: ['config'], message: '文字水印需要完整文字配置' });
}
if (value.kind === 'image' && !('image_asset_id' in value.config)) {
context.addIssue({ code: 'custom', path: ['config'], message: '图片水印需要完整图片配置' });
}
});
const partialConfig = z.object({
text_content: z.string().trim().min(1).max(200).optional(),
image_asset_id: uuid.optional(),
font_code: z.string().trim().min(1).max(120).optional(),
font_size_px: z.number().int().min(12).max(60).optional(),
opacity: opacity.optional(),
scale_ratio: scaleRatio.optional(),
position: watermarkPositionSchema.optional(),
}).strict();
export const updateWatermarkTemplateRequestSchema = z.object({
name: z.string().trim().min(1).max(120).optional(),
kind: z.enum(['text', 'image']).optional(),
config: partialConfig.optional(),
}).strict()
.refine((value) => Object.keys(value).length > 0, { message: '至少需要提交一个可修改字段' });
export const listWatermarkTemplatesQuerySchema = z.object({
cursor: z.string().min(1).optional(),
limit: z.coerce.number().int().min(1).max(100).default(20),
}).strict();
export const bindStoreWatermarkRequestSchema = z.object({
template_id: uuid.nullable(),
}).strict();
export type CreateWatermarkTemplateRequest = z.infer<typeof createWatermarkTemplateRequestSchema>;
export type UpdateWatermarkTemplateRequest = z.infer<typeof updateWatermarkTemplateRequestSchema>;
export type ListWatermarkTemplatesQuery = z.infer<typeof listWatermarkTemplatesQuerySchema>;
@@ -0,0 +1,8 @@
-- 首期文字水印使用随渲染运行时部署的 Noto Sans 字体。
SET search_path TO atlas, public;
INSERT INTO watermark_fonts (code, display_name, status)
VALUES ('NotoSans', 'Noto Sans', 'active')
ON CONFLICT (code) DO UPDATE
SET display_name = EXCLUDED.display_name,
status = 'active';