秒杀系统高并发优化实战(C++ / Drogon):4.1 秒杀商品列表接口开发
登录模块(第三章)做完,进入第四章——基础秒杀功能(阶段一:Drogon + MySQL 直连)。先来最轻松的一个:商品列表接口 GET /api/seckill/list。它只读、不碰事务、不校验 token,是秒杀系统里最"安静"的一条路,但有几个 C++/Drogon 细节值得讲。
配套代码:
src/service/SeckillService.cc(listSkus)、src/controllers/SeckillController.cc(listSkus)、src/main.cc路由注册。
一、是什么、坑在哪、本质一句话
是什么:商品列表 = 从 seckill_sku 读出所有在售商品(id/name/stock/total/起止时间),前端用来展示"有哪些可以抢"。只读查询,不分页(阶段一 LIMIT 100)。
坑在哪:
- DATETIME 直接绑成 tm/Date:Drogon ORM 把
DATETIME列默认当tm或Date类型,序列化 JSON 时容易类型歧义或丢精度。 - 返回内部字段:把
create_time、库存真实值无脑返回,可能泄漏不该给前端的字段。 - 在列表接口上挂事务:只读查询开事务是浪费连接、还拉长行锁占用。
本质一句话:列表接口的本质是"只读投影"——只用 DbClient 不用事务,DATETIME 显式 DATE_FORMAT 成字符串,字段只挑前端要的。
二、路由:GET /api/seckill/list(只读,无 token)
列表、详情是只读,不校验 token(阶段一 /api/seckill/* 整体走 body 双通道,鉴权留到前端阶段)。路由在 main.cc 手动挂:
1 | // src/main.cc |
三、SeckillService::listSkus:DATE_FORMAT 显式转字符串
关键细节:SQL 里用 DATE_FORMAT(start_time, '%Y-%m-%d %H:%i:%s') AS start_time,把 DATETIME 显式转成字符串,避免 Drogon 把列绑成 tm/Date 带来的歧义。列表接口只暴露"够前端展示"的字段,不返回 create_time 等内部字段。
1 | // src/service/SeckillService.cc(节选) |
四、控制器 replyData
SeckillService 回调拿到 Json::Value 数组,SeckillController::listSkus 用 3.8 抽出的 replyData 统一回包:成功 {code:0, data:[...]},失败 {code:1, msg:"query failed"} + 500。
1 | // src/controllers/SeckillController.cc(节选) |
五、可运行验证
1 | curl -s http://127.0.0.1:8080/api/seckill/list | python -m json.tool |
功能抉择(本篇核心权衡)
① 为什么 DATETIME 要显式 DATE_FORMAT,而不是直接读列?
Drogon 的 Result 把 DATETIME 默认当 tm/Date 类型,序列化成 JSON 时要自己处理格式,容易出"时区偏移/精度丢失/类型不匹配"的歧义。SQL 里先 DATE_FORMAT 成定长字符串,C++ 侧一律 as<std::string>,零歧义、零额外转换。
② 为什么列表不挂事务、只用 DbClient?
只读查询不需要原子性保证,开事务只会白白占一条 DB 连接、还可能拉长无关行锁的占用窗口。阶段一的"事务"只留给"扣库存 + 落订单"这种写组合(见 4.3)。
③ 为什么只挑 id/name/stock/total/时间,不返回 create_time?
列表是给前端展示的"橱窗",create_time 是内部审计字段,无展示价值且可能泄漏系统信息。最小暴露面原则:只读投影只给前端要的。
小结
- 商品列表 = 只读投影,
GET /api/seckill/list,不校验 token、不开事务。 - DATETIME 用
DATE_FORMAT显式转字符串,规避 Drogon 类型绑定歧义。 - 字段只挑前端要的(id/name/stock/total/起止时间),不泄漏内部字段。
- 控制器复用 3.8 的
replyData统一回包。🐾

