【Java项目-企悦抽】02-AI赋能产品需求规格说明书
声明:本文档AI辅助完成,内容仅供参考
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
🎯你正在阅读「Java项目-企悦抽」系列文章🎯
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨🔥弹简特 个人主页
❄️个人专栏直通车:
- 🔌接口测试从入门到跑路
- ☕一个后端的 JavaEE 续命指南
- 🛜网络原理续命手册
- ☕Java项目-轻聊
✨靠热爱去书写自己,靠勇敢去书写生活!
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
🌟 博主简介:
文章目录:
- 企悦抽-产品需求规格说明书
- 修订记录
- 1. 引言
- 1.1 编写目的
- 1.2 文档关系
- 2. 系统概述
- 2.1 系统名称
- 2.2 技术栈规格
- 2.3 模块划分
- 3. 系统架构规格
- 3.1 逻辑架构
- 3.2 抽奖异步链路规格
- 3.3 缓存规格
- 4. 数据规格
- 4.1 数据库表
- 4.2 枚举规格
- 4.3 字段校验规格
- 4.4 加密规格
- 5. 功能规格详述
- 5.1 FR-001 ~ FR-005 用户与认证
- 5.1.1 注册(FR-001)
- 5.1.2 发送验证码(FR-004)
- 5.1.3 密码登录(FR-002)
- 5.1.4 短信登录(FR-003)
- 5.1.5 登录拦截(FR-005)
- 5.2 FR-006 ~ FR-007 人员管理
- 5.2.1 创建普通用户(FR-006)
- 5.2.2 用户列表(FR-007)
- 5.3 FR-008 ~ FR-010 奖品管理
- 5.3.1 创建奖品(FR-008)
- 5.3.2 奖品分页列表(FR-009)
- 5.3.3 奖品全量列表(FR-010)
- 5.4 FR-011 ~ FR-013 活动管理
- 5.4.1 创建活动(FR-011)
- 5.4.2 活动列表(FR-012)
- 5.4.3 活动详情(FR-013)
- 5.5 FR-014 ~ FR-016 抽奖与结果
- 5.5.1 提交抽奖(FR-014)
- 5.5.2 查询中奖记录(FR-015)
- 5.5.3 抽奖大屏(FR-016)
- 5.6 FR-017 ~ FR-018 通知
- 5.6.1 中奖邮件(FR-017)
- 5.6.2 中奖短信(FR-018)
- 6. 接口规格索引
- 7. 前端页面规格
- 7.1 实现说明
- 7.2 页面清单
- 7.3 通用前端约定
- 8. 状态机规格
- 8.1 状态扭转顺序
- 8.2 抽奖正向扭转
- 8.3 异常回滚
- 9. 安全规格
- 10. 异常与容错规格
- 10.1 MQ 重试
- 10.2 死信队列
- 10.3 校验失败静默返回
- 11. 验收标准
- 11.1 功能验收
- 11.2 非功能验收
企悦抽-产品需求规格说明书
| 文档属性 | 内容 |
|---|---|
| 产品名称 | 企悦抽(QiYueChou) |
| 文档类型 | 产品需求规格说明书(PRS) |
| 文档版本 | V1.0 |
| 编写日期 | 2025-07-03 |
| 上游文档 | AI赋能产品需求文档 |
| 关联代码 | lottery-system |
| 文档状态 | 已定稿 |
修订记录
| 版本 | 日期 | 修订说明 |
|---|---|---|
| V1.0 | 2025-07-03 | 首版,由 PRD 拆解为可开发/可测试规格 |
1. 引言
1.1 编写目的
本文档在 PRD 基础上,将产品需求转化为可实施、可验证的功能规格、数据规格、状态规则与验收标准,供研发与测试直接使用。
1.2 文档关系
PRD(为什么做、做什么) ↓ PRS(怎么做、做到什么标准) ← 本文档 ↓ 接口设计文档(接口级定义) ↓ 接口测试需求文档(测试用例输入)2. 系统概述
2.1 系统名称
- 产品名:企悦抽
- 工程名:
lottery-system - 默认端口(开发用):
8080
2.2 技术栈规格
| 层次 | 技术 | 版本/说明 |
|---|---|---|
| 后端框架 | Spring Boot | 3.2.6 |
| 语言 | Java | 17 |
| ORM | MyBatis | 3.0.3(注解方式) |
| 数据库 | MySQL | 库名lottery_system |
| 缓存 | Redis | Lettuce,默认端口配置 8888 |
| 消息队列 | RabbitMQ | Direct 交换机 + 死信队列 |
| 认证 | JWT | jjwt 0.11.5,Header:user_token |
| 前端 | HTML + jQuery + CSS | 静态资源/static;UI 借助 AI 辅助实现,主打美观效果 |
| 日志 | SLF4J + Logback | logback-spring.xml |
| 短信 | Spug 推送 | SMSUtilHTTP 调用 |
| 邮件 | Spring Mail | QQ SMTP |
2.3 模块划分
| 模块 | 包路径/组件 | 职责 |
|---|---|---|
| 用户模块 | UserController、UserServiceImpl | 注册、登录、用户列表 |
| 验证码模块 | VerificationCodeServiceImpl | 验证码生成、Redis 缓存、Spug 发送 |
| 奖品模块 | PrizeController、PrizeServiceImpl | 奖品 CRUD、图片上传 |
| 活动模块 | ActivityController、ActivityServiceImpl | 活动创建、列表、详情、Redis 缓存 |
| 抽奖模块 | DrawPrizeController、DrawPrizeServiceImpl | 发 MQ、校验、落库、查记录 |
| MQ 消费 | MqReceiver、DlxReceiver | 异步抽奖、回滚、死信重投 |
| 状态管理 | ActivityStatusManagerImpl+ Operator | 活动/奖品/用户状态扭转 |
| 通用 | LoginInterceptor、GlobalException | 鉴权、统一响应 |
3. 系统架构规格
3.1 逻辑架构
3.2 抽奖异步链路规格
| 步骤 | 组件 | 行为 |
|---|---|---|
| 1 | DrawPrizeController | 接收 JSON,调用drawPrize() |
| 2 | DrawPrizeServiceImpl.drawPrize | 构造{messageId, messageData}发 MQ |
| 3 | Controller | 立即返回{code:200, data:true} |
| 4 | MqReceiver.process | 消费消息 |
| 5 | — | checkDrawPrizeParam校验 |
| 6 | — | ActivityStatusManager.handlerEvent扭转状态 |
| 7 | — | saveWinnerRecords写库 + 缓存 |
| 8 | — | 线程池异步发邮件(短信占位) |
| 9 | 异常 | 回滚 + 抛异常 → MQ 重试 → 死信 |
3.3 缓存规格
| 键前缀 | 内容 | TTL |
|---|---|---|
VERIFICATION_CODE_{phone} | 4 位验证码 | 60 秒 |
ACTIVITY_{activityId} | 活动详情 JSON | 3 天 |
WINNING_RECORDS_{activityId} | 活动全量中奖记录 | 2 天 |
WINNING_RECORDS_{activityId}_{prizeId} | 单奖品中奖记录 | 2 天 |
4. 数据规格
4.1 数据库表
| 表名 | 说明 | 关键约束 |
|---|---|---|
user | 用户 | 邮箱唯一、手机号唯一 |
prize | 奖品库 | — |
activity | 活动 | status: RUNNING/COMPLETED |
activity_prize | 活动-奖品 | uk(activity_id, prize_id) |
activity_user | 活动-用户 | uk(activity_id, user_id) |
winning_record | 中奖记录 | uk(winner_id, activity_id, prize_id) |
4.2 枚举规格
用户身份UserIdentityEnum
| 值 | 含义 |
|---|---|
| ADMIN | 管理员 |
| NORMAL | 普通用户 |
活动状态ActivityStatusEnum
| 值 | 含义 |
|---|---|
| RUNNING | 进行中 |
| COMPLETED | 已完成 |
活动奖品状态ActivityPrizeStatusEnum
| 值 | 含义 |
|---|---|
| INIT | 未抽取 |
| COMPLETED | 已抽取 |
活动用户状态ActivityUserStatusEnum
| 值 | 含义 |
|---|---|
| INIT | 未中奖 |
| COMPLETED | 已中奖 |
奖品等级ActivityPrizeTiersEnum
| 值 | code | 中文 |
|---|---|---|
| FIRST_PRIZE | 1 | 一等奖 |
| SECOND_PRIZE | 2 | 二等奖 |
| THIRD_PRIZE | 3 | 三等奖 |
4.3 字段校验规格
| 字段 | 规则 | 实现 |
|---|---|---|
| 邮箱 | 小写邮箱格式 | RegexUtil.checkMail |
| 手机号 | 1 开头 11 位 | RegexUtil.checkMobile |
| 密码 | 6~12 位字母数字 | RegexUtil.checkPassword |
| 管理员密码 | 必填 | 注册/创建时校验 |
4.4 加密规格
| 数据 | 算法 | 说明 |
|---|---|---|
| 密码 | SHA256 Hex | 注册/登录时DigestUtil.sha256Hex |
| 手机号 | AES | EncryptTypeHandler,密钥 16 字节 |
5. 功能规格详述
5.1 FR-001 ~ FR-005 用户与认证
5.1.1 注册(FR-001)
输入:name、mail、phoneNumber、password(管理员必填)、identity(ADMIN/NORMAL)
处理逻辑:
- JSR303 非空校验
- 邮箱/手机号格式校验
- 身份合法性校验
- 管理员密码必填且强度校验
- 邮箱/手机号唯一性校验
- 密码 SHA256 后入库;手机号 AES 加密入库
输出:{ userId }
页面:register.html;管理员后台创建时admin=false&jumpList=true
5.1.2 发送验证码(FR-004)
输入:Query 参数phone
处理逻辑:
- 手机号格式校验
MyCaptchaUtil.getCaptcha(4)生成 4 位数字SMSUtil.sendSms(phone, code)调用 Spug- Redis 存储,键
VERIFICATION_CODE_{phone},60 秒
输出:{ code:200, data:true }
5.1.3 密码登录(FR-002)
输入:loginName(手机/邮箱)、password、mandatoryIdentity(可选)
处理逻辑:
- 按格式查用户(手机需 Encrypt 包装)
- 校验用户存在、身份匹配、密码 SHA256 比对
- JWT Claims:
{ id, identity },有效期 1 小时
输出:{ token, identity }
5.1.4 短信登录(FR-003)
输入:loginMobile、verificationCode、mandatoryIdentity(可选)
处理逻辑:
- 查用户、校验身份
- 从 Redis 取验证码比对
- 签发 JWT
5.1.5 登录拦截(FR-005)
白名单(无需 Token):
- 静态资源
/**/*.html、/css/**、/js/**、/pic/**等 /**/login(含/password/login、/message/login)/register/verification-code/send/winning-records/show
鉴权失败:HTTP 401,无 JSON Body
5.2 FR-006 ~ FR-007 人员管理
5.2.1 创建普通用户(FR-006)
与注册共用/register,identity=NORMAL,不传密码。
5.2.2 用户列表(FR-007)
输入:Queryidentity(可选,ADMIN/NORMAL,空则全部)
输出:[{ userId, userName, identity }]
5.3 FR-008 ~ FR-010 奖品管理
5.3.1 创建奖品(FR-008)
Content-Type:multipart/form-data
| Part 名 | 类型 | 说明 |
|---|---|---|
| param | JSON 字符串 | { prizeName, description, price } |
| prizePic | File | 奖品图片,最大 10MB |
处理:图片存本地pic.local-path,文件名 UUID+后缀;imageUrl存文件名
输出:奖品 ID(Long)
5.3.2 奖品分页列表(FR-009)
输入:currentPage(默认 1)、pageSize(默认 10)
输出:{ total, records:[{ prizeId, prizeName, description, price, imageUrl }] }
5.3.3 奖品全量列表(FR-010)
输出:[{ prizeId, prizeName }]
5.4 FR-011 ~ FR-013 活动管理
5.4.1 创建活动(FR-011)
输入:
{"activityName":"string, 必填","description":"string, 必填","activityPrizeList":[{"prizeId":"long","prizeAmount":"long","prizeTiers":"FIRST_PRIZE|SECOND_PRIZE|THIRD_PRIZE"}],"activityUserList":[{"userId":"long","userName":"string"}]}校验:
- 奖品 ID、用户 ID 均存在于主表
- 参与人数 ≥ 奖品总数
- 奖品等级合法
事务:写 activity + activity_prize + activity_user,初始状态分别为 RUNNING、INIT、INIT
缓存:组装ActivityDetailDTO写入 Redis
输出:{ activityId }
5.4.2 活动列表(FR-012)
输出字段valid:status == RUNNING为 true
前端行为:
- valid=true → 「活动进行中,去抽奖」
- valid=false → 「活动已完成,查看中奖名单」
5.4.3 活动详情(FR-013)
输入:QueryactivityId
输出:
| 字段 | 说明 |
|---|---|
| valid | 活动是否进行中 |
| prizes[].valid | 奖品是否未抽取(INIT=true) |
| prizes[].prizeTierName | 等级中文名 |
| prizes | 按等级 code 升序排列 |
| users[].valid | 用户是否未中奖(INIT=true) |
读取顺序:Redis → MySQL 多表组装 → 回写 Redis
5.5 FR-014 ~ FR-016 抽奖与结果
5.5.1 提交抽奖(FR-014)
输入:
{"activityId":"long, 必填","prizeId":"long, 必填","winningTime":"Date, 必填","winnerList":[{"userId":"long, 必填","userName":"string, 必填"}]}同步行为:仅发送 RabbitMQ 消息,不做业务落库
MQ 消费校验(失败则 return 或回滚):
- 活动、活动奖品存在
- 活动非 COMPLETED
- 奖品非 COMPLETED
winnerList.size() == prizeAmount
5.5.2 查询中奖记录(FR-015)
输入:activityId(必填)、prizeId(可选)
逻辑:
- 无 prizeId:查活动维度全量
- 有 prizeId:查该奖品维度
读取:Redis → MySQL → 回写 Redis
免登录:在白名单中
5.5.3 抽奖大屏(FR-016)
URL 参数:activityId、activityName、valid(true/false)
权限:
- valid=true 且 localStorage
user_identity=ADMIN:可抽奖 - valid=true 且非 ADMIN:弹窗提示,不可操作
- valid=false:直接展示全量中奖名单
刷新恢复:已抽完奖品(valid=false)点击开始 → 直接查/winning-records/show?prizeId=展示名单
分享:复制链接,附加valid=false&hideButton=true,隐藏操作按钮
5.6 FR-017 ~ FR-018 通知
5.6.1 中奖邮件(FR-017)
触发:MQ 消费成功后,线程池异步执行
模板(实际代码):
Hi,{winnerName}! 有个好消息要告诉你:你在【{activityName}】活动中,获得了{prizeTier中文}的惊喜福利:{prizeName}! 福利发放时间:{HH:mm:ss},快来领取你的专属惊喜吧~收件人:中奖用户邮箱
5.6.2 中奖短信(FR-018)
状态:V1.0 未实现,MqReceiver.sendMessage仅打印日志
6. 接口规格索引
| 序号 | 方法 | 路径 | 鉴权 | 详细定义 |
|---|---|---|---|---|
| 1 | POST | /register | 否 | 见接口设计文档 §4.1 |
| 2 | GET | /verification-code/send | 否 | 见接口设计文档 §4.2 |
| 3 | * | /password/login | 否 | 见接口设计文档 §4.3 |
| 4 | * | /message/login | 否 | 见接口设计文档 §4.4 |
| 5 | GET | /base-user/find-list | 是 | 见接口设计文档 §4.5 |
| 6 | * | /pic/upload | 是 | 见接口设计文档 §5.1 |
| 7 | * | /prize/create | 是 | 见接口设计文档 §5.2 |
| 8 | GET | /prize/find-list | 是 | 见接口设计文档 §5.3 |
| 9 | GET | /prize/find-listAll | 是 | 见接口设计文档 §5.4 |
| 10 | * | /activity/create | 是 | 见接口设计文档 §6.1 |
| 11 | GET | /activity/find-list | 是 | 见接口设计文档 §6.2 |
| 12 | GET | /activity-detail/find | 是 | 见接口设计文档 §6.3 |
| 13 | * | /draw-prize | 是 | 见接口设计文档 §7.1 |
| 14 | * | /winning-records/show | 否 | 见接口设计文档 §7.2 |
注:标注
*的接口 Controller 使用@RequestMapping,未限定 HTTP 方法;前端实际使用 POST(JSON)或 GET(Query)。
7. 前端页面规格
7.1 实现说明
本项目 Web 前端(src/main/resources/static)在页面结构、样式与部分交互实现上借助 AI 辅助完成,设计取向为美观、现代化视觉与现场展示效果,而非传统 B 端「功能优先、样式从简」风格。典型体现包括:
- 登录/注册页:分栏布局、渐变背景与表单视觉优化
- 管理后台:iframe 导航 + 统一卡片/表格样式
- 抽奖大屏(
draw.html):全屏背景、奖品展示、人名滚动动效、中奖确认交互
规格边界:本文档对前端的约束以页面路由、功能行为、Token 传递、接口调用为准;颜色、间距、动效细节以当前 AI 辅助产出为基线,不作为像素级设计稿验收项。后端 REST 接口规格不受 UI 实现方式影响。
7.2 页面清单
| 页面 | 路径 | 功能 |
|---|---|---|
| 后台登录 | /blogin.html | 密码/验证码 Tab 登录 |
| 管理后台 | /admin.html | iframe 框架 |
| 注册用户 | /register.html | 管理员/普通用户注册 |
| 人员列表 | /user-list.html | 用户列表 |
| 创建奖品 | /create-prizes.html | 表单 + 图片 |
| 奖品列表 | /prizes-list.html | 分页 |
| 创建活动 | /create-activity.html | 圈选奖品/人员 |
| 活动列表 | /activities-list.html | 分页 + 跳转 draw |
| 抽奖大屏 | /draw.html | 核心抽奖交互 |
7.3 通用前端约定
Token 存储:localStorage.user_token、localStorage.user_identity
请求头:user_token: {JWT}
8. 状态机规格
8.1 状态扭转顺序
ActivityStatusManagerImpl.handlerEvent:
- Sequence 1:
PrizeOperator、UserOperator(并行遍历,先奖品/用户) - Sequence 2:
ActivityOperator(全部奖品 COMPLETED 后才转活动)
8.2 抽奖正向扭转
| 对象 | 原状态 | 目标状态 |
|---|---|---|
| activity_prize | INIT | COMPLETED |
| activity_user(中奖者) | INIT | COMPLETED |
| activity | RUNNING | COMPLETED(全部奖品抽完后) |
8.3 异常回滚
| 对象 | 回滚目标 |
|---|---|
| activity_prize | INIT |
| activity_user | INIT |
| activity | RUNNING |
| winning_record | 删除对应记录 + 清缓存 |
9. 安全规格
| 编号 | 规格 |
|---|---|
| SEC-01 | JWT Header 名称固定为user_token |
| SEC-02 | Token 解析失败返回 HTTP 401 |
| SEC-03 | 业务异常统一{ code:500, msg:业务消息 } |
| SEC-04 | 图片上传限制 10MB |
| SEC-05 | 手机号不得明文落库 |
10. 异常与容错规格
10.1 MQ 重试
spring.rabbitmq.listener.simple.retry.enabled=truemax-attempts=5
10.2 死信队列
- 正常队列:
DirectQueue→ 死信交换机DlxDirectExchange - 死信消费者
DlxReceiver重新投递到正常队列
10.3 校验失败静默返回
checkDrawPrizeParam返回 false 时,MqReceiver直接 return,不抛异常(消息被 ACK)
11. 验收标准
11.1 功能验收
| 编号 | 验收项 | 通过标准 |
|---|---|---|
| AC-01 | 管理员注册登录 | 可进入 admin.html |
| AC-02 | 创建普通用户 | 列表可见 NORMAL 用户 |
| AC-03 | 创建奖品 | 列表展示图片与信息 |
| AC-04 | 创建活动 | 返回 activityId,详情正确 |
| AC-05 | 完整抽奖流程 | 每轮落库,最终活动 COMPLETED |
| AC-06 | 刷新恢复 | 已抽奖品不重复抽 |
| AC-07 | 分享链接 | 仅展示结果,无操作按钮 |
| AC-08 | 邮件通知 | 中奖者收到邮件 |
| AC-09 | 未登录拦截 | 受保护接口返回 401 |
11.2 非功能验收
| 编号 | 验收项 | 通过标准 |
|---|---|---|
| AC-N01 | 抽奖接口响应 | 500ms 内返回(P95) |
| AC-N02 | 缓存命中 | 二次查活动详情走 Redis |
| AC-N03 | 异常回滚 | 模拟落库异常后状态恢复 |
文档结束,下一文档我们将手动实现数据库的设计。
