java抽奖系统(一)2.0

 1. 项⽬介绍

1.1 背景

        随着数字营销的兴起,企业越来越重视通过在线活动来吸引和留住客⼾。抽奖活动作为⼀种有效的营 销⼿段,能够显著提升⽤⼾参与度和品牌曝光率。于是我们就开发了以抽奖活动作为背景的Spring Boot项⽬,通过这个项⽬提供⼀个全⾯、可靠、易于维护的抽奖平台,该平台将采⽤以下策略:

         集成多种技术组件:利⽤MySQL、Redis、RabbitMQ等常⽤组件,构建⼀个稳定、⾼效、可扩展 的抽奖系统。

        活动、奖品与⼈员管理:允许管理员创建配置抽奖活动;管理奖品信息;管理⼈员信息。

         实现状态机管理:通过精⼼设计的状态机,精确控制活动及奖品状态的转换,提⾼系统的可控性和 可预测性。

         保障数据⼀致性:通过事务管理和数据同步机制,确保数据的⼀致性和完整性。

        加强安全性:实施安全措施,包括数据加密、⽤⼾认证,保护⽤⼾数据和系统安全。

        降低维护成本:提供全⾯的⽇志记录和异常处理机制,简化问题诊断和系统维护。

         提⾼扩展性:采⽤模块化设计与设计模式的使⽤,提⾼系统的灵活性和扩展性。

1.2 需求描述

        1. 包含管理员的注册与登录。

        a. 注册包含:姓名、邮箱、⼿机号、密码

        b. 登录包含两种⽅式:

                 i. 电话+密码登录;

                ii. 电话+短信登录; 验证码获取

                iii. 登录需要校验管理员⾝份。

        2. ⼈员管理:管理员⽀持创建普通⽤⼾,查看⽤⼾列表

                a. 创建普通⽤⼾:姓名,邮箱,⼿机号

                b. ⼈员列表:⼈员id、姓名、⾝份(普通⽤⼾、管理员)

        3. 管理端⽀持创建奖品、奖品列表展⽰功能。

                a. 创建的奖品信息包含:奖品名称、描述、价格、奖品图(上传)

                 b. 奖品列表展⽰(可翻⻚):奖品id、奖品图、奖品名、奖品描述、奖品价值(元)

        4. 管理端⽀持创建活动、活动列表展⽰功能。

                 a. 创建的活动信息包含:

                        i. 活动名称

                        ii. 活动描述

                        iii. 圈选奖品:勾选对应奖品,并设置奖品等级(⼀⼆三等奖),及奖品数量

                        iv. 圈选⼈员:勾选参与抽奖⼈员

                b. 活动列表展⽰(可翻⻚):

                         i. 活动名称

                         ii. 描述 

                        iii. 活动状态:

                                1. 活动状态为进⾏中:点击"活动进⾏中,去抽奖"按钮跳转抽奖⻚

                                2. 活动状态为已完成:点击"活动已完成,查看中奖名单"按钮跳转抽奖⻚查看结果

        5. 抽奖⻚⾯:

                a. 对于进⾏中的活动,管理员才可抽奖。

                b. 每轮抽奖的中奖⼈数跟随当前奖品数量。

                c. 每个⼈只能中⼀次奖

                d. 多轮抽奖,每轮抽奖有3个环节:展⽰奖品信息(奖品图、份数),⼈名闪动,停⽌闪动确定中 奖名单

                        i. 当前⻚展⽰奖品信息,点击‘开始抽奖’按钮,则跳转⾄⼈名闪动画⾯

                        ii. ⼈员闪动画⾯,点击’点我确定‘按钮,确认中奖名单。

                        iii. 当前⻚展⽰中奖名单,点击‘已抽完,下⼀步’按钮,若还有奖品未抽取,则展⽰下⼀个奖品 信息,否则展⽰全部中奖名单

                        iv. 点击’查看上⼀奖项‘按钮,展⽰上⼀个奖品信息

                e. 对于抽奖过程中的异常情况,如抽奖过程中刷新⻚⾯,要保证抽取成功的奖项不能重新抽取。

                        i. 刷新⻚⾯后,若当前奖品已抽完,点击"开始抽奖",则直接展⽰当前奖品中奖名单 f. 如该抽奖活动已完成:

                        ii. 展⽰所有奖项的全部中奖名单

                        iii. 新增"分享结果"按钮,点击可复制当前⻚链接,打开后隐藏其他按钮,只展⽰活动名称与中奖 结果,保留"分享结果"按钮

        6. 通知部分:抽奖完成需以邮件和短信⽅式通知中奖者。

                a. “Hi,xxx。恭喜你在xxx抽奖中获得⼀等奖:⼿机。中奖时间为:xx:xx。请尽快领取您的奖 品。”

        7. 管理端涉及的所有⻚⾯,包括抽奖⻚,需强制管理员登录后⽅可访问。

