SpringBoot 项目——抽奖系统

news2024/11/15 17:40:20

本项目主要实现的功能是:主要服务于管理员用户,其可圈选奖品,人员来创建抽奖活动,并进行在线抽奖,并可通过短信或邮件的方式通知中奖者,同时普通用户可查看已结束的抽奖活动的中奖结果;

一、项目技术栈及环境

编程语言:后端:Java 17,前端 JavaScript

后端框架:SpringBoot 3

数据库:Mysql

持久层框架:Mybatis,Mybatis-Plus

缓存:Redis

消息队列:RabbitMQ

日志服务:Slf4j

安全校验:JWT

加密解密:hutool

开发工具:lombok

其他:邮箱服务,阿里云短信服务

项目依赖的 pom.xml 文件:点这里

二、项目模块分类

  • 用户模块:管理员注册,登录,普通用户的创建,普通用户中奖状态的管理
  • 奖品模块:奖品的创建,图片的上传,奖品的状态
  • 活动模块:对抽奖活动及活动状态管理
  • 抽奖模块:完成抽奖功能,并展示抽奖结果
  • 中奖通知模块:通过发送邮件和短信来进行中奖通知

三、数据库设计

  • 用户表:存储用户信息,如用户名、密码、邮箱等
  • 奖品表:存储奖品信息,如奖品名称,奖品图片,奖品价值等
  • 活动表:存储活动信息,如活动名称、描述、活动状态等
  • 活动奖品关联表:存储每个活动下关联的奖品,包括奖品数量,奖品等级
  • 活动人员关联表:存储参与每个活动的人员
  • 中奖记录表:存储每个活动的中奖信息名单

sql 文件 点这里 使用source 命令执行 .sql 文件(路径不要存在中文)

数据库表 ER 图

四、统一处理

  • 全局错误码:描述错误的状态,便于更精确的指出问题所在
  • 自定义异常类:更精确地捕获和处理特定类型的错误,提高代码的可读性
  • 统一结果返回:作为控制器层方法的返回类型,封装 HTTP 接口调用的结果,包括成功数据,错误信息和状态码
  • 全局异常捕获:集中管理、统一处理和记录异常信息,提高了系统的健壮性、可维护性
  • 拦截器:验证用户身份,在请求处理的早期阶段进行检查,防止未授权的访问

五、枚举类

  1. GlobalErrorCodeEnum:全局错误码枚举,是错误码中的 code 部分

  2. UserIdentityEnum:用户身份信息枚举,包括普通用户 NORMAL 和 管理员 ADMIN

  3. ActivityStatusEnum:活动状态枚举,包括进行中 RUNNING 和 已完成 COMPLETED

  4. ActivityUserStatusEnum:参与某个活动的用户状态枚举,包括初始化态 INIT 和 已完成 COMPLETED,以此来区分用户是否已经中奖,已经中奖的用户不能参与该活动下的后续抽奖

  5. ActivityPrizeStatusEnum:参与某个活动的奖品状态枚举,包括初始化态 INIT 和 已完成 COMPLETED,以此来区分奖品是否已经被抽取完,被抽取完的奖品不能再次抽取

  6. PrizeGradeEnum:奖品等级枚举,包括一等奖 FIRST_PRIZE、二等奖 SECOND_PRIZE 和 三等奖 THIRED_PRIZE

六、工具类

  1. JacksonUtil:包含静态的序列化和反序列化的方法,实现对象与 json 格式的转化
  2. JWTUtil:包含生成 token 和校验 token 的静态方法,实现用户身份认证
  3. RegexUtil:包含校验邮箱格式,手机号格式是否正确的静态方法,完成参数的校验
  4. MailUtil:封装发送邮件的方法
  5. SMSUtil:封装向手机号发送短信的方法
  6. CaptchaUtil:封装生成随机短信验证码的方法
  7. RedisUtil:封装操作 redis 的方法

