项目:xyMall | 版本:v1.0 | 生成日期:2026-10-05 | 依据:源码分析
本文契约全部从后端 Controller 层源码提取(路径前缀 xy-mall/server/src/main/java/com/xiaoye/mall/server/modules/)。全站共 51 个 Controller;用户端前缀 /api/**,管理端 /api/admin/**,另有 WebSocket 端点 /ws/im 与静态资源 /upload/**。在线调试可启动后端后访问 Knife4j 文档 http://localhost:8081/doc.html(prod profile 关闭)。
R{code, message, data}(framework-core/.../result/R.java):成功 code=200, message="success";业务失败由 GlobalExceptionHandler 组装,HTTP 状态码规则为:BizException 携带 401/400 时透出对应 HTTP 状态,其余(含 500 语义)HTTP 一律 200 由 body code 区分;参数校验失败返回 code 400;系统异常返回 code 500"系统繁忙,请稍后再试";路径不存在返回 HTTP 404;方法不支持返回 HTTP 405。Authorization: Bearer <token>。用户端经 @LoginUser Long userId 注入当前用户(LoginUserArgumentResolver:验签 → 黑名单 → 账号状态 → 会话版本四重校验);管理端经 @AdminUser Long adminUserId 注入(AdminUserArgumentResolver,token subject 形如 admin:{adminUserId})。@RequirePermission("goods:update") 权限码,AOP 校验并异步写入 admin_operation_log 审计(含入参脱敏、IP、耗时、成败)。RateLimitFilter 对 /api/* IP 维度 300 次/分兜底;敏感接口叠加 @RateLimiter(如下单 10 次/60s、模拟支付 10 次/60s、支付宝创建支付 10 次/60s)。pageNum(默认 1)+ pageSize(默认 10,服务端 clamp 到上限 100),响应为 MyBatis-Plus IPage{records, total, size, current}。yyyy-MM-ddTHH:mm:ss(JSON 解析失败会收到"请求体格式错误"提示)。POST /api/order 支持请求头 X-Idempotency-Key,重放返回首次订单号。两点对整体契约风格的理解,有助于快速读后面各表。URL 设计:接口以资源名词复数为主(/api/goods、/api/order、/api/coupon),用 HTTP 方法表达意图,仅在语义确实无法用 CRUD 覆盖处引入动词子路径(如 POST /api/seckill/{seckillId}/grab、PUT /api/order/{orderNo}/cancel、POST /api/user/plus/purchase);管理端沿用同一风格再加 /admin 前缀隔离,便于网关/nginx 层按前缀施加统一策略(限流、访问控制)。鉴权模型:本系统没有"拦截器 + 公开路径白名单"的常见做法,而是参数解析器驱动——接口是否需要登录,取决于其方法签名是否声明 @LoginUser/@AdminUser 参数(分别由 framework-web 的 WebConfig 与业务侧 modules/admin/config/AdminWebConfig 注册解析器),不声明即为公开接口。这使"哪些接口公开"在 Controller 源码里一目了然,但也要求评审时把"注解与业务语义是否匹配"当固定检查项:给需登录的接口漏注解会直接裸奔,给公开接口误注解则会挡掉匿名流量。
| 方法 | 路径 | 用途 | 关键入参 | 响应 data |
|---|---|---|---|---|
| GET | /api/captcha | 获取图形验证码 | - | {captchaToken, captchaImage(base64)} |
| POST | /api/user/register | 注册(验证码校验 + 唯一索引兜底 + 注册赠券/赠余额) | {loginName, password, nickName?, captchaToken, captchaCode} | {token,...}(UserLoginVO) |
| POST | /api/user/login | 登录(登录失败计数锁定 LoginFailGuard) | {loginName, password, captchaToken, captchaCode} | {token,...} |
| GET | /api/user/info | 当前用户信息 | - | UserVO(含余额/积分/成长值) |
| PUT | /api/user/info | 修改资料 | {nickName, mobile?, email?...} | - |
| PUT | /api/user/password | 修改密码(成功后 token_version+1 全端失效) | {oldPassword, newPassword} | - |
| POST | /api/user/logout | 登出(当前 token 入 Redis 黑名单) | Header token | - |
| POST | /api/user/deactivate | 注销账户(验密码;余额积分清零写流水;token 全失效) | {password} | - |
/api/admin)| 方法 | 路径 | 用途 | 说明 |
|---|---|---|---|
| POST | /api/admin/login | 管理员登录 | 先过 sys_config: admin.ip-whitelist IP 白名单;返回 token + 昵称 |
| POST | /api/admin/logout | 登出 | token 入黑名单 |
| GET | /api/admin/profile | 当前管理员信息 | 返回昵称、roleCodes[]、perms[] 权限码集合(前端登录后刷新权限快照) |
| PUT | /api/admin/password | 修改密码 | 改后 token_version+1 强制重登 |
| PUT | /api/admin/name | 修改登录名/昵称 | - |
/api/cart)| 方法 | 路径 | 用途 | 关键入参 |
|---|---|---|---|
| GET | /api/cart/list | 我的购物车 | - |
| POST | /api/cart | 加购((user_id,goods_id,sku_id) 唯一键,重复加购累加数量) | {goodsId, goodsCount, skuId?} |
| PUT | /api/cart/{cartItemId} | 修改数量 | {goodsCount} |
| DELETE | /api/cart/{cartItemId} | 删除购物项 | - |
/api/order)| 方法 | 路径 | 用途 | 关键入参 | 响应 data |
|---|---|---|---|---|
| POST | /api/order | 从购物车下单(@RateLimiter(10/60s);X-Idempotency-Key 幂等头可选) | Body SaveOrderContext{cartItemIds[], addressId, userCouponId?, usePoints?, remark?, invoiceType?, invoiceTitle?, invoiceTaxNo?, groupBuyId?, teamId?};Header X-Idempotency-Key? | 订单号 string |
| GET | /api/order/list | 我的订单分页 | orderStatus?, pageNum?, pageSize? | IPage<OrderVO> |
| GET | /api/order/detail/{orderNo} | 订单详情(含 items、payDeadline;顺带惰性触发超时关单) | - | OrderVO |
| GET | /api/order/logistics/{orderNo} | 物流轨迹 | - | List<OrderLogistics> |
| PUT | /api/order/{orderNo}/cancel | 取消订单(状态机+CAS,回补库存/券/积分) | - | - |
| PUT | /api/order/{orderNo}/finish | 确认收货 | - | - |
| POST | /api/order/paySuccess | 模拟支付回调(xy-mall.payment.mock-enabled 开关,fail-closed) | orderNo, payType | - |
/api/pay/alipay)| 方法 | 路径 | 用途 | 说明 |
|---|---|---|---|
| POST | /api/pay/alipay/create | 创建支付宝支付(校验归属与状态,@RateLimiter) | 入参 orderNo;返回跳转所需信息 |
| GET | /api/pay/alipay/page | 生成支付宝收银台自动提交表单(整页跳转) | - |
| GET | /api/pay/alipay/return | 同步回跳:验签 + 主动查单确认后跳回商城 | - |
| POST | /api/pay/alipay/notify | 异步回调:RSA2 验签 + 幂等确认,返回文本 success | 迟到支付(订单已关闭)走 compensateLatePayment 按流水幂等退款 |
| 方法 | 路径 | 用途 |
|---|---|---|
| POST | /api/after-sales | 用户发起售后(按订单实付锁定 refund_amount,一单一条唯一) |
| GET | /api/after-sales | 我的售后列表 |
| GET | /api/admin/after-sale/page | 售后分页(管理端) |
| PUT | /api/admin/after-sale/{afterSaleId}/handle | 审核:status=1 同意退款至余额并 CAS 关单;2 驳回 |
| GET | /api/admin/order/{orderId}/packages | 查看订单拆单包裹 |
| POST | /api/admin/order/{orderId}/packages | 创建包裹(拆单发货演示) |
| PUT | /api/admin/order/packages/{packageId}/ship | 包裹发货 |
| 方法 | 路径 | 用途 | 关键入参 |
|---|---|---|---|
| GET | /api/goods/list | 商品分页(分类/品牌/价格区间/排序) | goodsCategoryId?, brand?, minPrice?, maxPrice?, sort?, pageNum?, pageSize? |
| GET | /api/goods/search | 搜索(ES 引擎可用时优先相关度,回落 MySQL like;记录 search_log) | keyword?, ...同上 |
| GET | /api/goods/suggest | 搜索联想词 | keyword |
| GET | /api/goods/top | 热卖排行榜 | limit?(默认 8) |
| GET | /api/goods/brands | 在售品牌去重列表 | - |
| GET | /api/goods/also-viewed | 看完又看(同品类他人最近浏览) | goodsId, limit? |
| GET | /api/goods/detail/{goodsId} | 商品详情(Redis 缓存 10min + 空值缓存 60s + 延迟双删) | - |
| GET | /api/category/list | 三级分类树 | - |
| GET | /api/index/info | 首页聚合(轮播 + 热销/新品/推荐楼层) | - |
| GET | /api/index-config/list | 楼层配置列表 | configType |
| GET | /api/carousel/list | 轮播图 | - |
| GET | /api/config/public | 公开运营配置(运费/积分/热搜词/首页装修/模拟支付开关) | - |
| GET | /api/promotion/active | 生效中的满减活动 | - |
| GET | /api/health | 健康检查 | - |
| 方法 | 路径 | 用途 |
|---|---|---|
| GET | /api/coupon/list | 可领券列表 |
| POST | /api/coupon/{couponId}/receive | 领券(receive_seq 唯一键防并发超领) |
| POST | /api/coupon/exchange | 兑换码换券 |
| GET | /api/coupon/mine | 我的券(按状态筛选) |
| POST | /api/coupon/usable | 结算可用券试算(body: cartItemIds[]) |
| GET | /api/seckill/list | 进行中秒杀场次 |
| GET | /api/seckill/server-time | 服务端时间(前端倒计时对时) |
| POST | /api/seckill/{seckillId}/grab | 抢购(@DistributeLock(seckill:grab) → Redis Lua 预扣 → DB 条件扣减建单) |
| GET | /api/group-buy/list | 拼团商品列表 |
| POST | /api/group-buy/{buyId}/teams | 开团 |
| POST | /api/group-buy/{buyId}/teams/{teamId}/join | 参团 |
| GET | /api/group-buy/teams/{teamId} | 队伍进度 |
| GET | /api/user/plus | PLUS 会员状态 |
| POST | /api/user/plus/purchase | 购买/续费 PLUS |
| 方法 | 路径 | 用途 |
|---|---|---|
| GET/POST | /api/user/balance、/api/user/balance/recharge | 余额查询/充值(prod user.recharge-enabled 默认关,防无凭证加钱) |
| GET/POST | /api/user/points、/api/user/points/sign | 积分查询/每日签到(sign_date 生成列唯一键防重复签到) |
| GET/POST | /api/address/list、POST /api/address、PUT/DELETE /api/address/{addressId} | 收货地址 CRUD |
| POST/GET | /api/user/footprint/{goodsId}、/api/user/footprint | 记录/查询浏览足迹 |
| GET/POST/DELETE | /api/favorite/list、/api/favorite/{goodsId}/toggle、/api/favorite/{goodsId} | 收藏列表/切换/移除 |
| GET | /api/goods-comments、/summary、/page | 商品评价列表/好评率/分页 |
| POST | /api/goods-comments、/{commentId}/append、/{commentId}/like | 发表评价(校验订单归属与已完成状态)/追评/点赞(comment_like 去重) |
| GET | /api/user/messages、/unread-count、PUT /{id}/read、/read-all | 站内消息 |
| GET | /api/user/data-export | 个人数据导出(CSV blob) |
| GET | /api/user/im/history | 用户侧 IM 历史 |
| POST/GET | /api/upload(UserUploadController)、GET /upload/{filename}(UploadViewController) | 图片上传(MinIO 失败回落本地)/ 访问 |
/api/admin/**)| 业务域 | 端点(方法 路径) | 说明 |
|---|---|---|
| 商品 | GET /admin/goods/page、GET /admin/goods/{goodsId}、GET /{goodsId}/skus、POST /admin/goods、PUT /{goodsId}、DELETE /{goodsId}、PUT /batch-status、GET /export | 商品 CRUD/SKU/批量上下架/CSV 导出(权限 goods:*;更新后触发缓存延迟双删) |
| 分类 | GET /admin/category/page、GET /options、POST、PUT /{categoryId}、DELETE /{categoryId}、DELETE /batch | 分类管理 |
| 订单 | GET /admin/order/page、GET /{orderId}/detail、GET /export、PUT /{orderId}/status、PUT /batch-status、GET /{orderId}/logistics、POST /{orderId}/logistics、PUT /{orderId}/express | 订单流转(状态机判定)、物流轨迹补写、快递单号录入 |
| 用户 | GET /admin/users/page、GET /{userId}/detail、GET /{userId}/assets、PUT /{userId}/lock、PUT /{userId}/password、POST /{userId}/balance/adjust、POST /{userId}/points/adjust、GET /export | 锁定/重置/资产调整(均写流水) |
| 优惠券 | GET /admin/coupon/page、POST、PUT /{couponId}、PUT /{couponId}/status、DELETE /{couponId}、GET /admin/user-coupons/page、PUT /{userCouponId}/void、POST /grant | 券定义与用户持券管理 |
| 秒杀 | GET /admin/seckill/page、POST、PUT /{seckillId}、PUT /{seckillId}/status、DELETE /{seckillId} | 场次管理(保存时预热 Redis 库存 key) |
| 首页运营 | GET/POST/PUT/DELETE /admin/carousel...、/admin/index-config...、DELETE /batch | 轮播与楼层配置 |
| 满减/拼团/卖家 | GET/POST/PUT/DELETE /admin/promotion...、/admin/group-buy...、/admin/sellers... | 营销与多商户管理 |
| 评价 | GET /admin/comments/page、DELETE /{commentId} | 评价治理(含商家回复经用户态接口) |
| 配置 | GET /admin/config/list、PUT /admin/config | sys_config 运营参数 |
| RBAC | GET /admin/rbac/roles、POST/PUT/DELETE /roles...、GET /permissions、GET /roles/{roleId}/permission-ids、PUT /roles/{roleId}/permissions、GET /admin/managers/page、POST /admin/managers、PUT /{managerId}、PUT /{managerId}/password、POST /migrate-md5、DELETE /{managerId} | 角色/权限点/管理员账号 |
| 审计 | GET /admin/operation-logs/page | 操作日志查询 |
| 仪表盘 | GET /admin/dashboard/stats、GET /trend、GET /top-searches、GET /operate | 核心指标/趋势/热搜词 |
| 库存预警 | GET /admin/stock/alerts(StockAlertController,归属 stock/) | 低库存清单。注:库存流水只写不查,GET /admin/stock-log/page 后端无实现、前端亦无调用,为纯文档债(见《13》D11、《15》§9) |
| IM | POST /admin/im/send、GET /admin/im/history、GET /admin/im/sessions | 客服发送/历史/会话列表 |
| 上传 | POST /admin/upload | 后台图片上传(upload:image 权限) |
管理端同链路但解析器为 AdminUserArgumentResolver(subject admin:{id}、校验 admin_user.token_version);写接口再过 @RequirePermission AOP:无权限抛业务异常,有权限则把模块/权限码/入参(脱敏)/IP/耗时异步落 admin_operation_log。WebSocket /ws/im 在握手拦截器中执行与 HTTP 相同的四重校验(见第 01 篇第 7 节)。
握手之后的消息帧契约(modules/im/ws/ImWebSocketHandler.handleTextMessage)值得单独记录:客户端发送 JSON 文本帧 {content, toUserId?},content 非空且不超过 500 字,管理员角色必须携带 toUserId 指定会话对象;非法 JSON 帧静默忽略(不断连),超长内容直接丢弃。服务端把消息落库 im_message(read_flag=0)后,向用户本人与在线管理端各推送一份 {from, userId, content, time};接收方不在线则只落库。历史消息由 HTTP 接口 /api/user/im/history、/api/admin/im/history 补取,形成"WebSocket 管实时、HTTP 管历史"的双通道分工。
| 场景 | HTTP | body code | body message 示例 | 前端行为 |
|---|---|---|---|---|
| 成功 | 200 | 200 | success | 拦截器直接返回 data |
| 未登录/token 失效 | 401 | 401 | 请先登录 / 登录已失效,请重新登录 | 清登录态,401 去重后跳 /login?redirect= |
| 参数/业务校验失败 | 400 | 400 | 商品库存不足 / 参数错误 | ElMessage 错误提示(silent 请求除外) |
| 业务规则失败 | 200 | 500(BizException 默认)或自定义 | 该场次每人限抢一件 / 秒杀尚未开始 | ElMessage 错误提示 |
| 系统异常 | 200 | 500 | 系统繁忙,请稍后再试 | ElMessage 错误提示 |
| JSON 解析失败 | 200 | 400 | 请求体格式错误,请检查字段与日期格式(ISO: yyyy-MM-ddTHH:mm:ss) | 提示修正 |
| 路径不存在 | 404 | 404 | 资源不存在 | - |
| 方法不支持 | 405 | 405 | 请求方法不支持 | - |
| 触发限流 | 200 | 自定义 | 下单过于频繁,请稍后再试 | 提示稍后再试 |
这套错误约定的设计哲学是"HTTP 层只区分两类失败,语义细节全在 body":401/400 透出真实状态码,是因为前端 axios 拦截器需要据 401 触发统一登出、据 400 触发表单类提示;其余业务失败一律 HTTP 200,让错误文案(如"该场次每人限抢一件")原样抵达前端组件直接展示给用户,避免大量业务性失败被网关与监控误判为服务故障。代价是监控告警不能只盯 HTTP 5xx,需配合 body code 分布与 Micrometer 耗时/异常指标(见《10-性能设计与优化》的可观测性部分)共同判断系统健康度;前端也应区分"可重试失败"(限流、库存竞争)与"终态失败"(参数错误),前者引导用户重试,后者不提示重试以免雪上加霜。
请求:
POST /api/order HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
Content-Type: application/json
X-Idempotency-Key: 6f1c2c9e-checkout-42
{
"cartItemIds": [101, 102],
"addressId": 3,
"userCouponId": 7,
"usePoints": true,
"remark": "尽快发货",
"invoiceType": 1,
"invoiceTitle": "张先生"
}成功响应(HTTP 200):
{ "code": 200, "message": "success", "data": "110517920000011234" }失败响应(库存不足,HTTP 400):
{ "code": 400, "message": "商品库存不足,下单失败", "data": null }说明:data 为订单号(userId + 时间戳 + 4 位随机,saveOrderWithRetry 在唯一键冲突时换号重试,最多 3 次);重放同 X-Idempotency-Key 返回首次订单号。服务端按"券→满减→积分→PLUS→运费"顺序重算总价写入 order_master.total_price,并在同事务内条件扣减库存、核销券、扣积分,随后把支付截止时间推入 Redis ZSet 延迟队列。
请求:
POST /api/seckill/5/grab HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...成功响应:
{ "code": 200, "message": "success", "data": "510517920030045678" }失败响应示例(均 HTTP 200 + body code):
{ "code": 500, "message": "手慢了,该场次已抢完或已结束", "data": null }
{ "code": 500, "message": "该场次每人限抢一件", "data": null }说明:data 为生成的秒杀订单号(seckill_stock 条件扣减成功 + seckill_success 记录建单/复用后返回);订单 30 分钟内未支付将由三重兜底关闭并回补秒杀库存(SeckillService.onOrderClosed)。