项目背景和介绍
抽奖系统类似于直播中的抽奖活动,在日常生活中也是比较常见,于是基于SpringBoot提供一个安全,可靠,易于维护的抽奖平台,本次项目采用了以下的策略:
1.集成多种技术组件:项目中集成了MySql,redis,RabbitMQ等常见的中间件.
2.允许管理员创建配置抽奖活动,管理人员和奖品信息.
3.使用了哈希加密,对称加密等加密方法保障用户的隐私数据.
4.提供了比较全面的日志处理与异常处理机制,简化维护过程.
项目结构

展示层:使用web的方式展示前端界面.
通信层:使用HTTP协议进行通信.
服务层:包含日志模块,业务模块,中间件.
数据层:使用mysql存储用户的数据和活动信息

本次项目的结构中,common层表示存放的一些全局使用例如错误码,统一返回结果,拦截器等
controller层中主要是与前端交互的接口,service层中就是接收controller层中任务处理.dao层是提供数据库中的查询等操作.
数据库设计
⽤户表:存储⽤⼾信息,如⽤户名、密码、邮箱等。
活动表:存储活动信息,如活动名称、描述、活动状态等。
奖品表:存储奖品信息,如奖品名称、奖品图等。
活动奖品关联表:存储⼀个活动下关联了哪些奖品。
活动⽤户关联表:存储⼀个活动下设置的参与⼈员。
中奖记录表:存储⼀个活动的中奖名单,如活动id,奖品id,中奖者id等。
项目启动
定义错误码
错误码是一种明确的表示错误信息的方式,与模糊的异常相比可以准确的指出错误所在,而错误码是由数字组成,也方便在日志中寻找.

本次项目的错误码主要包含两个信息,错误码与错误信息,方便区分错误的类别.
也定义了多模块中的错误码,将controller层与service层中的不同错误码分开,同时设计了全局的错误码,来对付意外的错误.
自定义异常类

自定义异常类作用是为了在可能抛出异常的地方,希望抛出我们可控的异常,这样可以方便定位到问题所在,异常类可以和错误码一起使用.
统一返回结果

为了方便返回,我们定义了CommonResult<T>来表示统一的返回结果,包括成功信息,错误信息,状态码.
这样做的好处是将无论调用哪个接口,都确保客户端收到的响应具有一至的结构.
错误码和错误消息也可以让客户端快速识别和处理错误.
在实际应⽤中,CommonResult<T>作为控制器层⽅法的返回类型,可以被Springboot框架等⾃动转换为JSON或其他格式的响应体,发送给客⼾端,这种⽅式提⾼了API的可⽤性和可维护性.
序列化工具
序列化工具本次使用的是Jackson,序列化工具的使用场景例如在日志打映的时候,将传入的JSON字符转为字符串.

我们自己实现的工具类中,也是参考了SpringBoot中源码的设计思路.

当我们调用parseMap时,就会调用tryparse来传入表示序列化的lamda表达式,而在t方法中,又调用了call方法,相当于执行那一段lamda表达式,此时又会捕捉异常,这样的做法可以防止每一次调用序列化时都需要捕捉异常,是一种很巧妙的做法.
日志处理
日志处理采用的是SLF4J+logback的处理机制,在本地测试时会将日志打映在控制台上,而在运行环境下又会分文件处理.
此处提供配置文件:
application文件配置和新增的logback.xml
logging.config=classpath:logback-spring.xml
spring.profiles.active=dev
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<springProfile name="dev">
<!--输出到控制台-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n%ex</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="console" />
</root>
</springProfile>
<springProfile name="prod,test">
<!--ERROR级别的日志放在logErrorDir目录下,INFO级别的日志放在logInfoDir目录下-->
<property name="logback.logErrorDir" value="/root/lottery-system/logs/error"/>
<property name="logback.logInfoDir" value="/root/lottery-system/logs/info"/>
<property name="logback.appName" value="lotterySystem"/>
<contextName>${logback.appName}</contextName>
<!--ERROR级别的日志配置如下-->
<appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则
如果同时有<File>和<FileNamePattern>,那么当天日志是<File>,明天会自动把今天
的日志改名为今天的日期。即,<File> 的日志都是当天的。
-->
<File>${logback.logErrorDir}/error.log</File>
<!-- 日志level过滤器,保证error.***.log中只记录ERROR级别的日志-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--文件路径,定义了日志的切分方式------把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
<FileNamePattern>${logback.logErrorDir}/error.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--只保留最近14天的日志-->
<maxHistory>14</maxHistory>
<!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
<!--<totalSizeCap>1GB</totalSizeCap>-->
</rollingPolicy>
<!--日志输出编码格式化-->
<encoder>
<charset>UTF-8</charset>
<pattern>%d [%thread] %-5level %logger{36} %line - %msg%n%ex</pattern>
</encoder>
</appender>
<!--INFO级别的日志配置如下-->
<appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则
如果同时有<File>和<FileNamePattern>,那么当天日志是<File>,明天会自动把今天
的日志改名为今天的日期。即,<File> 的日志都是当天的。
-->
<File>${logback.logInfoDir}/info.log</File>
<!--自定义过滤器,保证info.***.log中只打印INFO级别的日志, 填写全限定路径-->
<filter class="com.example.lotterysystem.common.filter"/>
<!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--文件路径,定义了日志的切分方式------把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
<FileNamePattern>${logback.logInfoDir}/info.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--只保留最近14天的日志-->
<maxHistory>14</maxHistory>
<!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
<!--<totalSizeCap>1GB</totalSizeCap>-->
</rollingPolicy>
<!--日志输出编码格式化-->
<encoder>
<charset>UTF-8</charset>
<pattern>%d [%thread] %-5level %logger{36} %line - %msg%n%ex</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="fileErrorLog" />
<appender-ref ref="fileInfoLog"/>
</root>
</springProfile>
</configuration>