项目:xiaoye-admin-react | 版本:v1.2 | 生成日期:2026-10-06 | 依据:源码分析
本篇是接口的"可执行样张":选取 10 个核心接口,给出完整的请求/响应示例、可直接复制的 curl 命令,以及错误信封的全场景示范。所有示例值都按 MSW handler 的数据规则构造(src/mocks/modules/ 四域文件),派生规则可追溯的值在注释里标注「示意值」及其来源索引,读者照着规则能手工复算出任意一行的值——这正是演示型 Mock"确定性数据"的设计意图(见《04-数据模型》第 7 节)。接口的完整清单(42 个)与字段级类型见《15-接口全量参考》,类型设计规约见《03-接口设计》2.3,本篇不重复列表,只给"拿着就能联调"的样张与"照着就能扩写"的模板。
读法建议:先读第 2 节的五条通用约定(读任何示例前必须知道的事),再按自己的任务跳读——联调登录看示例一,接菜单权限看示例二三,做列表页看示例四到七,接系统管理看示例八九,做仪表盘看示例十;接真实后端前必须通读第 4 节的错误信封全场景与第 5 节的 OpenAPI 信封约定。全部示例基于演示环境的 MSW(pnpm dev 或 pnpm build:mock 产物),接入真实后端后响应值会变,但信封结构与状态语义不变——那是第 5 节要固化的部分。
五条约定贯穿全部示例。第一,信封:所有接口返回 { code, msg, data, showType? }(类型定义 src/api/interface/index.ts 的 Result/ResultData<T>),成功 code = 200;MSW 侧由 src/mocks/modules/_shared.ts 的 ok(data) 产出(msg 固定为 "success"),错误由 fail(msg, code=500) 产出(data 固定为 null)。第二,鉴权头:除登录/验证码外的接口都需要 x-access-token 头(自定义头名,非标准 Authorization),演示环境的合法值形如 mock-token-admin(登录接口按 mock-token-{username} 规则签发)。第三,路径:契约路径为 /hooks/...(src/api/config/servicePort.ts 的 PORT1),实际请求为 baseURL(/api) + 契约路径,curl 直接打 /api/hooks/... 即可(dev server 端口 5173,演示站 443)。第四,分页:入参 ReqPage { current?, pageSize? },出参 ResPage<T> { list, current, pageSize, total }。第五,密码摘要:涉及密码的字段一律传 md5 摘要,演示环境初始密码 123456 的摘要为 e10adc3949ba59abbe56e057f20f883e(md5 是约定而非加密,见《08-安全与权限设计》2.3)。
错误行为的判定完全在前端拦截器(src/api/index.ts):HTTP 200 且 code == 401 走登录态清理与跳转;其他非 200 业务码按 showType 分流提示后以信封对象 reject(页面 catch 取 err.msg);HTTP 层错误(非 2xx、超时、断网)走另一条路径。这套语义的第 4 节有逐场景样例。
curl -s -X POST http://localhost:5173/api/hooks/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"e10adc3949ba59abbe56e057f20f883e"}'请求体字段(ReqLogin):username 明文登录名;password 为 md5 摘要(示意值:123456 的摘要)。
成功响应(HTTP 200):
{
"code": 200,
"data": { "access_token": "mock-token-admin" },
"msg": "success"
}access_token 为示意值:MSW 按 mock-token-{username} 规则签发(src/mocks/modules/auth.ts),真实后端应为 JWT 或等价凭证。失败响应两型:字段缺失返回 { "code": 500, "data": null, "msg": "用户名和密码不能为空" };摘要不匹配返回 { "code": 500, "data": null, "msg": "用户名或密码错误" }——两者都是 HTTP 200,呈现方式由拦截器按默认 ERROR_MESSAGE 弹出。注意演示环境密码是会话内可变的:调用示例八的改密接口后,currentPasswordMd5 即时更新、刷新页面即重置。
前端调用侧(src/views/login/components/LoginForm.tsx)唯一特别之处是传 { skipErrorHandler: true },把错误改为页面内联 Alert 呈现(但 401 的登录态清理照常执行,见《18-代码走读实录》2.1)。
curl -s http://localhost:5173/api/hooks/menu/list \
-H "x-access-token: mock-token-admin"无请求体,用户身份完全由鉴权头决定(这是权限接口的硬约定,见《15-接口全量参考》3.1)。响应为菜单树(77 个节点,此处摘前两个节点示意):
{
"code": 200,
"data": [
{
"path": "/home/index",
"element": "/home/index",
"meta": {
"key": "home", "icon": "HomeOutlined", "title": "首页",
"isLink": "", "isHide": false, "isFull": false, "isAffix": true, "isKeepAlive": true
}
},
{
"path": "/auth", "redirect": "/auth/page",
"meta": { "key": "auth", "icon": "LockOutlined", "title": "权限管理", "isHide": false },
"children": [
{ "path": "/auth/page", "element": "/auth/page/index",
"meta": { "key": "pageMenu", "title": "页面权限", "isKeepAlive": true } },
{ "path": "/auth/button", "element": "/auth/button/index",
"meta": { "key": "authButton", "title": "按钮权限", "isKeepAlive": true } }
]
}
],
"msg": "success"
}节点结构与 RouteObjectType(src/routers/interface/index.ts)同名对应:element 字符串会被 ConvertRouter 拼接为 /src/views{element}.tsx 去匹配视图文件,因此element 必须指向真实存在的 index.tsx;目录节点用 redirect 指向首个子菜单;meta 各字段的语义见《04-数据模型》3.4。MSW 的 handler 直接返回静态菜单 JSON 的 data(src/assets/json/authMenuList.json),所以 mock 与开发态短路用的是同一份数据。失败语义提醒:生产态此接口失败时前端清登录态跳登录页,绝不回退静态全量菜单(ADR-01)——联调时"接口不通就整站登出"是设计行为。
curl -s http://localhost:5173/api/hooks/auth/buttons \
-H "x-access-token: mock-token-admin"响应(示意值:来自 src/assets/json/authButtonList.json,键为页面 meta.key):
{
"code": 200,
"data": {
"authButton": ["add", "edit", "delete", "import", "export"],
"useProTable": ["add", "batchAdd", "export", "batchDelete", "status"]
},
"msg": "success"
}data 是 { [menuKey]: string[] } 的按页分组码表:页面内 <AuthButton authority="add"> 或 useAuthButton() 只查当前页 meta.key 对应的数组,同名码在不同页面互不干扰。生产接入时后端下发的键值集合必须与前端 AuthButton 的 authority 取值约定一致(核对清单见《11-扩展开发指南》场景五)。
curl -s -X POST http://localhost:5173/api/hooks/user/list \
-H "Content-Type: application/json" \
-H "x-access-token: mock-token-admin" \
-d '{"current":1,"pageSize":2,"gender":2,"createTimeSort":"desc"}'响应(数据规则可复算,全部为示意值):
{
"code": 200,
"data": {
"list": [
{
"id": "1", "username": "张伟", "gender": 2, "age": 20,
"idCard": "3301199001011000", "email": "user1@example.com",
"address": "浙江省杭州市西湖区文一路 1 号",
"createTime": "2024-01-01 08:00:00",
"status": false, "avatar": "/src/assets/images/avatar.png"
},
{
"id": "4", "username": "赵娜", "gender": 2, "age": 23,
"idCard": "3301199401011004", "email": "user4@example.com",
"address": "广东省深圳市南山区科技园 4 号",
"createTime": "2024-05-05 08:04:00",
"status": true, "avatar": "/src/assets/images/avatar.png"
}
],
"current": 1, "pageSize": 2, "total": 29
},
"msg": "success"
}复算规则(src/mocks/modules/demo.ts):固定 87 条,第 i 条(i 从 0 起)的 id 为 i+1;gender 为 i % 3 === 0 ? 2 : 1(所以 gender=2 筛出的恰好是 0、3、6、9…共 29 条,total 由此而来);姓名为姓氏表第 i % 10 项加名字表第 (i*7) % 10 项(0 号 = 张 + 伟);age = 20 + i % 25;status 为 i % 5 !== 0;createTime 的月/日/时/分分别取 i%12、i%28、i%10、(i*13)%60 派生(0 号 = 2024-01-01 08:00:00)。排序先于分页执行,createTime 的 YYYY-MM-DD HH:mm:ss 格式字符串比较即时间先后比较。这套确定性使任何分页边界、筛选命中数、排序结果在任意环境任意次运行中完全一致——出现分页 Bug 时可精确复现到某一条记录。
curl -s -X POST http://localhost:5173/api/hooks/user/add \
-H "Content-Type: application/json" \
-H "x-access-token: mock-token-admin" \
-d '{"username":"王小明","gender":1,"age":28,"email":"wxm@example.com","address":"上海市浦东新区世纪大道 88 号"}'成功响应:{ "code": 200, "data": null, "msg": "success" }。失败响应(唯一性校验,业务规则而非参数校验):{ "code": 500, "data": null, "msg": "用户名「王小明」已存在" }。
请求体为 ReqUserSave(无 id 形态),handler 侧补齐服务端生成字段:id 用自增游标 nextId()(起始 1000,避免 Date.now() 的同毫秒重复)、createTime 取 formatNow()、status 固定 true、idCard 置空串。注意"新增成功但响应 data 为 null"是本项目的惯例——写操作不回传实体,页面成功后以递增 searchVersion 触发列表重查拿到最新数据(见《05-核心流程》流程五),这避免了"前端本地拼实体"与"服务端补字段"的口径漂移。
curl -s -X POST http://localhost:5173/api/hooks/user/edit \
-H "Content-Type: application/json" \
-H "x-access-token: mock-token-admin" \
-d '{"id":"2","username":"李芳然","gender":1,"age":21,"email":"user2@example.com","address":"浙江省杭州市西湖区文一路 2 号","status":true}'成功响应:{ "code": 200, "data": null, "msg": "success" }。失败响应两型:id 不存在返回 "用户不存在或已被删除";用户名与他人冲突返回 "用户名「李芳然」已存在"(唯一性校验排除自身,这是编辑与新增校验逻辑的差异点)。编辑复用 ReqUserSave(含 id 形态),行内状态开关可以只提交 { id, status }——同一提交体覆盖全量编辑与局部更新是该域的简化取舍(系统管理域则拆出了显式 Patch 类型,见示例七的对照说明)。
curl -s -X POST http://localhost:5173/api/hooks/user/delete \
-H "Content-Type: application/json" \
-H "x-access-token: mock-token-admin" \
-d '{"id":"9"}'成功响应:{ "code": 200, "data": null, "msg": "success" };id 不存在返回 { "code": 500, "data": null, "msg": "用户不存在或已被删除" }。前端在发起前必须经 modal.confirm 二次确认(src/hooks/useMessage.tsx 封装,删除类操作的红按钮语义见《05-核心流程》流程五)——这是页面级约定,接口层不做任何确认语义。
curl -s -X POST http://localhost:5173/api/hooks/system/account/list \
-H "Content-Type: application/json" \
-H "x-access-token: mock-token-admin" \
-d '{"current":1,"pageSize":1}'响应(第 2 条数据的派生值,示意值标注见括号):
{
"code": 200,
"data": {
"list": [
{
"id": "2", "username": "user002", "nickname": "李强", "gender": 1,
"email": "user2@example.com", "phone": "13800000137",
"roleIds": ["2"], "roleNames": ["系统管理员"],
"status": true,
"remark": "外部合作账号,权限最小化",
"createTime": "2024-02-02 09:07:00"
}
],
"current": 1, "pageSize": 1, "total": 45
},
"msg": "success"
}45 条账号的派生规则(src/mocks/modules/system.ts):id 为 i+1;username 首条为 admin、其余为 user + 三位序号;roleIds 取 ROLES[i % 5],且 i % 3 === 0 时追加一个不重复的第二角色(一账号多角色场景);roleNames 与 roleIds 顺序严格一致(类型 JSDoc 契约:列表直接展示、避免前端二次查表);status 为 i % 7 !== 0;phone 由索引数值拼接。第 1 条(id "1")是内置超级管理员:不可删除、不可禁用、用户名不可改,三条业务保护都有专属错误文案(见示例九)。
curl -s -X POST http://localhost:5173/api/hooks/system/account/delete \
-H "Content-Type: application/json" \
-H "x-access-token: mock-token-admin" \
-d '{"id":"1"}'响应(HTTP 200,业务拒绝):
{
"code": 500,
"data": null,
"msg": "内置账号不可删除"
}这是"业务规则错误信封"的标准样例:HTTP 层完全成功,拒绝语义全部装在信封里。同域的其他保护文案照此模式(均为示意值,取自 handler 源码):批量删除含内置账号返回 "选中项包含内置账号,不可删除";删除仍被账号引用的角色返回 "该角色下仍有账号,请先调整这些账号的所属角色";删除有子级/有商品的分类分别返回 "该分类下仍有子分类,请先删除子分类" / "该分类下仍有商品,请先移除或改绑这些商品"。接真实后端时,这些文案就是后端业务校验的验收用例(handler 即规格,见《15-接口全量参考》第 4 节);若希望某类拒绝只静默提示而非 error 弹窗,让后端在信封加 showType: 1(见第 4 节)。
curl -s http://localhost:5173/api/hooks/business/overview \
-H "x-access-token: mock-token-admin"响应(数值为示意值——它们由内存数据实时聚合,任何一次增删商品/出入库后立即变化,不可当常量断言):
{
"code": 200,
"data": {
"goodsTotal": 60,
"stockTotal": 128,
"warningCount": 9,
"brandTotal": 6,
"trend": [
{ "date": "2026-09-30", "inCount": 34, "outCount": 21 },
{ "date": "2026-10-01", "inCount": 12, "outCount": 40 }
],
"categoryRatio": [
{ "name": "数码", "value": 18 },
{ "name": "家电", "value": 15 }
]
},
"msg": "success"
}BusinessOverview 是全项目唯一的"聚合读"形态:一次返回汇总数(四项)、近 7 日趋势(trend,日期为示意值)与分类占比(categoryRatio),无分页。三个统计口径都由服务端裁决——warningCount 是"库存低于 stockThreshold 阈值"的商品数、categoryRatio 从商品数据实时聚合,前端只呈现不推导(数据映射原则见《04-数据模型》3.4)。做仪表盘类接口时照此形态:一个端点返回一屏所需的全部聚合,不要让页面发四五个请求自己拼。
信封的可选字段 showType(src/api/helper/errorShowType.ts,借鉴 ant-design-pro 约定)决定错误以何种方式呈现,未携带按 ERROR_MESSAGE。以下五例覆盖全部取值(均为 HTTP 200,msg 为示意值):
| 场景 | 响应体 | 前端行为 |
|---|---|---|
| 静默失败(页面自行处理) | {"code":500,"data":null,"msg":"表单校验未通过","showType":0} | 不弹任何提示,页面 catch 自行处理 |
| 警告级(可恢复) | {"code":500,"data":null,"msg":"库存不足,已为你保留草稿","showType":1} | message.warning 黄色提示 |
| 普通错误(默认) | {"code":500,"data":null,"msg":"内置账号不可删除"} | message.error 红色提示 |
| 重要错误(需强提醒) | {"code":500,"data":null,"msg":"服务端数据异常,请联系管理员","showType":3} | notification.error,描述栏附 code |
| 登录态失效 | {"code":401,"data":null,"msg":"登录过期"} | 清 token + 清权限 + 跳登录页(带 redirect 回跳);此分支不受 skipErrorHandler 影响 |
与 HTTP 层错误的对照:HTTP 401(非 200 状态码)走 checkStatus 映射,行为与信封 401 完全一致(双路径收敛,见《03-接口设计》1.3);超时 30 秒提示 common.requestTimeout 词条;断网跳 /500。后端接入的错误契约口诀:能归为业务规则的一律 HTTP 200 + 信封 code + showType 控制呈现;只有"这个请求根本不该成功"的基础设施级失败才用非 2xx 状态码。两种登录态失效路径(信封 401 与 HTTP 401)必须至少实现其一,且行为对齐。
openapi/oneapi.json 用两层 schema 表达信封:业务实体独立建模(LoginBody、AccountItem 等),信封类型以 data 字段 $ref 业务实体组合(ResultLogin、ResultNull、ResultMenuList、ResultAuthButtons 等)。这与手写侧 Result/ResultData<T> 的泛型组合完全同构——两种写法在类型层面互相印证。四个信封原型的 JSON Schema 片段如下(节选自契约文件的结构):
{
"ResultNull": {
"type": "object",
"properties": {
"code": { "type": "integer", "example": 200 },
"msg": { "type": "string", "example": "success" },
"data": { "nullable": true }
}
},
"ResultLogin": {
"type": "object",
"properties": {
"code": { "type": "integer" },
"msg": { "type": "string" },
"data": { "$ref": "#/components/schemas/LoginToken" }
}
},
"ResultMenuList": {
"type": "object",
"properties": {
"code": { "type": "integer" },
"msg": { "type": "string" },
"data": { "type": "array", "items": { "$ref": "#/components/schemas/RouteMeta" } }
}
}
}全场景对照表(同一份业务数据在四种信封原型下的形态):
| 场景 | 信封原型 | data 取值 | showType |
|---|---|---|---|
| 登录成功 | ResultLogin | { "access_token": "..." } | — |
| 写操作成功(登出/更新/删除) | ResultNull | null | — |
| 读列表成功 | ResultMenuList 等按实体 | 数组或 ResPage 形态对象 | — |
| 业务校验失败 | ResultNull(code 500) | null | 0/1/2/3 按呈现需要 |
| 会话过期 | ResultNull(code 401) | null | — |
showType 在当前契约文件中是预留字段(schema 未逐路径声明,运行时由拦截器识别)⚠️ 待确认:接真实后端时若需要差异化错误呈现,建议在后端导出的契约中为 ResultNull 显式补上 showType 的 integer 枚举定义,避免生成代码丢字段。契约写作的两条硬规则重申(详见《15-接口全量参考》5):operationId 即生成函数名,改动等于改公开签名;接入真实后端时若后端 swagger 不含信封包装,需先补一层信封 schema 再执行 pnpm openapi,否则生成代码的返回类型与运行时信封不匹配。
本篇 10 例覆盖了四类接口形态(登录读、权限读、CRUD 写、聚合读)与全部错误语义(五档 showType、双 401 路径、业务保护拒绝),可当作三类任务的底稿:联调时直接 curl 验证后端行为是否符合信封约定;写新接口时按"示例五/六的请求体 + 示例九的错误文案"模式扩写;给后端同步契约时,第 4 节表格与第 5 节对照表就是交接文档。维护口径与《15-接口全量参考》一致:示例是结果不是源头——接口行为变更时先改源码三处对齐(api 模块、MSW handler、openapi 契约),再回填本篇对应示例;演示环境的所有派生值(87 条用户、45 条账号、签名规则、错误文案)都以 src/mocks/modules/ 四域文件为唯一事实源,本文与源码不一致时以源码为准并在下次维护时修正本文。