1.3 系统架构

前端:使⽤JavaScript管理各界⾯的动态性,使⽤AJAX技术从后端API获取数据。  

后端:采⽤SpringBoot3构建后端应⽤,实现业务逻辑。

数据库:使⽤MySQL作为主数据库,存储⽤⼾数据和活动信息。

缓存:使⽤Redis作为缓存层,减少数据库访问次数。

消息队列:使⽤RabbitMQ处理异步任务,如处理抽奖⾏为。

⽇志与安全:使⽤JWT进⾏⽤⼾认证,使⽤SLF4J+logback完成⽇志。 

1.4 项⽬环境

         编程语⾔:Java(后端),JavaScript(前端)。

        开发⼯具包:JDK17 (⻅《JDK17安装》⽂档)

        后端框架:SpringBoot3。

         数据库:MySQL。

        缓存:Redis。

         消息队列:RabbitMQ。

         ⽇志:logback。

         安全:JWT+加密。 

1.5 业务功能模块

⼈员业务模块:管理员注册、登录,及普通⽤⼾的创建。

活动业务模块:活动管理及活动状态管理。

 奖品业务模块:奖品管理与奖品的分配。还包括奖品图的上传。

 通知业务模块:发送短信、邮件等业务,例如验证码发送,中奖通知。

 抽奖业务模块:完成抽奖动作,以及抽奖后的结果展⽰;

1.6 数据库设计

 ⽤⼾表:存储⽤⼾信息,如⽤⼾名、密码、邮箱等。

活动表:存储活动信息,如活动名称、描述、活动状态等。

奖品表:存储奖品信息,如奖品名称、奖品图等。

活动奖品关联表:存储⼀个活动下关联了哪些奖品。

活动⽤⼾关联表:存储⼀个活动下设置的参与⼈员。

中奖记录表:存储⼀个活动的中奖名单,如活动id,奖品id,中奖者id等 

2.  数据库设计

2.1 表的关联图

sql代码:

-- 设置客户端与服务器之间的字符集为utf8mb4,这个字符集可以存储任何Unicode字符。
SET NAMES utf8mb4;
-- 关闭外键约束检查,这通常在创建或修改表结构时使用,以避免由于外键约束而导致的创建失败。
SET FOREIGN_KEY_CHECKS = 0;

drop database IF EXISTS `lottery_system1`;
create DATABASE `lottery_system` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

USE `lottery_system1`;

