Skip to content
wangpanbinPublic

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

80 Commits

Folders and files

Repository files navigation

影院/演出抢票选座系统

课程设计项目:Spring Boot 3 + Redis Bitmap + Lua 锁座 + 延迟关单 + WebSocket 实时选座 + 经营数据大屏。


项目亮点

维度 实现
防超卖 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)。


快速开始

1. 初始化数据库(一次性)

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.sql

2. 配置并启动后端(8080)

cd 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 时)

3. 启动前端(5173)

cd cinema-web
pnpm install
pnpm dev

打开 http://localhost:5173

  • 用户: /login 注册/登录 → / 首页选片/搜索 → /movie/:id 详情选场次 → /seat/:sessionId 选座/锁座 → /payment 支付 → /orders 我的订单(取消/退票/电子票)
  • 管理员: /admin → 经营看板/实时大屏 + 影片/影厅/场次管理
  • 路由守卫: 未登录访问 /seat*、/payment、/orders 跳 /login(带 redirect);非管理员访问 /admin* 跳首页;未知路径显示 404 兜底页

4. 跑单测

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)

API 总览

用户端

Method Path 说明 鉴权
POST /api/auth/register 注册(确认密码由前端校验) 公开
POST /api/auth/login 登录(JWT) 公开
GET /api/users/me 当前登录用户信息 需登录
GET /api/movies 影片搜索/筛选(?keyword=&genre=&region=) 公开
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> 包装,直接返回二进制流) 管理员

WebSocket

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 单测兜底 |

已实现 — 对话式订票助手(2026-09-21 落地)

状态: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)。

给系统加一个对话入口,让用户能用自然语言查影片/场次/余座/订单,并拿到可执行的座位建议。

三条核心约束:

约束 内容 依据
只读 + 只建议 助手不锁座、不支付、不退票。它返回「行动卡片」建议,执行权始终在用户手里,落在既有 /seat/:sessionId → POST /orders/lock 那一跳 ADR-0002
不引入 RAG 不接 Embedding / 向量库;事实只能来自只读工具查 MySQL / Redis ADR-0003
不流式 纯 Servlet MVC 栈,非流式单次返回;卡片必须有整块结构化载荷 ADR-0001

8 个只读工具(LLM 可调)

工具 后端调用 用途
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 注解即测试红。

行动卡片(cards[])

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 硬约束)。

启用(env-driven)

# 设环境变量后启动后端,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/ 为本次首次建立。


已实现 — 中英文双语(2026-09-22 落地)

三条设计约束

约束 内容 落在哪
首次按浏览器语言探测 首次进站读 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 生效,避免「图标已切换但文案未变」的中间态。

覆盖率保障(29 个专项用例)

测试文件 守住什么
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,非字典)。


端到端验证(W2-W5 + P0 已实测通过)

  • 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/message 200 + 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 访问即可;不是服务故障

压测结果(W5,详见 docs/压测报告.md)

场景 并发 总请求 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 增量设计 spec
  • docs/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 排除,不进入版本库。

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages