搭建过程记录
项目搭建
依赖版本管理
<properties>
<revision>1.0.0</revision>
<spring-boot.version>2.7.11</spring-boot.version>
<fastjson.version>2.0.5</fastjson.version>
<lombok.version>1.18.28</lombok.version>
<druid.version>1.2.16</druid.version>
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
<dynamic-datasource.version>3.6.1</dynamic-datasource.version>
<mysql.version>8.0.20</mysql.version>
<hutool.version>5.8.20</hutool.version>
<easyexcel.version>3.3.2</easyexcel.version>
<sa-token.version>1.37.0</sa-token.version>
<knife4j.version>4.3.0</knife4j.version>
<springdoc.version>1.6.15</springdoc.version>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
应用程序开发
使用版本 2.7.11
的 SpringBoot
框架
官方文档:Spring Boot
<!-- SpringBoot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
对象关系映射(持久化)
使用版本 3.5.3.1
的 MybatisPlus
框架
官方文档:MyBatis-Plus (baomidou.com)
<!-- Mybatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
Web应用开发(MVC框架)
使用版本 5.3.27
的 SpringMVC
框架
官方文档:Spring Web MVC :: Spring Framework
<!-- MVC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
安全
使用版本 1.37.0
的 Sa-Token
框架
官方文档:Sa-Token
<!-- sa-token -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- sa-token集成 redis -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-jackson</artifactId>
<version>${sa-token.version}</version>
</dependency>
缓存
使用版本 6.1.10.RELEASE
的 lettuce
框架操作 Redis
官方文档:Lettuce
<!-- 集成redis依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
spring:
# Redis 配置
redis:
# 连接地址
host: 127.0.0.1
# 端口
port: 6379
# 数据库
database: 3
# 用户名,如果有
# username:
# 密码,如果有
# password:
# 连接超时
connect-timeout: 5s
# 读超时
timeout: 5s
# Lettuce 客户端的配置
lettuce:
# 连接池配置
pool:
# 最小空闲连接
min-idle: 0
# 最大空闲连接
max-idle: 8
# 最大活跃连接
max-active: 8
# 从连接池获取连接 最大超时时间,小于等于0则表示不会超时
max-wait: -1ms
引入上述依赖并配置后,已经可以注入使用 StringRedisTemplate
了
@Resource
private StringRedisTemplate stringRedisTemplate;
但是目前序列化还是使用的 JDK自带的,我们需要做自定义序列化配置
@Configuration
public class RedisConf {
@Bean(name = "redisTemplate")
public RedisTemplate<String, Object> getRedisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(factory);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss.SSS")));
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")));
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss.SSS")));
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")));
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.registerModule(javaTimeModule);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer(objectMapper);
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(jsonRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(jsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
以上代码对 Java8的时间类属性在序列化时候报错进行了处理,注入使用:
@Resource
private RedisTemplate<String, Object> redisTemplate;
工具类
使用版本 5.8.20
的 Hutool
框架
官方文档:Hutool — 🍬A set of tools that keep Java sweet.
<!-- Hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
表格处理
使用版本 3.3.2
的 Easyexcel
框架
官方文档:EasyExcel官方文档 - 基于Java的Excel处理工具 | Easy Excel (alibaba.com)
<!-- easyexcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>${easyexcel.version}</version>
</dependency>
接口文档
使用版本 4.3.0
的 Knife4j
框架
官方文档:Knife4j · 集Swagger2及OpenAPI3为一体的增强解决方案. | Knife4j (xiaominfo.com)
<!-- Knife4j -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
@Configuration
public class Knife4jConf {
@Bean
public OpenAPI createApi() {
Info info = new Info()
.title("Leo")
.description("Leo后台管理系统")
.version("1.0.0")
.contact(new Contact().name("段友元").email("17371584524@163.com"))
.license(new License());
return new OpenAPI().info(info);
}
/**
* 自定义 OpenAPI 处理器
*/
@Bean
public OpenAPIService openApiBuilder(Optional<OpenAPI> openAPI,
SecurityService securityParser,
SpringDocConfigProperties springDocConfigProperties,
PropertyResolverUtils propertyResolverUtils,
Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomizers,
Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers,
Optional<JavadocProvider> javadocProvider) {
return new OpenAPIService(openAPI, securityParser, springDocConfigProperties,
propertyResolverUtils, openApiBuilderCustomizers, serverBaseUrlCustomizers, javadocProvider);
}
/**
* 描述:匹配所有接口
* @author 段友元(duanyouyuan)
* @return org.springdoc.core.GroupedOpenApi
* @create 2023年11月23日 10:19:19
**/
@Bean
public GroupedOpenApi allGroupedOpenApi() {
return GroupedOpenApi.builder()
.group("all")
.pathsToMatch("/**")
.addOperationCustomizer((operation, handlerMethod) -> operation)
.build();
}
/**
* 描述:匹配内网接口
* @author 段友元(duanyouyuan)
* @return org.springdoc.core.GroupedOpenApi
* @create 2023年11月23日 10:18:50
**/
@Bean
public GroupedOpenApi adminGroupedOpenApi() {
return GroupedOpenApi.builder()
.group("admin")
.pathsToMatch("/*/admin/**")
.addOperationCustomizer((operation, handlerMethod) -> operation)
.build();
}
/**
* 描述:匹配互联网接口
* @author 段友元(duanyouyuan)
* @return org.springdoc.core.GroupedOpenApi
* @create 2023年11月23日 10:18:50
**/
@Bean
public GroupedOpenApi internetGroupedOpenApi() {
return GroupedOpenApi.builder()
.group("internet")
.pathsToMatch("/*/web/**")
.addOperationCustomizer((operation, handlerMethod) -> operation)
.build();
}
}
安全
安全框架解决的问题重点是“认证”和“授权”,其中“授权”解释为“鉴权”合理一点
个人项目选择使用最简单的安全框架“sa-token”
认证
登录接口
Sa Token框架提供的方法 StpUtil.login(id, false)用来登录(查询数据库自己处理),登录后将 token信息返回给前端
public Authentication doLogin(String userName, String password) {
String getUserIdByNameAndPwdSql = "select id from sys_user where user_account = ? and password = ? ";
List<Map<String, Object>> queryResult = jdbcTemplate.queryForList(getUserIdByNameAndPwdSql, userName, password);
if (queryResult.isEmpty()) {
String queryUserSql = "select id from sys_user where user_account = ? ";
List<Map<String, Object>> uList = jdbcTemplate.queryForList(queryUserSql, userName);
if (uList.isEmpty()) {
throw new AuthenticationException("找不到用户!");
} else {
throw new AuthenticationException("密码错误,请重新输入!");
}
} else if (queryResult.size() == 1){
Object id = queryResult.get(0).get("id");
if (Objects.nonNull(id)) {
StpUtil.login(id, false);
} else {
throw new ServiceException("用户ID为空!");
}
}
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
return new Authentication()
.setToken(tokenInfo.getTokenValue())
.setUserId(Long.valueOf((String) tokenInfo.getLoginId()))
.setTokenName(tokenInfo.getTokenName());
}
授权(鉴权)
鉴权接口
实现框架的 StpInterface接口,重写获取权限集合和获取角色集合接口,操作数据库自己定义
@Component
public class StpInterfaceImpl implements StpInterface {
@Resource
private JdbcTemplate jdbcTemplate;
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
String getPermissionListSql = "select menu_perm from sys_menu where id in ( " +
"select menu_id from sys_role_menu where role_id in ( " +
"select role_id from sys_user_role where user_id= ? ))";
List<Map<String, Object>> permissionList = jdbcTemplate.queryForList(getPermissionListSql, loginId);
return permissionList.stream().map(permission -> (String) permission.get("menu_perm")).collect(Collectors.toList());
}
@Override
public List<String> getRoleList(Object loginId, String loginType) {
String getRoleListSql = "select role_code from sys_role where id in (select role_id from sys_user_role where user_id= ? )";
List<Map<String, Object>> roleList = jdbcTemplate.queryForList(getRoleListSql, loginId);
return roleList.stream().map(role -> (String) role.get("role_code")).collect(Collectors.toList());
}
}
注解鉴权
开启注解鉴权
@Configuration
public class WebMvcConf implements WebMvcConfigurer {
// 注册 Sa-Token 拦截器,打开注解式鉴权功能
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册 Sa-Token 拦截器,打开注解式鉴权功能
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
}
}
使用注解鉴权
要求用户有"system:user:list"权限才能调用方法获取数据
@SaCheckPermission("system:user:list")
@Operation(summary = "列表查询")
@RequestMapping(value = "admin/list", method = {RequestMethod.GET, RequestMethod.POST})
public CommonResult<List<User>> list() {
List<User> list = userService.list();
return CommonResult.success(list);
}
规范
实体
规范项目实体类统一继承 BaseEntity
BaseEntity类定义了开发技术字段,并利用了 Mybatis Plus框架的自动填充功能省去开发人员手动赋值
@Data
public class BaseEntity implements Serializable {
/**
* 创建时间
*/
@Schema(description = "创建时间")
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdTime;
/**
* 最后更新时间
*/
@Schema(description = "最后更新时间")
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedTime;
/**
* 创建者,目前使用 SysUser 的 id 编号
*/
@Schema(description = "创建者")
@TableField(fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
private Long createdBy;
/**
* 更新者,目前使用 SysUser 的 id 编号
*/
@Schema(description = "更新者")
@TableField(fill = FieldFill.INSERT_UPDATE, jdbcType = JdbcType.VARCHAR)
private Long updatedBy;
/**
* 是否删除
*/
@Schema(description = "是否删除")
@TableLogic
@TableField(value = "delete_flag",fill = FieldFill.INSERT)
private String deleteFlag;
}
上面提到的自动填充开发技术字段实现
public class BaseEntityFieldHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity) {
BaseEntity base = (BaseEntity) metaObject.getOriginalObject();
LocalDateTime current = LocalDateTime.now();
// 创建时间为空,则以当前时间为插入时间
if (Objects.isNull(base.getCreatedTime())) {
base.setCreatedTime(current);
}
// 更新时间为空,则以当前时间为更新时间
if (Objects.isNull(base.getUpdatedTime())) {
base.setUpdatedTime(current);
}
Long userId = StpUtil.getLoginIdAsLong();
// 当前登录用户不为空,创建人为空,则当前登录用户为创建人
if (Objects.isNull(base.getCreatedBy())) {
base.setCreatedBy(userId);
}
// 当前登录用户不为空,更新人为空,则当前登录用户为更新人
if (Objects.isNull(base.getUpdatedBy())) {
base.setUpdatedBy(userId);
}
if (Objects.isNull(base.getDeleteFlag())) {
base.setDeleteFlag("0");
}
}
}
@Override
public void updateFill(MetaObject metaObject) {
// 更新时间为空,则以当前时间为更新时间
Object modifyTime = getFieldValByName("updatedTime", metaObject);
if (Objects.isNull(modifyTime)) {
setFieldValByName("updatedTime", LocalDateTime.now(), metaObject);
}
// 当前登录用户不为空,更新人为空,则当前登录用户为更新人
Object modifier = getFieldValByName("updatedBy", metaObject);
Long userId = StpUtil.getLoginIdAsLong();
if (Objects.isNull(modifier)) {
setFieldValByName("updatedBy", userId, metaObject);
}
}
}
@Configuration
public class MybatisConf {
@Bean
public MetaObjectHandler defaultMetaObjectHandler() {
return new BaseEntityFieldHandler(); // 自动填充参数类
}
}
响应
规范项目响应同一报文
报文类 在 Controller类里调用此类的静态方法,暂不考虑全局响应统一封装成这个类
@Data
@Accessors(chain = true)
public class CommonResult<T> implements Serializable {
/**
* 响应码
*/
private Integer code;
/**
* 响应消息
*/
private String message;
/**
* 响应数据
*/
private T data;
/**
* 描述:成功 无数据 默认消息
* @author 段友元(duanyouyuan)
* @return com.ultraman.common.normative.entity.CommonResult<T>
* @create 2023年11月22日 09:47:53
**/
public static <T> CommonResult<T> success() {
return new CommonResult<T>()
.setCode(ResultEnum.SUCCESS.getCode())
.setMessage(ResultEnum.SUCCESS.getMessage());
}
/**
* 描述:成功 有数据 默认消息
* @author 段友元(duanyouyuan)
* @param data 数据
* @return com.ultraman.common.normative.entity.CommonResult<T>
* @create 2023年11月22日 09:48:08
**/
public static <T> CommonResult<T> success(T data) {
return new CommonResult<T>()
.setCode(ResultEnum.SUCCESS.getCode())
.setMessage(ResultEnum.SUCCESS.getMessage())
.setData(data);
}
/**
* 描述:成功 有数据 有消息
* @author 段友元(duanyouyuan)
* @param message 消息
* @param data 数据
* @return com.ultraman.common.normative.entity.CommonResult<T>
* @create 2023年11月22日 09:48:39
**/
public static <T> CommonResult<T> success(String message, T data) {
return new CommonResult<T>()
.setCode(ResultEnum.SUCCESS.getCode())
.setMessage(message)
.setData(data);
}
/**
* 描述:错误 默认消息 无数据
* @author 段友元(duanyouyuan)
* @return com.ultraman.common.normative.entity.CommonResult<T>
* @create 2023年11月22日 09:51:35
**/
public static <T> CommonResult<T> error() {
return new CommonResult<T>()
.setCode(ResultEnum.FAIL.getCode())
.setMessage(ResultEnum.FAIL.getMessage());
}
/**
* 描述:错误 有数据 默认消息
* @author 段友元(duanyouyuan)
* @param data 数据
* @return com.ultraman.common.normative.entity.CommonResult<T>
* @create 2023年11月22日 09:51:53
**/
public static <T> CommonResult<T> error(T data) {
return new CommonResult<T>()
.setCode(ResultEnum.FAIL.getCode())
.setData(data)
.setMessage(ResultEnum.FAIL.getMessage());
}
/**
* 描述:错误 有数据 有消息
* @author 段友元(duanyouyuan)
* @param message 消息
* @param data 数据
* @return com.ultraman.common.normative.entity.CommonResult<T>
* @create 2023年11月22日 09:52:31
**/
public static <T> CommonResult<T> error(String message, T data) {
return new CommonResult<T>()
.setCode(ResultEnum.FAIL.getCode())
.setMessage(message)
.setData(data);
}
}
报文类里用到的响应码枚举类
@AllArgsConstructor
@NoArgsConstructor
@Getter
public enum ResultEnum {
/**
* 成功
*/
SUCCESS(200, "操作成功"),
FAIL(500, "服务器内部错误"),
BAD_REQUEST(400, "请求参数不正确"),
UNAUTHORIZED(401, "账号未登录"),
FORBIDDEN(403, "没有该操作权限"),
NOT_FOUND(404, "请求未找到"),
METHOD_NOT_ALLOWED(405, "请求方法不正确"),
SERVICE_ERROR(520, "服务错误"),
;
/**
* 响应码
*/
private Integer code;
/**
* 响应消息
*/
private String message;
}
CommonResult使用示例
@RequestMapping(value = "admin/list", method = {RequestMethod.GET, RequestMethod.POST})
public CommonResult<List<User>> list() {
List<User> list = userService.list();
return CommonResult.success(list);
}
异常
规范全局异常处理,兜底处理
全局异常处理,其中 AuthenticationException是本人自定义异常,最后的 Exception是兜底所有异常
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 描述:处理请求参数缺失 比如接口上设置了 @RequestParam("xx") 参数,结果并未传递 xx 参数
* @param ex 异常
* @return com.ultraman.common.normative.entity.CommonResult<?>
* @author 段友元(duanyouyuan)
* @create 2023年11月22日 10:18:31
**/
@ExceptionHandler(value = MissingServletRequestParameterException.class)
public CommonResult<?> missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException ex) {
log.warn("[missingServletRequestParameterExceptionHandler]", ex);
return new CommonResult<>()
.setCode(ResultEnum.BAD_REQUEST.getCode())
.setMessage(String.format("请求参数缺失:%s", ex.getParameterName()));
}
/**
* 描述:处理请求参数缺失 比如接口上设置了 @RequestParam("xx") 参数为 Integer,结果传递 xx 参数类型为 String
* @param ex 异常
* @return com.ultraman.common.normative.entity.CommonResult<?>
* @author 段友元(duanyouyuan)
* @create 2023年11月22日 10:18:31
**/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public CommonResult<?> methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException ex) {
log.warn("[missingServletRequestParameterExceptionHandler]", ex);
return new CommonResult<>()
.setCode(ResultEnum.BAD_REQUEST.getCode())
.setMessage(String.format("请求参数类型错误:%s", ex.getMessage()));
}
/**
* 描述:处理 SpringMVC 请求地址不存在
* 注意,它需要设置如下两个配置项:
* 1. spring.mvc.throw-exception-if-no-handler-found 为 true
* 2. spring.mvc.static-path-pattern 为 /statics/**
* @param ex 异常
* @return com.ultraman.common.normative.entity.CommonResult<?>
* @author 段友元(duanyouyuan)
* @create 2023年11月22日 10:18:31
**/
@ExceptionHandler(NoHandlerFoundException.class)
public CommonResult<?> noHandlerFoundExceptionHandler(NoHandlerFoundException ex) {
log.warn("[noHandlerFoundExceptionHandler]", ex);
return new CommonResult<>()
.setCode(ResultEnum.NOT_FOUND.getCode())
.setMessage(String.format("请求地址不存在:%s", ex.getRequestURL()));
}
/**
* 描述:处理 SpringMVC 请求方法不正确 例如说,A 接口的方法为 GET 方式,结果请求方法为 POST 方式,导致不匹配
* @param ex 异常
* @return com.ultraman.common.normative.entity.CommonResult<?>
* @author 段友元(duanyouyuan)
* @create 2023年11月22日 10:18:31
**/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public CommonResult<?> httpRequestMethodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException ex) {
log.warn("[httpRequestMethodNotSupportedExceptionHandler]", ex);
return new CommonResult<>()
.setCode(ResultEnum.METHOD_NOT_ALLOWED.getCode())
.setMessage(String.format("请求地址不存在:%s", String.format("请求方法不正确:%s", ex.getMessage())));
}
/**
* 描述:认证异常处理
* @param ex 异常
* @return com.ultraman.common.normative.entity.CommonResult<?>
* @author 段友元(duanyouyuan)
* @create 2023年11月22日 10:18:31
**/
@ExceptionHandler(AuthenticationException.class)
public CommonResult<?> authenticationExceptionHandler(AuthenticationException ex) {
log.warn("[authenticationExceptionHandler]", ex);
String message = ex.getMessage();
return new CommonResult<>()
.setCode(ResultEnum.UNAUTHORIZED.getCode())
.setMessage(StringUtils.hasText(message) ? message : ResultEnum.UNAUTHORIZED.getMessage());
}
/**
* 描述:认证异常处理
* @param ex 异常
* @return com.ultraman.common.normative.entity.CommonResult<?>
* @author 段友元(duanyouyuan)
* @create 2023年11月22日 10:18:31
**/
@ExceptionHandler(NotLoginException.class)
public CommonResult<?> notLoginExceptionHandler(NotLoginException ex) {
log.warn("[authenticationExceptionHandler]", ex);
String message = ex.getMessage();
return new CommonResult<>()
.setCode(ResultEnum.UNAUTHORIZED.getCode())
.setMessage(StringUtils.hasText(message) ? message : ResultEnum.UNAUTHORIZED.getMessage());
}
/**
* 描述:没有权限异常处理
* @param ex 异常
* @return com.ultraman.common.normative.entity.CommonResult<?>
* @author 段友元(duanyouyuan)
* @create 2023年11月22日 10:18:31
**/
@ExceptionHandler(NotPermissionException.class)
public CommonResult<?> notPermissionExceptionHandler(NotPermissionException ex) {
log.warn("[notPermissionExceptionHandler]", ex);
String message = ex.getMessage();
return new CommonResult<>()
.setCode(ResultEnum.FORBIDDEN.getCode())
.setMessage(StringUtils.hasText(message) ? message : ResultEnum.FORBIDDEN.getMessage());
}
/**
* 描述:业务服务异常处理
* @param ex 异常
* @return com.ultraman.common.normative.entity.CommonResult<?>
* @author 段友元(duanyouyuan)
* @create 2023年11月22日 10:18:31
**/
@ExceptionHandler(ServiceException.class)
public CommonResult<?> serviceExceptionHandler(ServiceException ex) {
log.warn("[serviceExceptionHandler]", ex);
String message = ex.getMessage();
Integer code = ex.getCode();
return new CommonResult<>()
.setCode(null != code ? code : ResultEnum.SERVICE_ERROR.getCode())
.setMessage(StringUtils.hasText(message) ? message : ResultEnum.SERVICE_ERROR.getMessage());
}
/**
* 描述:兜底异常处理
* @param ex 异常
* @return com.ultraman.common.normative.entity.CommonResult<?>
* @author 段友元(duanyouyuan)
* @create 2023年11月22日 10:18:31
**/
@ExceptionHandler(value = Exception.class)
public CommonResult<?> defaultExceptionHandler(Throwable ex) {
log.error("[defaultExceptionHandler]", ex);
return new CommonResult<>()
.setCode(ResultEnum.FAIL.getCode())
.setMessage(ResultEnum.FAIL.getMessage());
}
}
日志
规范处理日志
注:这里不涉及保存日志到数据库
配置文件配置日志级别和日志文件位置
logging:
level:
root: info
com.ultraman.system: debug
file:
path: log
logback-spring.xml配置
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<!-- 属性文件:在properties文件中找到对应的配置项 -->
<springProperty scope="context" name="logging.file.path" source="logging.file.path"/>
<contextName>leo</contextName>
<appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出(配色):%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%yellow(%d{yyyy-MM-dd HH:mm:ss}) %red([%thread]) %highlight(%-5level) %cyan(%logger{50}) -
%magenta(%msg) %n
</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!--根据日志级别分离日志,分别输出到不同的文件-->
<appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>DENY</onMatch>
<onMismatch>ACCEPT</onMismatch>
</filter>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>
%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n
</pattern>
<charset>UTF-8</charset>
</encoder>
<!--滚动策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--按时间保存日志 修改格式可以按小时、按天、月来保存-->
<fileNamePattern>${logging.file.path}/leo.info.%d{yyyy-MM-dd}.log</fileNamePattern>
<!--保存时长-->
<MaxHistory>90</MaxHistory>
<!--文件大小-->
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
</appender>
<appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<encoder>
<pattern>
%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n
</pattern>
</encoder>
<!--滚动策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--路径-->
<fileNamePattern>${logging.file.path}/leo.error.%d{yyyy-MM-dd}.log</fileNamePattern>
<MaxHistory>90</MaxHistory>
</rollingPolicy>
</appender>
<root level="info">
<appender-ref ref="consoleLog"/>
<appender-ref ref="fileInfoLog"/>
<appender-ref ref="fileErrorLog"/>
</root>
</configuration>
最后附上源码地址,完全开源哦: