更新所有业务API文档,明确持久化资源的正式ID必须为服务端生成的稳定UUID,本地调试或接口失败时可使用语义ID作为fallback。新增数据库迁移脚本0022_opaque_ids,用于将历史语义ID转换为稳定UUID,并同步外键关联、详情记录的key字段以及审计日志的实体ID引用。新增该迁移的单元测试用例,验证ID替换与关联数据同步的逻辑正确性。调整MiniAPP前端代码,优化导航工具函数的格式,移除废弃函数并修改首页跳转逻辑,使用接口返回的UUID作为详情跳转参数。
12 KiB
玩法管理 Admin API
适用端:
WonderQ-Admin、WonderQ-Admin-UI。状态:已实现契约。本文件参考
WonderQ-MiniAPP/src/pages/play/components/playData.ts定义玩法分类和路线的数据结构,以及管理端需要的查询、维护和排序接口。它不是 MiniAPP Public API 文档。
当前实现:WonderQ-Admin 通过迁移 0015_wanfa 创建 WanfaCategory、WanfaRoute 表并导入初始数据;0022_opaque_ids 将历史语义 ID 转换为稳定 UUID;WonderQ-Admin-UI 已接入分类和路线的查询、新增、编辑、删除及排序操作。
所有 JSON 响应遵循 三端统一 API 响应契约,成功业务对象位于 data,失败时 data 为 null。
路线接口只维护分类和路线摘要字段。路线详情不写入 WanfaRoute,由独立 DetailRecord 通过 docs/detail-api.md 管理,详情记录的 key 等于路线 ID。首页玩法推荐和玩法页路线点击后统一跳转 /pages/detail/index?routeId={route.id};无关联路线时才回退到需求页。
分类和路线的正式 id 均为服务端生成的稳定 UUID 字符串。下方本地数据映射中的语义 ID 只用于 MiniAPP fallback 和迁移前数据识别,不作为正式接口响应 ID;不要在序列化时临时随机生成 ID。
领域边界
玩法管理只维护玩法分类和路线卡片内容:
- 分类名称和展示顺序。
- 路线标题、副标题、封面、路线数量和需求关键词。
- 分类与路线的新增、编辑、删除和排序。
- 首页玩法推荐与分类的关联由首页内容域维护,本域只提供可被关联的分类和路线数据。
玩法领域不负责:
- 商品、商品详情、预订或订单。
- 目的地实体和目的地维护。
- 线索创建或线索跟进。
- Product、ProductImage 或其他已移除商品关联表。
routeCount 只是前台卡片展示数量,不是 Product 表的外键或实时关联统计。
接口清单
API 前缀为 /api/admin,除登录接口外均需要后台 JWT。
| 方法 | 路径 | 用途 |
|---|---|---|
GET |
/api/admin/wanfa/categories |
获取全部玩法分类及其路线 |
POST |
/api/admin/wanfa/categories |
新增玩法分类 |
PATCH |
/api/admin/wanfa/categories/{categoryId} |
编辑玩法分类 |
DELETE |
/api/admin/wanfa/categories/{categoryId} |
删除玩法分类 |
PATCH |
/api/admin/wanfa/categories/reorder |
调整玩法分类顺序 |
POST |
/api/admin/wanfa/categories/{categoryId}/routes |
新增分类路线 |
PATCH |
/api/admin/wanfa/categories/{categoryId}/routes/{routeId} |
编辑分类路线 |
DELETE |
/api/admin/wanfa/categories/{categoryId}/routes/{routeId} |
删除分类路线 |
PATCH |
/api/admin/wanfa/categories/{categoryId}/routes/reorder |
调整分类内路线顺序 |
通用约定
- 请求和响应使用 JSON,字段使用 camelCase。
- 所有管理接口需要
Authorization: Bearer <admin-jwt>。 - 创建、编辑、删除和排序成功后写入审计日志,再提交事务。
- 变更接口返回最新变更对象;排序接口返回排序后的
items。 - ID 由后端生成并作为非空字符串返回。初始化迁移时应优先保留
playData.ts中已有的稳定 ID。 - 分类和路线的数组顺序就是管理端和前台的展示顺序,接口不要求前端依赖
sortOrder字段。 - 空集合返回
[],不能返回null或省略字段。 - 成功结果放入
data;失败返回数字code、msg、data: null,可选errorCode和details。 - 被首页玩法推荐关联的分类不能直接删除;需先调用首页内容域的移除关联接口,否则返回
409 WANFA_CATEGORY_RECOMMENDED。
数据类型
以下类型与 playData.ts 保持字段兼容。管理端接口不应向前台模型强制增加商品 ID、预订 ID 或数据库关联字段。
type WanfaConfig = {
categories: WanfaCategory[];
};
type WanfaCategory = {
id: string;
label: string;
routes: WanfaRoute[];
};
type WanfaRoute = {
id: string;
title: string;
subtitle: string;
image: string;
routeCount: number;
demandKeyword: string;
};
type WanfaCategoryCreate = {
label: string;
};
type WanfaCategoryPatch = {
label?: string;
};
type WanfaRouteCreate = {
title: string;
subtitle: string;
image: string;
routeCount: number;
demandKeyword: string;
};
type WanfaRoutePatch = Partial<WanfaRouteCreate>;
type WanfaReorderRequest = {
itemIds: string[];
};
字段约束
| 字段 | 类型 | 必填 | 约束和用途 |
|---|---|---|---|
category.id |
string |
响应必填 | 分类稳定标识,服务端生成 UUID;创建后在列表、详情、排序和删除请求中保持不变。 |
category.label |
string |
是 | 左侧分类显示名称,去除首尾空白后不得为空。 |
category.routes |
WanfaRoute[] |
响应必填 | 当前分类下的路线,按展示顺序返回。 |
route.id |
string |
响应必填 | 路线稳定标识,服务端生成 UUID;详情 key 与该 ID 一致。 |
route.title |
string |
是 | 路线卡片标题,去除首尾空白后不得为空。 |
route.subtitle |
string |
是 | 路线卡片副标题或目的地说明。 |
route.image |
string |
是 | 可直接用于图片组件的封面 URL。 |
route.routeCount |
number |
是 | 卡片右下角展示的路线数量,服务端应校验为不小于 0 的整数。 |
route.demandKeyword |
string |
是 | 跳转需求页时可使用的预填关键词,去除首尾空白后不得为空。 |
字段最大长度应由后端 schema 统一定义,并同步到 Admin UI 表单校验;在未形成统一长度常量前,前端不能通过截断文本代替服务端校验。
接口详情
获取分类及路线
GET /api/admin/wanfa/categories
Authorization: Bearer <admin-jwt>
成功响应:
{
"code": 200,
"msg": "success",
"data": {
"categories": [
{
"id": "family-route",
"label": "亲子路线",
"routes": [
{
"id": "family-water",
"title": "亲子玩水",
"subtitle": "贵州·轻松节奏与自然课堂",
"image": "https://example.test/assets/family-water.jpg",
"routeCount": 4,
"demandKeyword": "亲子玩水"
}
]
}
]
}
}
新增分类
POST /api/admin/wanfa/categories
Authorization: Bearer <admin-jwt>
Content-Type: application/json
请求:
{ "label": "亲子路线" }
成功返回 201 和 data 内的新分类对象,初始 routes 为 [],并追加到分类列表末尾。
编辑分类
PATCH /api/admin/wanfa/categories/{categoryId}
Authorization: Bearer <admin-jwt>
Content-Type: application/json
请求只允许修改分类名称:
{ "label": "家庭路线" }
成功返回 data 内的更新后 WanfaCategory。不存在的分类返回 404,业务码为 WANFA_CATEGORY_NOT_FOUND。
删除分类
DELETE /api/admin/wanfa/categories/{categoryId}
Authorization: Bearer <admin-jwt>
为避免误删路线,分类仍包含路线时不得级联删除,应返回 409,业务码为 WANFA_CATEGORY_NOT_EMPTY。删除前由 Admin UI 提示先移除分类内路线。删除成功返回:
{
"code": 200,
"msg": "success",
"data": { "id": "family-route" }
}
分类排序
PATCH /api/admin/wanfa/categories/reorder
Authorization: Bearer <admin-jwt>
Content-Type: application/json
请求必须完整包含当前全部分类 ID,不能重复:
{ "itemIds": ["photo-route", "family-route", "healing-route"] }
成功返回:
{
"code": 200,
"msg": "success",
"data": { "items": [] }
}
其中 items 为排序后的 WanfaCategory[]。缺少 ID、包含未知 ID 或出现重复 ID 时返回 400,业务码为 WANFA_CATEGORY_REORDER_INVALID。
新增路线
POST /api/admin/wanfa/categories/{categoryId}/routes
Authorization: Bearer <admin-jwt>
Content-Type: application/json
请求:
{
"title": "亲子玩水",
"subtitle": "贵州·轻松节奏与自然课堂",
"image": "https://example.test/assets/family-water.jpg",
"routeCount": 4,
"demandKeyword": "亲子玩水"
}
成功返回 201 和 data 内新建的 WanfaRoute,并追加到对应分类路线末尾。分类不存在返回 404,业务码为 WANFA_CATEGORY_NOT_FOUND。
编辑路线
PATCH /api/admin/wanfa/categories/{categoryId}/routes/{routeId}
Authorization: Bearer <admin-jwt>
Content-Type: application/json
请求体为 WanfaRoutePatch,只更新提交的字段。成功返回 data 内更新后的 WanfaRoute;分类或路线不存在时分别返回 404,业务码为 WANFA_CATEGORY_NOT_FOUND 或 WANFA_ROUTE_NOT_FOUND。
删除路线
DELETE /api/admin/wanfa/categories/{categoryId}/routes/{routeId}
Authorization: Bearer <admin-jwt>
成功返回 data: { "id": "..." }。删除后,分类内路线保持原有相对顺序。
分类内路线排序
PATCH /api/admin/wanfa/categories/{categoryId}/routes/reorder
Authorization: Bearer <admin-jwt>
Content-Type: application/json
请求必须完整包含该分类当前全部路线 ID,不能重复:
{ "itemIds": ["family-grassland", "family-water", "family-village"] }
成功返回排序后的路线:
{
"code": 200,
"msg": "success",
"data": { "items": [] }
}
分类不存在返回 404,业务码为 WANFA_CATEGORY_NOT_FOUND;路线 ID 不完整、重复或不属于该分类时返回 400,业务码为 WANFA_ROUTE_REORDER_INVALID。
当前本地数据映射
迁移初始数据时,分类和路线应按以下 ID 与顺序导入:
| 分类 ID | 分类名称 | 路线 ID(当前顺序) |
|---|---|---|
family-route |
亲子路线 | family-water、family-village、family-grassland |
photo-route |
旅拍路线 | miao-photo、peak-photo、terrace-photo |
healing-route |
疗愈路线 | mountain-healing、hot-spring-healing、river-healing |
team-building |
团建 | team-challenge、team-stream、team-culture |
guizhou-panorama |
贵州全景 | classic-panorama、mountain-panorama、wild-panorama |
private-custom |
私人定制 | private-family、private-business、private-wild |
图片别名的当前解析规则位于 playData.ts 的 routeImageByAsset 和 resolveRouteImage:已配置别名解析为完整远程 URL,完整 http URL 直接使用,其他值按 /assets/guizhou/{image}.jpg 解析。Admin API 建议保存最终 URL,Admin UI 通过现有媒体上传接口获取 URL 后再提交 image。
Admin UI 对接要求
Admin UI 应按以下方式调用:
- 进入玩法管理页时调用
GET /api/admin/wanfa/categories,以返回数组顺序渲染分类和路线。 - 添加分类调用
POST /categories;编辑和删除分类分别调用对应PATCH、DELETE。 - 添加、编辑和删除路线使用分类嵌套路由。
- 上移或下移分类时提交完整分类 ID 列表;上移或下移路线时提交完整路线 ID 列表。
- 每次变更成功后以接口返回数据更新本地状态;必要时重新请求列表,不直接拼接数据库字段。
- 删除非空分类前展示阻止性提示,不自动级联删除路线。
- 处理
401、404、409、422和5xx,并在保存中禁用重复提交。
建议的 Admin UI API 封装函数:
getWanfaCategories();
createWanfaCategory(input: WanfaCategoryCreate);
updateWanfaCategory(categoryId: string, input: WanfaCategoryPatch);
deleteWanfaCategory(categoryId: string);
reorderWanfaCategories(itemIds: string[]);
createWanfaRoute(categoryId: string, input: WanfaRouteCreate);
updateWanfaRoute(categoryId: string, routeId: string, input: WanfaRoutePatch);
deleteWanfaRoute(categoryId: string, routeId: string);
reorderWanfaRoutes(categoryId: string, itemIds: string[]);
后端落地边界
玩法数据由 WonderQ-Admin 的 WanfaCategory、WanfaRoute ORM 模型和 Admin API 负责持久化;WonderQ-Admin-UI 负责 API 类型、请求封装、表单、删除确认和排序交互。playData.ts 仅作为迁移初始数据来源,不能视为数据库或 API 数据。
相关文档: