Java SpringBoot 后端快速开发模板功能介绍--非核心业务篇

模板大致介绍

这是一个基于 Java SpringBoot 的项目初始模板,整合了常用框架和主流业务的示例代码,来减少平常开发项目时所做的重复工作,并可以使用 Swagger + Knife4j 接口文档来调试接口功能。
所有需要修改的地方最好都标记 todo,便于后续开发找到修改的位置~

主流框架 & 特性

  • Spring Boot 2.7.x (包含调试工具和项目处理器)
  • Spring MVC
  • MyBatis + MyBatis Plus 数据访问
  • Spring AOP 切面编程

本篇功能模块

  • 自定义权限注解 + 全局校验
  • 全局请求响应拦截器(记录日志)
  • 通用响应类、自定义错误码
  • 配置类(框架配置、全局跨域处理配置)
  • 全局异常处理器
  • 单元测试

本篇功能模块详细介绍

自定义注解(Annotation)

(package annotation)

@Target(ElementType.METHOD) // 表示这个注解只能应用于方法上
@Retention(RetentionPolicy.RUNTIME) // 表示这个注解的元数据信息在运行时可用
public @interface AuthCheck {

    String mustRole() default "";
    ...

}

实现原理:
这个注解使用了 Java 1.8 引入的注解处理器(Annotation Processor)功能。
是通过 Java 编译器(Javac)在编译时处理注解,将注解信息添加到类或方法上。在运行时,可以通过反射(Reflection)获取注解的元数据信息。

用途:
用于检查方法调用者是否具有某个角色,通常用于身份验证权限控制。通过在方法上添加这个注解,可以确保只有具有指定角色的用户才能调用该方法。

定义注解通常涉及的步骤:

  1. 定义注解:使用@interface关键字来声明一个新的注解类型。
    @interface和传统接口定义的语法非常相似,但实际上它定义的是一个注解,而不是一个接口。
  2. 设置注解参数:在注解中定义方法,这些方法实际上是注解的参数。如String mustRole() default "";
  3. 指定注解的元注解:使用元注解(例如@Retention@Target)来指定注解的保留策略和适用目标。

AOP 模块

权限校验

当一个方法被调用且该方法上有@AuthCheck注解时,Spring AOP会触发doInterceptor方法。

@Aspect
@Component
public class AuthInterceptor {

    @Resource
    private UserService userService;

    /**
     * 执行拦截
     *
     * @param joinPoint
     * @param authCheck
     * @return
     */
    @Around("@annotation(authCheck)")
    public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable {
    	// 获取调用当前方法所需要的调用者角色
        String mustRole = authCheck.mustRole();
        UserRoleEnum mustRoleEnum = UserRoleEnum.getEnumByValue(mustRole);
        // 使用RequestContextHolder获取当前请求的属性,然后从中获取HttpServletRequest对象。
        RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
        // 当前登录用户
        User loginUser = userService.getLoginUser(request);
        // 调用当前方法不需要权限,放行
        if (mustRoleEnum == null) {
            return joinPoint.proceed();
        }
        // 必须有该权限才通过,
        UserRoleEnum userRoleEnum = UserRoleEnum.getEnumByValue(loginUser.getUserRole());
		
		...权限校验逻辑
		
        // 通过权限校验,放行
        return joinPoint.proceed();
    }
}

@Aspect:标记该类是一个切面,定义了切面逻辑。
@Resource:注入一个服务对象,这里是UserService,用于获取当前登录用户的信息。
@Around("@annotation(authCheck)"):定义一个环绕通知,当目标方法上有@AuthCheck注解时,会执行这个切面方法。
ProceedingJoinPoint:提供对被拦截方法的访问。
AuthCheck authCheck:传入目标方法上的@AuthCheck注解实例。
RequestContextHolder.currentRequestAttributes():这是Spring框架提供的一个方法,用于获取当前请求的属性,包含了当前请求的一些基本信息,如请求的URL、请求方法、请求头等。

请求日志

实现了一个日志拦截器,可以记录请求的详细信息和方法执行的时间。这对于调试和监控应用的性能非常有用。

@Aspect
@Component
@Slf4j
public class LogInterceptor {

    /**
     * 执行拦截
     */
    @Around("execution(* ...controller.*.*(..))")
    public Object doInterceptor(ProceedingJoinPoint point) throws Throwable {
        // 计时
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // 获取请求路径
        RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
        HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
        String url = httpServletRequest.getRequestURI();
        // 生成请求唯一 id
        String requestId = UUID.randomUUID().toString();
        // 获取请求参数
        Object[] args = point.getArgs();
        String reqParam = "[" + StringUtils.join(args, ", ") + "]";
        // 输出请求日志
        log.info("request start,id: {}, path: {}, ip: {}, params: {}", requestId, url,
                httpServletRequest.getRemoteHost(), reqParam);
        // 执行被拦截的方法,并获取其返回值。
        Object result = point.proceed();
        // 输出响应日志
        stopWatch.stop();
        long totalTimeMillis = stopWatch.getTotalTimeMillis();
        log.info("request end, id: {}, cost: {}ms", requestId, totalTimeMillis);
        return result;
    }
}

@Slf4j:SLF4J(Simple Logging Facade for Java)库,使用 Lombok 的注解,自动生成一个 Logger 实例,用于日志记录。
logo.info:在Java中,{}是字符串格式化的一种方式,称为占位符。当你在字符串中使用{}时,Java编译器会自动将其替换为对应格式的值。所以当运行这段代码时,request start,id: {}, path: {}, ip: {}, params: {}中的{}会被替换为requestIdurlhttpServletRequest.getRemoteHost()reqParam的值。

