flnxi-Wu/docs/api/模板系统API文档.md
2026-05-25 16:57:08 +08:00

10 KiB
Raw Blame History

上传模板系统 API 文档

更新时间:2026-05-13

本文档按当前代码重新核对,覆盖主应用里真实存在的模板、字段和分类相关 API。旧文档中的 /api/field-pool、/api/admin/field-pool/sync 当前没有路由,不应再作为可调用接口。

1. 基础规则

  • Base URL:/api
  • 认证方式:Session Cookie
  • 管理端接口要求当前用户角色为 ADMIN
  • 自定义模板的创建、更新、删除、迁移会检查许可证能力 customTemplates
  • 字段池同步是内部实现,主要由 syncFieldPoolFromTemplates() 在模板和分类接口中触发

常见错误结构:

{
  "error": "错误信息",
  "errorCode": "ERROR_CODE",
  "details": {}
}

当前常见错误码:

错误码 含义
UNAUTHORIZED 未登录或不是管理员
LICENSE_REQUIRED 当前许可证不允许使用自定义模板能力
VALIDATION_ERROR 请求参数不合法
LIST_FAILED / GET_FAILED / UPDATE_FAILED 服务端读取或写入失败
VERSION_CONFLICT 更新模板时版本号冲突
TEMPLATE_STRUCTURE_LOCKED 模板已有素材使用,不能直接做破坏性结构变更
TEMPLATE_IN_USE 模板已被素材使用,不能删除
DUPLICATE_ENTRY / DUPLICATE_NAME 名称或字段等唯一约束冲突

2. 管理端模板接口

2.1 获取模板列表

GET /api/admin/upload-templates

返回所有数据库模板,包含字段和 materialCount。

关键返回字段:

  • id
  • name
  • description
  • status:draft、active、archived
  • materialType
  • materialKind:IMAGE、VIDEO、AUDIO、OTHER
  • version
  • fieldGroups
  • displayConfig
  • fields
  • materialCount

2.2 获取单个模板详情

GET /api/admin/upload-templates/:id

返回模板字段、使用该模板的素材预览和字段填写率统计。

补充说明:

  • materials 最多返回 10 条,用于后台预览。
  • fieldStats 是管理员模板详情能力,不是普通用户侧接口。
  • 填写率低于 30% 的字段会标记为低填写率。

2.3 创建模板

POST /api/admin/upload-templates
Content-Type: application/json

示例:

{
  "name": "家具音频素材模板",
  "description": "用于音频素材上传",
  "status": "draft",
  "materialType": "音频",
  "materialKind": "AUDIO",
  "fieldGroups": null,
  "displayConfig": null,
  "fields": [
    {
      "key": "skuCode",
      "label": "SKU编码",
      "type": "text",
      "required": false,
      "orderIndex": 0,
      "layoutWidth": "half",
      "validations": null,
      "optionsSource": null,
      "optionsStatic": null,
      "allowedExtensions": null,
      "visibleOnHomeFilter": false,
      "isSearchable": true,
      "isReadOnly": false,
      "addToCategory": false,
      "conditions": null
    }
  ]
}

当前后端强校验:

  • name 必填且不能为空
  • materialType 必填且不能为空
  • materialKind 必须是 IMAGE、VIDEO、AUDIO、OTHER
  • fields 必须是数组;当前后端允许空数组,但业务上建议至少保留一个有效字段
  • fields[].key 必填且在同一模板内唯一

注意:

  • 旧文档写过“字段 key 必须以字母开头、只能包含字母数字下划线”,当前后端模板保存接口没有做这条正则强校验;它可以作为前端/团队约定,但不能写成后端事实。
  • 创建成功会触发字段池内部同步。

2.4 更新模板

PUT /api/admin/upload-templates/:id
Content-Type: application/json

更新时建议带上 version:

{
  "name": "更新后的模板名称",
  "status": "active",
  "materialType": "音频",
  "materialKind": "AUDIO",
  "version": 1,
  "fields": []
}

规则:

  • 如果 version 与服务端当前版本不一致,返回 VERSION_CONFLICT。
  • 更新成功后版本号自动递增。
  • 模板已有素材使用时,不能直接修改素材类型、素材大类、删除字段、修改字段 key 或修改字段类型;这类修改会返回 TEMPLATE_STRUCTURE_LOCKED。
  • 更新成功会触发字段池内部同步。

2.5 删除模板

DELETE /api/admin/upload-templates/:id

规则:

  • 模板不存在返回 NOT_FOUND。
  • 模板已有素材使用时返回 TEMPLATE_IN_USE。
  • 删除成功返回:
{
  "success": true
}

2.6 迁移素材模板

POST /api/admin/upload-templates/:id/migrate

该接口用于把素材迁移到目标模板。它同样要求管理员权限和自定义模板许可证能力。

当前行为:

  • 迁移前会检查目标模板必填字段是否能从旧素材自动补齐;不能补齐时返回 TARGET_TEMPLATE_FIELDS_UNRESOLVED,不会写库。
  • 迁移写库阶段按数据库事务提交;任一素材失败或已不属于源模板时,整体失败并回滚。
  • 目标模板不接收的旧自定义字段和旧标准字段会进入 metadata.templateMigrationBackups 备份区。
  • 可安全清空的旧标准字段会从素材主表清理,避免继续污染新模板字段。
  • 迁移生成的 metadata 超限时返回 METADATA_TOO_LARGE,不会进入事务。