七、业务实现

1. 用户模块

使用 hutool 将敏感字段如(手机号,密码)加密存储

1)注册功能

时序图:

前后端交互接口:

请求 

url: '/register',
type: 'POST',
contentType: 'application/json',
data: {"name": "xxx", "email": "xxx", "phoneNumber": "xxx", "password": "xxx", "identity": "admin"}

响应

"code": 200,
"data": { userId: 11 },
"msg": ""

后端处理

  1. 首先校验用户输入的注册信息,包括邮箱格式,手机号格式等
  2. 校验通过之后,构造 User 对象,并存储在数据库中,同时对用户密码和手机号进行加密
  3. 构造响应,返回 userId

2)发送验证码功能

时序图

前后端交互接口

请求

url: '/verifyCode/send',
type: 'POST',
data: {"phoneNumber": "xxx"}

响应

"code": 200,
"data": true,
"msg": ""

后端实现

首先校验手机号格式是否正确,若正确则通过 CaptchaUtil 类生成随机验证码,并使用 SMSUtil 类的方法发送验证码,并将验证码缓存在 Redis 中,最后构造响应并返回;

3)登录功能

时序图

1. 登录名(可为手机号或邮箱)+ 密码登录

前后端交互接口

请求

url: '/login/password',
type: 'POST',
contentType: 'application/json',
data: { "loginName": "xxx", "password": "xxx", "mandatoryIdentity": "admin" }

响应

"code": 200,
"data": { "token": "xxx", "userName": "xxx", "identity": "xxx" },
"msg": ""

后端处理

根据前端传来的登录名 loginName 判断其格式是邮箱格式还是手机号格式,并根据邮箱/ 手机号在数据库中查找相应的用户,通过 MD5 算法加密用户输入的密码,判断其是否和数据库中的密码是否一致,若一致,则登录成功,构造响应,设置 token 并返回;

2. 手机号 + 验证码登录

请求

url: '/login/verifyCode',
type: 'POST',
contentType: 'application/json',
data: { "phoneNumber": "xxx", "verifyCode": "xxx", "mandatoryIdentity": "admin" }

响应

"code": 200,
"data": { "token": "xxx", "userName": "xxx", "identity": "xxx" },
"msg": ""

后端处理

判断前端传递的手机号格式是否正确,若正确,则根据手机号在数据库中用户表中查询该用户,若查询成功,再判断用户输入的验证码是否正确,若正确则登录成功,构造响应,设置 token 并返回;

4)获取用户列表

时序图

前后端交互接口

请求

url: '/base-user/getUserInfoList',
type: 'GET',
data: {"identity": "admin/noraml/null 查询管理员用户/普通用户/全量用户"}

响应

"code": 200,
"data": [ 
            { "userId": 1, "userName": "xxx", "identity": "admin/normal" },
            { "userId": 2, "userName": "xxx", "identity": "admin/normal" },
            ......
        ],
"msg": "" 

后端实现

先根据 identity 确定查询哪部分用户信息,然后执行相应的数据库查询语句,查询出用户信息集合后,最后构造响应并返回

2. 奖品模块

1)创建奖品

时序图

前后端交互接口

请求

url: '/prize/create',
type: 'POST',
contentType: false, // 通过 formData 方式传递
data: {"prizeName": "xxx", "description": "xxx", "price": "xxx", file}

 响应

"code": 200,
"data": { "prizeId": 19 },
"msg": ""

后端处理

首先在数据库奖品表中查询相应奖品名是否已经存在,若存在则不能继续创建,否则构造一个奖品对象,设置前端传来的数据,调用图片上传的方法,保存图片,最后构造响应并返回;

2)查询奖品列表

时序图

前后端交互接口

请求

url: '/prize/getPrizeInfoList',
type: 'POST',
data: { "currentPage": 1, "pageSize": 10 }

响应 

