feat:
- 添加logback-spring.xml配置 - 添加GlobalExceptionHandler全局异常处理 - 添加JsonException和PageException
This commit is contained in:
parent
b7a05a1ef2
commit
ddf106c2e6
3
.gitignore
vendored
3
.gitignore
vendored
@ -35,4 +35,5 @@ build/
|
|||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
### Mac OS ###
|
### Mac OS ###
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
/logs/
|
||||||
|
|||||||
@ -13,5 +13,19 @@
|
|||||||
</jdbc-additional-properties>
|
</jdbc-additional-properties>
|
||||||
<working-dir>$ProjectFileDir$</working-dir>
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
</data-source>
|
</data-source>
|
||||||
|
<data-source source="LOCAL" name="workflow@122.152.201.90" uuid="ef18523f-13a8-4f05-b7a1-136f3abe519b">
|
||||||
|
<driver-ref>mysql.8</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<imported>true</imported>
|
||||||
|
<remarks>$PROJECT_DIR$/src/main/resources/application.properties</remarks>
|
||||||
|
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:mysql://122.152.201.90:9912/workflow?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT</jdbc-url>
|
||||||
|
<jdbc-additional-properties>
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.host.port" />
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.container.port" />
|
||||||
|
</jdbc-additional-properties>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@ -1,12 +1,21 @@
|
|||||||
package asia.yulinling.workflow;
|
package asia.yulinling.workflow;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
|
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
|
@Slf4j
|
||||||
public class WorkFlowMain {
|
public class WorkFlowMain {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(WorkFlowMain.class, args);
|
ConfigurableApplicationContext context = SpringApplication.run(WorkFlowMain.class, args);
|
||||||
|
int length = context.getBeanDefinitionCount();
|
||||||
|
log.trace("Number of beans in application context: " + length);
|
||||||
|
log.debug("Number of beans in application context: " + length);
|
||||||
|
log.info("Number of beans in application context: " + length);
|
||||||
|
log.warn("Number of beans in application context: " + length);
|
||||||
|
log.error("Number of beans in application context: " + length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4,7 +4,6 @@ import cn.hutool.core.util.ArrayUtil;
|
|||||||
import cn.hutool.http.useragent.UserAgent;
|
import cn.hutool.http.useragent.UserAgent;
|
||||||
import cn.hutool.http.useragent.UserAgentUtil;
|
import cn.hutool.http.useragent.UserAgentUtil;
|
||||||
import cn.hutool.json.JSONUtil;
|
import cn.hutool.json.JSONUtil;
|
||||||
import cn.hutool.log.Log;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
|
|||||||
50
src/main/java/asia/yulinling/workflow/constant/Status.java
Normal file
50
src/main/java/asia/yulinling/workflow/constant/Status.java
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package asia.yulinling.workflow.constant;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 状态码封装
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author yulinling
|
||||||
|
* @since 2025/6/5
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum Status {
|
||||||
|
/** 操作成功 */
|
||||||
|
OK(200, "success"),
|
||||||
|
|
||||||
|
/** 未知异常 */
|
||||||
|
UNKNOWN_ERROR(500, "Unknown error");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态码
|
||||||
|
*/
|
||||||
|
private final Integer code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内容
|
||||||
|
*/
|
||||||
|
private final String message;
|
||||||
|
|
||||||
|
Status(Integer code, String message) {
|
||||||
|
this.code = code;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据code反查枚举
|
||||||
|
*
|
||||||
|
* @param code 状态码
|
||||||
|
* @return Status
|
||||||
|
*/
|
||||||
|
public static Status fromCode(int code) {
|
||||||
|
for (Status status : Status.values()) {
|
||||||
|
if (status.getCode().equals(code)) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UNKNOWN_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,9 @@
|
|||||||
package asia.yulinling.workflow.controller;
|
package asia.yulinling.workflow.controller;
|
||||||
|
|
||||||
|
import asia.yulinling.workflow.constant.Status;
|
||||||
|
import asia.yulinling.workflow.exception.JsonException;
|
||||||
|
import asia.yulinling.workflow.exception.PageException;
|
||||||
|
import asia.yulinling.workflow.model.ApiResponse;
|
||||||
import cn.hutool.core.lang.Dict;
|
import cn.hutool.core.lang.Dict;
|
||||||
import cn.hutool.json.JSONUtil;
|
import cn.hutool.json.JSONUtil;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -43,4 +47,15 @@ public class TestController {
|
|||||||
log.info(jsonStr);
|
log.info(jsonStr);
|
||||||
return Dict.create().set("json", jsonStr);
|
return Dict.create().set("json", jsonStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/json")
|
||||||
|
public ApiResponse jsonException() {
|
||||||
|
throw new JsonException(Status.UNKNOWN_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/page")
|
||||||
|
public ApiResponse PageException() {
|
||||||
|
throw new PageException(Status.UNKNOWN_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,32 @@
|
|||||||
|
package asia.yulinling.workflow.exception;
|
||||||
|
|
||||||
|
import asia.yulinling.workflow.constant.Status;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 异常基类
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author yulinling
|
||||||
|
* @since 2025/6/5
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class BaseException extends RuntimeException{
|
||||||
|
private final Integer code;
|
||||||
|
private final String message;
|
||||||
|
|
||||||
|
public BaseException(Status status) {
|
||||||
|
super(status.getMessage());
|
||||||
|
this.code = status.getCode();
|
||||||
|
this.message = status.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BaseException(Integer code, String message) {
|
||||||
|
super(message);
|
||||||
|
this.code = code;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package asia.yulinling.workflow.exception;
|
||||||
|
|
||||||
|
import asia.yulinling.workflow.constant.Status;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* JSON异常
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author yulinling
|
||||||
|
* @since 2025/6/5
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public class JsonException extends BaseException {
|
||||||
|
|
||||||
|
public JsonException(Status status) {
|
||||||
|
super(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonException(Integer code, String message) {
|
||||||
|
super(code, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package asia.yulinling.workflow.exception;
|
||||||
|
|
||||||
|
import asia.yulinling.workflow.constant.Status;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 页面异常
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author yulinling
|
||||||
|
* @since 2025/6/5
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public class PageException extends BaseException {
|
||||||
|
public PageException(Status status) {
|
||||||
|
super(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PageException(Integer code, String message) {
|
||||||
|
super(code, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
package asia.yulinling.workflow.handler;
|
||||||
|
|
||||||
|
import asia.yulinling.workflow.exception.JsonException;
|
||||||
|
import asia.yulinling.workflow.exception.PageException;
|
||||||
|
import asia.yulinling.workflow.model.ApiResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 全局异常处理
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author yulinling
|
||||||
|
* @since 2025/6/5
|
||||||
|
*/
|
||||||
|
@ControllerAdvice
|
||||||
|
@Slf4j
|
||||||
|
public class GlobalExceptionHandler {
|
||||||
|
private static final String DEFAULT_ERROR_VIEW = "error";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一 json 异常处理
|
||||||
|
*
|
||||||
|
* @param e JsonException
|
||||||
|
* @return 统一返回 json 格式
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(Exception.class)
|
||||||
|
@ResponseBody
|
||||||
|
public ApiResponse jsonErrorHandler(JsonException e) {
|
||||||
|
log.error(e.getMessage());
|
||||||
|
return ApiResponse.ofException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一 页面 异常处理
|
||||||
|
*
|
||||||
|
* @param e PageException
|
||||||
|
* @return 统一跳转到异常页面
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(PageException.class)
|
||||||
|
public ModelAndView pageErrorHandler(PageException e) {
|
||||||
|
log.error(e.getMessage());
|
||||||
|
ModelAndView view = new ModelAndView();
|
||||||
|
view.addObject("exception", e);
|
||||||
|
view.setViewName(DEFAULT_ERROR_VIEW);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
package asia.yulinling.workflow.mapper;
|
package asia.yulinling.workflow.mapper;
|
||||||
|
|
||||||
import asia.yulinling.workflow.entity.User;
|
import asia.yulinling.workflow.model.entity.User;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
import org.apache.ibatis.annotations.Select;
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
|||||||
125
src/main/java/asia/yulinling/workflow/model/ApiResponse.java
Normal file
125
src/main/java/asia/yulinling/workflow/model/ApiResponse.java
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
package asia.yulinling.workflow.model;
|
||||||
|
|
||||||
|
import asia.yulinling.workflow.constant.Status;
|
||||||
|
import asia.yulinling.workflow.exception.BaseException;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 通用API接口封装
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author yulinling
|
||||||
|
* @since 2025/6/5
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ApiResponse {
|
||||||
|
/**
|
||||||
|
* 状态码
|
||||||
|
*/
|
||||||
|
private Integer code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回内容
|
||||||
|
*/
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回数据
|
||||||
|
*/
|
||||||
|
private Object data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 无参构造器
|
||||||
|
*/
|
||||||
|
private ApiResponse() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全参构造器
|
||||||
|
*
|
||||||
|
* @param code 状态码
|
||||||
|
* @param message 返回内容
|
||||||
|
* @param data 返回数据
|
||||||
|
*/
|
||||||
|
private ApiResponse(Integer code, String message, Object data) {
|
||||||
|
this.code = code;
|
||||||
|
this.message = message;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造一个自定义的Api返回
|
||||||
|
*
|
||||||
|
* @param code 状态码
|
||||||
|
* @param message 返回内容
|
||||||
|
* @param data 返回数据
|
||||||
|
* @return ApiResponse
|
||||||
|
*/
|
||||||
|
public static ApiResponse of(Integer code, String message, Object data) {
|
||||||
|
return new ApiResponse(code, message, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造一个成功且带数据的Api返回
|
||||||
|
*
|
||||||
|
* @param data 返回数据
|
||||||
|
* @return ApiResponse
|
||||||
|
*/
|
||||||
|
public static ApiResponse ofSuccess(Object data) {
|
||||||
|
return ofStatus(Status.OK, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造一个成功且自定义数据的Api返回
|
||||||
|
*
|
||||||
|
* @param message 返回内容
|
||||||
|
* @return ApiResponse
|
||||||
|
*/
|
||||||
|
public static ApiResponse ofMessage(String message) {
|
||||||
|
return ofStatus(Status.OK, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造一个有状态不带数据的Api返回
|
||||||
|
*
|
||||||
|
* @param status 状态{@link Status}
|
||||||
|
* @return ApiResponse
|
||||||
|
*/
|
||||||
|
public static ApiResponse ofStatus(Status status) {
|
||||||
|
return ofStatus(status, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造一个有状态且带数据的Api返回
|
||||||
|
*
|
||||||
|
* @param status 状态{@link Status}
|
||||||
|
* @param data 返回数据
|
||||||
|
* @return ApiResponse
|
||||||
|
*/
|
||||||
|
public static ApiResponse ofStatus(Status status, Object data) {
|
||||||
|
return of(status.getCode(), status.getMessage(), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造一个异常且带数据的Api返回
|
||||||
|
*
|
||||||
|
* @param t 异常
|
||||||
|
* @param data 返回数据
|
||||||
|
* @return ApiResponse
|
||||||
|
* @param <T> {@link BaseException} 子类
|
||||||
|
*/
|
||||||
|
public static <T extends BaseException> ApiResponse ofException(T t, Object data) {
|
||||||
|
return of(t.getCode(), t.getMessage(), data);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 构造一个异常且不带数据的Api返回
|
||||||
|
*
|
||||||
|
* @param t 异常
|
||||||
|
* @return ApiResponse
|
||||||
|
* @param <T> {@link BaseException} 子类
|
||||||
|
*/
|
||||||
|
public static <T extends BaseException> ApiResponse ofException(T t) {
|
||||||
|
return ofException(t, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package asia.yulinling.workflow.entity;
|
package asia.yulinling.workflow.model.entity;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
62
src/main/resources/logback-spring.xml
Normal file
62
src/main/resources/logback-spring.xml
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration>
|
||||||
|
|
||||||
|
<!-- 设置通用变量 -->
|
||||||
|
<property name="LOG_PATH" value="logs/demo-logback" />
|
||||||
|
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n" />
|
||||||
|
<property name="LOG_CHARSET" value="UTF-8" />
|
||||||
|
|
||||||
|
<!-- 控制台输出 -->
|
||||||
|
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>${LOG_PATTERN}</pattern>
|
||||||
|
<charset>${LOG_CHARSET}</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- INFO 文件输出(不包括 ERROR)-->
|
||||||
|
<appender name="FILE_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${LOG_PATH}/info.log</file>
|
||||||
|
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||||
|
<level>ERROR</level>
|
||||||
|
<onMatch>DENY</onMatch>
|
||||||
|
<onMismatch>ACCEPT</onMismatch>
|
||||||
|
</filter>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>${LOG_PATH}/info.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
|
||||||
|
<maxFileSize>2MB</maxFileSize>
|
||||||
|
<maxHistory>30</maxHistory>
|
||||||
|
<totalSizeCap>1GB</totalSizeCap>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>${LOG_PATTERN}</pattern>
|
||||||
|
<charset>${LOG_CHARSET}</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- ERROR 文件输出 -->
|
||||||
|
<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${LOG_PATH}/error.log</file>
|
||||||
|
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||||
|
<level>ERROR</level>
|
||||||
|
</filter>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>${LOG_PATH}/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
|
||||||
|
<maxFileSize>2MB</maxFileSize>
|
||||||
|
<maxHistory>30</maxHistory>
|
||||||
|
<totalSizeCap>1GB</totalSizeCap>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>${LOG_PATTERN}</pattern>
|
||||||
|
<charset>${LOG_CHARSET}</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 根日志配置 -->
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="CONSOLE"/>
|
||||||
|
<appender-ref ref="FILE_INFO"/>
|
||||||
|
<appender-ref ref="FILE_ERROR"/>
|
||||||
|
</root>
|
||||||
|
|
||||||
|
</configuration>
|
||||||
11
src/main/resources/templates/error.html
Normal file
11
src/main/resources/templates/error.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org" lang="en">
|
||||||
|
<head lang="en">
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<title>统一页面异常处理</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>统一页面异常处理</h1>
|
||||||
|
<div th:text="${message}"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -1,7 +1,7 @@
|
|||||||
package asia.yulinling.workflow.mapper;
|
package asia.yulinling.workflow.mapper;
|
||||||
|
|
||||||
import asia.yulinling.workflow.WorkFlowMainTests;
|
import asia.yulinling.workflow.WorkFlowMainTests;
|
||||||
import asia.yulinling.workflow.entity.User;
|
import asia.yulinling.workflow.model.entity.User;
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.date.DateTime;
|
import cn.hutool.core.date.DateTime;
|
||||||
import cn.hutool.core.util.IdUtil;
|
import cn.hutool.core.util.IdUtil;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user