声明:本文档AI辅助完成,内容仅供参考

✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
🎯 你正在阅读「Java项目-企悦抽」系列文章 🎯
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨

🔥 弹简特 个人主页

❄️ 个人专栏直通车:


靠热爱去书写自己,靠勇敢去书写生活!
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨


🌟 博主简介:


在这里插入图片描述



企悦抽-产品需求规格说明书

文档属性内容
产品名称企悦抽(QiYueChou)
文档类型产品需求规格说明书(PRS)
文档版本V1.0
编写日期2025-07-03
上游文档AI赋能产品需求文档
关联代码lottery-system
文档状态已定稿

修订记录

版本日期修订说明
V1.02025-07-03首版,由 PRD 拆解为可开发/可测试规格

1. 引言

1.1 编写目的

本文档在 PRD 基础上,将产品需求转化为可实施、可验证的功能规格、数据规格、状态规则与验收标准,供研发与测试直接使用。

1.2 文档关系

PRD(为什么做、做什么)
  ↓
PRS(怎么做、做到什么标准)  ← 本文档
  ↓
接口设计文档(接口级定义)
  ↓
接口测试需求文档(测试用例输入)

2. 系统概述

2.1 系统名称

  • 产品名:企悦抽
  • 工程名:lottery-system
  • 默认端口(开发用):8080

2.2 技术栈规格

层次技术版本/说明
后端框架Spring Boot3.2.6
语言Java17
ORMMyBatis3.0.3(注解方式)
数据库MySQL库名 lottery_system
缓存RedisLettuce,默认端口配置 8888
消息队列RabbitMQDirect 交换机 + 死信队列
认证JWTjjwt 0.11.5,Header: user_token
前端HTML + jQuery + CSS静态资源 /staticUI 借助 AI 辅助实现,主打美观效果
日志SLF4J + Logbacklogback-spring.xml
短信Spug 推送SMSUtil HTTP 调用
邮件Spring MailQQ SMTP

2.3 模块划分

模块包路径/组件职责
用户模块UserControllerUserServiceImpl注册、登录、用户列表
验证码模块VerificationCodeServiceImpl验证码生成、Redis 缓存、Spug 发送
奖品模块PrizeControllerPrizeServiceImpl奖品 CRUD、图片上传
活动模块ActivityControllerActivityServiceImpl活动创建、列表、详情、Redis 缓存
抽奖模块DrawPrizeControllerDrawPrizeServiceImpl发 MQ、校验、落库、查记录
MQ 消费MqReceiverDlxReceiver异步抽奖、回滚、死信重投
状态管理ActivityStatusManagerImpl + Operator活动/奖品/用户状态扭转
通用LoginInterceptorGlobalException鉴权、统一响应

3. 系统架构规格

3.1 逻辑架构

在这里插入图片描述

3.2 抽奖异步链路规格

步骤组件行为
1DrawPrizeController接收 JSON,调用 drawPrize()
2DrawPrizeServiceImpl.drawPrize构造 {messageId, messageData} 发 MQ
3Controller立即返回 {code:200, data:true}
4MqReceiver.process消费消息
5checkDrawPrizeParam 校验
6ActivityStatusManager.handlerEvent 扭转状态
7saveWinnerRecords 写库 + 缓存
8线程池异步发邮件(短信占位)
9异常回滚 + 抛异常 → MQ 重试 → 死信

3.3 缓存规格

键前缀内容TTL
VERIFICATION_CODE_{phone}4 位验证码60 秒
ACTIVITY_{activityId}活动详情 JSON3 天
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_PRIZE1一等奖
SECOND_PRIZE2二等奖
THIRD_PRIZE3三等奖

4.3 字段校验规格

字段规则实现
邮箱小写邮箱格式RegexUtil.checkMail
手机号1 开头 11 位RegexUtil.checkMobile
密码6~12 位字母数字RegexUtil.checkPassword
管理员密码必填注册/创建时校验

4.4 加密规格

数据算法说明
密码SHA256 Hex注册/登录时 DigestUtil.sha256Hex
手机号AESEncryptTypeHandler,密钥 16 字节

5. 功能规格详述

5.1 FR-001 ~ FR-005 用户与认证

5.1.1 注册(FR-001)

输入namemailphoneNumberpassword(管理员必填)、identity(ADMIN/NORMAL)

处理逻辑

  1. JSR303 非空校验
  2. 邮箱/手机号格式校验
  3. 身份合法性校验
  4. 管理员密码必填且强度校验
  5. 邮箱/手机号唯一性校验
  6. 密码 SHA256 后入库;手机号 AES 加密入库

输出{ userId }

页面register.html;管理员后台创建时 admin=false&jumpList=true

5.1.2 发送验证码(FR-004)

输入:Query 参数 phone

处理逻辑

  1. 手机号格式校验
  2. MyCaptchaUtil.getCaptcha(4) 生成 4 位数字
  3. SMSUtil.sendSms(phone, code) 调用 Spug
  4. Redis 存储,键 VERIFICATION_CODE_{phone},60 秒

输出{ code:200, data:true }

5.1.3 密码登录(FR-002)

输入loginName(手机/邮箱)、passwordmandatoryIdentity(可选)

处理逻辑

  1. 按格式查用户(手机需 Encrypt 包装)
  2. 校验用户存在、身份匹配、密码 SHA256 比对
  3. JWT Claims: { id, identity },有效期 1 小时

输出{ token, identity }

5.1.4 短信登录(FR-003)

输入loginMobileverificationCodemandatoryIdentity(可选)

处理逻辑

  1. 查用户、校验身份
  2. 从 Redis 取验证码比对
  3. 签发 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)

与注册共用 /registeridentity=NORMAL,不传密码。

5.2.2 用户列表(FR-007)

输入:Query identity(可选,ADMIN/NORMAL,空则全部)

输出[{ userId, userName, identity }]


5.3 FR-008 ~ FR-010 奖品管理

5.3.1 创建奖品(FR-008)

Content-Typemultipart/form-data

Part 名类型说明
paramJSON 字符串{ prizeName, description, price }
prizePicFile奖品图片,最大 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)

输出字段 validstatus == RUNNING 为 true

前端行为

  • valid=true → 「活动进行中,去抽奖」
  • valid=false → 「活动已完成,查看中奖名单」
5.4.3 活动详情(FR-013)

输入:Query activityId

输出

字段说明
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 参数activityIdactivityNamevalid(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. 接口规格索引

序号方法路径鉴权详细定义
1POST/register见接口设计文档 §4.1
2GET/verification-code/send见接口设计文档 §4.2
3*/password/login见接口设计文档 §4.3
4*/message/login见接口设计文档 §4.4
5GET/base-user/find-list见接口设计文档 §4.5
6*/pic/upload见接口设计文档 §5.1
7*/prize/create见接口设计文档 §5.2
8GET/prize/find-list见接口设计文档 §5.3
9GET/prize/find-listAll见接口设计文档 §5.4
10*/activity/create见接口设计文档 §6.1
11GET/activity/find-list见接口设计文档 §6.2
12GET/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.htmliframe 框架
注册用户/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_tokenlocalStorage.user_identity

请求头user_token: {JWT}


8. 状态机规格

8.1 状态扭转顺序

ActivityStatusManagerImpl.handlerEvent

  1. Sequence 1PrizeOperatorUserOperator(并行遍历,先奖品/用户)
  2. Sequence 2ActivityOperator(全部奖品 COMPLETED 后才转活动)

8.2 抽奖正向扭转

对象原状态目标状态
activity_prizeINITCOMPLETED
activity_user(中奖者)INITCOMPLETED
activityRUNNINGCOMPLETED(全部奖品抽完后)

8.3 异常回滚

对象回滚目标
activity_prizeINIT
activity_userINIT
activityRUNNING
winning_record删除对应记录 + 清缓存

9. 安全规格

编号规格
SEC-01JWT Header 名称固定为 user_token
SEC-02Token 解析失败返回 HTTP 401
SEC-03业务异常统一 { code:500, msg:业务消息 }
SEC-04图片上传限制 10MB
SEC-05手机号不得明文落库

10. 异常与容错规格

10.1 MQ 重试

  • spring.rabbitmq.listener.simple.retry.enabled=true
  • max-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异常回滚模拟落库异常后状态恢复

文档结束,下一文档我们将手动实现数据库的设计。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