【精品文章】springboot 项目起步配置【值得收藏,绝对原创】

springboot 项目起步配置

前言

springboot 也是配置地狱,插件太多,配置太多,也要做模板化工程,如果每个项目都从头来做,那

等配置完黄花菜都凉了,突然还是觉得django香了

模板源码:https://gitee.com/jpruby/springboot-template.git

生成目录方法

IDEA

在写文档的时候,想把项目输出成文档树的形式,可以使用以下命令

tree  >> D:/tree.txt 只有文件夹
tree /f >> D:/tree.txt 包括文件夹和文件
          
├─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

目前最新稳定版本 2.6.7

image-20220425091356998

构建项目

创建项目

image-20220425091700777

image-20220425091839380

meven配置阿里云镜像

settings.xml (maven的配置文件)

 <!-- 阿里云镜像 -->
    <mirror>
      <id>alimaven</id>
      <name>aliyun maven</name>
      <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
      <mirrorOf>central</mirrorOf>
    </mirror>
    <!-- 私服 -->
    <!-- <mirror>
      <id>nexus</id>
      <name>nexus maven</name>
      <url>http://192.168.126.128:8081/repository/maven-public/</url>
      <mirrorOf>*</mirrorOf>
    </mirror> -->
  </mirrors>

   <profile>    
    <id>jdk-1.8</id>    
      <activation>    
     	  <activeByDefault>true</activeByDefault>    
      	  <jdk>1.8</jdk>    
        </activation>    
      <properties>    
        <maven.compiler.source>1.8</maven.compiler.source>    
        <maven.compiler.target>1.8</maven.compiler.target>    
        <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>    
      </properties>    
    </profile>
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!--最新的稳定版-->
    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.6.6</version>
    </parent>

    <groupId>com.jpruby</groupId>
    <artifactId>springboot-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

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

    <!--//打包jar--重要>
    <build>
        <plugins>
           <plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
        </plugins>
    </build>

</project>
自动热部署

image-20220425101221514

image-20220425101917169

添加依赖

<!--热部署-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<optional>true</optional>
		</dependency>
整合junit5

注意包的结构和启动器的的包的所在位置要一致

<!--单元测试-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

使用

image-20220425102537657

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

	<!--整合mybatis-->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.5.1</version>
		</dependency>
		<!--mysql驱动-->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
	</dependencies>

application.yml 配置数据库连接池,默认自带希卡利 HikariPool,如果用德鲁伊自己去引

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

分页插件的配置,需要注入容器

package com.jpruby.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan("com.jpruby.mapper")
public class MybatisPlusConfig {

    /**
     * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }

}

image-20220425105507659

效果:

image-20220425111453700

注意! 如果自己写xml文件,必须以下配置

指定location

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
  mapper-locations:
    - classpath*:/mapper/*.xml

修改pom文件build

<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
		<resources>
			<resource>
				<directory>src/main/java</directory>
				<includes>
					<include>**/*.xml</include>
				</includes>
			</resource>
			<resource>
				<directory>src/main/resources</directory>
			</resource>
		</resources>
	</build>

xml文件常用头文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="">

</mapper>
统一返回接口格式

枚举类定义返回code 和 msg

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;
    }
}

定义统一返回的实体类

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

swagger的配置

pom.xml

<!--swagger-->
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-boot-starter</artifactId>
			<version>3.0.0</version>
		</dependency>
		<!--swagger ui-->
		<dependency>
			<groupId>com.github.xiaoymin</groupId>
			<artifactId>swagger-bootstrap-ui</artifactId>
			<version>1.9.6</version>
		</dependency>

application.yml

spring:
  mvc:
    pathmatch.matching-strategy: ant_path_matcher

config 配置类

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

跨域cors解决

增加配置类

@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

<!--jwt-->
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt</artifactId>
			<version>0.9.1</version>
		</dependency>

jwt 工具类创建

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

  <dependency>
  	<groupId>javax.xml.bind</groupId>
  	<artifactId>jaxb-api</artifactId>
  </dependency>

测试效果

 // 测试效果
    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

拦截器

首先分清楚什么是拦截器,什么是过滤器

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

img

定义一个拦截器 自己建立一个文件夹

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);
    }
}

配置类走一波

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

异常统一处理

优雅的处理统一异常处理与统一结果返回

定义全局异常处理器 GlobalExceptionHandler

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 哇哈哈哈

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

自定义异常需要一个枚举类支持,这个类在别的地方也有大作用

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 了解一下

先定义一个属于自己的注解

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 作为自己的注解解析器

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;
    }
}

最后再配置一下自己的解析器否则系统认你谁谁啊

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<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(userIdArgumentResolver);
    }
}

测试效果,成功!

image-20220425160640108

image-20220425160927109

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OeWrLwzi-1651208363575)(https://gitee.com/jpruby/typora_img/raw/master/image-20220425160851224.png)]

声明式事务

直接在需要事务控制的方法上加上对应的注解 @Transactional

直接上图

image-20220425163751120

AOP 登场

批量的增强

springboot 默认开启Aop

pom.xml

<!--aop登场-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>

用法举例

先来一个注解

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的依赖先引进来

<!--fastjson依赖-->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.33</version>
		</dependency>

创建aop啦!

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

效果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zQGggccf-1651208363576)(https://gitee.com/jpruby/typora_img/raw/master/image-20220425170706065.png)]

整合Redis

pom.xml

<!--redis-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>

application.yml

spring: 
 redis:
    host: 192.168.126.128
    port: 6379

测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xe3GdX6S-1651208363577)(https://gitee.com/jpruby/typora_img/raw/master/image-20220425175648715.png)]

效果

image-20220425175757659

多环境配置

直接上图

image-20220425192829222

jar包测试环境切换>java -jar .\demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=test

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.Application               : 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.Application               : 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.Application               : 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 #设置日志级别
最终版Application.yml文件

相比之前增加后端传前端的时间格式化

注意里面有多环境,这里只写目前环境

server:
  port: 1984

spring:
  profiles:
    active: test  #环境切换

  jackson:     #后台返回前台的时间 格式化
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.126.128/qing
    username: root
    password: 123456
  mvc:
    pathmatch.matching-strategy: ant_path_matcher #swagger必备
  aop:
    proxy-target-class: true  #cjlib动态代理 ,可以不配置默认就是true 写这里就是看看
  redis:
    host: 192.168.126.128
    port: 6379

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
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值