分享一下个人使用项目脚手架搭建过程,已实现后端的认证鉴权,等后续脚手架搭完再补充~

搭建过程记录

项目搭建

依赖版本管理

<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.11SpringBoot框架

官方文档: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.1MybatisPlus框架

官方文档: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.27SpringMVC框架

官方文档:Spring Web MVC :: Spring Framework

<!-- MVC -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

安全

使用版本 1.37.0Sa-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.RELEASElettuce框架操作 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.20Hutool框架

官方文档: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.2Easyexcel框架

官方文档:EasyExcel官方文档 - 基于Java的Excel处理工具 | Easy Excel (alibaba.com)

<!-- easyexcel -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>${easyexcel.version}</version>
</dependency>

接口文档

使用版本 4.3.0Knife4j框架

官方文档: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>

最后附上源码地址,完全开源哦:

leo: 存放个人项目脚手架 (gitee.com)


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值