秒杀系统高并发优化实战(C++ / Drogon):4.2 秒杀商品详情接口开发
列表是"有哪些可以抢",详情是"这一个还能抢多少"。GET /api/seckill/{skuId} 按 id 查单个商品——它引出一个 Drogon 路由的关键细节:路径参数怎么取、静态路径和参数路径撞名怎么办。
配套代码:
src/service/SeckillService.cc(detailSku)、src/controllers/SeckillController.cc(detailSku)、src/main.cc三参 handler 路由。
一、是什么、坑在哪、本质一句话
是什么:商品详情 = 按 skuId 从 seckill_sku 查出单条商品,前端用来展示"这个商品的库存/时间"。只读、按主键查。
坑在哪:
- 路径参数取错形式:Drogon 三参 handler
(req, callback, path)只在该路由有{}占位符时才正确绑定第 3 参;无占位符路由用三参会被fromRequest<std::string>兜底坑到运行时exit(1)。 - skuId 不校验:拿路径里的字符串直接拼 SQL,注入/非法值风险;应
stoll+ 正整数校验。 - 静态路径 /api/seckill/list 与参数路径 /api/seckill/{skuId} 冲突:同前缀两路由,Drogon 怎么选?
本质一句话:详情接口的本质是"按主键只读投影"——路径参数走三参 handler,skuId 先 stoll 校验,静态/参数路径靠 Drogon 静态优先规则共存。
二、路由:GET /api/seckill/{skuId}(三参 handler)
带 {} 占位符的路由,handler 用三参形式,第 3 参 skuIdStr 来自占位符:
1 | // src/main.cc |
Drogon 路由铁律:handler 必须是两参
(req, callback)形式,除非路由有{}占位符才用三参。无占位符路由用三参,HttpBinder 会对第 3 参按req->as<std::string>()兜底绑定,而fromRequest<std::string>未特化 → 运行时exit(1)。所以/api/seckill(无占位符)用两参,/api/seckill/{skuId}(有占位符)用三参。
三、路径参数校验:stoll + 正整数
detailSku 先把 skuIdStr 用 std::stoll 转 int64_t,非法或 ≤0 直接 400,不进 DB:
1 | // src/controllers/SeckillController.cc(节选) |
四、静态路径 vs 参数路径不冲突
/api/seckill/list(静态)和 /api/seckill/{skuId}(参数)同前缀,Drogon 按"静态优先"匹配——请求 /api/seckill/list 命中静态路由,请求 /api/seckill/1 命中参数路由,两者不冲突、不用手写区分。
1 | // src/service/SeckillService.cc(detailSku 节选) |
五、可运行验证
1 | curl -s http://127.0.0.1:8080/api/seckill/1 | python -m json.tool |
功能抉择(本篇核心权衡)
① 为什么路径参数要用三参 handler,而不是从 query string 取?
REST 语义上"资源标识"放路径(/api/seckill/1)比放 query(/api/seckill?skuId=1)更规范,且 Drogon 对 {} 占位符原生支持三参绑定,干净。代价是必须记牢"无占位符路由不能用三参"的铁律(否则运行时 exit)。
② 为什么 skuId 必须先 stoll 校验再查?
路径里的字符串不可信:非数字会 stoll 抛异常,负数/0 是非法资源。先转 + 校验,非法直接 400 不进 DB,既防注入(走参数化 ?)也防无谓查询。
③ 为什么不存在返回 404 而不是 409/500?
404 = 资源不存在,语义最准(前端据此展示"商品下架/错误链接")。业务拒绝(409)留给"售罄/重复下单",系统错误(500)留给 DB 异常——三态分明,客户端才能正确分支。
小结
- 商品详情 = 按主键只读投影,
GET /api/seckill/{skuId},路径参数走三参 handler。 - skuId 先
stoll+ 正整数校验,非法 400,不进 DB;不存在 404。 - 静态路径
/api/seckill/list与参数路径/api/seckill/{skuId}靠 Drogon 静态优先共存,不冲突。 - DATETIME 同样
DATE_FORMAT转字符串,复用 3.8replyData回包。🐾