"code": 200,
"data": { "total": 10, 
          "records": [
            { "prizeId": 19, "prizeName": "xxx", "description": "xxx", "price": 200,                                 
               "imageUrl": "xxx" }, 
            { "prizeId": 20, "prizeName": "xxx", "description": "xxx", "price": 500,                                 
               "imageUrl": "xxx" },  
            {......}
          ] 
        },
"msg": ""

后端处理

首先查询数据库中的所有奖品的总数,然后根据前端传来的分页参数在数据库中查询相应的记录,构造为一个集合,最后构造响应并返回

3. 活动模块

1)创建活动

时序图

前后端交互接口

请求 

url: '/activity/create',
type: 'POST',
contentType: 'application/json',
data: 
{      
        "activityName": "xxx", 
        "description": "xxx", 
        "activityPrizeList": [    
                                {"prizeId": 5, "prizeAmount": 2, "prizeTiers": "二等奖"}, 
                                {"prizeId": 6, "prizeAmount": 1, "prizeTiers": "一等奖"}, 
                             ],
        "activityUserList": [
                                {"userId": 3, "userName": "xxx"},
                                {"userId": 4, "userName": "xxx"},
                                {"userId": 5, "userName": "xxx"},
                            ]
}

响应

"code": 200,
"data": { "activityId": 10 },
"msg": ""

后端处理

首先校验活动信息:包括活动名是否已经存在,活动关联的人员 id 是否在人员表中存在,活动关联的奖品 id 是否在奖品表中存在,并且参加活动的人员数量必须大于等于奖品数量,奖品等级命名是否符合规范等,活动信息通过校验之后,将活动信息保存到数据库中的活动表,活动人员管理表,活动奖品关联表,并将以上信息整合为一个对象,并保存在 redis 中,最后构造响应并返回

2)获取活动列表

时序图

前后端交互接口

请求 

url: '/activity/getActivityList',
type: 'POST',
data: { "currentPage": 1, "pageSize": 10 }

响应

"code": 200,
"data": { "total": 10, 
          "records": [
            {"activityId": 19, "activityName": "xxx", "description": "xxx", "valid": true},                           
            {"activityId": 20, "activityName": "xxx", "description": "xxx", "valid": true},                             
            {......}
          ] 
        },
"msg": ""

后端处理

首先查询数据库活动表中的活动总量,然后根据前端传递的分页参数,查询活动表的相应记录,最后构造响应并返回

3)获取活动详情

时序图

前后端交互接口

请求

url: '/activity/getActivityDetail',
type: 'GET',
data: { "activityId": 10 }

响应

"code": 200,
"data": { "activityId": 10, 
          "activityName": "xxx",
          "description": "xxx", 
          "valid": true,
          "prizes": 
            [
                { "prizeId": 19, "name": "xxx", "description": "xxx", "price": 200,                                 
               "imageUrl": "xxx", "prizeAmount": 2, "prizeTierName": "xxx", "valid": true}, 
                { "prizeId": 20, "name": "xxx", "description": "xxx", "price": 500,                                 
               "imageUrl": "xxx", "prizeAmount": 1, "prizeTierName": "xxx", "valid": true},
                {......}
            ] 
          "users": 
            [
                {"userId": 10, "userName": "xxx", "valid": true},
                {"userId": 11, "userName": "xxx", "valid": true},
                {"userId": 12, "userName": "xxx", "valid": true},
                {......}
            ]
        },
"msg": ""

 后端实现

先根据活动 id 在缓存中查询对应的活动,若查到了,则直接返回,否则在数据库中查询,若没有查询到,则说明活动不存在,否则将对应活动信息缓存到 redis 中,最后构造响应并返回

4. 抽奖模块

1)抽奖功能

抽奖是按照奖品来抽取的,每次抽取一种奖品(按照三等奖,二等奖,一等奖的顺序进行),例如:若该奖品有 n 个,则会抽取 n 个用户,该 n 个用户中奖,中奖用户不能再次参与后续抽奖,同时已抽取的奖品不能再次抽取;