-- ----------------------------
-- Table structure for activity
-- ----------------------------
drop table IF EXISTS `activity`;
create TABLE `activity`  (
  `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT comment '主键',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP comment '创建时间',
  `gmt_modified` 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 = 0 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 '主键',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP comment '创建时间',
  `gmt_modified` 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 '主键',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP comment '创建时间',
  `gmt_modified` 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 '主键',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP comment '创建时间',
  `gmt_modified` 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 '主键',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP comment '创建时间',
  `gmt_modified` 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 '主键',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP comment '创建时间',
  `gmt_modified` 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.2 使⽤source命令导⼊.sql⽂件

说明:

drop database IF EXISTS `lottery_system1`;
create DATABASE `lottery_system1` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

        utf8mb4_general_ci` 是 MySQL 数据库中的一个字符集排序规则(collation),它支持存储任何有效的 UTF-8 多字节字符,包括表情符号(emoji)和其他 4 字节字符。排序规则 `ci` 在结尾的 `ci` 代表大小写不敏感(case-insensitive)。

        在数据库中,字符集和排序规则决定了如何存储和比较字符串数据。使用 `utf8mb4` 字符集可以确保你的数据库能够存储任何可能的 Unicode 字符,而 `general_ci` 排序规则则允许在进行字符串比较时忽略大小写。

        例如,如果你在创建数据库或表时指定了 `utf8mb4_general_ci`,那么存储在该数据库或表中的字符串将能够正确地处理包括表情符号在内的所有 Unicode 字符,并且在进行字符串比较时,"hello" 和 "HELLO" 将被视为相同。

`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP comment '创建时间',

CURRENT_TIMESTAMP返回当前时区的本地日期和时间;

`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON update CURRENT_TIMESTAMP comment '更新时间',

      timestamp有两个属性,分别是CURRENT_TIMESTAMP 和ON UPDATE CURRENT_TIMESTAMP两种,使用情况分别如下:
 1.CURRENT_TIMESTAMP :当要向数据库执行insert操作时,如果有个timestamp字段属性设为 CURRENT_TIMESTAMP,则无论这个字段有木有set值都插入当前系统时间 
2.ON UPDATE CURRENT_TIMESTAMP:当执行update操作是,并且字段有ON UPDATE CURRENT_TIMESTAMP属性。则字段无论值有没有变化,他的值也会跟着更新为当前UPDATE操作时的时间。

        UTF8mb3和UTF8mb4的区别:
UTF-8是一种用于编码Unicode字符的可变长度字符编码标准。MySQL中的“utf8”编码只支持最大3字节每字符,就是"utf8mb3"。真正的大家正在使用的UTF-8编码是应该能支持4字节每个字符。他们在2010年增加了一个变通的方法:一个新的字符集“utf8mb4”。"utf8mb4"和"utf8mb3"则是UTF-8的两个变种。它们的主要区别在于能够表示的字符范围。

PRIMARY KEY (`id`) USING BTREE,

Primary Key的提出就是为了唯一标示表中的字段,就像我们的身份证号一样。此外,所有字段都必须是not null的Unique Key则是为了保证表中有些字段是唯一的。

 UNIQUE INDEX `uk_id`(`id` ASC) USING BTREE,

唯一索引(UNIQUE Index):保证索引列的每个值都是唯一的。这意味着不能在索引列中插入重复的值

首先使用指令进入mysql:

mysql -u root -p

source指令一个sql脚本文件;

注意:使用的sql文件的绝对路径且在路径中不能有中文;

mysql> source E:\java_xiangmu\lottery_system\lottery_system.sql

结果如下:指令执行成功;

3. 新建项目

3.1 选择相应的框架

pom文件配置略:


3.2 代码结构设计

分层结构如下:

        上层依赖下层:

controller层(web层):接收来自前端的请求;

service层:里面含有具体的业务逻辑,是业务逻辑服务层;

数据持久层(dao层):数据访问层,与mysql进行数据交互;

common:通用处理层,只要存放util工具,config配置;

 

4. 功能模块设计

4.1 错误码

        我们对dao层不进行定义错误码(1、许多sql错误定义不出来;2、可以在service层包含一些dao层的错误),对controller层和servoce层进行定义错误码(分别对人员模块,活动模块,奖品模块,抽奖模块进行分别定义)

        由于errcode错误码是遍布两个层级,所以放在common通用层里面; errorcode类主要包含两个信息,一个是错误状态码code,另外一个是错误状态的信息msg;其次对于不同的层级来进行定义错误码;

        对于抓不到,没有思考到的错误,使用全局通用错误码来定义;

public interface GlobalErrorCodeConstants {
    ErrorCode SUCCESS = new ErrorCode(200,"成功!");
    ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500,"系统异常!");
    ErrorCode UNKNOW = new ErrorCode(999,"未知错误!");
}

4.2 ⾃义异常类

        对当前的系统进行设计一套自定义异常类;

        根据控制层和服务层来分别写相应的代码,

        同时在构建异常时引入错误码,比如controller的异常构造,引入controller层的errorcode来进一步完善该层的异常信息;

@Data
public class ServiceException extends RuntimeException{
    private Integer code;//异常码
    private String message;//异常信息

    //添加无参构造主要是为了进行序列化
    public ServiceException(){

    }

    public ServiceException(Integer code,String message){
        this.code = code;
        this.message = message;
    }

    public ServiceException(ErrorCode errorCode) {
        this.code = errorCode.getCode();
        this.message = errorCode.getMsg();
    }
}

        @EqualsAndHashCode(callSuper = true)注解: 

        @data注解会生成自己的@equals和hashcode方法,我们想要使用父类的属性和方法,就要使用上面的注解;

4.3. CommonResult<T>

        对所有接口返回的结果进行封装的类型;

        CommonResult<T>作为控制器层⽅法的返回类型,封装HTTP接⼝调⽤的结果,包括成功数据、错 误信息和状态码。它可以被SpringBoot框架等⾃动转换为JSON或其他格式的响应体,发送给客⼾端。

关于为什么要封装?

1. 统⼀的返回格式:确保客⼾端收到的响应具有⼀致的结构,⽆论处理的是哪个业务逻辑。 2. 错误码和消息:提供错误码(code)和错误消息(msg),帮助客⼾端快速识别和处理错误。

3. 泛型数据返回:使⽤泛型允许返回任何类型的数据,增加了返回对象的灵活性。

4. 静态⽅法:提供了error()和success()静态⽅法,⽅便快速创建错误或成功的响应对象。

