项目:xyMall | 版本:v1.2 | 生成日期:2026-10-06 | 依据:源码分析
本文选取 10 个最能覆盖交易主线的接口,给出可直接复制的完整请求/响应示例、错误响应示例与 curl 命令。全部 JSON 字段与字段类型取自后端 VO/Context 源码(xy-mall/server/src/main/java/com/xiaoye/mall/server/modules/**/vo/ 与 context/),数值按以下原则构造:金额一律分(INT,与《03-接口设计》第 1 节全局约定一致);时间 ISO 格式 yyyy-MM-ddTHH:mm:ss;订单号按 userId + 13 位毫秒时间戳 + 4 位随机 的真实格式拼装(OrderServiceImpl.generateOrderNo);商品/用户等演示数据参照 docs/HANDOVER.md 记载的种子数据风格(如演示账号 test001、800 件中文商品)。凡源码无法确认取值的字段(token 明文、具体 ID 等)标注「示意」——它们在真实环境中的形态正确、值不同。响应外层统一为 R{code, message, data},成功 code=200;错误码语义表见《15-接口全量参考》第 2 节,此处只给高频错误样例。curl 命令默认后端 8081、验证码旁路开启(dev 环境验证码填 0000,见《06-部署运行》2.2)。
它与既有两篇契约文档的分工可以这样理解:《03-接口设计》讲"契约为什么这样设计"(风格、鉴权模型、错误哲学),《15-接口全量参考》是"全部 143 个端点的速查字典"(查得到、对得上),而本文是"拿去就能跑的样例集"——三篇分别对应理解、查阅、动手三个动作。选择这 10 个接口的标准是覆盖一条完整的资金链路:拿到验证码、登录、看商品、加购、试算优惠、下单、支付(模拟与真实回调两条通道)、抢一次秒杀、最后以管理端视角查数据。跑通这十步,商城端的核心旅程与后台的治理动作就都触碰过了;其余接口的报文形态都是这十步的变体(分页类、CRUD 类、流水类),按本文的构造原则举一反三即可。联调时的推荐用法是:先跑第 1 节拿验证码,用 shell 变量把登录 token 接力给后续命令(示例中的 $TOKEN/$ADMIN_TOKEN),逐节验证响应结构与本文一致后再接入真实业务数据。
登录与注册的前置接口,返回一次性 captchaToken 与 base64 图片;验证码的一次性消费与 TTL 语义见《05-核心流程》1.2 节①。
{ "code": 200, "message": "success", "data": { "captchaToken": "9f2c1a7e-4b8d-4c3a-b1e2-7d5f6a8c9e01", "captchaImage": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAAoCAIAAACfAViZAAAA..." } }curl -s http://localhost:8081/api/captchadev 旁路开启时(mall.captcha.allow-bypass=true,仅 dev profile),验证码输入固定 0000,但 captchaToken 仍需真实获取并随表单提交。
入参 UserLoginContext(loginName/password 必填,验证码两字段随表单携带)。成功响应 UserLoginVO——只有一个 token 字段,用户信息需再调 /api/user/info 获取,这是一个刻意的轻量契约(token 与资料分离,避免登录接口的响应体随用户模型膨胀)。
{
"loginName": "test001",
"password": "123456",
"captchaToken": "9f2c1a7e-4b8d-4c3a-b1e2-7d5f6a8c9e01",
"captchaCode": "0000"
}成功响应(HTTP 200):
{ "code": 200, "message": "success", "data": { "token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMDAxIiwidmVyIjowfQ.xK9mQ2sT7vB4nE8wL1pR6yH3jC5dF0aZ——示意,真实值为 7 天有效期的 HS256 JWT" } }错误响应示例(凭据错误,HTTP 200 + body code;"用户不存在"与"密码错误"共用同一文案防账号枚举):
{ "code": 500, "message": "登录名或密码错误", "data": null }{ "code": 500, "message": "登录失败次数过多,请 15 分钟后再试", "data": null }curl -s -X POST http://localhost:8081/api/user/login \
-H "Content-Type: application/json" \
-d '{"loginName":"test001","password":"123456","captchaToken":"9f2c1a7e-4b8d-4c3a-b1e2-7d5f6a8c9e01","captchaCode":"0000"}'注册接口 POST /api/user/register 请求体同构({loginName, password, nickName?, captchaToken, captchaCode},密码 6-32 位由 @Size 校验),响应同为 {token},此处不重复展开。
关于这个 token 还有三点值得在联调前知道。第一,有效期 7 天(xy-mall.jwt.expire-millis=604800000),期间无需刷新机制——这是单 token + 会话版本的取舍(《16-决策记录ADR》ADR-12),不要在客户端实现"静默续签"逻辑,过期就重登。第二,token 的 claims 里带 ver(签发时刻的 token_version),用户改密或注销后该值与 DB 不再一致,所有旧 token 立即 401——所以客户端收到 401 时应当清空本地登录态而不是无限重试。第三,登出接口 POST /api/user/logout 会把当前 token 拉入黑名单,黑名单 TTL 与 token 剩余寿命对齐,因此"登出后再用旧 token"与"过期后再用"在客户端看来是同一种 401,无需区分处理。
需请求头 Authorization: Bearer <token>。响应 UserVO,余额/积分单位均为分。
{
"code": 200, "message": "success",
"data": {
"userId": 1001,
"loginName": "test001",
"nickName": "小野同学",
"mobile": "13800001234",
"email": "test001@example.com",
"createTime": "2026-08-01T09:12:45",
"balance": 1000000,
"points": 3280,
"growth": 1520
}
}错误响应(token 过期/黑名单/会话版本不符,HTTP 401):
{ "code": 401, "message": "登录已失效,请重新登录", "data": null }curl -s http://localhost:8081/api/user/info -H "Authorization: Bearer $TOKEN"($TOKEN 为登录返回值;mobile/email「示意」,源码只确认字段存在与类型 String。)
列表接口按分类(含子孙展开)、品牌、价格区间、排序分页查询,响应为 MyBatis-Plus IPage{records, total, size, current}。注意列表项不含 goodsDetail 与 skus(toListVO 刻意置空大字段,《07-模块详解》4.1)。
curl -s "http://localhost:8081/api/goods/list?goodsCategoryId=769&brand=&minPrice=5000&maxPrice=200000&sort=price_asc&pageNum=1&pageSize=2"{
"code": 200, "message": "success",
"data": {
"records": [
{
"goodsId": 21360,
"goodsName": "轻量双肩包 经典款",
"goodsIntro": "轻量耐磨 日常通勤",
"goodsCoverImg": "/upload/6f1c2c9e-backpack.jpg",
"goodsCarousel": "/upload/6f1c2c9e-backpack-1.jpg,/upload/6f1c2c9e-backpack-2.jpg",
"originalPrice": 19900,
"sellingPrice": 15900,
"stockNum": 12,
"goodsCategoryId": 769,
"brand": "XY 甄选",
"sellerId": 1,
"presale": 0,
"goodsSellStatus": 0
},
{ "goodsId": 21361, "goodsName": "三防冲锋伞 自动开合", "goodsIntro": "八骨抗风 一键开收", "goodsCoverImg": "/upload/7a2b3d4e-umbrella.jpg", "originalPrice": 8900, "sellingPrice": 6900, "stockNum": 40, "goodsCategoryId": 769, "brand": "XY 甄选", "sellerId": 1, "presale": 0, "goodsSellStatus": 0 }
],
"total": 46, "size": 2, "current": 1
}
}详情接口响应 GoodsVO 全量(含 goodsDetail 富文本与 skus 列表),此处只展示 SKU 段与预售段(富文本 HTML 内容「示意」):
{
"code": 200, "message": "success",
"data": {
"goodsId": 21360,
"goodsName": "轻量双肩包 经典款",
"goodsDetail": "<p>轻量耐磨,日常通勤首选……(示意,wangEditor 产出的富文本 HTML)</p>",
"originalPrice": 19900,
"sellingPrice": 15900,
"stockNum": 12,
"presale": 0,
"goodsSellStatus": 0,
"skus": [
{ "skuId": 2047, "goodsId": 21360, "skuName": "黑色 标准", "price": 15900, "originalPrice": 19900, "stock": 8 },
{ "skuId": 2048, "goodsId": 21360, "skuName": "灰色 大容量", "price": 17900, "originalPrice": 21900, "stock": 4 }
]
}
}错误响应(商品不存在/已下架/命中 60 秒空值缓存,三种来源同一文案,《12-FAQ》条目 33):
{ "code": 500, "message": "商品不存在或已下架", "data": null }加购入参 {goodsId, goodsCount, skuId?};(user_id, goods_id, sku_id) 唯一键使重复加购累加数量而非新增行(V7 语义,《04-数据模型》第 2 节)。
curl -s -X POST http://localhost:8081/api/cart \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"goodsId": 21360, "goodsCount": 1, "skuId": 2047}'成功响应 {"code":200,"message":"success","data":null}。购物车列表响应 CartItemVO[],invalid=true 的行是已下架/删除商品(前端置灰且不计入合计):
{
"code": 200, "message": "success",
"data": [
{ "cartItemId": 501, "goodsId": 21360, "skuId": 2047, "skuAttrs": "黑色 标准", "goodsName": "轻量双肩包 经典款", "goodsCoverImg": "/upload/6f1c2c9e-backpack.jpg", "sellingPrice": 15900, "goodsCount": 1, "invalid": false },
{ "cartItemId": 502, "goodsId": 21361, "skuId": 0, "skuAttrs": "", "goodsName": "三防冲锋伞 自动开合", "goodsCoverImg": "/upload/7a2b3d4e-umbrella.jpg", "sellingPrice": 6900, "goodsCount": 2, "invalid": false }
]
}错误响应示例:
{ "code": 500, "message": "商品不存在或已下架", "data": null }请求体为 cartItemIds 数组,响应 UserCouponVO[]——其中 discountAmount 是按当前结算金额实算出的可抵扣金额(分),前端券单选直接展示它,提交订单时只回传 userCouponId。
curl -s -X POST http://localhost:8081/api/coupon/usable \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"cartItemIds": [501, 502]}'{
"code": 200, "message": "success",
"data": [
{ "userCouponId": 7, "couponId": 3, "couponName": "满 100 减 10", "couponDesc": "全品类可用", "discount": 1000, "minAmount": 10000, "couponType": 0, "goodsType": 0, "goodsValue": null, "endTime": "2026-10-31T23:59:59", "useStatus": 0, "orderNo": null, "discountAmount": 1000 },
{ "userCouponId": 9, "couponId": 5, "couponName": "无门槛减 5", "couponDesc": "新人专享", "discount": 500, "minAmount": 0, "couponType": 0, "goodsType": 0, "goodsValue": null, "endTime": "2026-11-30T23:59:59", "useStatus": 0, "orderNo": null, "discountAmount": 500 }
]
}错误响应(券门槛不足的场景不在本接口出现——不满足门槛的券直接不出现在返回列表;门槛报错发生在下单链路):
{ "code": 500, "message": "未满足优惠券最低消费金额", "data": null }(上例为 POST /api/order 携带不满足门槛券时的下单侧报错,此处一并收录便于对照。)
入参 SaveOrderContext 全字段(cartItemIds/addressId 必填语义见《15》4.2);请求头 X-Idempotency-Key 可选,24h 内重放返回首次订单号(《05-核心流程》2.2 节②)。本例沿用前文的购物车行:两件商品合计 15900 + 6900×2 = 29700 分,满 100 减 10 券后 28700,命中满减门槛档减免 2000(「示意」,实际取决于 promotion_full 种子),不使用积分,PLUS 未激活,29700 ≥ 9900 包邮——应付 26700 分。
curl -s -X POST http://localhost:8081/api/order \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: 6f1c2c9e-checkout-42" \
-d '{"cartItemIds":[501,502],"addressId":3,"userCouponId":7,"usePoints":false,"remark":"尽快发货","invoiceType":1,"invoiceTitle":"张先生"}'成功响应(data 即订单号):
{ "code": 200, "message": "success", "data": "10011791321600001234" }随后 GET /api/order/detail/10011791321600001234 返回的 OrderVO(extraInfo 为地址与优惠快照拼接的真实格式;payDeadline 为服务端计算的 epoch 毫秒,前端倒计时以此为准):
{
"code": 200, "message": "success",
"data": {
"orderId": 9001,
"orderNo": "10011791321600001234",
"totalPrice": 26700,
"shippingFee": 0,
"pointsUsed": 0,
"pointsDeduct": 0,
"payStatus": 0,
"orderStatus": 0,
"extraInfo": "张先生 13800001234 浙江省杭州市西湖区文一西路 100 号 | 尽快发货 | 优惠券抵扣 10.0 元 | 满减优惠 20.0 元 | 发票:普通电子-张先生",
"createTime": "2026-10-06T10:30:00",
"payDeadline": 1791333000000,
"items": [
{ "orderItemId": 12001, "goodsId": 21360, "goodsName": "轻量双肩包 经典款", "goodsCoverImg": "/upload/6f1c2c9e-backpack.jpg", "sellingPrice": 15900, "goodsCount": 1 },
{ "orderItemId": 12002, "goodsId": 21361, "goodsName": "三防冲锋伞 自动开合", "goodsCoverImg": "/upload/7a2b3d4e-umbrella.jpg", "sellingPrice": 6900, "goodsCount": 2 }
]
}
}错误响应示例(HTTP 200 + body code,语义为业务校验失败):
{ "code": 400, "message": "商品库存不足,下单失败", "data": null }{ "code": 400, "message": "请选择收货地址", "data": null }两处示例的 detail 响应有几个值得对照的字段。extraInfo 是下单时刻的不可变快照(地址 + 备注 + 各层优惠 + 发票按固定顺序拼接),列表接口刻意不返回它(listOrders 中 vo.setExtraInfo(null)),只有详情接口可见——这是隐私最小化的契约细节。payDeadline 只在 orderStatus=0(待支付)时非空,它是服务端按 createTime + order.pay.timeout-minutes 计算的 epoch 毫秒,前端倒计时必须以它为准而非本地时钟(超时分钟数可被运营热调,客户端写死 30 分钟会与后台配置漂移)。items 中的价格是下单时刻的成交快照(sellingPrice),商品随后改价不影响已生成订单——这也是为什么订单金额永远以 order_master.total_price 为准、前端任何"重新试算"都只是展示。幂等行为再强调一次:携带同一个 X-Idempotency-Key 重复提交,返回的是首次的订单号 10011791321600001234,不会产生第二笔订单,因此客户端可以把幂等键做成"每次进入结算页生成一次"的安全重试机制。
paySuccess 是模拟支付入口,受 xy-mall.payment.mock-enabled 开关 fail-closed 控制(代码默认 false,dev/prod 演示配置显式 true,《16-决策记录ADR》ADR-11)。注意两个参数走 query string(@RequestParam),不是请求体。
curl -s -X POST "http://localhost:8081/api/order/paySuccess?orderNo=10011791321600001234&payType=1" \
-H "Authorization: Bearer $TOKEN"成功响应:
{ "code": 200, "message": "success", "data": null }支付成功后订单 orderStatus=1、payStatus=1,并触发站内信、成长值累加与(秒杀/拼团单的)联动回写——联动清单见《05-核心流程》2.2 节④。错误响应示例:
{ "code": 500, "message": "支付通道未开放,请通过收银台完成支付", "data": null }{ "code": 500, "message": "订单当前状态不可支付", "data": null }这是全站唯一的"非 JSON 契约":支付宝以 application/x-www-form-urlencoded 推送签名参数,服务端 RSA2 验签 + 金额比对(《05-核心流程》2.2 节④)后返回纯文本 success(非 R 包装——支付宝网关按此字面值确认)。以下表单字段为支付宝异步通知标准字段(值「示意」):
curl -s -X POST https://pay-callback.zhangzhengyang.com/api/pay/alipay/notify \
-d "out_trade_no=10011791321600001234" \
-d "trade_no=2026100622001400001234567890" \
-d "total_amount=267.00" \
-d "trade_status=TRADE_SUCCESS" \
-d "sign_type=RSA2" \
-d "sign=NONFaJcK1m2XxYyZz0oPq9RsT8uVv7Ww6——示意"成功响应(Content-Type 为 text/html,body 仅一个词):
success验签失败/金额不一致时返回非 success(支付宝将按退避策略重试),服务端同时留 error 日志:
{ "code": 500, "message": "回调金额与订单不一致", "data": null }订单已超时关闭后钱到账的"迟到支付"不再置单,而是走 compensateLatePayment 幂等退款(balance_log reason="迟到支付补偿退款",《12-FAQ》条目 27)——对支付宝仍应答 success 使其停止重试。
四级漏斗(限流 USER 5 次/2s → 分布式锁 → Lua 预扣 → DB 权威扣减)见《05-核心流程》流程四。查询场次列表 GET /api/seckill/list 的响应 SeckillVO[](含剩余库存,供前端渲染"已抢 X%"进度条):
{
"code": 200, "message": "success",
"data": [
{ "seckillId": 5, "goodsId": 21362, "goodsName": "无线蓝牙耳机 Pro", "goodsCoverImg": "/upload/8b3c4d5e-earphone.jpg", "originalPrice": 39900, "seckillPrice": 19900, "seckillStock": 37, "seckillTotal": 100, "seckillStatus": 1, "startTime": "2026-10-06T10:00:00", "endTime": "2026-10-06T12:00:00" }
]
}抢购成功响应(data 为生成的秒杀订单号):
{ "code": 200, "message": "success", "data": "10011791321601005678" }失败响应示例(均 HTTP 200 + body code;前端应对这两类失败设计专门的 UI 态而非当异常处理,《15》6.2 节):
{ "code": 500, "message": "手慢了,该场次已抢完或已结束", "data": null }{ "code": 500, "message": "该场次每人限抢一件", "data": null }{ "code": 500, "message": "抢购繁忙,请重试", "data": null }curl -s -X POST http://localhost:8081/api/seckill/5/grab -H "Authorization: Bearer $TOKEN"管理端登录先过 sys_config: admin.ip-whitelist IP 白名单,入参字段名与用户端不同(loginUserName/loginPassword)。演示账号 admin/admin123(role_id=1,见《00-项目总览》第 5 节;注意《13》D1——全新环境需手工建 admin 账号行):
curl -s -X POST http://localhost:8081/api/admin/login \
-H "Content-Type: application/json" \
-d '{"loginUserName":"admin","loginPassword":"admin123","captchaToken":"9f2c1a7e-...","captchaCode":"0000"}'{ "code": 200, "message": "success", "data": { "token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbjoxIn0.—示意", "nickName": "超级管理员" } }商品分页(权限 goods:list)响应 IPage,含已下架商品(goodsSellStatus=1):
curl -s "http://localhost:8081/api/admin/goods/page?keyword=双肩包&goodsSellStatus=0&pageNum=1&pageSize=1" \
-H "Authorization: Bearer $ADMIN_TOKEN"{
"code": 200, "message": "success",
"data": {
"records": [
{ "goodsId": 21360, "goodsName": "轻量双肩包 经典款", "goodsIntro": "轻量耐磨 日常通勤", "goodsCoverImg": "/upload/6f1c2c9e-backpack.jpg", "originalPrice": 19900, "sellingPrice": 15900, "stockNum": 12, "goodsCategoryId": 769, "brand": "XY 甄选", "sellerId": 1, "presale": 0, "goodsSellStatus": 0 }
],
"total": 1, "size": 1, "current": 1
}
}无权限时的错误响应(@RequirePermission 切面拦截,AdminUserArgumentResolver 的 token 与用户端同密钥但 subject 为 admin:{id} 空间隔离):
{ "code": 500, "message": "无操作权限:goods:update", "data": null }最后把散在十节里的注意事项收拢成四条。第一,响应外层的两种形态:绝大多数接口是 R{code,message,data} JSON;唯一例外是支付宝 notify 的纯文本 success 应答——对接支付网关时切勿套统一响应。第二,HTTP 状态码与 body code 的分工:401/400 语义透出真实 HTTP 状态(前端拦截器据此登出/提示表单错误),其余业务失败一律 HTTP 200,排障时先看 HTTP 码再看 body code(《03-接口设计》第 8 节的哲学说明)。第三,轻量写响应:下单/抢购/审核等写操作的成功 data 是订单号、teamId 这类轻量标量,完整视图一律通过 detail/list 二次获取——调用方不要期待写接口回吐全量对象。第四,字段可信度分级:本文中结构(字段名/类型/嵌套关系)全部对齐 VO 源码可直接使用;数值与 ID 是按种子风格构造的演示值,联调时应以真实环境数据为准;标注「示意」的值(token 明文、富文本 HTML、支付宝签名)形态正确但内容需替换。新增接口的示例报文维护建议:随《11-扩展开发指南》SOP 二的验收步骤,把新端点的三件套(请求/响应/错误示例)追加到本文对应域的章节,保持"契约文档与代码同步漂移为零"(《15》第 14 节的告诫)。
再补充两条容易踩坑的调用纪律。其一是失败响应也是"正常业务结果":秒杀的"已抢完"、下单的"库存不足"、领券的"已达上限"这一类响应在设计上就是高频正常分支(并发竞争的天然结果),调用方应把它们建模为业务态而非异常——前端对抢购失败有专门的 UI 态,重试逻辑只对"可重试失败"(限流、竞争类)生效,对"终态失败"(参数错误、状态不可流转)不提示重试。其二是示例与真实环境的数值对不上时先查三处:sys_config 的运营参数(运费阈值、积分比例、超时分钟数都是热调的,本文按种子值构造)、promotion_full/coupon 的演示活动窗口(过期活动不参与计算)、以及登录账号的 PLUS 状态与积分余额(影响四层优惠的后两层)。三处都排除后仍有出入,再回到《05-核心流程》对照 OrderServiceImpl 的计算顺序逐步对账——顺序错一位(例如先算积分再算满减)就会产生稳定的几分钱级差异,这类"规律性小额偏差"几乎总是计算顺序或取整规则的问题,而非数据问题。