通用类模块

通用响应类

每个请求的响应值增加 code、data、message 三个字段来丰富请求的执行状况给前端:

public BaseResponse(int code, T data) {
        this(code, data, "");
    }

    public BaseResponse(ErrorCode errorCode) {
        this(errorCode.getCode(), null, errorCode.getMessage());
    }

code:为了强化状态码与业务的相关性并更友好地提示错误信息,因此除了复用 HTTP 的状态码之外还自定义了 ErrorCode 类将更详细的错误状态码与错误信息 message 绑定。
message:一些请求成功或失败的额外提示信息。

请求结果封装类

对请求执行成功和各类失败情况响应信息的静态方法的封装,主要是为了简化调用。例如:

public class ResultUtils {

    /**
     * 成功
     *
     * @param data
     * @param <T>
     * @return
     */
    public static <T> BaseResponse<T> success(T data) {
        return new BaseResponse<>(0, data, "success/200/...");
    }

    /**
     * 失败
     *
     * @param errorCode
     * @return
     */
    public static BaseResponse error(ErrorCode errorCode) {
        return new BaseResponse<>(errorCode);
    }

	...
}

配置类模块

配置类在Spring 项目中的作用:

  1. 集中配置管理:配置类将配置和初始化逻辑集中在一个地方,便于管理和维护。配置参数可以从外部配置文件读取,易于修改和扩展。
  2. 依赖注入:通过使用@Bean注解,配置类定义的 Bean 可以被 Spring 容器管理,并在需要的地方通过依赖注入(如@Autowired)获取,促进了松耦合设计。
  3. 多环境:通过@ConfigurationProperties,可以轻松地根据不同的环境(开发、测试、生产)使用不同的配置文件,实现环境的快速切换。

包含的类

MyBatisPlusConfig - - 配置一个拦截器和分页插件等,除此之外还有乐观锁等插件。MyBatisPlus
JsonConfig - - 在将 Java 对象转换为 JSON 字符串时,通过将 Long 类型的对象将转换为字符串,从而避免精度丢失的问题。
CorsConfig - - 解决全局跨域问题。

后续还需要添加一些业务的配置类,例如我们 AIAppMarket项目 中的 AI 配置类:

@Configuration
@ConfigurationProperties(prefix = "ai")
@Data
public class AiConfig {
    /**
     * api key,需要从开放平台获取
     */
    private String apiKey;

    @Bean
    public ClientV4 getClientV4(){
        return new ClientV4.Builder(apiKey).build();
    }
}

全局异常处理

全局异常处理器能够捕获应用程序中抛出的异常,并对这些异常进行统一的处理, 从而避免因未处理的异常导致系统崩溃或无法正常工作,能够提高系统的稳定性。
1)错误信息统一处理:全局异常处理器可以将不同种类的异常转化为统一的错误信息格式,提供一致的错误响应给客户端,增强了用户体验。
2)错误日志记录:可以在全局异常处理器中记录异常信息,包括异常类型、发生时间、请求参数等,有助于排查问题和分析系统健康状况。
3)异常信息隐藏:通过全局异常处理器,可以隐藏敏感信息,以防止敏感信息泄露到客户端。

实现方法:BusinessException、GlobalExceptionHandler和ThrowUtils

  1. 定义一个全局异常处理类GlobalExceptionHandler,并且使用@RestControllerAdvice (或者@ControllerAdvice)注解该类,表示这是一个全局异常处理器类。
    然后针对每种异常类定义一个处理异常的方法,并且使用@ExceptionHandler(异常类.class) 注解标注这些方法,可以在这些方法中进行日志记录等具体的异常处理操作,并返回一个响应对象,便于前端识别并给用户友好的错误提示。例如:
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    public BaseResponse<?> businessExceptionHandler(BusinessException e) {
        log.error("BusinessException", e);
        return ResultUtils.error(e.getCode(), e.getMessage());
    }
    ...
}
  1. 针对定义的异常处理方法编写封装类
  2. 编写异常抛出类,针对各类特定的条件抛出异常

工具类

NetUtils

获取客户端的 IP 地址,并处理一些特殊情况,例如 unknown 或 127.0.0.1 。

SpringContextUtils

提供用于通过名称、类型、名称和类型获取 Spring 上下文的容器的方法。

SqlUtils

校验排序字段是否合法(防止 SQL 注入)

多环境 yml

为什么需要?鱼皮多环境介绍
在这里插入图片描述

# 公共配置文件
# @author xht
# 
spring:
  application:
    name: ....
  # 默认 dev 环境
  profiles:
    active: dev
  # 支持 swagger3
  mvc:
    pathmatch:
      matching-strategy: 
  # session 配置
  session:
    # todo 取消注释开启分布式 session(须先配置 Redis)
    # store-type: redis
    # 30 天过期
    timeout: 2592000
  # 数据库配置
  # todo 需替换配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/....
    username: ....
    password: ....
  # Redis 配置
  #   todo 需替换配置,然后取消注释
  redis:
    database: 1
    host: localhost
    port: 6379
    timeout: 5000
  #    password:
  # 文件上传
  ....
  # 分库分表配置
  ....
server:
	....
mybatis-plus:
	....
# 对象存储
# todo 需替换配置
cos:
  client:
    accessKey: xxx
    secretKey: xxx
    region: xxx
    bucket: xxx
# 接口文档配置
knife4j:
	....
其他配置....
  • 18
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值