# springboot-template **Repository Path**: jpruby/springboot-template ## Basic Information - **Project Name**: springboot-template - **Description**: springboot 也是配置地狱,插件太多,配置太多,也要做模板化工程,如果每个项目都从头来做,那 等配置完黄花菜都凉了,突然还是觉得django香了 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-04-25 - **Last Updated**: 2025-08-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # springboot 项目起步配置 ### 前言 springboot 也是配置地狱,插件太多,配置太多,也要做模板化工程,如果每个项目都从头来做,那 等配置完黄花菜都凉了,突然还是觉得django香了 模板源码: 生成目录方法 ``` IDEA 在写文档的时候,想把项目输出成文档树的形式,可以使用以下命令 tree >> D:/tree.txt 只有文件夹 tree /f >> D:/tree.txt 包括文件夹和文件 ``` ```bash ├─src │ ├─main │ │ ├─java │ │ │ └─com │ │ │ └─jpruby │ │ │ │ DemoApplication.java │ │ │ │ │ │ │ ├─annotation │ │ │ │ CurrentUserId.java │ │ │ │ SystemLog.java │ │ │ │ │ │ │ ├─aspect │ │ │ │ LogAspect.java │ │ │ │ │ │ │ ├─config │ │ │ │ ArgumentResolverConfig.java │ │ │ │ InterceptorConfig.java │ │ │ │ SwaggerConfig.java │ │ │ │ WebConfig.java │ │ │ │ │ │ │ ├─controller │ │ │ │ UserController.java │ │ │ │ │ │ │ ├─domain │ │ │ │ ├─common │ │ │ │ │ AppHttpCodeEnum.java │ │ │ │ │ ResponseResult.java │ │ │ │ │ │ │ │ │ └─entity │ │ │ │ User.java │ │ │ │ │ │ │ ├─exception │ │ │ │ SystemException.java │ │ │ │ │ │ │ ├─handler │ │ │ │ ├─exception │ │ │ │ │ GlobalExceptionHandler.java │ │ │ │ │ │ │ │ │ └─resolver │ │ │ │ UserIdArgumentResolver.java │ │ │ │ │ │ │ ├─interceptor │ │ │ │ LoginInterceptor.java │ │ │ │ │ │ │ ├─mapper │ │ │ │ UserMapper.java │ │ │ │ │ │ │ ├─service │ │ │ │ │ UserService.java │ │ │ │ │ │ │ │ │ └─impl │ │ │ │ UserServiceImpl.java │ │ │ │ │ │ │ └─utils │ │ │ JwtUtil.java │ │ │ │ │ └─resources │ │ application-test.yml │ │ application.yml │ │ ``` ### 起步 官网:[Spring Boot](https://spring.io/projects/spring-boot#learn) 目前最新稳定版本 2.6.7 ![image-20220425091356998](https://gitee.com/jpruby/typora_img/raw/master/image-20220425091356998.png) ### 构建项目 #### 创建项目 ![image-20220425091700777](https://gitee.com/jpruby/typora_img/raw/master/image-20220425091700777.png) ![image-20220425091839380](https://gitee.com/jpruby/typora_img/raw/master/image-20220425091839380.png) #### meven配置阿里云镜像 settings.xml (maven的配置文件) ```xml alimaven aliyun maven http://maven.aliyun.com/nexus/content/groups/public/ central jdk-1.8 true 1.8 1.8 1.8 1.8 ``` #### pom.xml ```xml 4.0.0 spring-boot-starter-parent org.springframework.boot 2.6.6 com.jpruby springboot-demo 1.0-SNAPSHOT 8 8 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-devtools true ``` #### 整合junit5 注意包的结构和启动器的的包的所在位置要一致 ```xml org.springframework.boot spring-boot-starter-test test ``` 使用 ![image-20220425102537657](https://gitee.com/jpruby/typora_img/raw/master/image-20220425102537657.png) ```java package com.jpruby; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class DemoApplicationTests { @Test void contextLoads() { } } ``` #### 整合mybatis pom.xml ```xml com.baomidou mybatis-plus-boot-starter 3.5.1 mysql mysql-connector-java ``` application.yml 配置数据库连接池,默认自带希卡利 HikariPool,如果用德鲁伊自己去引 ```yml spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://192.168.126.128/test username: root password: 123456 mybatis-plus: configuration: # 日志 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: delFlag # 逻辑删除, 1 删除 0 不删除 logic-delete-value: 1 logic-not-delete-value: 0 id-type: auto ``` ![image-20220425105507659](https://gitee.com/jpruby/typora_img/raw/master/image-20220425105507659.png) 效果: ![image-20220425111453700](https://gitee.com/jpruby/typora_img/raw/master/image-20220425111453700.png) #### 统一返回接口格式 枚举类定义返回code 和 msg ```java package com.jpruby.domain.common; public enum AppHttpCodeEnum { // 成功 SUCCESS(200,"操作成功"), // 登录 NEED_LOGIN(401,"需要登录后操作"), NO_OPERATOR_AUTH(403,"无权限操作"), SYSTEM_ERROR(500,"出现错误"), USERNAME_EXIST(501,"用户名已存在"), PHONENUMBER_EXIST(502,"手机号已存在"), EMAIL_EXIST(503, "邮箱已存在"), REQUIRE_USERNAME(504, "必需填写用户名"), LOGIN_ERROR(505,"用户名或密码错误"); int code; String msg; AppHttpCodeEnum(int code, String errorMessage){ this.code = code; this.msg = errorMessage; } public int getCode() { return code; } public String getMsg() { return msg; } } ``` 定义统一返回的实体类 ```java package com.jpruby.domain.common; import com.fasterxml.jackson.annotation.JsonInclude; import com.jpruby.domain.common.AppHttpCodeEnum; import java.io.Serializable; @JsonInclude(JsonInclude.Include.NON_NULL) public class ResponseResult implements Serializable { private Integer code; private String msg; private T data; public ResponseResult() { this.code = AppHttpCodeEnum.SUCCESS.getCode(); this.msg = AppHttpCodeEnum.SUCCESS.getMsg(); } public ResponseResult(Integer code, T data) { this.code = code; this.data = data; } public ResponseResult(Integer code, String msg, T data) { this.code = code; this.msg = msg; this.data = data; } public ResponseResult(Integer code, String msg) { this.code = code; this.msg = msg; } public static ResponseResult errorResult(int code, String msg) { ResponseResult result = new ResponseResult(); return result.error(code, msg); } public static ResponseResult okResult() { ResponseResult result = new ResponseResult(); return result; } public static ResponseResult okResult(int code, String msg) { ResponseResult result = new ResponseResult(); return result.ok(code, null, msg); } public static ResponseResult okResult(Object data) { ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS, AppHttpCodeEnum.SUCCESS.getMsg()); if (data != null) { result.setData(data); } return result; } public static ResponseResult errorResult(AppHttpCodeEnum enums) { return setAppHttpCodeEnum(enums, enums.getMsg()); } public static ResponseResult errorResult(AppHttpCodeEnum enums, String msg) { return setAppHttpCodeEnum(enums, msg); } public static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums) { return okResult(enums.getCode(), enums.getMsg()); } private static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums, String msg) { return okResult(enums.getCode(), msg); } public ResponseResult error(Integer code, String msg) { this.code = code; this.msg = msg; return this; } public ResponseResult ok(Integer code, T data) { this.code = code; this.data = data; return this; } public ResponseResult ok(Integer code, T data, String msg) { this.code = code; this.data = data; this.msg = msg; return this; } public ResponseResult ok(T data) { this.data = data; return this; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public T getData() { return data; } public void setData(T data) { this.data = data; } } ``` 效果 ![image-20220425113824333](https://gitee.com/jpruby/typora_img/raw/master/image-20220425113824333.png) #### swagger的配置 pom.xml ```xml io.springfox springfox-boot-starter 3.0.0 com.github.xiaoymin swagger-bootstrap-ui 1.9.6 ``` application.yml ```yaml spring: mvc: pathmatch.matching-strategy: ant_path_matcher ``` config 配置类 ```java package com.jpruby.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.oas.annotations.EnableOpenApi; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.Contact; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; @Configuration @EnableOpenApi public class SwaggerConfig { /** * 创建API应用 * apiInfo() 增加API相关信息 * 通过select()函数返回一个ApiSelectorBuilder实例,用来控制哪些接口暴露给Swagger来展现, * 本例采用指定扫描的包路径来定义指定要建立API的目录。 * * @return */ @Bean public Docket restApi() { return new Docket(DocumentationType.SWAGGER_2) .groupName("后台api") .apiInfo(apiInfo("Spring Boot中使用Swagger 构建RESTful APIs", "1.0")) .useDefaultResponseMessages(true) .forCodeGeneration(false) .select() .apis(RequestHandlerSelectors.basePackage("com.jpruby.controller")) .paths(PathSelectors.any()) .build(); } /** * 创建该API的基本信息(这些基本信息会展现在文档页面中) * 访问地址:http://ip:port/swagger-ui.html * http://ip:port/doc.html (新UI插件的地址) * * @return */ private ApiInfo apiInfo(String title, String version) { return new ApiInfoBuilder() .title(title) .description("项目后台api") .termsOfServiceUrl("") .contact(new Contact("jpruby", "", "japan8364@163.com")) .version(version) .build(); } } ``` 效果 http://localhost:1983/doc.html ![image-20220425130233909](https://gitee.com/jpruby/typora_img/raw/master/image-20220425130233909.png) 跨域cors解决 增加配置类 ```java @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { // 设置允许跨域的路径 registry.addMapping("/**") // 设置允许跨域请求的域名 .allowedOriginPatterns("*") // 是否允许cookie .allowCredentials(true) // 设置允许的请求方式 .allowedMethods("GET", "POST", "DELETE", "PUT") // 设置允许的header属性 .allowedHeaders("*") // 跨域允许时间 .maxAge(3600); } } ``` #### token生成方案 jwt pom.xml ```xml io.jsonwebtoken jjwt 0.9.1 ``` jwt 工具类创建 ```java package com.jpruby.utils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; import java.util.Date; import java.util.UUID; /** * JWT工具类 */ public class JwtUtil { //有效期为 public static final Long JWT_TTL = 24 * 60 * 60 * 1000L;// 60 * 60 *1000 一个小时 //设置秘钥明文 public static final String JWT_KEY = "jpruby"; public static String getUUID() { String token = UUID.randomUUID().toString().replaceAll("-", ""); return token; } /** * 生成jtw * * @param subject token中要存放的数据(json格式) * @return */ public static String createJWT(String subject) { JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间 return builder.compact(); } /** * 生成jtw * * @param subject token中要存放的数据(json格式) * @param ttlMillis token超时时间 * @return */ public static String createJWT(String subject, Long ttlMillis) { JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间 return builder.compact(); } private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) { SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; SecretKey secretKey = generalKey(); long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); if (ttlMillis == null) { ttlMillis = JwtUtil.JWT_TTL; } long expMillis = nowMillis + ttlMillis; Date expDate = new Date(expMillis); return Jwts.builder() .setId(uuid) //唯一的ID .setSubject(subject) // 主题 可以是JSON数据 .setIssuer("sg") // 签发者 .setIssuedAt(now) // 签发时间 .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥 .setExpiration(expDate); } /** * 创建token * * @param id * @param subject * @param ttlMillis * @return */ public static String createJWT(String id, String subject, Long ttlMillis) { JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间 return builder.compact(); } public static void main(String[] args) throws Exception { String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg"; Claims claims = parseJWT(token); System.out.println(claims); } /** * 生成加密后的秘钥 secretKey * * @return */ public static SecretKey generalKey() { byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY); SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES"); return key; } /** * 解析 * * @param jwt * @return * @throws Exception */ public static Claims parseJWT(String jwt) throws Exception { SecretKey secretKey = generalKey(); return Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(jwt) .getBody(); } } ``` 注意!!JWT报错提示Exception in thread “main“ java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter 解决方案 在jdk8以后,就不再引入javax包了,其实当我们看到 NoClassDefFoundError 就应该意识到JDK出了问题 解决问题的方法有两个 更换JDK版本,我用的是JDK11(本人感觉很香),更换为jdk8以后就OK了 引入相关jar包 pom.xml ```xml javax.xml.bind jaxb-api ``` 测试效果 ```java // 测试效果 public static void main(String[] args) throws Exception { //生成token String token = createJWT(UUID.randomUUID().toString(),"nihao",null); System.out.println("token = " + token); //解析token Claims subject = parseJWT(token); System.out.println("subject.getSubject() = " + subject.getSubject()); } ``` ![image-20220425134152670](https://gitee.com/jpruby/typora_img/raw/master/image-20220425134152670.png) #### 拦截器 首先分清楚什么是拦截器,什么是过滤器 1. Filter:过滤器,过滤从客户端向服务器发送的请求。 2. Interceptor:拦截器,更细粒度化的拦截。(拦截其中的具体的方法)。 ![img](https://gitee.com/jpruby/typora_img/raw/master/20181031203117677.png) 定义一个拦截器 自己建立一个文件夹 ```java package com.jpruby.interceptor; import com.jpruby.utils.JwtUtil; import io.jsonwebtoken.Claims; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Component public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //获取请求头的token String token = request.getHeader("token"); if(!StringUtils.hasText(token)){ //token为空 直接拦截 response.sendError(HttpServletResponse.SC_UNAUTHORIZED); return false; } //解析token try { Claims claims = JwtUtil.parseJWT(token); String subject = claims.getSubject(); } catch (Exception e) { e.printStackTrace(); response.sendError(HttpServletResponse.SC_UNAUTHORIZED); return false; } //如果出现异常未登录 直接返回异常统一处理 return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { HandlerInterceptor.super.afterCompletion(request, response, handler, ex); } } ``` 配置类走一波 ```java package com.jpruby.config; import com.jpruby.interceptor.LoginInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class InterceptorConfig implements WebMvcConfigurer { /** * 加入自己写好的拦截器 * @param registry */ @Autowired private LoginInterceptor loginInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) //添加拦截器 .addPathPatterns("/user") //指定一个拦截对象 .excludePathPatterns("/doc.html/**","/webjars/**"); //放行 } } ``` 测试效果,401成功完美! ![image-20220425142827394](https://gitee.com/jpruby/typora_img/raw/master/image-20220425142827394.png) #### 异常统一处理 优雅的处理统一异常处理与统一结果返回 定义全局异常处理器 GlobalExceptionHandler ```java package com.jpruby.handler.exception; import com.jpruby.domain.common.AppHttpCodeEnum; import com.jpruby.domain.common.ResponseResult; import com.jpruby.exception.SystemException; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @RestControllerAdvice @Slf4j public class GlobalExceptionHandler { @ExceptionHandler(SystemException.class) public ResponseResult systemExceptionHandler(SystemException e){ //打印异常信息 log.error("出现了异常! {}",e); //从异常对象中获取提示信息封装返回 return ResponseResult.errorResult(e.getCode(),e.getMsg()); } @ExceptionHandler(Exception.class) public ResponseResult exceptionHandler(Exception e){ //打印异常信息 log.error("出现了异常! {}",e); //从异常对象中获取提示信息封装返回 return ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR.getCode(),e.getMessage()); } } ``` 自定义一种异常可以表示很多内容的那种SystemException 哇哈哈哈 ```java package com.jpruby.exception; import com.jpruby.domain.common.AppHttpCodeEnum; public class SystemException extends RuntimeException{ private int code; private String msg; public int getCode() { return code; } public String getMsg() { return msg; } public SystemException(AppHttpCodeEnum httpCodeEnum) { super(httpCodeEnum.getMsg()); this.code = httpCodeEnum.getCode(); this.msg = httpCodeEnum.getMsg(); } } ``` 自定义异常需要一个枚举类支持,这个类在别的地方也有大作用 ```java package com.jpruby.domain.common; public enum AppHttpCodeEnum { // 成功 SUCCESS(200,"操作成功"), // 登录 NEED_LOGIN(401,"需要登录后操作"), NO_OPERATOR_AUTH(403,"无权限操作"), SYSTEM_ERROR(500,"出现错误"), USERNAME_EXIST(501,"用户名已存在"), PHONENUMBER_EXIST(502,"手机号已存在"), EMAIL_EXIST(503, "邮箱已存在"), REQUIRE_USERNAME(504, "必需填写用户名"), LOGIN_ERROR(505,"用户名或密码错误"); int code; String msg; AppHttpCodeEnum(int code, String errorMessage){ this.code = code; this.msg = errorMessage; } public int getCode() { return code; } public String getMsg() { return msg; } } ``` #### 自定义参数解析 反反复复获取header 里的token 信息没意义 自定义即可 HandlerMethodArgumentResolver 了解一下 先定义一个属于自己的注解 ```java package com.jpruby.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface CurrentUserId { } ``` 再定义一个handler 作为自己的注解解析器 ```java package com.jpruby.handler.resolver; import com.jpruby.annotation.CurrentUserId; import com.jpruby.utils.JwtUtil; import io.jsonwebtoken.Claims; import org.springframework.core.MethodParameter; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; /** * Created with IntelliJ IDEA. * Project: demo * Author: jpruby * Date: 2022/04/25/15:36 * Description: 一看就会,一写就废 * FilePath: com.jpruby.handler.resolver * HandlerMethodArgumentResolver 是spring的一个处理器 还有设置config * Copyright (c) 2022, All Rights Reserved. */ @Component public class UserIdArgumentResolver implements HandlerMethodArgumentResolver { // 判断方法参数能使用当前的参数解析器,进行处理,自己要需要定义一个注解 @Override public boolean supportsParameter(MethodParameter parameter) { // 如果方法参数含有@CurrentUserId 就能被我自定义的注解解析器解析 return parameter.hasParameterAnnotation(CurrentUserId.class); } // 真正的参数解析的方法,可以在方法中获取对应的数据,然后把数据作为返回值返回,方法的返回值就会赋值给对应的方法参数 @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { // 获取请求头中的token String token = webRequest.getHeader("token"); // 解析token if (StringUtils.hasText(token)) { Claims claims = JwtUtil.parseJWT(token); // 如果解析不到也会被我的全局异常捕获 return claims.getSubject(); } return null; } } ``` 最后再配置一下自己的解析器否则系统认你谁谁啊 ```java package com.jpruby.config; import com.jpruby.handler.resolver.UserIdArgumentResolver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; @Configuration public class ArgumentResolverConfig implements WebMvcConfigurer { @Autowired UserIdArgumentResolver userIdArgumentResolver; @Override public void addArgumentResolvers(List resolvers) { resolvers.add(userIdArgumentResolver); } } ``` 测试效果,成功! ![image-20220425160640108](https://gitee.com/jpruby/typora_img/raw/master/image-20220425160640108.png) ![image-20220425160927109](https://gitee.com/jpruby/typora_img/raw/master/image-20220425160927109.png) ![image-20220425160851224](https://gitee.com/jpruby/typora_img/raw/master/image-20220425160851224.png) #### 声明式事务 直接在需要事务控制的方法上加上对应的注解 **@Transactional** 直接上图 ![image-20220425163751120](https://gitee.com/jpruby/typora_img/raw/master/image-20220425163751120.png) #### AOP 登场 批量的增强 springboot 默认开启Aop pom.xml ```xml org.springframework.boot spring-boot-starter-aop ``` 用法举例 先来一个注解 ```java package com.jpruby.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface SystemLog { String businessName(); } ``` aop 需要一个JSON的依赖先引进来 ```java com.alibaba fastjson 1.2.33 ``` 创建aop啦! ```java package com.jpruby.aspect; import com.alibaba.fastjson.JSON; import com.jpruby.annotation.SystemLog; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; @Component @Aspect @Slf4j public class LogAspect { // 确定切点 @Pointcut("@annotation(com.jpruby.annotation.SystemLog)") public void pt() { } // 定义通知方法 @Around("pt()") public Object printLog(ProceedingJoinPoint joinPoint) throws Throwable { Object ret; try { handleBefore(joinPoint); ret = joinPoint.proceed(); handleAfter(ret); } finally { // 结束后换行 log.info("=======End=======" + System.lineSeparator()); } return ret; } private void handleAfter(Object ret) { // 打印出参 log.info("Response : {}", JSON.toJSONString(ret)); } private void handleBefore(ProceedingJoinPoint joinPoint) { ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = requestAttributes.getRequest(); //获取被增强方法上的注解对象 SystemLog systemLog = getSystemLog(joinPoint); log.info("=======Start======="); // 打印请求 URL log.info("URL : {}", request.getRequestURL()); // 打印描述信息 log.info("BusinessName : {}", systemLog.businessName()); // 打印 Http method log.info("HTTP Method : {}", request.getMethod()); // 打印调用 controller 的全路径以及执行方法 log.info("Class Method : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), ((MethodSignature) joinPoint.getSignature()).getName()); // 打印请求的 IP log.info("IP : {}", request.getRemoteHost()); // 打印请求入参 log.info("Request Args : {}", JSON.toJSONString(joinPoint.getArgs())); } private SystemLog getSystemLog(ProceedingJoinPoint joinPoint) { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); return methodSignature.getMethod().getAnnotation(SystemLog.class); } } ``` 使用 ![image-20220425170816113](https://gitee.com/jpruby/typora_img/raw/master/image-20220425170816113.png) 效果 ![image-20220425170706065](https://gitee.com/jpruby/typora_img/raw/master/image-20220425170706065.png) #### 整合Redis pom.xml ```xml org.springframework.boot spring-boot-starter-data-redis ``` application.yml ```yml spring: redis: host: 192.168.126.128 port: 6379 ``` 测试 ![image-20220425175648715](https://gitee.com/jpruby/typora_img/raw/master/image-20220425175648715.png) 效果 ![image-20220425175757659](https://gitee.com/jpruby/typora_img/raw/master/image-20220425175757659.png) #### 多环境配置 直接上图 ![image-20220425192829222](https://gitee.com/jpruby/typora_img/raw/master/image-20220425192829222.png) jar包测试环境切换>java -jar .\demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=test ```bash PS D:\后端练习\三更springboot\code\demo\target> java -jar .\demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=test . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.6.6) 2022-04-25 18:20:54.567 INFO 3652 --- [ main] com.jpruby.DemoApplication : Starting DemoApplication v0.0.1-SNAPSHOT using Java 17.0.2 on LAPTOP-jpruby with PID 3652 (D:\后端练习\三更springboot\code\demo\target\demo-0.0.1-SNAPSHOT.jar started by jpruby in D:\后端练习\三更springboot\code\demo\target) 2022-04-25 18:20:54.571 INFO 3652 --- [ main] com.jpruby.DemoApplication : The following 1 profile is active: "test" 2022-04-25 18:20:55.545 INFO 3652 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode! 2022-04-25 18:20:55.550 INFO 3652 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Redis repositories in DEFAULT mode. 2022-04-25 18:20:55.579 INFO 3652 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 10 ms. Found 0 Redis repository interfaces. 2022-04-25 18:20:56.955 INFO 3652 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 1999 (http) 2022-04-25 18:20:56.968 INFO 3652 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2022-04-25 18:20:56.968 INFO 3652 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.60] 2022-04-25 18:20:57.065 INFO 3652 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2022-04-25 18:20:57.066 INFO 3652 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2436 ms Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter. Property 'mapperLocations' was not specified. _ _ |_ _ _|_. ___ _ | _ | | |\/|_)(_| | |_\ |_)||_|_\ / | 3.5.1 2022-04-25 18:20:59.694 INFO 3652 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 1999 (http) with context path '' 2022-04-25 18:20:59.959 INFO 3652 --- [ main] com.jpruby.DemoApplication : Started DemoApplication in 5.884 seconds (JVM running for 6.291) context.getBean(UserController.class).getClass().getName() = com.jpruby.controller.UserController$$EnhancerBySpringCGLIB$$8638c748 2022-04-25 18:21:38.436 INFO 3652 --- [nio-1999-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' 2022-04-25 18:21:38.437 INFO 3652 --- [nio-1999-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 2022-04-25 18:21:38.441 INFO 3652 --- [nio-1999-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 4 ms ``` #### 日志 开启日志 application.yaml ``` debug: true #开启日志 logging: level: com.jpruby: debug #设置日志级别 ```