抽奖系统(1)(Java 实现)
1. 需求描述
1. 包含管理员的注册与登录
1) 注册包含:姓名、邮箱、手机号、密码
2) 登录包含两种方式
(1) 电话 + 密码登录
(2) 电话 + 短信登录;验证码获取
(3) 登录需要校验管理员身份
2. 人员管理:管理员支持创建普通用户,查看用户列表
1) 创建普通用户:姓名、邮箱、手机号
2) 人员列表:人员ID、姓名、身份(普通用户、管理员)
3. 管理端支持创建奖品、奖品列表展示功能
1) 创建的奖品信息包含:奖品名称、描述、价格、奖品图(上传)
2) 奖品列表展示(可翻页):奖品ID、奖品图、奖品名、奖品描述、奖品价值(元)
4. 管理端支持创建活动、活动列表展示功能
1) 创建的活动信息包含
(1) 活动名称
(2) 活动描述
(3) 圈选奖品:勾选对应奖品,并设置奖品等级(一二三等奖)及奖品数量
(4) 圈选人员:勾选参与抽奖人员
2) 活动列表展示(可翻页)
(1) 活动名称
(2) 描述
(3) 活动状态
a. 活动状态为进行中:点击 “活动进行中,去抽奖” 按钮跳转抽奖页
b. 活动状态为已完成:点击 “活动已完成,查看中奖名单” 按钮跳转抽奖页查看结果
5. 抽奖页面
1) 对于进行中的活动,管理员才可抽奖
2) 每轮抽奖的中奖人数跟随当前奖品数量
3) 每个人只能中一次奖
4) 多轮抽奖,每轮抽奖有 3 个环节:展示奖品信息(奖品图、份数),人名闪动,停止闪动确定中奖名单
(1) 当前页面展示奖品信息,点击 “开始抽奖” 按钮,则跳转至人名闪动画面
(2) 人员闪动画面,点击 “点我确定” 按钮,确认中奖名单
(3) 当前页展示中奖名单,点击 “已抽完,下一步” 按钮,若还有奖品未抽取,则展示下一个奖品信息,否则展示全部中奖名单
(4) 点击 “查看上一奖项” 按钮,展示上一个奖品信息
对于抽奖过程中的异常情况,如抽奖过程中刷新页面,要保证抽取成功的奖项不能重新抽取
(5) 刷新页面后,若奖品已抽完,点击 “开始抽奖” 则直接展示当前奖品中奖名单
如该抽奖活动已完成
(6) 展示所有奖项的全部中奖名单
(7) 新增 “分享结果” 按钮,点击可复制当前页链接,打开后隐藏其他按钮,只展示活动名称与中奖结果,保留 “分享结果” 按钮
6. 通知部分:抽奖完成需以邮件和短信方式通知中奖者
“Hi,xxx,恭喜你在xxx抽奖中获取一等奖:手机。中奖时间为:xx:xx。请尽快领取您的奖品”
7. 管理端涉及的所有页面,包括抽奖页,需强制管理员登录后方可访问
未登录则强制跳转登录页面
2. 系统设计
系统架构
- 前端:使用 JavaScript 管理各界面的动态性,使用 AJAX 技术从后端 API 获取数据
- 后端:采用 Spring Boot 3 构建后端应用,实现业务逻辑
- 数据库:使用 MySQL 作为主数据库,存储用户数据和活动信息
- 缓存:使用 Redis 作为缓存层,减少数据库访问次数
- 消息队列:使用 RabbitMQ 处理异步任务,如:处理抽奖行为
- 日志与安全:使用 SLF4J + logback 完成日志,使用 JWT 进行用户认证
业务功能模块
- 人员业务模块:管理员注册、登录及普通用户的创建
- 活动业务模块:活动管理及活动状态管理
- 奖品业务模块:奖品管理与奖品的分配,包括奖品图的上传
- 通知业务模块:发送短信、邮件等业务,如:验证码发送、中奖通知等
- 抽奖业务模块:完成抽奖动作,以及抽奖后的结果展示
数据库设计
- 用户表:存储用户信息,如:用户名、密码、邮箱等
- 活动表:存储活动信息,如:活动名称、描述、活动状态等
- 奖品表:存储奖品信息,如:奖品名称、奖品图等
- 活动奖品关联表:存储一个活动下关联了哪些奖品
- 活动用户关联表:存储一个活动下设置的参与人员
- 中奖记录表:存储一个活动的中间名单,如:活动ID、奖品ID、中奖者ID 等
建表:使用 source 命令导入 .sql 文件
在数据库中,可以通过 .sql 文件来执行一些 SQL 语句,可以使用 help 命令查看命令列表,可以看到有一个 source 命令如下所示:
mysql> help# 省略source (\.) Execute an SQL script file. Takes a file name as an argument.# 省略
1. 准备要执行的 .sql 文件,名为 lottery_system.sql,内容如下:
-- 设置客户端与服务器之间的字符集为utf8mb4,这个字符集可以存储任何Unicode字符。
SET NAMES utf8mb4;
-- 关闭外键约束检查,这通常在创建或修改表结构时使用,以避免由于外键约束而导致的创建失败。
SET FOREIGN_KEY_CHECKS = 0;drop database IF EXISTS `lottery_system`;
create DATABASE `lottery_system` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;USE `lottery_system`;-- ----------------------------
-- Table structure for activity
-- ----------------------------
drop table IF EXISTS `activity`;
create TABLE `activity` (`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT comment '主键',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP comment '创建时间',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON update CURRENT_TIMESTAMP comment '更新时间',`activity_name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '活动名称',`description` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '活动描述',`status` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '活动状态',PRIMARY KEY (`id`) USING BTREE,UNIQUE INDEX `uk_id`(`id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 24 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = DYNAMIC;
-- ENGINE = InnoDB:指定表的存储引擎为InnoDB,这是MySQL的默认存储引擎,支持事务、外键等特性。
-- AUTO_INCREMENT = 24:为自动增长的ID字段设置起始值。
-- ROW_FORMAT = DYNAMIC:设置行的存储格式为动态,允许行随着数据的变化而变化。-- ----------------------------
-- Table structure for activity_prize
-- ----------------------------
drop table IF EXISTS `activity_prize`;
create TABLE `activity_prize` (`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT comment '主键',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP comment '创建时间',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON update CURRENT_TIMESTAMP comment '更新时间',`activity_id` bigint NOT NULL comment '活动id',`prize_id` bigint NOT NULL comment '活动关联的奖品id',`prize_amount` bigint NOT NULL DEFAULT 1 comment '关联奖品的数量',`prize_tiers` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '奖品等级',`status` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '活动奖品状态',PRIMARY KEY (`id`) USING BTREE,UNIQUE INDEX `uk_id`(`id` ASC) USING BTREE,UNIQUE INDEX `uk_a_p_id`(`activity_id` ASC, `prize_id` ASC) USING BTREE,INDEX `idx_activity_id`(`activity_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 32 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = DYNAMIC;-- ----------------------------
-- Table structure for activity_user
-- ----------------------------
drop table IF EXISTS `activity_user`;
create TABLE `activity_user` (`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT comment '主键',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP comment '创建时间',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON update CURRENT_TIMESTAMP comment '更新时间',`activity_id` bigint NOT NULL comment '活动时间',`user_id` bigint NOT NULL comment '圈选的用户id',`user_name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '用户名',`status` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '用户状态',PRIMARY KEY (`id`) USING BTREE,UNIQUE INDEX `uk_id`(`id` ASC) USING BTREE,UNIQUE INDEX `uk_a_u_id`(`activity_id` ASC, `user_id` ASC) USING BTREE,INDEX `idx_activity_id`(`activity_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = DYNAMIC;-- ----------------------------
-- Table structure for prize
-- ----------------------------
drop table IF EXISTS `prize`;
create TABLE `prize` (`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT comment '主键',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP comment '创建时间',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON update CURRENT_TIMESTAMP comment '更新时间',`name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '奖品名称',`description` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL comment '奖品描述',`price` decimal(10, 2) NOT NULL comment '奖品价值',`image_url` varchar(2048) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL comment '奖品展示图',PRIMARY KEY (`id`) USING BTREE,UNIQUE INDEX `uk_id`(`id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 18 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = DYNAMIC;-- ----------------------------
-- Table structure for user
-- ----------------------------
drop table IF EXISTS `user`;
create TABLE `user` (`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT comment '主键',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP comment '创建时间',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON update CURRENT_TIMESTAMP comment '更新时间',`user_name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '用户姓名',`email` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '邮箱',`phone_number` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '手机号',`password` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL comment '登录密码',`identity` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '用户身份',PRIMARY KEY (`id`) USING BTREE,UNIQUE INDEX `uk_id`(`id` ASC) USING BTREE,UNIQUE INDEX `uk_email`(`email`(30) ASC) USING BTREE,UNIQUE INDEX `uk_phone_number`(`phone_number`(11) ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 39 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = DYNAMIC;-- ----------------------------
-- Table structure for winning_record
-- ----------------------------
drop table IF EXISTS `winning_record`;
create TABLE `winning_record` (`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT comment '主键',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP comment '创建时间',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON update CURRENT_TIMESTAMP comment '更新时间',`activity_id` bigint NOT NULL comment '活动id',`activity_name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '活动名称',`prize_id` bigint NOT NULL comment '奖品id',`prize_name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '奖品名称',`prize_tier` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '奖品等级',`winner_id` bigint NOT NULL comment '中奖人id',`winner_name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '中奖人姓名',`winner_email` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '中奖人邮箱',`winner_phone_number` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '中奖人电话',`winning_time` datetime NOT NULL comment '中奖时间',PRIMARY KEY (`id`) USING BTREE,UNIQUE INDEX `uk_id`(`id` ASC) USING BTREE,UNIQUE INDEX `uk_w_a_p_id`(`winner_id` ASC, `activity_id` ASC, `prize_id` ASC) USING BTREE,INDEX `idx_activity_id`(`activity_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 69 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = DYNAMIC;-- SET FOREIGN_KEY_CHECKS = 1;:在脚本的最后,重新开启外键约束检查。
SET FOREIGN_KEY_CHECKS = 1;
2. 确定 .sql 文件的绝对路径,例如:D:\haha\lottery_system.sql(注意:路径不能存在中文!!!)
3. 连接数据库查看已有数据库
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| java_blog_spring |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.00 sec)
4. 使用 source 命令执行 .sql 文件的 SQL 语句
mysql> source D:\haha\lottery_system.sql
# 也可以使用下面的缩写形式
mysql> \. D:\haha\lottery_system.sql
tip:若无法直接导入本地的 .sql 文件,可将该文件上传到云服务器中,使用 pwd 命令查看其路径,然后再用 source 将其导入
5. 查看数据库,验证导入是否成功
mysql> show databases; # 查看数据库
+--------------------+
| Database |
+--------------------+
| information_schema |
| java_blog_spring |
| lottery_system |
| mysql |
| performance_schema |
| sys |
+--------------------+ # 创建成功
6 rows in set (0.00 sec)mysql> use lottery_system # 选择数据库
Database changed
mysql> show tables; # 查看所有表
+--------------------------+
| Tables_in_lottery_system |
+--------------------------+
| activity |
| activity_prize |
| activity_user |
| prize |
| user |
| winning_record |
+--------------------------+
6 rows in set (0.00 sec)
数据库表 E-R 图
安全设计
用户登录身份验证:使用 JWT 进行用户身份验证,需强制用户在某些页面必须进行登录操作
加密:敏感信息数据加密:例如:手机号、用户密码等敏感数据落库需要加密
3. 项目创建
代码结构设计参考自《阿里巴巴 Java 开发手册》——第六章 工程结构
4. 功能模块设计
通用处理
1. 错误码
为什么需要错误码?
1. 明确性:错误码提供了一种明确的方式来表示错误的状态。与模糊的异常消息相比,错误码能够精确地指出问题所在。
2. 易检索:错误码通常是数字,便于在日志、监控系统或错误跟踪系统中检索和过滤。
3. 客户端处理:客户端可以根据错误码进行特定的错误处理,而不是依赖于通用的异常处理。
4. 维护性:集中管理错误码使得它们更容易维护和更新。如果业务逻辑发生变化,只需要更新错误码的定义,而不需要修改每个使用它们的地方。在接口文档中,错误码也可以清晰地列出所有可能的错误情况,使开发者更容易理解和使用接口。
5. 调试和测试:错误码可以用于自动化测试,确保特定的错误情况被正确处理。
6. 错误分类:错误码可以帮助将错误分类为不同的级别或类型,如客户端错误、服务器错误、业务逻辑错误等。
定义错误码类型:
package com.example.lotterysystem.common.errorcode;import lombok.Data;@Data
public class ErrorCode {/*** 错误码*/private final Integer code;/*** 错误描述*/private final String msg;public ErrorCode(Integer code, String msg) {this.code = code;this.msg = msg;}
}
定义全局错误码
package com.example.lotterysystem.common.errorcode;/*** 全局异常错误码*/
public interface GlobalErrorCodeConstants {ErrorCode SUCCESS = new ErrorCode(200, "成功!");ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统错误!");ErrorCode UNKNOWN = new ErrorCode(999, "未知错误!");
}
controller 层错误码
package com.example.lotterysystem.common.errorcode;public interface ControllerErrorCodeConstants {// 后续补充
}
service 层错误码
package com.example.lotterysystem.common.errorcode;public interface ServiceErrorCodeConstants {// 后续补充
}
2. 自定义异常类
controller 层异常类
package com.example.lotterysystem.common.exception;import com.example.lotterysystem.common.errorcode.ErrorCode;
import lombok.Data;
import lombok.EqualsAndHashCode;@Data
@EqualsAndHashCode(callSuper = true)
public class ControllerException extends RuntimeException{/*** 异常码* @see com.example.lotterysystem.common.errorcode.ControllerErrorCodeConstants*/private Integer code;/*** 异常消息*/private String message;// 该无参构造方法是为了序列化public ControllerException() {}public ControllerException(Integer code, String message) {this.code = code;this.message = message;}public ControllerException(ErrorCode code) {this.code = code.getCode();this.message = code.getMsg();}
}
service 层异常类
package com.example.lotterysystem.common.exception;import com.example.lotterysystem.common.errorcode.ErrorCode;
import lombok.Data;
import lombok.EqualsAndHashCode;/*** @Data 会生成自己的 equals 和 hashcode 方法* 这里继承了 RuntimeException,需要 RuntimeException 里面的属性* 但是 @Data 生成的方法并不会带有父类的属性* 加上 @EqualsAndHashCode(callSuper = true) 即可解决该问题*/
@Data
@EqualsAndHashCode(callSuper = true)
public class ServiceException extends RuntimeException{/*** 异常码* @see com.example.lotterysystem.common.errorcode.ServiceErrorCodeConstants*/private Integer code;/*** 异常消息*/private String message;public ServiceException() {}public ServiceException(Integer code, String message) {this.code = code;this.message = message;}public ServiceException(ErrorCode code) {this.code = code.getCode();this.message = code.getMsg();}
}
3. CommonResult<T>(统一结果返回)
CommonResult<T> 作为控制器层方法的返回类型,封装 HTTP 接口调用的结果,包括成功数据、错误信息和状态码。它可以被 Spring Boot 框架等自动转换为 JSON 或其他格式的响应体,发送给客户端。
为什么要封装?
1. 统一的返回格式:确保客户端收到的响应具有一致的结构,无论处理的是哪个业务逻辑。
2. 错误码和消息:提供错误码(code)和错误消息(msg),帮助客户端快速识别和处理错误。
3. 泛型数据返回:使用泛型 <T> 允许返回任何类型的数据,增加了返回对象的灵活性。
4. 静态方法:提供了 error() 和 success() 静态方法,方便快速创建错误或成功的响应对象。
5. 错误码常量集成:通过 ErrorCode 和 GlobalErrorCodeConstants 使用预定义的错误码,保持错误码的一致性和可维护性。
6. 序列化:实现了 Serializable 接口,使得 CommonResult<T> 对象可以被序列化为多种格式,如 JSON 或 XML,方便网络传输。
7. 业务逻辑解耦:将业务逻辑与 API 的响应格式分离,使得后端开发者可以专注于业务逻辑的实现,而不必关心如何构建 HTTP 响应。
8. 客户端友好:客户端开发者可以通过统一的接口获取数据和错误信息,无需针对每个 API 编写特定的错误处理逻辑。
其定义如下:
package com.example.lotterysystem.common.pojo;import com.example.lotterysystem.common.errorcode.ErrorCode;
import com.example.lotterysystem.common.errorcode.GlobalErrorCodeConstants;
import lombok.Data;
import org.springframework.util.Assert;import java.io.Serializable;/*** 由于是在 http 进行传输,通过 http 进行调用* 为了能进行各种协议的序列化,此处需实现 Serializable* @param <T>*/
@Data
public class CommonResult<T> implements Serializable {/*** 返回的错误码*/private Integer code;/*** 正常返回数据*/private T data;/*** 错误码描述*/private String msg;public static <T> CommonResult<T> success(T data) {CommonResult<T> result = new CommonResult<>();result.code = GlobalErrorCodeConstants.SUCCESS.getCode();result.data = data;result.msg = "";return result;}public static <T> CommonResult<T> error(Integer code, String msg) {Assert.isTrue(!GlobalErrorCodeConstants.SUCCESS.getCode().equals(code), "code 不是错误的异常");CommonResult<T> result = new CommonResult<>();result.code = code;result.msg = msg;return result;}public static <T> CommonResult<T> error(ErrorCode errorCode) {return error(errorCode.getCode(), errorCode.getMsg());}
}
4. jackson
package com.example.lotterysystem.common.utils;import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.json.JsonParseException;import java.util.List;
import java.util.concurrent.Callable;public class JacksonUtil {public JacksonUtil() {}/*** 单例*/private final static ObjectMapper OBJECT_MAPPER;static {OBJECT_MAPPER = new ObjectMapper();}private static ObjectMapper getObjectMapper() {return OBJECT_MAPPER;}// 再包装一层,实际上调用的都是该方法private static <T> T tryParse(Callable<T> parser) {return tryParse(parser, JacksonException.class);}// 该方法参考自 Spring Boot 对反序列化中 try catch 的处理方式private static <T> T tryParse(Callable<T> parser, Class<? extends Exception> check) {try {return parser.call();} catch (Exception var4) {Exception ex = var4;if (check.isAssignableFrom(ex.getClass())) {throw new JsonParseException(ex);}throw new IllegalStateException(ex);}}/*** 序列化方法* @param object* @return*/public static String writeValueAsString(Object object) {return JacksonUtil.tryParse(() -> {return JacksonUtil.getObjectMapper().writeValueAsString(object);});}/*** 反序列化方法* @param content* @param valueType* @return* @param <T>*/public static <T> T readValue(String content, Class<T> valueType) {return JacksonUtil.tryParse(() -> {return JacksonUtil.getObjectMapper().readValue(content, valueType);});}/*** 反序列化 List* @param content* @param paramClasses* @return* @param <T>*/public static <T> T readListValue(String content, Class<?> paramClasses) {JavaType javaType = JacksonUtil.getObjectMapper().getTypeFactory().constructParametricType(List.class, paramClasses);return JacksonUtil.tryParse(() -> {return JacksonUtil.getObjectMapper().readValue(content, javaType);});}
}
在 JacksonTest 中测试:
@Testvoid JacksonUtilTest() {CommonResult<String> result = CommonResult.success("success");String str;// 序列化str = JacksonUtil.writeValueAsString(result);System.out.println(str);// 反序列化result = JacksonUtil.readValue(str, CommonResult.class);System.out.println(result.getData());// 序列化 ListList<CommonResult<String>> commonResults = Arrays.asList(CommonResult.success("success1"),CommonResult.success("success2"));str = JacksonUtil.writeValueAsString(commonResults);System.out.println(str);// 反序列化 ListcommonResults = JacksonUtil.readListValue(str, CommonResult.class);for (CommonResult<String> commonResult : commonResults) {System.out.println(commonResult.getData());}}
运行结果:
5. 日志处理
application.properties 配置
## logback xml ##
# 指定 .xml 文件
logging.config=classpath:logback-spring.xml
# 指定当前运行环境 dev 表示本地开发环境
spring.profiles.active=dev
logback-spring.xml 配置文件
<?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><!--指定打印时分秒、线程名称、日志级别、日志所在类路径(若长度超过36会做截断处理)、正文、换行、若有异常则打印堆栈信息--><pattern>%d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n%ex</pattern></encoder></appender><!--info 及以上级别日志都会被记录--><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> <!--日志被放在上面指定路径中的 error.log 文件中--><!-- 日志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>
自定义过滤器
package com.example.lotterysystem.common.filter;import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;/*** 自定义过滤器* 只让 info 级别的日志存储到文件中*/
public class InfoLevelFilter extends Filter<ILoggingEvent> {@Overridepublic FilterReply decide(ILoggingEvent event) {if (event.getLevel().toInt() == Level.INFO.toInt()) {return FilterReply.ACCEPT;}return FilterReply.DENY;}
}
在 LogTest 中测试
package com.example.lotterysystem;import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
public class LogTest {private final static Logger logger = LoggerFactory.getLogger(LogTest.class);@Testvoid logTest() {System.out.println("hello world");logger.info("hello world");}
}
运行结果:
用户模块
移步:
相关文章:
抽奖系统(1)(Java 实现)
1. 需求描述 1. 包含管理员的注册与登录 1) 注册包含:姓名、邮箱、手机号、密码 2) 登录包含两种方式 (1) 电话 密码登录 (2) 电话 短信登录;验证码获取 (3) 登录需要校验管理员身份 2. 人员管理:管理员支持创建普通用户,查看…...
数据库系统原理复习汇总
数据库系统原理复习汇总 一、数据库系统原理重点内容提纲 题型:主观题 1、简答题 第一章:数据库的基本概念:数据库、数据库管理系统、三级模式;两级映像、外码 第二章:什么是自然连接、等值连接; 第三…...
基于16QAM的载波同步和定时同步性能仿真,采用四倍采样,包括Costas环和gardner环
目录 1.算法仿真效果 2.算法涉及理论知识概要 3.MATLAB核心程序 4.完整算法代码文件获得 1.算法仿真效果 matlab2022a仿真结果如下(完整代码运行后无水印): 仿真操作步骤可参考程序配套的操作视频。 2.算法涉及理论知识概要 载波同步是…...
鸿蒙next RCP网络请求工具类进阶版来了
前言: 各位同学大家好,有一段时间没有更新文章了,最近因为鸿蒙官方的网络请求换掉了了rcp 之前是使用http 这些都是原生开发的 当然有那种三方大家熟知的 axios (这个也是基于http 后面也会过时)所以大家还是要了解一下rcp的原生的网络请求的。那么我们…...
driftingblues6_vh靶机
首先把靶机换成NAT模式 使用 arp-scan 命令扫描网段内存活的主机,以获取靶机ip地址 arp-scn -l 尝试访问ip 使用御剑扫描子域名,尝试访问robots.txt文件 通过访问文件我们发现了一个/textpattern/textpattern目录 访问一下目录发现了登录页面 他还给了…...
Go语言入门
文章目录 零、Linux下Go的安装1.下载、解压2.添加环境变量3.验证安装4.初始化Go模块(1)cd到项目目录(2)初始化模块(3)获取依赖包(4)清理和验证依赖(5)检查 go.mod 文件(6)介绍 go.mod 和 go.sum 文件 5.项目目录结构 一、感性认识1.从 Hello world 开始2.加法函数 二、Go语法1.…...
VS Code中怎样查看某分支的提交历史记录
VsCode中无法直接查看某分支的提交记录,需借助插件才行,常见的插件如果git history只能查看某页面的改动记录,无法查看某分支的整体提交记录,我们可以安装GIT Graph插件来解决这个问题 1.在 VSCode的插件库中搜索 GIT Graph安装&a…...
【杂谈】-AI搜索引擎如何改变传统SEO及其在内容营销中的作用
AI搜索引擎如何改变传统SEO及其在内容营销中的作用 文章目录 AI搜索引擎如何改变传统SEO及其在内容营销中的作用1、什么是AI搜索引擎2、AI搜索引擎对SEO策略的影响3、AI搜索引擎在内容营销转型中的作用4、AI搜索引擎在营销领域的挑战、道德问题和未来5、总结 在当今的数字营销世…...
快速掌握Haproxy原理架构
文章目录 一、原理架构二、无负载均衡三、四层负载均衡的工作流程四、七层负载均衡工作流程五、基础属性mode 属性retries 属性maxconn 属性clitimeout 属性servtimeout 属性states uri 属性 一、原理架构 四层tcp代理:Haproxy仅在客户端和服务器之间双向转发流量&…...
Java中以某字符串开头且忽略大小写字母如何实现【正则表达式(Regex)】
第一种思路是先将它们都转换为小写或大写,再使用String类的startsWith()方法实现: 例如,如下的二个示例: "Session".toLowerCase().startsWith("sEsSi".toLowerCase()); //例子之一//例子之二String str "Hello Wo…...
如何提高Redis服务器的最大打开文件数限制
文章目录 如何提高Redis服务器的最大打开文件数限制问题诊断解决步骤1. 修改系统级别的限制2. 为Redis进程特别设置限制3. 修改Redis配置文件4. 修改systemd服务文件5. 重新加载systemd并重启Redis6. 验证更改 注意事项 如何提高Redis服务器的最大打开文件数限制 在运行高并发…...
React 组件通信完整指南 以及 自定义事件发布订阅系统
React 组件通信完整指南 1. 父子组件通信 1.1 父组件向子组件传递数据 // 父组件 function ParentComponent() {const [data, setData] useState(Hello from parent);return <ChildComponent message{data} />; }// 子组件 function ChildComponent({ message }) {re…...
代码随想录算法【Day5\Day6】
DAY5\Day6 1.熟悉哈希表的数据结构:数组、map和set,使用方法、使用场景 2.哈希表应用场景:解决给你一个元素,判断它在集合里是否出现过。 242.有效的字母异位词 本题用数组解决的。 class Solution { public:bool isAnagram(…...
Oracle 数据库执行计划的查看与分析技巧
目录 Oracle 数据库执行计划的查看与分析技巧一、什么是执行计划二、查看执行计划的方法(一)使用 EXPLAIN PLAN 命令(二)通过 SQL Developer 工具查看(三)启用 AUTOTRACE 功能 三、执行计划中的关键信息解读…...
VMD-SSA-BiLSTM、VMD-BiLSTM、BiLSTM时间序列预测对比
VMD-SSA-BiLSTM、VMD-BiLSTM、BiLSTM时间序列预测对比 目录 VMD-SSA-BiLSTM、VMD-BiLSTM、BiLSTM时间序列预测对比预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.MATLAB实现VMD-SSA-BiLSTM、VMD-BiLSTM、BiLSTM时间序列预测对比; 2.单变量时间序列预测 就是先vmd把变…...
QGIS二次开发(地图符号库操作)
实习三 地图符号库操作 3.1 任务要求 基于QGIS,实现地图符号的设计/存储与显示;基于QGIS实现一个点、线、面shp矢量图层文件的显示。通过设置引用的符号,改变矢量图层的显示效果;可编辑地图的符号库汇中的点符号、线符号、面符号…...
wordpress网站用token登入开发过程
生成跳转token 示例: function generate_login_token($user_id, $secret_key) {$payload [user_id > $user_id,timestamp > time(),];$payload_json json_encode($payload);$signature hash_hmac(sha256, $payload_json, $secret_key);return base64_en…...
Uniapp在浏览器拉起导航
Uniapp在浏览器拉起导航 最近涉及到要在浏览器中拉起导航,对目标点进行路线规划等功能,踩了一些坑,找到了使用方法。(浏览器拉起) 效果展示 可以拉起三大平台及苹果导航 点击选中某个导航,会携带经纬度跳转…...
在 CentOS 上安装 FFmpeg
在CentOS 上安装 FFmpeg 方法一:在线安装 添加 EPEL 和 RPM Fusion 源: sudo yum install epel-release sudo yum install https://download1.rpmfusion.org/free/el/rpmfusion-free-release-$(rpm -E %rhel).noarch.rpm安装 FFmpeg: sudo yu…...
影刀进阶指令 | liblib反推 (SD AI绘图反推)
文章目录 影刀进阶指令 | liblib反推 (SD AI绘图反推)一. 需求二. 流程三. 实现3.1 流程概览3.2 流程步骤讲解1\. 获取png地址2\. 打开页面3\. 上传png文件4\. 获取png的prompt信息 四. 运维 影刀进阶指令 | liblib反推 (SD AI绘图反推) 先看看我们要实现的功能,li…...
WebStorm 创建一个Vue项目
一、下载并安装WebStorm 步骤一 步骤二 选择激活方式 激活码: I2A0QUY8VU-eyJsaWNlbnNlSWQiOiJJMkEwUVVZOFZVIiwibGljZW5zZWVOYW1lIjoiVU5JVkVSU0lEQURFIEVTVEFEVUFMIERFIENBTVBJTkFTIiwiYXNzaWduZWVOYW1lIjoiVGFvYmFv77yaSkVU5YWo5a625qG25rAIOa0uW3peS9nOWupC…...
回归预测 | MATLAB实现CNN-LSTM卷积长短期记忆神经网络多输入单输出回归预测
回归预测 | MATLAB实现CNN-LSTM卷积长短期记忆神经网络多输入单输出回归预测 目录 回归预测 | MATLAB实现CNN-LSTM卷积长短期记忆神经网络多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 MATLAB实现CNN-LSTM卷积长短期记忆神经网络多输入单输出回归…...
rust windwos 两个edit框
use winapi::shared::minwindef::LOWORD; use windows::{core::*,Win32::{Foundation::*,Graphics::Gdi::{BeginPaint, EndPaint, PAINTSTRUCT},System::LibraryLoader::GetModuleHandleA,UI::WindowsAndMessaging::*,}, };// 两个全局静态变量,用于保存 Edit 控件的…...
ArcGIS计算矢量要素集中每一个面的遥感影像平均值、最大值等统计指标
本文介绍在ArcMap软件中,基于矢量面要素集,计算在其中每一个面区域内,遥感影像的像元个数、平均值、总和等统计值,并将统计信息附加到矢量图层的属性表中的方法。 首先,明确一下本文的需求。现在有一个矢量面要素集&am…...
JavaScript高级程序设计基础(十一)
上接语言基础:JavaScript高级程序设计基础(十) 五、集合引用类型 5.1 object 此处简单介绍object,object由两种方式可以创造:由构造函数new Object(),或者对象字面量。如果想要修改对象的值可以用点语法,如object.na…...
【服务器学习专栏 1.2 -- 带外管理】
请阅读 嵌入式学习必备专栏 文章目录 Overview服务器带外管理BMC 介绍BMC 特点BMC 工作原理 Overview 从技术的角度,网络管理可分为带外管理(out-of-band)和带内管理(in-band)两种管理模式。 带内管理,是指…...
《Vue3 二》Vue 的模板语法
在 React 中,想要编写 HTML,是使用 JSX,之后通过 Babel 将 JSX 编译成 React.createElement 函数调用;在 Vue 中,也支持 JSX 的开发模式,但大多数情况下都是使用基于 HTML 的模板语法,在模板中允…...
手机租赁平台开发全攻略打造高效便捷的租赁服务系统
内容概要 手机租赁平台开发,简单说就是让用户能轻松租赁各类手机的高效系统。这一平台不仅帮助那些想要临时使用高端手机的人们节省了不少资金,还为商家开辟了新的收入渠道。随着智能手机的普及,很多人并不需要长期拥有一部手机,…...
【Ext.js 初步入门】Ext.js 作用以及用法 概述
一、Ext.js的作用 首先我们需要了解类似Ext.js以及easyui类似产品的作用是什么。 我的理解是就是相当于一个组件库,它与bootstrap类似产品的区别在于,bootstrap与ext.js和easyui面向的问题不同,bootstrap面向的问题样式,ext.js和…...
基于源码剖析:深度解读JVM底层运行机制
每日禅语 佛说,给你修路的,是你自己;埋葬你的,也是你自己;帮助你的,是你自己;毁灭你的,也是你自己;成就你的,自然还是你自己。所以佛说:自作自受&…...
redis延迟队列
Redis延迟队列 Redis延迟队列是基于Redis构建的消息队列,用来处理需延迟执行的任务。 基本原理 它借助Redis的有序集合(Sorted Set)数据结构达成目的。会把任务及其执行时间分别当成成员与分值存进有序集合,由于执行时间作为分值&…...
GDPU Vue前端框架开发 期末赛道出勇士篇(更新ing)
记住,年底陪你跨年的不会仅是方便面跟你的闺蜜,还有孑的笔记。 选择题 1.下列选项用于设置Vue.js页面视图的元素是()。 A. Template B. script C. style D. title 2.下列选项中能够定义Vuejs根实例对象的元素是(&…...
WordPress TutorLMS插件 SQL注入漏洞复现(CVE-2024-10400)(附脚本)
0x01 产品描述: Tutor LMS是一个功能强大的...
小程序配置文件 —— 14 全局配置 - tabbar配置
全局配置 - tabBar配置 tabBar 字段:定义小程序顶部、底部 tab 栏,用以实现页面之间的快速切换;可以通过 tabBar 配置项指定 tab 栏的表现,以及 tab 切换时显示的对应页面; 在上面图中,标注了一些 tabBar …...
基于python+Django+mysql文档格式转换工具系统设计与实现
博主介绍:黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者,CSDN博客专家,在线教育专家,CSDN钻石讲师;专注大学生毕业设计教育、辅导。 所有项目都配有从入门到精通的基础知识视频课程ÿ…...
异步线程池中,ThreadPoolTaskExecutor和ThreadPoolExecutor有什么区别?
异步线程池中,org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor和java.util.concurrent.ThreadPoolExecutor有什么区别? org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor 和 java.util.concurrent.ThreadPoolExec…...
【机器学习】SVM支持向量机(二)
介绍 支持向量机(Support Vector Machine, SVM)是一种监督学习模型,广泛应用于分类和回归分析。SVM 的核心思想是通过找到一个最优的超平面来划分不同类别的数据点,并且尽可能地最大化离该超平面最近的数据点(支持向量…...
基于NodeMCU的物联网电灯控制系统设计
最终效果 基于NodeMCU的物联网电灯控制系统设计 小程序关灯 上图展现了小程序关灯过程的数据传输过程:用户下达关灯指令→小程序下发关灯指令→MQTT服务器接收关灯指令→下位机接收与处理关灯指令。 项目介绍 该项目是“物联网实验室监测控制系统设计(…...
源码安装redis后配置redis开机自启动
initd风格 #!/bin/sh ### BEGIN INIT INFO # Provides: redis # Required-Start: $remote_fs $syslog # Required-Stop: $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Start/stop Redis server ### END…...
WebRTC服务质量(11)- Pacer机制(03) IntervalBudget
WebRTC服务质量(01)- Qos概述 WebRTC服务质量(02)- RTP协议 WebRTC服务质量(03)- RTCP协议 WebRTC服务质量(04)- 重传机制(01) RTX NACK概述 WebRTC服务质量(…...
22.跳过报错(最简) C#例子
这是一个跳过报错的例子,如果代码出错了会自动跳过,代码正确了就正常执行。 然而这不是一种推荐的做法,正确的做法应该是使用bool类型的值去检测代码运行结果,然后分支判断,在代码内部解决错误。 这只是一种好玩的做…...
windows 本地node版本快速升级
文章目录 前言一、前置条件二、使用步骤1.查看node 安装位置2.下载指定的node 版本3.下载后进行解压缩4. 删除覆盖原来的node文件夹内容5. 验证 总结 前言 Node.js 是一个开源、跨平台的JavaScript运行时环境,它允许开发者在服务器端运行JavaScript代码。Node.js 基…...
windows安装mongodb
一.安装包准备 1.服务端下载 https://www.mongodb.com/try/download/communitya 按需选择,我们这里选择社区版 直接点download浏览器下载可能比较慢,可以尝试copy link后用三方下载软件,会快很多。 2.命令行客户端下载 https://www.mong…...
一文读懂混合专家模型(MoE)
一文读懂混合专家模型(MoE) 概述 混合专家模型(Mixture of Experts,MoE)是一种机器学习和深度学习中的模型架构,它通过多个“专家”子模型来处理不同类型的输入数据或任务。在MoE模型中,输入数…...
自然语言处理(NLP)中的事件检测和事件抽取
事件检测和事件抽取是自然语言处理(NLP)中的两个重要任务,主要用于从文本中识别和提取事件及其相关信息。这两个任务在信息检索、情报分析、新闻摘要等应用中具有重要意义。 事件检测(Event Detection) 事件检测的目…...
深度学习:基于MindSpore NLP的数据并行训练
什么是数据并行? 数据并行(Data Parallelism, DP)的核心思想是将大规模的数据集分割成若干个较小的数据子集,并将这些子集分配到不同的 NPU 计算节点上,每个节点运行相同的模型副本,但处理不同的数据子集。…...
unity NAudio 获取电脑是否静音
测试,这两个办法都可以 打包出来也可以hu 想获取电脑是否静音出现编辑器模式下正常 打包出来失败 需要把 Api Compatibility Level改成.NET 4.x...
SemiDrive E3 MCAL 开发系列(6)– Icu 模块的使用
一、 概述 本文将会介绍 SemiDrive E3 MCAL Icu 模块的简介以及基本配置,其中还会涉及到 Xtrg 模块的配置。此外会结合实际操作的介绍,帮助新手快速了解并掌握这个模块的使用,文中的 MCAL 是基于 PTG3.0 的版本,开发板是官方的 …...
leetCode322.零钱兑换
题目: 给你一个整数数组coins,表示不同面额的硬币;以及一个整数amount,表示总金额。 计算并返回可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回-1。 你可以认为每种硬币的数量是无限的。 示例1࿱…...
【Python】什么是元组(Tuple)?
什么是元组(Tuple)? 元组(Tuple)是 Python 中的一种 不可变 的数据结构,用于存储多个元素。它类似于列表(List),但与列表不同的是,元组一旦创建,…...