抽奖流程

时序图

前后端交互接口

请求

url: '/lottery/prize',
type: 'POST',
contentType: 'application/json',
data: { 
        "activityId": 10, "prizeId": 16, "winningTime": Date, 
        "winnerList": [{ "userId": 8, "userName": "xxx" },
                       {......} ] 
      }

响应

"code": 200,
"data": true,
"msg": ""

后端处理

为了使抽奖过程高效,因此采用异步抽奖来进行,有前端生成抽奖用户,并发送请求到后端,后端收到请求后,往消息队列(RabbitMQ)中发送该抽奖请求,之后直接返回成功,有消息队列的消费者来消费该消息,并完成后端的工作,消费者在消费消息的过程中,如果发生了异常,则会进行消息消费重试 5 次,若重试 5 次都失败了,则会进入到死信队列,等待 bug 处理完之后,再处理该消息,确保消息不会丢失;

消费者处理抽奖请求的具体流程为:

  1. 校验该抽奖请求是否有效,比如校验活动状态是否为正在抽奖中,此次抽取的奖品是否已经抽取过,此次抽取的奖品数量是否等于传来的中奖用户数量等
  2. 进行相应状态的扭转(由于是对多个表进行修改操作,因此要确保以事务的方式执行)(此处使用责任链模式(由于在业务上判断活动是否已完成,需要奖品已完成(存在执行顺序),涉及活动及奖品等多横向维度扭转,不能避免未来不会有其他内容牵扯进活动中)+ 策略模式(各个状态的扭转存在相似性,但又有其各自的条件,使用策略模式让每个状态的扭转都有其各自的策略)):
    1. 判断该奖品的状态是否为 INIT 初始化状态,若为 INIT 状态则该奖品抽取完后,应将该奖品的状态改变为 COPMLETED 已完成状态
    2. 判断该用户的状态是否为 INIT 初始化状态,若为 INIT 状态该用户中奖后,应将该用户的状态改变为 COMPLETED 已完成状态
    3. 判断该活动是否处于 RUNNING 正在进行状态,若为正在进行状态,并且该活动下的奖品已经抽取完毕,则需将该活动的状态修改为 COMPLETED 已完成状态
  3. 保存中奖名单到数据库的中奖记录表和缓存中,将活动信息,奖品信息和中奖人员信息进行整合并存储,在存放在缓存中时,分别已奖品为维度,已活动为维度存储
  4. 通知中奖者(见中奖通知模块)

如果处理上述流程时,发生异常需要进行事务的回滚,即使该次抽奖请求的奖品,活动及相应中奖人员的状态回滚 为 INIT 状态,并且删除相应数据库中奖人员记录表中的数据,并删除对应缓存;

2)查看中奖名单 

时序图

前后端交互接口

请求

url: '/lottery/getWinningRecordList',
type: 'POST',
contentType: 'application/json',
data: { "activityId": 10, "prizeId": 16 }

响应

"code": 200,
"data": [
            { "winnerId": 19, "winnerName": "xxx", "prizeName": "xxx", "prizeTier": "xxx",                                 
               "winningTime": Date }, 
            { "winnerId": 20, "winnerName": "xxx", "prizeName": "xxx", "prizeTier": "xxx",                                 
               "winningTime": Date },  
            {......}
        ],
"msg": ""

后端处理

首先根据前端传递的参数判断,活动 id 为空,则直接返回空,否则判断若奖品 id 不为空,则需查询活动下对应奖品的中奖信息,否则需查询活动下所有奖品的中奖信息,查询时,首先在缓存中查询,若查询成功,则直接返回,否则在数据库中查询,如果数据库中也不存在,则返回空,否则写入缓存中,再构造返回

5. 中奖通知模块

该模块直接被后端调用,不涉及前后端交互,使用两个线程并发执行,分别完成短信通知和邮件通知

