课程设计项目:Spring Boot 3 + Redis Bitmap + Lua 锁座 + 延迟关单 + WebSocket 实时选座 + 经营数据大屏。
- 架构与实现细节见 docs/实现方案.md
- P0 增量设计 spec: docs/superpowers/specs/2026-08-31-p0-increment-design.md
- 对话式订票助手(已落地):设计 spec docs/superpowers/specs/2026-09-21-chat-assistant-design.md · 决策记录 docs/adr/ · code review docs/agents/chat-assistant-code-review.md
- 领域词汇表: CONTEXT.md
- 原始方案见 plan.md
| 维度 | 实现 |
|---|---|
| 防超卖 | Redis Bitmap + Lua 原子锁座;DB CAS + 生成列唯一索引双保险;250 并发抢同一座位 0 超卖 |
| 实时同步 | WebSocket /ws/seat/{sid} + Redis Pub/Sub 跨实例广播 |
| 延迟关单 | Redis ZSet 延迟队列(主链路 5s 扫描) + 定时补偿任务(1min 兜底),双保险 |
| 冷启动守护 | 服务重启/Redis flush 后锁座前自动从 DB 重建 Bitmap,杜绝"看似可选实则已售" |
| 交易闭环 | 锁座 → 支付 → 退票(状态机 PAID→REFUNDING→REFUNDED) + 电子票(HMAC 签名,24h 过期,一次性验票) |
| 工程严谨 | 139 个 JUnit5 + Mockito 单元测试覆盖核心链路;@RateLimit/@Idempotent AOP 注解;OrderService 拆 4 service + 1 core(单文件 ≤ 300 行) |
| 数据可视化 | 管理端经营看板(4 卡片 + 3 图 + 1 表);实时数据大屏(WebSocket 推送锁座事件) |
| 前端体验 | 全局路由守卫(未登录/非管理员自动拦截) · 影片搜索/筛选 · 注册确认密码 · 移动端适配 · 座位图重构 |
| 国际化 | vue-i18n 11 双语(zh-CN / en-US);浏览器语言自动探测 + 本地持久化 + fallbackLocale: zh-CN 防裸 key;29 个专项用例,其中 key-coverage 兜底「两边都漏」的盲点 |
| 对话助手 | LangChain4j + DeepSeek 全局浮窗;8 个只读工具(searchMovies / getSeatSummary / findContiguousSeats / searchFaq 等);返回行动卡片(SEAT_SUGGESTION)跳选座页预选;只读不写(ADR-0002),零副作用;限流分桶匿名 2/min / 登录 10/min(spec #17);LOGIN_REQUIRED 引导匿名用户登录;FAQ 知识库兜底通用问答(spec #20) |
| 功能 | 状态 | 备注 |
|---|---|---|
| 注册/登录(JWT) | ✅ | BCrypt 密码;注册需确认密码;登录态写入 localStorage 供路由守卫读取 |
| 影片列表 + 详情 | ✅ | 包含海报/时长/简介 |
| 影片搜索 + 类型/地区筛选 | ✅ P0 F1 | MySQL LIKE + 精确匹配 |
| 场次选择 | ✅ | 关联影厅+时间 |
| 实时选座(座位图) | ✅ | WebSocket 推送座位变化 |
| 锁座下单(15min 待支付) | ✅ | Lua 原子防超卖 |
| 模拟支付 | ✅ | CAS 防双花 |
| 我的订单 + 状态机 | ✅ | PENDING_PAY/PAID/CANCELLED/REFUNDING/REFUNDED |
| 主动取消 | ✅ | CAS 取消 |
| 退票(模拟退款) | ✅ P0 N1 | 状态机 + 退款日志 + 卡死补偿 |
| 电子票二维码 | ✅ P0 N2 | HMAC 签名 + 24h 过期 + 一次性验票 |
| 中英文切换 | ✅ 2026-09-22 | 顶栏一键切换;首次进站按浏览器语言探测,之后按用户选择持久化 |
| 功能 | 状态 | 备注 |
|---|---|---|
| 影片/影厅/场次 CRUD | ✅ | 含座位自动生成 |
| 手动恢复位图 | ✅ P0 P5 | POST /api/admin/sessions/bitmaps/recover |
| 经营数据看板 | ✅ P0 O3 | 今日票房/订单/趋势/TOP 影片/上座率 |
| 实时数据大屏 | ✅ P0 D1 | WebSocket 推送,/admin/live |
| 功能 | 状态 | 备注 |
|---|---|---|
接口限流(@RateLimit) |
✅ P0 E4 | Redis 滑动窗口 Lua,锁座/支付/退票/聊天分级限流 |
幂等键(@Idempotent) |
✅ P0 E5 | SETNX,锁座/支付前置挡重试 |
| OrderService 拆分 | ✅ P0 E1 | 4 service + 1 core,单文件 ≤ 300 行 |
| 关键路径单测 | ✅ P0 E2 | 139 个 JUnit5 + Mockito 用例(26 个测试类,2026-09-29 实测) |
| 营收明细导出 Excel | ✅ | GET /api/admin/revenue/export,独立 axios 实例走 blob,看板导出对话框 |
| 错误码体系 | ✅ | 0/4xxxx/5xxxx + data 携带附加信息 |
| 对话式订票助手 | ✅ 2026-09-21 | LangChain4j + DeepSeek;8 只读工具(含 FAQ searchFaq);行动卡片 SEAT_SUGGESTION;@RateLimit 分桶(匿名 2/min,登录 10/min);LOGIN_REQUIRED 引导登录;FAQ 知识库 18 条种子数据;key-missing 走 50000 短路 |
| 端 | 技术 |
|---|---|
| 后端 | JDK 21 · Spring Boot 3.5 · MyBatis-Plus 3.5.12 · Redis 8(Bitmap/Lua/ZSet/PubSub) · WebSocket · JWT · AspectJ AOP · HMAC-SHA256 · LangChain4j 1.20.0(对话助手) |
| 前端 | Vue 3 · Vite · TypeScript · Element Plus · Pinia · Axios · dayjs · vue-i18n 11(中英双语) |
| 中间件 | MySQL 8(本机) · Redis 8(本机,127.0.0.1:6379) |
| LLM | DeepSeek(OpenAI 兼容端点,env 注入 API key) |
| 测试 | JUnit5 · Mockito · AssertJ · Python 压测脚本 · JMeter 5.6.3(可选) · Vitest 3.x(前端) |
本机无 Docker/RabbitMQ:延迟关单采用 Redis ZSet 延迟队列(接口抽象,
DelayQueue可替换为 RabbitMQ 实现,见docs/实现方案.md §5)。
mysql -uroot -p < sql/01_schema.sql
mysql -uroot -p < sql/02_init_data.sql
mysql -uroot -p < sql/03_p0_increment.sql # P0 增量: movie 加列 + refund_log + ticket
# 可选: 更多演示影片(12 部含真实海报) + 未来 14 天场次; 必须带 --default-character-set=utf8mb4
mysql -uroot -p --default-character-set=utf8mb4 < sql/04_extra_demo_data.sql
# 可选: 对话助手 FAQ 知识库(18 条常见问答; 迁移 PR #15 数据,spec #20)
# ⚠️ 需手动执行并**重启后端**才生效; 未执行时 searchFaq 工具查不到表,
# KnowledgeService 会记录「知识库不可用」日志并降级(其余 7 个工具不受影响)。
mysql -uroot -p < sql/06_qa_knowledge.sqlcd cinema-server
# 首次: 复制配置模板并填入 MySQL 密码 + 二维码 HMAC secret
cp src/main/resources/application-dev.yml.example src/main/resources/application-dev.yml
# 编辑 application-dev.yml, 将 <MYSQL_PASSWORD> 改为真实密码
# (该文件已被 .gitignore 排除)
# 可选:启用对话助手(不设则 /api/chat/message 走 50000 短路)
# Linux/Mac: echo 'export DEEPSEEK_API_KEY=sk-xxx' >> ~/.bashrc && source ~/.bashrc
# Windows cmd: setx DEEPSEEK_API_KEY "sk-xxx" (需新开 cmd)
# Windows PS: [Environment]::SetEnvironmentVariable("DEEPSEEK_API_KEY","sk-xxx","User") (需新开 PS)
mvn spring-boot:run- 接口文档: http://localhost:8080/swagger-ui.html
- 测试账号:
user1 / 123456、user2 / 123456、admin / 123456(后端启动时自动初始化密码) - 启动日志关注:
c.cinema.modules.chat.config.ChatConfig : [chat] 注册 CinemaAssistant Bean ...(env 有 key 时)
cd cinema-web
pnpm install
pnpm dev- 用户:
/login注册/登录 →/首页选片/搜索 →/movie/:id详情选场次 →/seat/:sessionId选座/锁座 →/payment支付 →/orders我的订单(取消/退票/电子票) - 管理员:
/admin→ 经营看板/实时大屏 + 影片/影厅/场次管理 - 路由守卫: 未登录访问
/seat*、/payment、/orders跳/login(带 redirect);非管理员访问/admin*跳首页;未知路径显示 404 兜底页
cd cinema-server
mvn test
# Tests run: 139, Failures: 0, Errors: 0
# (跨 26 个测试类,2026-09-29 实测)
cd ../cinema-web
pnpm test
# Test Files: 14 passed, Tests: 137 passed (vitest 3.x)| Method | Path | 说明 | 鉴权 |
|---|---|---|---|
| POST | /api/auth/register |
注册(确认密码由前端校验) | 公开 |
| POST | /api/auth/login |
登录(JWT) | 公开 |
| GET | /api/users/me |
当前登录用户信息 | 需登录 |
| GET | /api/movies |
影片搜索/筛选(?keyword=&genre=®ion=) |
公开 |
| GET | /api/movies/{id} |
影片详情 | 公开 |
| GET | /api/movies/{movieId}/sessions |
影片场次列表(注意:路径以 /api/movies 开头) |
公开 |
| GET | /api/sessions/{sessionId} |
场次详情 | 公开 |
| GET | /api/sessions/{sessionId}/seat-map |
场次座位图 | 公开 |
| POST | /api/orders/lock |
锁座下单 | 需登录 + @RateLimit + @Idempotent |
| POST | /api/orders/{orderNo}/pay |
模拟支付 | 需登录 + @RateLimit + @Idempotent |
| POST | /api/orders/{orderNo}/cancel |
主动取消 | 需登录 + @RateLimit |
| POST | /api/orders/{orderNo}/refund |
退票(P0 N1) | 需登录 + @RateLimit + @Idempotent |
| GET | /api/orders/{orderNo}/ticket |
取电子票(P0 N2) | 需登录 |
| GET | /api/orders/my |
我的订单(分页) | 需登录 |
| GET | /api/orders/{orderNo} |
订单详情 | 需登录 |
| GET | /api/tickets/verify |
验票(公开,P0 N2) | 公开 |
| POST | /api/chat/message |
对话式订票助手(2026-09-21):{chatSessionId, message, context} → {reply, cards[], followUps[]} |
公开(env DEEPSEEK_API_KEY 未设则 50000 短路;@RateLimit 分桶:匿名 2/min,登录 10/min) |
| Method | Path | 说明 | 鉴权 |
|---|---|---|---|
| CRUD | /api/admin/movies |
影片管理 | 管理员 |
| CRUD | /api/admin/halls |
影厅管理 | 管理员 |
| CRUD | /api/admin/sessions |
场次管理 | 管理员 |
| POST | /api/admin/sessions/bitmaps/recover?sessionId= |
手动恢复位图(P0 P5) | 管理员 |
| GET | /api/admin/dashboard/summary |
经营看板汇总(P0 O3) | 管理员 |
| GET | /api/admin/revenue/export?from=&to=&mode= |
营收明细导出 xlsx(不走 R<T> 包装,直接返回二进制流) |
管理员 |
| URL | 用途 | 鉴权 |
|---|---|---|
/ws/seat/{sessionId} |
座位变化广播(本场观众) | 公开 |
/ws/admin |
管理端大屏(P0 D1) | 公开(演示用) |
F:/test/work/
├── plan.md 原始架构方案
├── README.md 本文件
├── docs/
│ ├── 实现方案.md 详细实现方案(W1-W5 + P0)
│ ├── 压测报告.md W5 压测结果
│ ├── 压测报告-v2.md P0 后回归压测结果
│ └── superpowers/specs/ P0 增量设计 spec
├── sql/
│ ├── 01_schema.sql 建库建表
│ ├── 02_init_data.sql 演示数据(3 部影片 + 3 天场次)
│ ├── 03_p0_increment.sql P0 增量: movie 扩列 + refund_log + ticket
│ ├── 04_extra_demo_data.sql 扩展演示: 12 部影片(带 TMDB 海报) + 14 天场次
│ └── 06_qa_knowledge.sql 对话助手 FAQ 知识库(18 条常见问答,spec #20)
├── cinema-server/ Spring Boot 后端(按领域分包)
│ ├── pom.xml
│ └── src/main/java/com/cinema/
│ ├── CinemaApplication.java
│ ├── common/ 公共(注解/AOP/上下文/异常/加密/限流/响应)
│ │ ├── annotation/ RateLimit, Idempotent
│ │ ├── aop/ RateLimitAspect, IdempotentAspect
│ │ ├── context/ UserContext
│ │ ├── crypto/ HmacSigner
│ │ ├── exception/ BizException + 全局异常处理
│ │ ├── ratelimit/ RedisSlidingWindow
│ │ └── result/ R + ResultCode(0/4xxxx/5xxxx)
│ ├── config/ 配置(Web/WebSocket/Redis+PubSub/MyBatis/Jackson/OpenApi)
│ ├── interceptor/ Jwt / AuthRequired / Admin 拦截器
│ ├── modules/ 业务模块(按领域)
│ │ ├── user/ 认证 + 用户信息
│ │ ├── movie/ 影片(搜索/筛选 F1)
│ │ ├── hall/ 影厅
│ │ ├── cinema/ 影院
│ │ ├── session/ 场次
│ │ ├── seat/ 座位图
│ │ ├── order/ 订单(E1 拆分: Lock/Pay/Cancel/Query + Core) + 电子票 Ticket
│ │ ├── payment/ 模拟支付/退款(MockPayment / MockRefund)
│ │ ├── chat/ 对话助手(T9 落地:ChatConfig + CinemaAssistant + ChatTools 8 个 + ChatController + ReplyCardParser)
│ │ └── admin/ 管理端(CRUD + Dashboard O3)
│ ├── infra/ 基础设施
│ │ ├── delay/ DelayQueue(Redis ZSet 实现)
│ │ ├── jwt/ JwtUtil
│ │ ├── mq/ OrderTimeoutScanner
│ │ ├── redis/ Lua 服务 + SeatBitmapGuard(P5) + cache
│ │ └── ws/ WebSocket(Seat + Admin 大屏 D1)
│ ├── support/ DataInitializer(测试账号自动初始化)
│ └── job/ 定时任务
│ └── OrderTimeoutCompensateJob 超时关单 + 退款卡死补偿(N1)
└── cinema-web/ Vue3 前端
├── package.json
└── src/
├── api/ axios 封装 + 各模块 API(admin.ts 含 exportRevenue 独立实例)
├── components/ SeatItem, Countdown
│ └── chat/ ChatWidget, ChatMessage, ActionCard, actionCardRoute(卡片→选座预选)
├── composables/ useChatContext.ts(聊天会话上下文注入)
├── constants/ auth.ts(角色/令牌常量)
├── i18n/ vue-i18n 11 实例
│ ├── index.ts createI18n(legacy:false) + i18nSetLocale
│ ├── detect.ts 首次进站浏览器语言探测
│ ├── persist.ts 用户选择持久化(localStorage)
│ ├── fallback.ts 缺 key 回退逻辑
│ └── locales/ zh-CN.ts, en-US.ts
├── router/index.ts 路由 + 全局守卫(登录/管理员)
├── stores/ Pinia(user/seat/movieCache)
├── styles/main.css 全局样式
├── types/ TS 类型定义
├── utils/ ws 封装, bitmap 解析
└── views/
├── Home.vue 首页(F1 搜索筛选)
├── Login.vue 登录/注册(确认密码)
├── MovieDetail.vue 影片详情 + 场次
├── SeatSelect.vue 选座(WebSocket 实时)
├── Payment.vue 模拟支付 + 倒计时
├── OrderList.vue 我的订单(取消/退票/电子票)
├── NotFound.vue 404 兜底
└── admin/
├── AdminHome.vue 后台骨架 + 嵌套路由
├── Dashboard.vue 经营看板(O3)+ 营收导出对话框
├── LiveDashboard.vue 实时大屏(D1)
├── MovieManage.vue 影片管理
├── HallManage.vue 影厅管理
└── SessionManage.vue 场次管理
- W1 环境适配、建表、骨架(认证/影片/场次)、前端骨架
- W2 座位图接口 + Redis Bitmap + Lua 锁座 + 下单
- W3 模拟支付 + ZSet 超时关单(5s 扫描器 + 1min 补偿) + WebSocket 推送
- W4 前端选座/支付/订单页 + 路由联通
- W5 管理端 CRUD(电影/影厅/场次) + Python+JMeter 压测三场景 + docs/压测报告.md
- P0-1 E1 拆
OrderService为 4 service + 1 core - P0-2 E2 补关键路径单测(现有 29 个)
- P0-3 P5 bitmap 冷启动重建 + 管理端手动恢复
- P0-4 E4
@RateLimit注解 + Redis 滑动窗口 - P0-5 E5
@Idempotent注解 + Redis SETNX - P0-6 F1 影片搜索 + 类型/地区筛选
- P0-7 N1 退票流程(PAID→REFUNDING→REFUNDED + 补偿)
- P0-8 N2 电子票二维码(HMAC 签名 + 24h 过期 + 一次性)
- P0-9 O3 管理端经营看板
- P0-10 D1 实时数据大屏(/ws/admin)
- UX-1 前端体验升级(全局路由守卫 / 注册确认密码 / 移动端适配 / 座位图重构 / 11 处 UI 修复 + 404 兜底页)
- CHAT-1 对话式订票助手全链路(T1-T9,2026-09-21):LangChain4j + DeepSeek + 8 只读工具(含 FAQ searchFaq) + 行动卡片 SEAT_SUGGESTION + 多跳记忆 + @RateLimit 分桶(匿名 2/min,登录 10/min) + env-driven key 注入 + LOGIN_REQUIRED 引导登录 + FAQ 知识库兜底
- CHAT-2 锁座调用路径零触动 + ADR-0002 反射白名单(
ChatToolsStructureTest兜底) - I18N-1 ~ I18N-8 中英文双语(2026-09-22):vue-i18n 11 基础设施(zh-CN + en-US 字典 + 浏览器探测 + 持久化 + fallback)→ 顶栏切换按钮 + App → Home/Login → MovieDetail → SeatSelect → Payment/OrderList → Admin 6 视图 → Chat/NotFound → 运行时缺 key 修复 +
key-coverage单测兜底 |
状态:T1-T9 全链路完成(issue #8-#16 全 close,共 9 个 TDD micro-commit + 1 fixup);锁座调用路径零触动(
git diff cinema-server/src/main/java/com/cinema/modules/order/0 行);redis 副作用 0(chat:*/user:locked:*/seat:bitmap:*/seat:locked:*聊天前后全 0)。
给系统加一个对话入口,让用户能用自然语言查影片/场次/余座/订单,并拿到可执行的座位建议。
- 设计文档:docs/superpowers/specs/2026-09-21-chat-assistant-design.md
- 技术选型:LangChain4j 1.20.0(Java)嵌入
cinema-server,进程内直调既有领域 Service;模型走 DeepSeek 的 OpenAI 兼容端点 - 代码评审:docs/agents/chat-assistant-code-review.md — Standards + Spec 双轴 13 项 + live curl 验证矩阵
三条核心约束:
| 约束 | 内容 | 依据 |
|---|---|---|
| 只读 + 只建议 | 助手不锁座、不支付、不退票。它返回「行动卡片」建议,执行权始终在用户手里,落在既有 /seat/:sessionId → POST /orders/lock 那一跳 |
ADR-0002 |
| 不引入 RAG | 不接 Embedding / 向量库;事实只能来自只读工具查 MySQL / Redis | ADR-0003 |
| 不流式 | 纯 Servlet MVC 栈,非流式单次返回;卡片必须有整块结构化载荷 | ADR-0001 |
| 工具 | 后端调用 | 用途 |
|---|---|---|
searchMovies(keyword?, genre?, region?) |
MovieService.search |
影片检索 |
getMovieDetail(movieId) |
MovieService.detail |
影片详情 |
listSessions(movieId, date?) |
SessionService.listByMovieAndDate |
某片某日场次 |
getSeatSummary(sessionId, userId?) |
SeatService.seatMap |
摘要(总数/可选/已售 + 连座片段) |
findContiguousSeats(sessionId, userId?, count, preferRow?) |
SeatService.seatMap |
找 N 连座返回座位索引 |
getMyOrders(userId, status?, page?, size?) |
OrderQueryService.myOrders |
userId 必非空,否则 LOGIN_REQUIRED |
getMyOrder(orderNo, userId) |
OrderQueryService.detail |
同上 |
searchFaq(question, topK?) |
KnowledgeService.search |
FAQ 知识库兜底通用问答(spec #20) |
ChatToolsStructureTest 反射白名单兜底 — 任何 lockSeats / pay / cancel / refund / forceRecover / 管理端写方法被 @Tool 注解即测试红。
LLM 在 reply 末尾输出 fenced ```json-cards ``` 块,后端 ReplyCardParser 解析后填 ChatResponseVO.cards + followUps,前端 ChatMessage.vue 按 v-if="response.cards.length" 渲染可点击的 ActionCard。点击跳 /seat/{sessionId}?preselect=52,53,由既有 applyPreselect(纯函数,13/13 用例覆盖)做追加语义 + 超 maxSelect 按最小索引剔除。
约束:卡片不携带任何可直接提交的载荷,只携带 {sessionId, seatIndexes} 建议(ADR-0002 硬约束)。
# 设环境变量后启动后端,ChatConfig @ConditionalOnExpression 自动激活
setx DEEPSEEK_API_KEY "sk-xxx" # cmd (新开)
[Environment]::SetEnvironmentVariable("DEEPSEEK_API_KEY","sk-xxx","User") # PowerShell (新开)
export DEEPSEEK_API_KEY=sk-xxx # bash (source)
# 然后 mvn spring-boot:run,看到 "[chat] 注册 CinemaAssistant Bean" 即接通未设 key 时:/api/chat/message 返 50000 + "对话功能未配置"(这是设计:不挂外部 key 也能让 server 起来)。
为什么不引入 RAG:本项目 4 个领域对象(movie/session/seat/order)总数小、单工具查询 < 50ms,RAG 收益不抵复杂度;详见 ADR-0003。
为什么"不让助手直接锁座"不是保守而是必要 — 三条代码级证据(OrderLockService 的 closeIfUnpaid 会静默释放用户已锁座位;锁座的 @Idempotent 键含精确座位组合;lock_seat.lua 冲突语义为整单失败)详见 spec §2.3。
同时新增的领域文档: CONTEXT.md 补充了 7 个选座领域术语(座位索引 / 选座 / 锁座 / 锁座冲突 / 待支付订单 / 场次上下文 / 行动卡片);docs/adr/ 为本次首次建立。
- 设计文档:docs/superpowers/specs/2026-09-22-i18n-zh-en-design.md
- 技术选型:vue-i18n 11(
legacy: false组合式 API),两个字典src/i18n/locales/zh-CN.ts/en-US.ts
| 约束 | 内容 | 落在哪 |
|---|---|---|
| 首次按浏览器语言探测 | 首次进站读 navigator.language,命中 zh-CN/en-US 才采用,其余一律中文兜底 |
src/i18n/detect.ts |
| 之后听用户的 | 用户手动切换后写入 localStorage,优先级高于浏览器语言,刷新不丢 | src/i18n/persist.ts |
| 缺 key 不露馅 | fallbackLocale: 'zh-CN',任一语言缺 key 时回退中文而非显示裸 key |
src/i18n/index.ts + fallback.ts |
切换入口在顶栏(首页右上角「🌐 EN / 中文」按钮),全站生效不刷新页面。setLocale + reactive i18n.global.locale.value 同一 tick 生效,避免「图标已切换但文案未变」的中间态。
| 测试文件 | 守住什么 |
|---|---|
tests/i18n/detect.test.ts |
语言探测的边界(非中英文浏览器语言、zh/zh-TW 变体) |
tests/i18n/persist.test.ts |
持久化读写与优先级 |
tests/i18n/fallback.test.ts |
缺 key 回退行为 |
tests/i18n/locales-shape.test.ts |
两个字典结构一致(嵌套层级对齐) |
tests/i18n/key-coverage.test.ts |
扫描源码里的 t('...') 静态 key,比对两个字典 |
key-coverage 是防「运行时缺 key」的最后一道闸:它能抓到两个语言都漏的 key —— 那种情况 fallbackLocale 也救不了,只会在页面上显示裸 key。改任何 locale 文件或新增 t() 调用后必须跑 pnpm test。
收尾验证实测(2026-09-29):浏览器点切换后「星辉影城」→ "Star Cinema"、「登录 / 注册」→ "Sign in / Register"、"分钟" → "min";影片标题保持中文属预期(数据来自 DB,非字典)。
- 200/250 并发抢同场次同一座位:0 超卖,任意瞬间同座位最多 1 张待支付单 ✓
- 锁座冲突:返回
code=40002+data.conflict=[...],前端自动刷新座位图 ✓ - 支付:DB CAS 成功 → Lua confirm_seat (lock→sold) ✓
- 主动取消:DB CAS + Lua release_seat ✓
- 超时关单:把 expire_at + ZSet score 同步调为过去 → 5 秒内主链路扫描器释放座位 ✓
- 兜底补偿:仅改 DB expire_at(不同步 ZSet) → 1 分钟内补偿任务兜底释放 ✓
- WebSocket:
/ws/seat/{sessionId}HTTP 101 握手成功 ✓ - 经 Vite 代理(前端 5173 → 后端 8080)调用全部接口 ✓
- 管理端权限:role=1 放行 / role=0 返回 40301 ✓
- 管理端 CRUD:影片/影厅/场次的增删改查与座位自动生成 ✓
- P0: 退票成功 → 座位实时变可选(WebSocket 推 RELEASED) ✓
- P0: 电子票扫描 → 二次扫描返"已使用" ✓
- P0: 手动恢复位图(模拟 flush)→ 锁座能正确恢复 bitmap ✓
- P0: 重复锁座请求(< 3s)→ 第二次返
code=40900幂等冲突 ✓ - P0: 100 次同用户锁座 → 第 6 次起返
code=42900限流 ✓ - P0: 看板数据与 DB 直接查询一致(随机抽 3 个数对账) ✓
- P0: 大屏下单事件 1s 内反映到事件流 ✓
- UX: 未登录访问
/seat*//payment//orders→ 跳/login并带 redirect ✓ - UX: 普通用户访问
/admin→ 拦截回首页 ✓ | 已登录访问/login→ 跳首页 ✓ - UX: 注册确认密码不一致 → 前端拦下,不发请求 ✓
- CHAT: env 注入 DEEPSEEK_API_KEY 后,curl
/api/chat/message200 + LLM 真打 3-4s + cards[] 11 字段 SEAT_SUGGESTION 完整 ✓ - CHAT: 无 key 启动 →
50000+ "对话功能未配置"(短路);@RateLimit匿名 2/min / 登录 11 次/min 触发42900✓ - CHAT: 匿名问 "我的订单" → 前端 ChatWidget 检测 LOGIN_REQUIRED → 弹 ElMessageBox 引导跳转
/login?redirect=<current>✓ - CHAT: redis 副作用 0(ADR-0002 锁死);锁座调用路径 0 行触动 ✓
- CHAT:
ChatToolsStructureTest反射白名单兜底禁 lockSeats/pay/cancel/refund/forceRecover ✓ - I18N: 顶栏切换中↔英,「星辉影城」→ "Star Cinema"、时长「分钟」→ "min";影片标题保持中文(数据驱动,非字典) ✓
| 限制 | 现象 | 原因 | 处理 |
|---|---|---|---|
| 种子场次是固定日期窗口 | 隔一段时间后进影片详情,场次列表为空 | sql/02_init_data.sql / 04_extra_demo_data.sql 生成的是一次性固定区间场次(如 2026-09-08 ~ 09-21),不会随日期滚动;GET /api/movies/{id}/sessions 不带 ?date= 时默认查当天 |
演示前重跑 sql/04_extra_demo_data.sql 生成新区间;或调接口时显式传 ?date=YYYY-MM-DD(实测传 2026-09-10 可正常返回 4 场) |
| FAQ 知识库需手动灌 | 对话助手问流程类问题不走知识库兜底 | sql/06_qa_knowledge.sql 默认不执行,qa_knowledge 表可能不存在 |
按「快速开始 §1」执行该 SQL 并重启后端;不执行时 searchFaq 降级,其余 7 个工具不受影响 |
| Vite dev 只监听 IPv6 | 用 http://127.0.0.1:5173 探测会「连接被拒绝」,误判前端没起 |
Vite 6 默认绑 [::1](localhost 解析到 IPv6) |
用 http://localhost:5173 访问即可;不是服务故障 |
| 场景 | 并发 | 总请求 | QPS | P99 | 错误率 |
|---|---|---|---|---|---|
| A 座位图读 | 100×10s | 18,333 | 1833 | 65ms | 0% |
| B 锁座抢购 | 200×10s | 15,933 | 1593 | 108ms | 0%(业务冲突除外) |
| C 混合 | 200×10s | 14,953 | 1495 | 83ms | 0%(业务冲突除外) |
核心防超卖结论(250 并发抢同一座位):任意瞬间同座位最多 1 张待支付单,0 超卖。
- 普通用户:
user1 / 123456,user2 / 123456 - 管理员:
admin / 123456
cinema-server/src/test/java/...— 139 个 JUnit5 + Mockito 单元测试(26 个测试类,E2 + 营收导出 + 对话助手全链路,2026-09-29 实测)cinema-web/tests/— 137 个 Vitest 单元测试(14 个.test.ts文件,2026-09-29 实测)test/load_test.py— 三场景 Python 压测驱动test/concurrency_strict.py— 防超卖专项test/concurrency_test.py— 并发基础压测test/e2e/e2e_regression.py— E2E 回归用例(含截图产物)test/jmeter/scenario-*.jmx— JMeter 模板test_mixed.py— 200 并发混合读/锁/查压测(根目录)test_webapp_e2e.py— Web 端 E2E 脚本(根目录)docs/压测报告.md— 完整压测报告(W5)docs/压测报告-v2.md— P0 后回归压测报告docs/superpowers/specs/2026-08-31-p0-increment-design.md— P0 增量设计 specdocs/superpowers/specs/2026-09-21-chat-assistant-design.md— 对话式订票助手设计 spec(已实现)docs/adr/— 架构决策记录(ADR-0001/0002/0003)docs/agents/chat-assistant-code-review.md— 对话助手双轴 code review
test/目录与根目录测试脚本均属本地测试产物,已由.gitignore排除,不进入版本库。