java抽奖系统(二)
3. 新建项目
3.1 选择相应的框架
pom文件配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.2.6</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>lottery_system</artifactId><version>0.0.1-SNAPSHOT</version><name>lottery_system</name><description>lottery_system</description><url/><licenses><license/></licenses><developers><developer/></developers><scm><connection/><developerConnection/><tag/><url/></scm><properties><java.version>17</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.amqp</groupId><artifactId>spring-rabbit-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>
3.2 代码结构设计
分层结构如下:
4. 功能模块设计
4.1 错误码
我们对dao层不进行定义错误码;
对controller层和servoce层进行定义错误码;
对于一个全局的错误码,成功,失败,未知;
代码如下:
package com.example.lotterysystem.common;/*** @version 1.0* @Author 作者名* @Date 2024/11/24 19:55* @注释*/
public interface GlobalErrorCodeConstants {ErrorCode SUCCESS = new ErrorCode(200,"成功!");ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500,"系统异常!");ErrorCode UNKNOW = new ErrorCode(101,"未知错误!");
}
4.2 ⾃定义异常类
根据控制层和服务层来分别写相应的代码,其实两部分的代码都相近,同时引入了@EqualsAndHashCode(callSuper = true)注解:
@Data
@EqualsAndHashCode(callSuper = true)
public class ControllerException extends RuntimeException{/*** @see com.example.lotterysystem.common.errcode.ControllerErrorCodeConstants*/private int code;//异常吗private String message;//异常消息//为了序列化public ControllerException(){}public ControllerException(Integer code,String message){this.code = code;this.message = message;}public ControllerException(ErrorCode errorCode){this.code = errorCode.getCode();this.message= errorCode.getMessage();}
}
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编写特 定的错误处理逻辑。
@Data
//因为时进行http传输,为了成功传输就要进行序列化
public class CommonResult<T> implements Serializable {private Integer code;private T data;private String message;public static <T> CommonResult<T> success(T data){CommonResult<T> result = new CommonResult<>();result.code = GlobalErrorCodeConstants.SUCCESS.getCode();result.data = data;result.message = "";return result;}public static <T> CommonResult<T> error(Integer code,String message){Assert.isTrue(!GlobalErrorCodeConstants.SUCCESS.getCode().equals(code),"code不是错误的异常");CommonResult<T> result = new CommonResult<>();result.code = code;result.message = message;return result;}public static <T> CommonResult<T> error(ErrorCode errorCode){return error(errorCode.getCode(),errorCode.getMessage());}}
4.4 jackson工具实现序列化
相对而言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);});}
}
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的日志进行接受;@Overridepublic FilterReply decide(ILoggingEvent iLoggingEvent) {if (iLoggingEvent.getLevel().toInt() == Level.INFO.toInt()){return FilterReply.ACCEPT;}return FilterReply.DENY;}
}
使用测试类来进行日志打印测试:
ps:未完待续