5. 错误码常量集成:通过ErrorCode和GlobalErrorCodeConstants使⽤预定义的错误码,保持错 误码的⼀致性和可维护性。

6. 序列化:实现了Serializable接⼝,使得CommonResult对象可以被序列化为多种格式,如 JSON或XML,⽅便⽹络传输。

7. 业务逻辑解耦:将业务逻辑与API的响应格式分离,使得后端开发者可以专注于业务逻辑的实 现,⽽不必关⼼如何构建HTTP响应。

8. 客⼾端友好:客⼾端开发者可以通过统⼀的接⼝获取数据和错误信息,⽆需针对每个API编写特 定的错误处理逻辑。

public class CommonResult<T> implements Serializable {
//    CommonResult<T>是在http中进行传输,所以要进行序列化
    private Integer code;//返回的错误码
    private T data;//返回的数据
    private String msg;//对错误码的描述
    
    //返回成功结果的CommonResult<T>静态构造
    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;
    }

    //返回异常结果的CommonResult<T>静态构造
    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.4 jackson工具实现序列化

        方便将对象转化为string类,本项目中适用于日志打印个redis,rabbitmq的使用场景中;

        相对而言protobuf(pb实现序列化非常快,但是可视化非常差);

public class JacksonUtil {
    private JacksonUtil(){

    }

    private final static ObjectMapper OBJECT_MAPPER;//1点对objectmapper进行私有化单例操作

    static {
        OBJECT_MAPPER = new ObjectMapper();
    }

    private static ObjectMapper getObjectMapper(){
        return  OBJECT_MAPPER;
    }

    //2点。使用callable支持使用lamda表达式来进行处理,无论序列化的操作过程中出现的
    //异常都可以tryparse进行捕捉
    private static <T > T tryParse(Callable<T> parser) {
        return tryParse(parser, JacksonException.class);
    }

    private static <T > T tryParse(Callable<T> parser, Class<? extends Exception> check){
        try {
            return parser.call();
            } catch (Exception ex) {
                if (check.isAssignableFrom(ex.getClass())) {
                    throw new JsonParseException(ex);
                }
            throw new IllegalStateException(ex);
        }
    }

    /**
     *序列化objectmapper
     * @param value
     * @return
     */
    public static String writeValueAsString(Object value) {
        return JacksonUtil.tryParse(()->
                JacksonUtil.getObjectMapper().writeValueAsString(value));
    }

    /**
     反序列化objectmapper
     * @param content
     * @param valueType
     * @return
     * @param <T>
     */
    public static <T > T readValue(String content, Class<T> valueType) {
        return JacksonUtil.tryParse(()->
                JacksonUtil.getObjectMapper().readValue(content, valueType));
    }
    /**
     反序列化list
     * @param content
     * @param parameterClasses
     * @return
     * @param <T>
     */
    public static <T > T readListValue(String content, Class<?> parameterClasses) {
        JavaType javaType =  JacksonUtil.getObjectMapper().getTypeFactory().constructParametricType(List.class, parameterClasses);
        return JacksonUtil.tryParse(()->
        { return JacksonUtil.getObjectMapper().readValue(content,javaType);
        });
    }
}

在这里特别注意两点:jackson里面的部分方法是对springboot框架进行学习处理的

1、 对objectmapper进行私有单例化处理;

2、使用tryparse的方法,其第一个参数是callable<T> parse,支持使用lambda表达式进行处理,无论在lambda表达式哪里出现了问题,都可以在tryparse里面使用try-catch对异常进行处理;

4.5. ⽇志处理

        使用SLF4J+logback的配置:

        在本地(dev),通过控制台来打印出日志;

        服务器:日志主要存在目标目录文件;

application.properties配置:

spring.application.name=lottery_system
## logback xml ##
logging.config=classpath:logback-spring.xml
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>
                <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>
                <!--只保留最近30天的日志-->
                <maxHistory>30</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.InfoLevelFilter"/>
            <!--滚动策略,按照时间滚动 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>

在配置的包中自定义过滤info级别日志的类:

public class InfoLevelFilter extends Filter<ILoggingEvent> {
    //过滤器来过滤info级别的日志,将info的日志进行接受;
    @Override
    public FilterReply decide(ILoggingEvent iLoggingEvent) {
        if (iLoggingEvent.getLevel().toInt() == Level.INFO.toInt()){
            return FilterReply.ACCEPT;
        }
        return FilterReply.DENY;
    }
}

使用测试类来进行日志打印测试:

ps:谢谢观看

记:2025/02/19修;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值