1)短信通知

后端接口:SMSUtil 类

使用阿里云短信服务进行短信的发送,向所有中奖用户的手机号发送中奖短信模版

2)邮件通知

后端接口:MailUtil 类

使用 QQ 邮箱服务进行邮件的发送,向所有中奖用户的邮件发送中奖邮件模版

项目原码 gitee

lottery_system: 抽奖系统

项目链接

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2070620.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ViT篇外:NVIDIA Llama-3.1-Minitron 4B

相关阅读: ViT:3 Compact Architecture MobileLLM:“苗条”的模型比较好! 大家也许会很好奇为什么在ViT章节插入了NVIDIA Llama-3.1-Minitron 4B,ViT因为应用场景的特殊性所以都寄希望于高效率的模型,因…

【C语言】浮点型数据在内存中的储存

浮点型数据在内存中的储存 文章目录 浮点型数据在内存中的储存引例概念提出浮点型数据储存规定对于有效数字M的特别规定对于指数E的特别规定指数E的储存指数E的读取 利用规则解释原因 在之前学习过整形数据在内存中的储存后,浮点型数据在内存中的储存又会怎样呢&…

AI辅助论文写作已成大趋势,这些AI工具分享给你

近年来,人工智能语言模型迅速发展,特别是在美国人工智能研究实验室 OpenAI 于 2022 年 11 月发布了聊天机器人 ChatGPT 后,引发了全球范围内的广泛讨论。人们惊叹着一个新的人工智能时代已经到来,预示着许多工作将被这类机器人所取…

二叉树刷题(1)

二叉树题目讲解(1) 一、构建二叉树并且遍历(1)思路(2)代码 二、对称二叉树1、思路2、代码 三、相同的树1、思路2、代码 四、单值二叉树1、思路2、代码 五、另一棵树的子树1、思路2、代码 一、构建二叉树并且…

【Rust日报】一本新书:黑帽Rust

2024 Rust中国大会大会将于 9 月 07 日 - 08 日在上海举办。精彩议题逐步放出中,欢迎大家面对面交流。 2024 Rust中国大会报名链接暨第一批精彩演讲主题介绍 2024 Rust中国大会第二批精彩演讲主题列表 2024 Rust中国大会第三批精彩演讲主题列表 马尔科夫文本生成算法…

多线程(5)——锁策略、CAS、JUC常见类

1. 常见锁策略 1.1 乐观锁 & 悲观锁 乐观锁 & 悲观锁 也不是指具体某个锁,而是 “锁的一种特点”,描述了 “一类锁” 乐观锁:加锁的时候,假设出现锁冲突的概率不大 > 接下来围绕加锁要做的工作就会更少悲观锁&#…

minio 后端大文件分片上传,合并,删除分片

背景 网上大多数minio大文件上传都是采用后台返回前端预上传链接,然后由前端去put请求直接和minio通信上传分片文件,然后调用后台合并分片逻辑来达到快申诉上传的目的,详情可以参考我的上两篇文章 最近有个项目域名是https的,但…

# 低代码和无代码开发初探

低代码和无代码开发初探 低代码和无代码都是近年来在软件开发领域兴起的技术趋势,它们旨在提高开发效率、降低开发门槛,让更多人能够参与到软件开发过程中。以下是对低代码和无代码的介绍: 一、低代码 1、低代码定义 低代码开发平台&…

Linux nice/renice 命令 - 进程的NI、PRI属性

进程NI、PRI属性的联系 共同影响进程调度:NI和PRI都是Linux进程调度机制中的重要参数,它们共同决定了进程在CPU资源竞争中的优先级。NI值通过影响PRI值来间接影响进程的调度顺序。NI值可调整以改变PRI值:用户可以通过调整进程的NI值来间接改…

较难!第15届蓝桥杯青少组省赛Scratch中级组编程真题

