springboot项目脚手架搭建之一一文全解

1. springboot项目创建

项目创建

前置条件

  • 配置java环境
  • 配置maven环境

在IDEA中创建一个springboot项目,创建的时候不选择任何的pom依赖

pom.xml文件示例:

这里建议使用2.6以下的springboot版本,不然后面会有swagger的冲突,虽然有其它的方式解决,但是还是选2.6以下版本更简单方便。

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
         <version>2.5.7</version>
         <relativePath/> <!-- lookup parent from repository -->
     </parent>
     <groupId>com.bwss</groupId>
     <artifactId>ai-management-platform</artifactId>
     <version>0.0.1-SNAPSHOT</version>
     <description>Demo project for Spring Boot</description>
 
     <properties>
         <java.version>1.8</java.version>
     </properties>
 
     <dependencies>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter</artifactId>
         </dependency>
 
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>
             <scope>test</scope>
             <exclusions>
                 <exclusion>
                     <groupId>org.junit.vintage</groupId>
                     <artifactId>junit-vintage-engine</artifactId>
                 </exclusion>
             </exclusions>
         </dependency>
     </dependencies>
 
     <build>
         <plugins>
             <plugin>
                 <groupId>org.springframework.boot</groupId>
                 <artifactId>spring-boot-maven-plugin</artifactId>
             </plugin>
         </plugins>
     </build>
 
</project>

项目配置

引入web依赖

<!-- 引入web相关 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

修改application.properties后缀为application.yml,添加启动的端口号

server:
  port: 8288

启动设置

package com.maple.demo;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author lh
 **/
@RestController
public class TestController {

    @GetMapping(value = "test")
    public String test() {
        return "项目启动测试";
    }
}

项目启动成功,在浏览器输入http://127.0.0.1:8288/test地址进行测试。

2. swagger2的knife4j配置

引入Knife4j的依赖包

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>2.0.9</version>
</dependency>

Swagger配置

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.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;

/**
 * @Description swagger配置
 * @Author LH
 **/
@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfig
{
    @Bean(value = "knife4j")
    public Docket knife4j()
    {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(new ApiInfoBuilder().title("AI管理平台接口").description("AI管理平台接口")
                        .termsOfServiceUrl("http://127.0.0.1:8288/doc.html").version("1.0").build())
                //分组名称
                .groupName("AI管理平台接口").select()
                //这里指定Controller扫描包路径
                .apis(RequestHandlerSelectors.basePackage("com.bwss")).paths(PathSelectors.any()).build();
    }
}

浏览器访问http://127.0.0.1:8288/doc.html

3. 集成Mybatis Plus

引入相应的pom依赖

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.7</version>
        <relativePath/>
    </parent>

    <groupId>com.bwss</groupId>
    <artifactId>ai-management-platform</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <description>AI管理平台</description>

	<properties>
		<java.version>1.8</java.version>
        <lombok-version>1.18.12</lombok-version>
        <druid-version>1.2.11</druid-version>
        <mybatis-plus-version>3.5.2</mybatis-plus-version>
        <velocity-version>2.3</velocity-version>
        <fastjson-version>2.0.7</fastjson-version>
        <mysql-connector-version>8.0.29</mysql-connector-version>
	</properties>

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

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

		<!-- 引入web相关 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<!--使用Mysql数据库-->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>${mysql-connector-version}</version>
		</dependency>
		<!--使用阿里巴巴druid数据库连接池-->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
			<version>${druid-version}</version>
		</dependency>

		<!-- mybatis-plus的依赖 -->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>${mybatis-plus-version}</version>
		</dependency>
		<!-- mybatis-plus的自动生成代码插件 -->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-generator</artifactId>
			<version>${mybatis-plus-version}</version>
		</dependency>
		<dependency>
			<groupId>org.apache.velocity</groupId>
			<artifactId>velocity-engine-core</artifactId>
			<version>${velocity-version}</version>
		</dependency>

		<!--Lombok管理Getter/Setter/log等-->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<scope>provided</scope>
			<version>${lombok-version}</version>
		</dependency>

		<!-- 处理JSON -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>${fastjson-version}</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

数据库创建

创建用户表

CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `account` varchar(30) DEFAULT NULL COMMENT '用户账号',
  `user_name` varchar(30) DEFAULT NULL COMMENT '用户姓名',
  `nick_name` varchar(30) DEFAULT NULL COMMENT '用户昵称',
  `user_type` varchar(2) DEFAULT '00' COMMENT '用户类型(00系统用户,01小程序用户)',
  `email` varchar(50) DEFAULT '' COMMENT '用户邮箱',
  `phone` varchar(11) DEFAULT '' COMMENT '手机号码',
  `sex` char(1) DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)',
  `avatar` varchar(100) DEFAULT '' COMMENT '头像地址',
  `salt` varchar(32) DEFAULT NULL COMMENT '用户加密盐值',
  `password` varchar(100) DEFAULT '' COMMENT '密码',
  `status` char(1) DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',
  `create_id` bigint(20) DEFAULT NULL COMMENT '创建人id',
  `create_name` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_id` bigint(20) DEFAULT NULL COMMENT '更新人id',
  `update_name` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `delete_flag` tinyint(1) DEFAULT '0' COMMENT '删除标志',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户信息表';

配置项目的application.yml文件

server:
  port: 8288

spring:
  application:
    name: ai
  # 配置数据库连接
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/maple?useUnicode=yes&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC
    driverClassName: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    # 配置druid数据库连接池
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      initialSize: 5
      minIdle: 5
      maxActive: 20
      maxWait: 60000
      timeBetweenEvictionRunsMillis: 60000
      minEvictableIdleTimeMillis: 300000
      validationQuery: SELECT 'x'
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      poolPreparedStatements: true
      maxPoolPreparedStatementPerConnectionSize: 20
      filters: stat,wall
      # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
      useGlobalDataSourceStat: true

# 配置mybatis-plus的xml和bean的目录
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  #逻辑删除配置
  global-config:
    db-config:
      logic-delete-value: 1
      logic-not-delete-value: 0

配置文件配置

package com.demo.config;

import com.alibaba.druid.support.http.StatViewServlet;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.servlet.Servlet;
import java.util.Date;

/**
 * Mybatis plus配置
 */
@EnableTransactionManagement
@Configuration
@MapperScan("com.demo.mapper")
public class MybatisPlusConfig implements MetaObjectHandler {

    /**
     * 新增时,自动填充数据
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        this.setFieldValByName("isDeleted", false, metaObject);
        this.setFieldValByName("createId", 1L, metaObject);
        this.setFieldValByName("createName", "占位符", metaObject);
        this.setFieldValByName("createTime", new Date(), metaObject);
        this.setFieldValByName("updateId", 1L, metaObject);
        this.setFieldValByName("updateName", "占位符", metaObject);
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }

    /**
     * 修改时,自动填充数据
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updateId", 1L, metaObject);
        this.setFieldValByName("updateName", "占位符", metaObject);
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }

    /**
     * 配置druid监控服务器
     *
     * @return 返回监控注册的servlet对象
     */
    @Bean
    public ServletRegistrationBean<Servlet> statViewServlet() {
        ServletRegistrationBean<Servlet> servletRegistrationBean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
        // 添加IP白名单,如果不需要黑名单、白名单的功能,注释掉即可
        servletRegistrationBean.addInitParameter("allow", "127.0.0.1");
        // 添加IP黑名单,当白名单和黑名单重复时,黑名单优先级更高
        servletRegistrationBean.addInitParameter("deny", "127.0.0.1");
        // 添加控制台管理用户
        servletRegistrationBean.addInitParameter("loginUsername", "druid");
        servletRegistrationBean.addInitParameter("loginPassword", "123456");
        // 是否能够重置数据
        servletRegistrationBean.addInitParameter("resetEnable", "false");
        return servletRegistrationBean;
    }

      /**
     * 分页插件。如果你不配置,分页插件将不生效
     */
    @Bean
    public MybatisPlusInterceptor paginationInterceptor()
    {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        CustomPaginationInterceptor paginationInnerInterceptor = new CustomPaginationInterceptor();
        // 设置最大单页限制数量,默认 500 条,-1 不受限制
        paginationInnerInterceptor.setMaxLimit(-1L);
        // 分页合理化
        paginationInnerInterceptor.setOverflow(true);
        interceptor.addInnerInterceptor(paginationInnerInterceptor);
        return interceptor;
    }
}

配置分页查询,当超过页面数时,跳到最后一页:

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;

/**
 * @Description mybatis-plus自定义分页查询
 * @Author LH
 * @Date 2024/2/6 15:31
 **/

public class CustomPaginationInterceptor extends PaginationInnerInterceptor {

    @Override
    protected void handlerOverflow(IPage<?> page) {
        //原来的逻辑是超出范围返回第一页
        //page.setCurrent(1L);

        //修改成返回最后一页
        page.setCurrent(page.getPages());
    }
}

创建系统基类BaseEntity.java,所有的实体类都继承该对象,用于存放系统字段

package com.demo.config.bean;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;


@Data
public class BaseEntity implements Serializable {

    @TableId(type = IdType.AUTO)
    protected Long id;

    @ApiModelProperty("创建人id")
    @TableField(value = "create_id", fill = FieldFill.INSERT)
    private Long createId;

    @ApiModelProperty("创建人名称")
    @TableField(value = "create_name", fill = FieldFill.INSERT)
    private String createName;

    @ApiModelProperty("创建时间")
    @TableField(value = "create_time", fill = FieldFill.INSERT)
    private Date createTime;

    @ApiModelProperty("更新人id")
    @TableField(value = "update_id", fill = FieldFill.INSERT_UPDATE)
    private Long updateId;

    @ApiModelProperty("更新人名称")
    @TableField(value = "update_name", fill = FieldFill.INSERT_UPDATE)
    private String updateName;

    @ApiModelProperty("更新时间")
    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
}

创建代码生成器Generator.java

package com.bwss.aimanagementplatform.generator;

import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.bwss.aimanagementplatform.entity.BaseEntity;

/**
 * @Description 代码生成类
 * @Author LH
 **/
public class Generator
{
    public static void main(String[] args)
    {

        // 设置作者
        String auth = "lh";
        // 设置父包名
        String packageName = "com.bwss.aimanagementplatform";
        // 设置父包模块名
        String moduleName = "";
        // 指定输出目录
        String path = "generator";
        String url = "jdbc:mysql://127.0.0.1:3306/ai?useUnicode=true&useSSL=false&characterEncoding=utf8";
        String username = "root";
        String password = "xxxx";
        // 设置需要生成的表名
        String table = "user";
        // 设置过滤表前缀
        String[] tablePrefix =
        { "usc_", "sys_" };
        generateTest(auth, packageName, path, moduleName, url, username, password, table, tablePrefix);
    }

    private static void generateTest(String auth, String packageName, String path, String moduleName, String url,
            String username, String password, String table, String[] tablePrefix)
    {
        FastAutoGenerator.create(url, username, password).globalConfig(builder -> builder.author(auth)
                // 开启 swagger 模式
                .enableSwagger().outputDir(path))
                .packageConfig(builder -> builder.parent(packageName).moduleName(moduleName)
                // 设置mapperXml生成路径
                //.pathInfo(Collections.singletonMap(OutputFile.xml, path))
                )
                .strategyConfig(builder -> builder.addInclude(table).addTablePrefix(tablePrefix).entityBuilder()
                        .superClass(BaseEntity.class).enableLombok().logicDeleteColumnName("delete_flag")
                        .controllerBuilder().enableRestStyle())
                .execute();
    }
}

运行代码,可以在目录生成对应的表文件代码

阿里巴巴的Druid数据库管理 

浏览器访问http://127.0.0.1:8288/druid/login.html

可以看到sql的执行情况。

4. 后端返回统一结果包装

封装一个统一返回类

public class BwResult<D>
{
	/**
	 * 0 为成功,1为失败
	 */
	public static final String SUCCESS = "0";
	public static final String FAIL = "1";

	private String code;
	private D data;
	private String msg;

	private BwResult(String code)
	{
		this.code = code;
	}

	public static <T> BwResult<T> get(String code)
	{
		return new BwResult<>(code);
	}

	public static <T> BwResult<T> success()
	{
		return new BwResult<T>(SUCCESS).setMsg("操作成功");
	}

	public static <T> BwResult<T> success(T data)
	{
		return new BwResult<T>(SUCCESS).setMsg("操作成功").setData(data);
	}

	public static <T> BwResult<T> success(String msg)
	{
		return new BwResult<T>(SUCCESS).setMsg(msg);
	}

	public static <T> BwResult<T> success(String msg, T data)
	{
		return new BwResult<T>(SUCCESS).setMsg(msg).setData(data);
	}

	public static <T> BwResult<T> fail()
	{
		return new BwResult<T>(FAIL).setMsg("操作失败");
	}

	public static <T> BwResult<T> fail(String msg)
	{
		return new BwResult<T>(FAIL).setMsg(msg);
	}

	public static <T> BwResult<T> fail(String msg, T data)
	{
		return new BwResult<T>(FAIL).setMsg(msg).setData(data);
	}

	public static <T> BwResult<T> fail(T data)
	{
		return new BwResult<T>(FAIL).setMsg("操作失败").setData(data);
	}

	public D getData()
	{
		return this.data;
	}

	public BwResult<D> setData(D data)
	{
		this.data = data;
		return this;
	}

	public String getCode()
	{
		return code;
	}

	public BwResult<D> setCode(String code)
	{
		this.code = code;
		return this;
	}

	public String getMsg()
	{
		return msg;
	}

	public BwResult<D> setMsg(String msg)
	{
		this.msg = msg;
		return this;
	}

	/**
	 * 重载空参for 跨服务调用实例化该类用的构造函数
	 */
	private BwResult()
	{
	}
}

controller调用测试

import com.bwss.aimanagementplatform.entity.BwResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Description 统一返回测试类
 * @Author LH
 **/
@RestController
public class TestResultController
{

    @GetMapping("/testResultJsonSuccess")
    public BwResult<?> testResultJsonSuccess()
    {
        return BwResult.success();
    }

    @GetMapping("/testResultJsonError")
    public BwResult<?> testResultJsonError()
    {
        return BwResult.fail();
    }
}

5. 后端返回统一异常处理 

编写未知异常测试类

import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Description
 * @Author LH
 **/
@RestController
@RequiredArgsConstructor
public class TestErrorResultController {

    /**
     * 模拟系统空指针异常
     */
    @GetMapping("/testResultError")
    public Test testResultError() {
        Test test = null;
        test.setName("抛个空指针");
        return test;
    }

    @Data
    static class Test {
        private String name;
    }
}

浏览器请求处理不了类似的未知异常, 因此对未知异常统一编写一个异常拦截处理类,所有抛出去的未知异常都先经过这个类处理,再返回给前端。

/**
 * @Description 异常信息统一处理
 * @Author LH
 **/
@Slf4j
@RestControllerAdvice
public class ExceptionAdvice
{

    /**
     * 系统异常
     * @param e 异常信息
     * @return R
     */
    @ExceptionHandler(Exception.class)
    @Order(99)
    public BwResult exception(Exception e)
    {
        log.error("系统异常信息 ex={}", e.getMessage(), e);
        return BwResult.fail("未知异常,请联系管理员");
    }

}

前端再次请求测试

上面是处理未知异常的,但是对于代码中可能出现并且需要处理的异常,使用自定义异常类来进行处理

import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * @Description 自定义异常
 * @Author LH
 **/
@EqualsAndHashCode(callSuper = true)
@Data
public class CustomException extends RuntimeException
{

    private final String code = "1";

    private final String errorMsg;

    public CustomException(String errorMsg)
    {
        this.errorMsg = errorMsg;
    }
}

异常拦截类里添加

import com.bwss.aimanagementplatform.entity.BwResult;
import com.bwss.aimanagementplatform.exception.CustomException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * @Description 异常信息统一处理
 * @Author LH
 **/
@Slf4j
@RestControllerAdvice
public class ExceptionAdvice
{

    /**
     * 系统异常
     * @param e 异常信息
     * @return R
     */
    @ExceptionHandler(Exception.class)
    @Order(99)
    public BwResult<?> exception(Exception e)
    {
        log.error("系统异常信息 ex={}", e.getMessage(), e);
        return BwResult.fail("未知异常,请联系管理员");
    }

    /**
     * 自定义异常处理
     * @param e 异常信息
     * @return 返回结果
     */
    @ExceptionHandler(CustomException.class)
    public BwResult<?> mapleBaseException(CustomException e)
    {
        log.error("自定义异常信息 ex={}", e.getMessage(), e);
        return BwResult.fail(e.getErrorMsg());
    }

}

编写测试类

  /**
     * 模拟系统空指针异常
     */
    @GetMapping("/testResultError")
    public Test testResultError() {
        Test test = new Test();
        if (test.getName() == null) {
            throw new CustomException("姓名为空!");
        }
        return test;
    }

前端调用测试

6. SpringBoot集成日志打印Logback 

在resources目录下创建文件logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>

<configuration scan="true" scanPeriod="60 seconds">

    <springProperty scope="context" name="spring.application.name" source="spring.application.name"/>

    <!-- 定义参数 -->
    <property name="log.lever" value="debug"/>
    <property name="log.maxHistory" value="365"/>
    <property name="log.filePath" value="logs"/>
    <property name="log.pattern" value="%-12(%d{MM-dd HH:mm:ss}) %c [%L] | %msg%n"/>

    <!-- 控制台设置 -->
    <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!--格式化输出:%d:表示日期    %thread:表示线程名     %-5level:级别从左显示5个字符宽度  %msg:日志消息    %n:是换行符-->
            <pattern>%boldMagenta%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %yellow(%logger) - %cyan(%msg%n)</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- DEBUG -->
    <appender name="debugAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 文件路径 -->
        <file>${log.filePath}/${spring.application.name}_debug.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 文件名称 -->
            <fileNamePattern>${log.filePath}/debug/${spring.application.name}_debug.%d{yyyy-MM-dd}.log.gz
            </fileNamePattern>
            <!-- 文件最大保存历史数量 -->
            <MaxHistory>${log.maxHistory}</MaxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- INFO -->
    <appender name="infoAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 文件路径 -->
        <file>${log.filePath}/${spring.application.name}_info.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 文件名称 -->
            <fileNamePattern>${log.filePath}/info/${spring.application.name}_info.%d{yyyy-MM-dd}.log.gz
            </fileNamePattern>
            <!-- 文件最大保存历史数量 -->
            <MaxHistory>${log.maxHistory}</MaxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- ERROR -->
    <appender name="errorAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 文件路径 -->
        <file>${log.filePath}/${spring.application.name}_error.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 文件名称 -->
            <fileNamePattern>${log.filePath}/error/${spring.application.name}_error.%d{yyyy-MM-dd}.log.gz
            </fileNamePattern>
            <!-- 文件最大保存历史数量 -->
            <MaxHistory>${log.maxHistory}</MaxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 上线后如果要查看错误日志,可以把level=info改为level=debug -->
    <root level="info">
        <appender-ref ref="consoleAppender"/>
        <appender-ref ref="debugAppender"/>
        <appender-ref ref="infoAppender"/>
        <appender-ref ref="errorAppender"/>
    </root>

</configuration>

启动查看

7. 启动banner图片生成

传送门

8. springboot集成redis

添加redis的pom依赖

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

<!-- 引入redis连接池的依赖 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

配置文件配置

spring:
  redis:
    database: 0
    host: 127.0.0.1
    port: 6379
    password:
    timeout: 5000
    lettuce:
      pool:
        max-active: 32
        max-wait: -1
        max-idle: 16
        min-idle: 8

编写配置类

import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @author LH
 **/
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig {

    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        //使用fastjson序列化
        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
        // value值的序列化采用fastJsonRedisSerializer
        template.setValueSerializer(fastJsonRedisSerializer);
        template.setHashValueSerializer(fastJsonRedisSerializer);
        // key的序列化采用StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

编写redis工具类RedisUtil.java

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Redis常用的一些操作
 *
 * @author LH
 */
@Component
public class RedisUtil {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 写入缓存
     */
    public boolean set(final String key, Object value) {
        boolean result = false;
        try {
            ValueOperations<String, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 写入缓存设置时效时间
     */
    public boolean set(final String key, Object value, Long expireTime) {
        boolean result = false;
        try {
            ValueOperations<String, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 更新缓存
     */
    public boolean getAndSet(final String key, String value) {
        boolean result = false;
        try {
            redisTemplate.opsForValue().getAndSet(key, value);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 批量删除对应的value
     */
    public void remove(final String... keys) {
        for (String key : keys) {
            remove(key);
        }
    }

    /**
     * 批量删除key
     */
    public void removePattern(final String pattern) {
        Set<String> keys = redisTemplate.keys(pattern);
        if (CollectionUtils.isNotEmpty(keys)) {
            redisTemplate.delete(keys);
        }
    }

    /**
     * 删除对应的value
     */
    public void remove(final String key) {
        if (exists(key)) {
            redisTemplate.delete(key);
        }
    }

    /**
     * 判断缓存中是否有对应的value
     */
    public boolean exists(final String key) {
        Boolean isExists = redisTemplate.hasKey(key);
        return BooleanUtils.isTrue(isExists);
    }

    /**
     * 读取缓存
     */
    public Object get(final String key) {
        ValueOperations<String, Object> operations = redisTemplate.opsForValue();
        return operations.get(key);
    }

    /**
     * 哈希 添加
     */
    public void hmSet(String key, Object hashKey, Object value) {
        HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
        hash.put(key, hashKey, value);
    }

    /**
     * 哈希获取数据
     */
    public Object hmGet(String key, Object hashKey) {
        HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
        return hash.get(key, hashKey);
    }

    /**
     * 列表添加
     */
    public void lPush(String k, Object v) {
        ListOperations<String, Object> list = redisTemplate.opsForList();
        list.rightPush(k, v);
    }

    /**
     * 列表获取
     */
    public List<Object> lRange(String k, long l, long l1) {
        ListOperations<String, Object> list = redisTemplate.opsForList();
        return list.range(k, l, l1);
    }

    /**
     * 集合添加
     */
    public void addSet(String key, Object value) {
        SetOperations<String, Object> set = redisTemplate.opsForSet();
        set.add(key, value);
    }

    /**
     * 删除集合下的所有值
     */
    public void removeSetAll(String key) {
        SetOperations<String, Object> set = redisTemplate.opsForSet();
        Set<Object> objectSet = set.members(key);
        if (objectSet != null && !objectSet.isEmpty()) {
            for (Object o : objectSet) {
                set.remove(key, o);
            }
        }
    }

    /**
     * 判断set集合里面是否包含某个元素
     */
    public Boolean isMember(String key, Object member) {
        SetOperations<String, Object> set = redisTemplate.opsForSet();
        return set.isMember(key, member);
    }

    /**
     * 集合获取
     */
    public Set<Object> setMembers(String key) {
        SetOperations<String, Object> set = redisTemplate.opsForSet();
        return set.members(key);
    }

    /**
     * 有序集合添加
     */
    public void zAdd(String key, Object value, double source) {
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        zset.add(key, value, source);
    }

    /**
     * 有序集合获取指定范围的数据
     */
    public Set<Object> rangeByScore(String key, double source, double source1) {
        ZSetOperations<String, Object> zSet = redisTemplate.opsForZSet();
        return zSet.rangeByScore(key, source, source1);
    }

    /**
     * 有序集合升序获取
     */
    public Set<Object> range(String key, Long source, Long source1) {
        ZSetOperations<String, Object> zSet = redisTemplate.opsForZSet();
        return zSet.range(key, source, source1);
    }

    /**
     * 有序集合降序获取
     */
    public Set<Object> reverseRange(String key, Long source, Long source1) {
        ZSetOperations<String, Object> zSet = redisTemplate.opsForZSet();
        return zSet.reverseRange(key, source, source1);
    }
}

redis Key过期处理事件

修改redis的配置文件

找到notify-keyspace-events

看一下notify-keyspace-events Ex是否被注释(默认是注释),放开注释即可。

修改RedisConfig.java

import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @Description redis配置
 * @Author LH
 **/
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig
{

    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
    {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        //使用fastjson序列化
        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
        // value值的序列化采用fastJsonRedisSerializer
        template.setValueSerializer(fastJsonRedisSerializer);
        template.setHashValueSerializer(fastJsonRedisSerializer);
        // key的序列化采用StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
    {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    /**
     * 开启监听redis Key过期事件
     */
    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory)
    {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        return container;
    }
}

 定义过期监听器RedisKeyExpireListener

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;

/**
 * @Description redis过期监听器
 * @Author LH
 **/
@Slf4j
public class RedisKeyExpireListener extends KeyExpirationEventMessageListener
{

    public RedisKeyExpireListener(RedisMessageListenerContainer listenerContainer)
    {
        super(listenerContainer);
    }

    @Override
    public void onMessage(Message message, byte[] pattern)
    {
        String expireKey = message.toString();
        // 根据过期的key处理对应的业务逻辑
        log.info(expireKey + "已过期-------------------");
    }
}

9. springBoot集成用户登录功能

首先知道什么是JWT

简单来讲,就是一种认证机制,让后台知道该请求是来自于受信任的客户端。

优点不讲,讲下缺点

  • 安全性没法保证:jwt的payload并没有加密,只是用Base64编码而已,不能存储敏感数据。
  • 无法中途废弃:一旦签发了一个jwt,在到期之前始终都是有效的,如果用户信息发生更新了,只能等旧的jwt过期后重新签发新的jwt。
  • 续签问题:当签发的jwt保存在客户端,客户端一直在操作页面,按道理应该一直为客户端续长有效时间,否则当jwt有效期到了就会导致用户需要重新登录。

缺陷应对

  • 在使用的过程中,只储存常用的无敏感数据,比如用户ID,用户角色等。
  • 通过和redis配合使用,将token返回时,同步保存redis,通过控制token在redis的有效期来进行控制。
  • 通过统计redis有效数据,对在线用户进行统计或强制下线等操作。

用户名密码登录

基于Token的身份验证流程 

1、客户端使用用户名或密码请求登录;

2、服务端收到请求,去验证用户名与密码;

3、验证成功后,服务端会使用JWT签发一个Token,保存到Redis中,同时再把这个Token发送给客户端;

4、客户端收到Token以后可以把它存储起来,比如放在Cookie里或者Local Storage里;

5、客户端每次向服务端请求资源的时候需要在请求Header里面带着服务端签发的Token;

6、服务端收到请求,然后去验证客户端请求里面带着的Token,如果验证成功,就向客户端返回请求的数据。验证失败,返回失败原因。

引入pom依赖

<!-- 引入JWT相关 -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.18.3</version>
</dependency>

创建全局常量类GlobalConfig.java

/**
 * @author LH
 */
public class GlobalConfig {

    private GlobalConfig() {

    }

    /**
     * 用户储存在redis中的过期时间 12 小时
     */
    public static final long EXPIRE_TIME = 60 * 60 * 12L;

    /**
     * 生成token的私钥
     */
    public static final String SECRET = "xxxx";
    
    /**
     * 前端传递token的header名称
     */
    public static final String TOKEN_NAME = "Authorization";

    /**
     * 用户登录token保存在redis的key值
     *
     * @param account 用户登录帐号
     * @return token保存在redis的key
     */
    public static String getRedisUserKey(String account) {
        return "ADMIN:" + account;
    }
}

创建保存的jwt的类TokenBean.java

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.RequiredArgsConstructor;

/**
 * @author LH
 */
@Data
@Builder
@RequiredArgsConstructor
@AllArgsConstructor
public class TokenBean {
    /**
     * 用户ID
     */
    private Long userId;

    /**
     * 用户账号
     */
    private String account;

    /**
     * 用户类型
     */
    private String userType;
}

创建JwtUtil.java工具类

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.bwss.aimanagementplatform.config.GlobalConfig;
import com.bwss.aimanagementplatform.entity.TokenBean;
import com.bwss.aimanagementplatform.exception.CustomException;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Objects;

/**
 * @Description Jwt常用操作
 * @Author LH
 **/
public class JwtUtil
{
    private static final String ACCOUNT = "account";
    private static final String USER_ID = "userId";
    private static final String USER_TYPE = "userType";

    /**
     * 校验token是否正确
     *
     * @param token 密钥
     * @return 是否正确
     */
    public static boolean verify(String token, String account)
    {
        try
        {
            Algorithm algorithm = Algorithm.HMAC256(GlobalConfig.SECRET);
            JWTVerifier verifier = JWT.require(algorithm).withClaim(ACCOUNT, account).build();
            verifier.verify(token);
            return true;
        } catch (Exception exception)
        {
            return false;
        }
    }

    /**
     * 获得token中的信息无需secret解密也能获得
     *
     * @return token中包含的用户登录帐号
     */
    public static String getAccount()
    {
        try
        {
            DecodedJWT jwt = getJwt();
            if (jwt == null)
            {
                return null;
            }
            return jwt.getClaim(ACCOUNT).asString();
        } catch (JWTDecodeException e)
        {
            return null;
        }
    }

    /**
     * 获得token中的信息无需secret解密也能获得
     *
     * @return token中包含的用户登录帐号
     */
    public static String getAccount(String token)
    {
        try
        {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim(ACCOUNT).asString();
        } catch (JWTDecodeException e)
        {
            return null;
        }
    }

    public static Long getUserId()
    {
        try
        {
            DecodedJWT jwt = getJwt();
            if (jwt == null)
            {
                return null;
            }
            return jwt.getClaim(USER_ID).asLong();
        } catch (JWTDecodeException e)
        {
            return null;
        }
    }

    /**
     * 获得token中的信息无需secret解密也能获得
     *
     * @return token中包含的用户ID
     */
    public static Long getUserId(String token)
    {
        try
        {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim(USER_ID).asLong();
        } catch (JWTDecodeException e)
        {
            return null;
        }
    }

    public static TokenBean getTokenMsg()
    {
        TokenBean tokenBean = new TokenBean();
        try
        {
            DecodedJWT jwt = getJwt();
            if (jwt == null)
            {
                return tokenBean;
            }
            tokenBean.setUserId(jwt.getClaim(USER_ID).asLong());
            tokenBean.setAccount(jwt.getClaim(ACCOUNT).asString());
            return tokenBean;

        } catch (JWTDecodeException e)
        {
            return tokenBean;
        }
    }

    private static DecodedJWT getJwt()
    {
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
        if (Objects.isNull(servletRequestAttributes))
        {
            throw new CustomException("请求参数有误,请重试");
        }
        HttpServletRequest request = servletRequestAttributes.getRequest();

        String authorization = request.getHeader(GlobalConfig.TOKEN_NAME);
        if (authorization == null)
        {
            return null;
        }
        return JWT.decode(authorization);
    }

    /**
     * 校验token是否有效
     *
     * @param token token信息
     * @return 返回结果
     */
    public static boolean verifyToken(String token)
    {
        try
        {
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(GlobalConfig.SECRET)).build();
            DecodedJWT jwt = verifier.verify(token);
            jwt.getClaims();
            return true;
        } catch (Exception e)
        {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 创建token
     *
     * @param tokenBean token保存的信息
     * @return token
     */
    public static String createToken(TokenBean tokenBean)
    {
        Algorithm algorithm = Algorithm.HMAC256(GlobalConfig.SECRET);
        return JWT.create().withClaim(USER_ID, tokenBean.getUserId()).withClaim(ACCOUNT, tokenBean.getAccount())
                .withClaim(USER_TYPE, tokenBean.getUserType()).sign(algorithm);
    }
}

创建异常枚举类ErrorCode.java

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * @Description
 * @Author LH
 **/
@AllArgsConstructor
@Getter
public enum ErrorCode {
    COMMON_ERROR("9998", "未知异常,请联系管理员"),
    NO_TOKEN("1001", "用户未登录"),
    TOKEN_EXPIRE("1002", "登陆超时,请重新登录"),
    TOKEN_EXCHANGE("1003", "账号在其他地方登录,账号被踢出"),
    USER_LOGIN_ERROR("2001", "用户名或密码错误"),
    USER_STATUS_ERROR("2002", "用户已被停用,请联系管理员");

    private final String code;

    private final String msg;
}

Application.java启动项上添加注解@ServletComponentScan

SpringBootApplication 上使用@ServletComponentScan 注解后

Servlet可以直接通过@WebServlet注解自动注册

Filter可以直接通过@WebFilter注解自动注册

Listener可以直接通过@WebListener 注解自动注册
创建登录拦截类
JwtFilter

import com.alibaba.fastjson.JSON;
import com.bwss.aimanagementplatform.config.GlobalConfig;
import com.bwss.aimanagementplatform.entity.BwResult;
import com.bwss.aimanagementplatform.enums.ErrorCode;
import com.bwss.aimanagementplatform.util.JwtUtil;
import com.bwss.aimanagementplatform.util.RedisUtil;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;

/**
 * @Description 判断用户登录token
 * @Author LH
 **/
@WebFilter(filterName = "jwtFilter", urlPatterns =
{ "/*" })
@AllArgsConstructor
@Order(1)
public class JwtFilter implements Filter
{

    private final List<String> excludedUrlList;

    @Override
    public void init(FilterConfig filterConfig)
    {
        excludedUrlList.addAll(Arrays.asList("/sso/login", "/sso/logout", "/example/*", "/webjars/**", "/swagger/**",
                "/v2/api-docs", "/doc.html", "/swagger-ui.html", "/swagger-resources/**", "/swagger-resources"));
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException
    {
        String url = ((HttpServletRequest) request).getRequestURI();
        boolean isMatch = false;
        for (String excludedUrl : excludedUrlList)
        {
            if (Pattern.matches(excludedUrl.replace("*", ".*"), url))
            {
                isMatch = true;
                break;
            }
        }
        if (isMatch)
        {
            chain.doFilter(request, response);
        } else
        {
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            //处理跨域问题,跨域的请求首先会发一个options类型的请求
            if (httpServletRequest.getMethod().equals(HttpMethod.OPTIONS.name()))
            {
                chain.doFilter(request, response);
            }
            BeanFactory factory = WebApplicationContextUtils
                    .getRequiredWebApplicationContext(request.getServletContext());
            RedisUtil redisService = (RedisUtil) factory.getBean("redisUtil");
            String account;
            String authorization = httpServletRequest.getHeader(GlobalConfig.TOKEN_NAME);
            // 判断token是否存在,不存在代表未登录
            if (StringUtils.isEmpty(authorization))
            {
                writeRsp(httpServletResponse, ErrorCode.NO_TOKEN);
                return;
            } else
            {
                account = JwtUtil.getAccount(authorization);
                String token = (String) redisService.get(GlobalConfig.getRedisUserKey(account));
                // 判断token是否存在,不存在代表登陆超时
                if (StringUtils.isEmpty(token))
                {
                    writeRsp(httpServletResponse, ErrorCode.TOKEN_EXPIRE);
                    return;
                } else
                {
                    // 判断token是否相等,不相等代表在其他地方登录
                    if (!token.equalsIgnoreCase(authorization))
                    {
                        writeRsp(httpServletResponse, ErrorCode.TOKEN_EXCHANGE);
                        return;
                    }
                }
            }
            // 保存redis,每次调用成功都刷新过期时间
            redisService.set(GlobalConfig.getRedisUserKey(account), authorization, GlobalConfig.EXPIRE_TIME);
            chain.doFilter(httpServletRequest, httpServletResponse);
        }
    }

    @Override
    public void destroy()
    {
        Filter.super.destroy();
    }

    private void writeRsp(HttpServletResponse response, ErrorCode errorCode)
    {
        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        response.setHeader("content-type", "application/json;charset=UTF-8");
        try
        {
            response.getWriter().println(JSON.toJSON(BwResult.fail(errorCode.getCode(), errorCode.getMsg())));
        } catch (IOException e)
        {
            e.printStackTrace();
        }
    }
}

 创建md5加密类

import com.bwss.aimanagementplatform.config.GlobalConfig;
import com.bwss.aimanagementplatform.exception.CustomException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;

/**
 * @Description MD5撒盐加密 及MD5加密
 * @Author LH
 **/
@Slf4j
public class Md5Util
{
    private Md5Util()
    {

    }

    /**
     * 密码加密处理
     *
     * @param password 密码明文
     * @param salt     盐
     * @return 加密后密文
     */
    public static String encrypt(String password, String salt)
    {
        if (StringUtils.isEmpty(password) || StringUtils.isEmpty(salt))
        {
            log.error("密码加密失败原因: password and salt cannot be empty");
            throw new CustomException("请求参数有误,请重试");
        }
        return DigestUtils.md5DigestAsHex((salt + password).getBytes());
    }

    /**
     * 校验密码
     *
     * @param target 待校验密码
     * @param source 原密码
     * @param salt   加密原密码的盐
     */
    public static boolean verifyPassword(String target, String source, String salt)
    {
        if (StringUtils.isEmpty(target) || StringUtils.isEmpty(source) || StringUtils.isEmpty(salt))
        {
            log.info("校验密码失败,原因 target ={}, source ={}, salt ={}", target, source, salt);
            return false;
        }
        String targetEncryptPwd = encrypt(target, salt);
        return targetEncryptPwd.equals(source);
    }

    public static void main(String[] args)
    {
        log.info(encrypt("密码", GlobalConfig.SECRET));
    }
}

用户登录相关

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * @Description 登录请求对象
 * @Author LH
 **/
@Data
@ApiModel(value = "用户登录请求对象", description = "用户中心-用户登录请求对象")
public class LoginQuery
{
    @ApiModelProperty(value = "登录账号")
    private String account;

    @ApiModelProperty(value = "登录密码")
    private String password;
}

返回的用户信息对象UserModel.java

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Builder;
import lombok.RequiredArgsConstructor;

/**
 * @Description 用户信息VO
 * @Author LH
 **/
@Data
@Builder
@RequiredArgsConstructor
@AllArgsConstructor
@ApiModel(value = "用户视图对象", description = "用户中心-用户信息")
public class UserVo
{

    @ApiModelProperty(value = "用户ID")
    private Long id;

    @ApiModelProperty(value = "用户账号")
    private String account;

    @ApiModelProperty(value = "用户姓名")
    private String userName;

    @ApiModelProperty(value = "用户类型")
    private String userType;

    @ApiModelProperty(value = "用户邮箱")
    private String email;

    @ApiModelProperty(value = "手机号码")
    private String phone;

    @ApiModelProperty(value = "用户性别")
    private String sex;

    @ApiModelProperty(value = "头像地址")
    private String avatar;

    @ApiModelProperty(value = "帐号状态")
    private String status;

    @ApiModelProperty(value = "备注")
    private String remark;

    @ApiModelProperty(value = "用户验证Token")
    private String token;
}

controller

import com.bwss.aimanagementplatform.entity.vo.LoginQuery;
import com.bwss.aimanagementplatform.entity.vo.UserVo;
import com.bwss.aimanagementplatform.service.IBwUserService;
import com.bwss.aimanagementplatform.util.JwtUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * @Description 系统登录Controller
 * @Author LH
 **/
@Api(tags = "管理系统-系统登录操作")
@RestController
@AllArgsConstructor
@RequestMapping(value = "/sso")
public class LoginController
{

    @Autowired
    private IBwUserService iBwUserService;

    @ApiOperation(value = "用户登录")
    @PostMapping("/login")
    public UserVo login(@RequestBody LoginQuery req)
    {
        return iBwUserService.login(req);
    }

    @ApiOperation(value = "用户退出登录")
    @GetMapping("/logout")
    public void logout()
    {
        iBwUserService.logout();
    }

    @ApiOperation(value = "获取登录用户信息")
    @GetMapping("/getUserId")
    public String getUserId()
    {
        return "当前登录用户的ID为" + JwtUtil.getUserId();
    }
}

service

import com.bwss.aimanagementplatform.entity.BwUser;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bwss.aimanagementplatform.entity.vo.LoginQuery;
import com.bwss.aimanagementplatform.entity.vo.UserVo;

/**
 * 用户信息表 服务类
 * @author lh
 */
public interface IBwUserService extends IService<BwUser>
{
    /**
     * 用户登录
     *
     * @param req 用户信息
     * @return 用户登录信息
     */
    UserVo login(LoginQuery req);

    /**
     * 退出系统,清除用户token
     */
    void logout();
}

serviceImpl

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.bwss.aimanagementplatform.config.GlobalConfig;
import com.bwss.aimanagementplatform.entity.BwUser;
import com.bwss.aimanagementplatform.entity.TokenBean;
import com.bwss.aimanagementplatform.entity.vo.LoginQuery;
import com.bwss.aimanagementplatform.entity.vo.UserVo;
import com.bwss.aimanagementplatform.enums.ErrorCode;
import com.bwss.aimanagementplatform.exception.CustomException;
import com.bwss.aimanagementplatform.mapper.BwUserMapper;
import com.bwss.aimanagementplatform.service.IBwUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bwss.aimanagementplatform.util.JwtUtil;
import com.bwss.aimanagementplatform.util.Md5Util;
import com.bwss.aimanagementplatform.util.RedisUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Objects;

/**
 * 用户信息表 服务实现类
 * @author lh
 */
@Service
public class BwUserServiceImpl extends ServiceImpl<BwUserMapper, BwUser> implements IBwUserService
{
    @Autowired
    private BwUserMapper bwUserMapper;

    @Autowired
    private RedisUtil redisUtil;

    @Override
    public UserVo login(LoginQuery req)
    {
        BwUser user = bwUserMapper
                .selectOne(Wrappers.lambdaQuery(BwUser.class).eq(BwUser::getAccount, req.getAccount()).last("LIMIT 1"));
        if (Objects.isNull(user))
        {
            throw new CustomException(ErrorCode.USER_LOGIN_ERROR.getMsg());
        }
        if ("1".equals(user.getStatus()))
        {
            throw new CustomException(ErrorCode.USER_STATUS_ERROR.getMsg());
        }
        if (!Md5Util.verifyPassword(req.getPassword(), user.getPassword(), user.getSalt()))
        {
            throw new CustomException(ErrorCode.USER_LOGIN_ERROR.getMsg());
        }

        TokenBean tokenBean = TokenBean.builder().userId(user.getId()).userType(user.getUserType())
                .account(user.getUserName()).build();

        UserVo userVo = new UserVo();
        BeanUtils.copyProperties(user, userVo);
        String token;
        try
        {
            token = JwtUtil.createToken(tokenBean);
        } catch (Exception e)
        {
            log.error(e.getMessage());
            throw new CustomException(ErrorCode.COMMON_ERROR.getMsg());
        }
        userVo.setToken(token);
        redisUtil.set(GlobalConfig.getRedisUserKey(user.getAccount()), token);
        return userVo;
    }

    @Override
    public void logout()
    {
        redisUtil.remove(GlobalConfig.getRedisUserKey(JwtUtil.getAccount()));
    }
}

 到此,用户登录功能实现。

10. springBoot处理请求跨域问题

import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Description 跨域拦截
 * @Author LH
 **/
@WebFilter(filterName = "corsFilter", urlPatterns = "/*")
@Order(0)
public class CorsFilter implements Filter
{

    private static final String HEADER_ORIGIN = "Origin";
    private static final String METHOD_OPTIONS = "OPTIONS";

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        final HttpServletRequest request = (HttpServletRequest) req;
        final HttpServletResponse response = (HttpServletResponse) res;
        if (request.getHeader(HEADER_ORIGIN) != null) {
            response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, request.getHeader(HEADER_ORIGIN));
        }

        response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
        // 如果允许所有请求方式,用*
        response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, PUT, DELETE, OPTIONS");
        response.setHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "3600");
        // 如果允许所有header,用*
        response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Authorization, Content-Type, Accept, X-Requested-With, remember-me");
        response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "Content-Disposition");

        if (METHOD_OPTIONS.equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
            return;
        }
        chain.doFilter(req, res);
    }
}

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您好!要搭建Spring Boot脚手架,您可以按照以下步骤进行操作: 1. 首先,确保您已经安装了Java和Maven。您可以在命令行窗口中运行`java -version`和`mvn -v`命令来验证它们的安装情况。 2. 接下来,您可以选择使用Spring Initializr来生成一个新的Spring Boot项目。您可以访问https://start.spring.io/ 并填写所需的项目元数据,例如项目名称、包名、依赖等。然后点击"Generate"按钮来下载生成的项目文件。 3. 解压下载的项目文件,并使用您喜欢的集成开发环境(IDE)打开它。例如,您可以使用IntelliJ IDEA或Eclipse。 4. 在IDE中,导入解压后的项目文件。具体操作取决于您使用的IDE。在IntelliJ IDEA中,您可以选择"Import Project",然后选择项目文件夹并点击"Open"。 5. 一旦项目导入成功,您可以开始编写代码了。在src/main/java目录下,您可以找到一个自动生成的主应用程序类(通常以`Application`结尾)。您可以在这个类中编写您的业务逻辑和处理程序。 6. 您还可以在src/main/resources目录下找到应用程序的配置文件。您可以使用它来配置数据库连接、端口号等。 7. 当您完成代码编写后,您可以使用Maven构建项目。在命令行窗口中,导航到项目根目录并运行`mvn clean install`命令。这将编译代码并生成可执行的JAR文件。 8. 最后,您可以在命令行窗口中运行`java -jar target/your-project-name.jar`命令来启动应用程序。请确保将"your-project-name"替换为您的实际项目名称。 这样,您就成功搭建了一个Spring Boot脚手架。您可以根据您的需求进行进一步的开发和定制化。祝您好运!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值