具体请求体以当前代码 app/api/admin/upload-templates/[id]/migrate/route.ts 为准,执行前必须先在测试环境核对目标模板和素材范围。

3. 普通读取接口

3.1 上传页模板列表

GET /api/upload-templates

用于上传弹窗选择模板,返回启用的预制模板和自定义模板。

特点:

  • 不要求管理员权限。
  • 预制模板由 lib/upload-template-presets.ts 生成。
  • 当前预制模板包含音频模板,builtin-audio-material 的 materialKind 为 AUDIO。
  • 自定义模板只返回 status = active 且非 builtin- 开头的模板。

3.2 首页模板筛选列表

GET /api/templates

用于首页模板标签筛选,只返回模板 id、name 和 isBuiltin。

4. 分类字段接口

4.1 分类字段配置

GET /api/category-fields
PATCH /api/category-fields
PUT /api/category-fields

GET 返回:

{
  "managedFields": ["categoryName", "sceneName"],
  "availableFields": [
    {
      "key": "categoryName",
      "label": "使用店铺",
      "source": "standard",
      "type": "select",
      "filterDisplayName": "店铺",
      "filterOrder": 0
    }
  ]
}

PATCH 用于启用或停用某个字段作为首页筛选字段:

{
  "fieldKey": "categoryName",
  "enabled": true
}

PUT 用于批量保存筛选显示名和排序:

{
  "updates": [
    {
      "fieldKey": "categoryName",
      "filterDisplayName": "店铺",
      "filterOrder": 1
    }
  ]
}

4.2 分类选项读取

GET /api/template-categories
GET /api/template-categories?includeAll=true

默认只返回已启用为筛选项的字段;includeAll=true 返回全部字段。

返回示例:

[
  {
    "fieldKey": "categoryName",
    "fieldLabel": "店铺",
    "fieldDefaultLabel": "使用店铺",
    "filterOrder": 0,
    "options": ["默认店铺"]
  }
]

4.3 单个分类字段选项

GET /api/template-categories/:fieldKey
PUT /api/template-categories/:fieldKey

GET 返回单个字段的选项:

{
  "fieldKey": "categoryName",
  "fieldLabel": "店铺",
  "options": ["默认店铺"]
}

PUT 仅管理员可用,用于重写该字段的选项:

{
  "options": ["默认店铺", "旗舰店"]
}

保存时会去重、规范化,并同步更新同 key 的下拉字段。

5. 字段与模板数据边界

模板字段常用结构:

字段 说明
key 数据键名,同一模板内必须唯一
label 页面显示名
type 字段类型,如 text、textarea、number、select、checkbox、radio、date、uppercase
required 是否必填
layoutWidth full 或 half
optionsStatic 下拉/单选选项,后端可接收数组并保存为 JSON 字符串
visibleOnHomeFilter 历史字段,当前首页筛选主要看字段池里的 isFilterEnabled
isSearchable 是否参与搜索
isReadOnly 上传页是否过滤只读字段
addToCategory 是否同步进分类管理
conditions 字段条件配置

conditions 当前已在上传页、编辑页和后端模板字段校验中生效。条件不满足的字段不会显示,不参与必填校验,上传时不会写入本次 metadata;编辑时隐藏字段不参与本次提交覆盖,已有历史值默认保留,避免用户只是保存其他字段时误删旧数据。

模板字段提交链路:

  • POST /api/materials/upload:只接收当前可见字段,隐藏字段会被过滤;metadata 超限返回 METADATA_TOO_LARGE。
  • PUT /api/materials/update:用素材现有值和本次提交值合并判断条件,只保存当前可见字段;metadata 超限返回 METADATA_TOO_LARGE。
  • PUT /api/materials/[id]:旧 JSON 更新入口也执行同一套模板字段校验,不能绕过条件规则。

6. 性能与索引

当前 Prisma schema 中已为模板、字段、字段池和选项保留常用索引,重点包括:

  • UploadTemplate.status
  • UploadTemplate.materialType
  • UploadTemplate.materialKind
  • UploadField.templateId
  • UploadField.key
  • UploadField.visibleOnHomeFilter
  • UploadField.isSearchable
  • UploadField.addToCategory
  • FieldDefinition.source
  • FieldDefinition.isFilterEnabled
  • FieldOption.fieldId

7. 使用建议

  1. 更新模板前先读取详情,带上最新 version 再保存。
  2. 已被素材使用的模板不要直接改结构;新建模板后迁移更稳。
  3. 迁移前仍建议先在测试环境核对字段差异;系统会保留旧字段备份,但管理员仍应确认目标模板字段设计。
  4. 字段 key 虽然当前后端只强校验非空和唯一,但团队约定仍建议使用英文、数字、下划线,避免后续搜索、导出和脚本处理出问题。
  5. 分类管理只适合稳定、选项可控的下拉字段;不要把高频变化的大字段放到首页筛选。
  6. 不要直接调用不存在的字段池 API;需要刷新字段池时,通过模板保存或分类接口触发内部同步。