# 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

### 构建项目
#### 创建项目


#### 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
```
使用

```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
```

效果:

#### 统一返回接口格式
枚举类定义返回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;
}
}
```
效果

#### 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

跨域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());
}
```

#### 拦截器
首先分清楚什么是拦截器,什么是过滤器
1. Filter:过滤器,过滤从客户端向服务器发送的请求。
2. Interceptor:拦截器,更细粒度化的拦截。(拦截其中的具体的方法)。

定义一个拦截器 自己建立一个文件夹
```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成功完美!

#### 异常统一处理
优雅的处理统一异常处理与统一结果返回
定义全局异常处理器 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);
}
}
```
测试效果,成功!



#### 声明式事务
直接在需要事务控制的方法上加上对应的注解 **@Transactional**
直接上图

#### 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);
}
}
```
使用

效果

#### 整合Redis
pom.xml
```xml
org.springframework.boot
spring-boot-starter-data-redis
```
application.yml
```yml
spring:
redis:
host: 192.168.126.128
port: 6379
```
测试

效果

#### 多环境配置
直接上图

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 #设置日志级别
```