今天上午第15届蓝桥杯青少组省赛Scratch初级组考完试以后,Scratch实验室就预估今天下午的Scratch中级组比较难,结果不出所料,还是比较有难度,据好几个学生及家长说,好几道题不会做时间不够。 来源:结束啦&a…

三级_网络技术_43_综合题(报文)

一、 某客户机使用DHCP获取IP地址等信息,其获取lP地址过程中捕获的4条报文及对第2条报文分析如下所示。请分析其中的信息,补全内容。 编号 报文摘要 DHCP:Request, Type:DHCP discover DHCP:Reply, Type:DHCP__________ DHCP:Request, Type:DHCP Re…

DBeaver安装使用

文章目录 简介支持的数据库支持的系统 下载安装DBeaver使用修改Maven下载jar地址窗口->首选项连接->驱动->Maven配置仓库地址 选择需要连接的数据库进行连接 简介 DBeaver 是一个通用的数据库管理工具和 SQL 客户端,支持 MySQL, PostgreSQL, Oracle, DB2,…

人生苦短,转行程序员要趁早啊

前言 最近有朋友咨询关于如何自学编程语言的问题,发现要回答这个问题,不是一俩句就可以回答清楚并减少当事人的困惑和迷茫。 笔者不知道提问者是问的学习方法还是学习路径,所以特此写一篇文章,斗胆表达一下我对自学编程的一点点…

解决Gradle下载依赖速度慢的问题

🌟 前言 欢迎来到我的技术小宇宙!🌌 这里不仅是我记录技术点滴的后花园,也是我分享学习心得和项目经验的乐园。📚 无论你是技术小白还是资深大牛,这里总有一些内容能触动你的好奇心。🔍 &#x…

盘古信息MES制造执行系统,赋能制造企业智能化运营管理

随着工业智能化的不断深入,MES系统在制造业中扮演着越来越重要的角色。盘古信息自主研发的IMS MES,通过提供包括制造数据管理、计划排程管理、生产调度管理、库存管理、质量管理、人力资源管理、设备管理、采购管理、成本管理、看板管理、生产过程控制、…

TCP粘包和抓包

在 TCP 套接字中,发送和接收缓冲区用于暂存数据,以确保数据的可靠传输。具体来说,TCP 的 socket 收发缓冲区的主要特点和概念如下: 1. 发送缓冲区(Send Buffer) 定义: 发送缓冲区用于存储待发送的数据。应…

如何在不格式化的情况下解锁 Android 智能手机密码

如果您忘记密码,您的 Android 移动设备将锁定您。发生这种情况时,通常可以通过恢复出厂设置来重新获得对设备的访问权限。可悲的是,这将导致所有数据丢失。下面列出的是解锁锁定的Android 手机而不会丢失任何个人数据的有效方法。 Android 手…

排查Maven问题的步骤

0.检查pom文件完整性 1.检查IDEA中配置是否正确 2.使用清楚工具将所有的lastupdate清除, 3.有些依赖是公司的依赖 —>配置私服 —>拷贝同事仓库,覆盖自己的仓库 4.有了私服地址,但是还是下载不到 —>查看地址是否能访问 —>挂VPN

linux多进程与多线程总结

这里写自定义目录标题 2 linux多进程与多线程2.1 进程间通信2.1.1 管道2.1.2 信号2.1.3 消息队列2.1.4 共享内存 3 线程4 IO多路复用4.1 非阻塞IO4.2 IO多路复用 2 linux多进程与多线程 学习并发程序。 linux系统中,使用树型管理进程。因此进程之间有父子关系。通…

如何使用ssm实现学生公寓管理系统的设计与实现

TOC ssm106学生公寓管理系统的设计与实现jsp 绪论 1.1 研究背景 当前社会各行业领域竞争压力非常大,随着当前时代的信息化,科学化发展,让社会各行业领域都争相使用新的信息技术,对行业内的各种相关数据进行科学化,…