当前位置: 首页 > article >正文

抽奖系统(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 中测试:

    @Test
    void 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());

        // 序列化 List
        List<CommonResult<String>> commonResults = Arrays.asList(
                CommonResult.success("success1"),
                CommonResult.success("success2")
        );
        str = JacksonUtil.writeValueAsString(commonResults);
        System.out.println(str);

        // 反序列化 List
        commonResults = 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> {

    @Override
    public 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);

    @Test
    void logTest() {
        System.out.println("hello world");
        logger.info("hello world");
    }
}

运行结果:

用户模块

移步:


http://www.kler.cn/a/459218.html

相关文章:

  • 招银网路Java后端一面,难度有点大!
  • 解决chatgpt(mac app登陆)出现报错:获取您的 SSO 信息时出错
  • Spring Boot 中 TypeExcludeFilter 的作用及使用示例
  • 1、pycharm、python下载与安装
  • 静默模式下安装Weblogic 14.1.1.0.0
  • STM32控制PCA9685产生16路PWM波控制舵机
  • WEB开发 - Flask 入门:Jinja2 模板语法进阶 Python
  • Flink CDC 监听 MySQL 数据变化并发送到 Kafka 技术指南
  • cuda-cuDnn
  • 金融租赁系统的创新发展与市场竞争力提升探讨
  • 专项附加扣除原理及操作流程
  • Android 底部tab,使用recycleview实现
  • MYSQL版本:创新尝鲜版和LTS长期支持版
  • springboot 项目托管 vue 项目
  • 深入解析Android ImageWriter与Java层的交互通信
  • QT------------QT框架中的模块
  • GAMES101:现代计算机图形学入门-作业五
  • 基于neurokit2的呼吸仿真数据生成实例解析
  • 解决海康相机SDK导致 `libusb_set_option` 问题的经验总结
  • 论文解读 | 《我国桑黄产业发展现状、问题及展望:桑黄产业发展千岛湖宣言》
  • Springboot:后端接收数组形式参数
  • 【漏洞复现】NetMizer 日志管理系统 hostdelay.php 前台RCE漏洞复现
  • Mono里运行C#脚本9—do_mono_image_open
  • STM32-笔记20-测量按键按下时间
  • CGAL windows 安装教程
  • ABAQUS随机多面体骨料再生混凝土细观力学分析