快速构建一个简单的Springboot-web项目
阅读原文时间:2023年07月08日阅读:5

web项目基本的核心成分

  • 数据落地 MYSQL数据库
  • 登录标识 JWT :{Java web token }
  • 记录有效登录状态 以及缓存常用数据: Redis
  • 数据库与JAVA实体的快速自动映射ORM:mybatis
  • 数据库连接池化技术:Druid
  • 视图解析器模板引擎:Thymeleaf
  • 基于接口测试和接口文档工具:Swagger
  • 单元测试:Junit
  • 实体类简化工具:lombok
  • SpringbootTest:

项目码云地址:https://gitee.com/gtnotgod/springboot-demo-idea.git

demo示例数据库的表 结构 自己按需更改

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL,
  `gender` varchar(5) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `address` varchar(32) DEFAULT NULL,
  `qq` varchar(20) DEFAULT NULL,
  `email` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

POM文件如下:

SpringBoot主版本:2.5.1

<?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>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.gton</groupId>
    <artifactId>hander</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!--    JDK-版本:编码设置-->
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <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-web</artifactId>
        </dependency>
        <!--Mybatis ORM-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
        <!--德鲁伊数据库连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.6</version>
        </dependency>
        <!--mysql 驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <!-- thymeleaf 模板引擎-视图解析器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <!--单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!--Springboot Test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <!--lombok-->
        <!--        lombok-实体类简化依赖-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>
        <!--     swagger2 接口API文档 接口测试      -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!--JWT登录认证-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.11.0</version>
        </dependency>
        <!--   redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    </dependencies>
    <build>
        <!--xml-mapper 资源过滤-->
        <resources>
            <resource>
                <directory>src/main/resource</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>
</project>

YML核心配置文件内容如下

server:
  port: 8888
spring:
  application:
    name: springboot-app
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/study?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
    username: root
    password: root
    druid:
      test-while-idle: false
    #Springboot 整合redis数据库
  redis:
    host: 127.0.0.1
    port: 6379
    password: 123456
    jedis:
      pool:
        max-active: 8
        max-wait: -1
        max-idle: 500
        min-idle: 0
    lettuce:
      shutdown-timeout: 0
  thymeleaf:
    cache: false #关闭缓存,即使刷新 默认 true,关闭之后可以及时刷新页面
    mode: HTML5 #默认 HTML5
    encoding: UTF-8 # 默认  UTF-8
    prefix: classpath:/templates/ #默认 classpath:/templates/
    suffix: .html # 默认  .html
  mvc:
    static-path-pattern: classpath:/static/**
mybatis:
  config-location: classpath:maybati-config.xml
  mapper-locations: classpath:mapper/*.xml

YML外部引用了Mybatis-config配置文件和指定了Mapper扫描路径;

Mybatis-config:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--开启二级缓存-->
    <settings>
        <!--开启二级缓存-->
        <setting name="cacheEnabled" value="true"/>
        <!--配置日志输出-->
        <setting name="logImpl" value="STDOUT_LOGGING" />
        <!--开启驼峰字段自动转化-->
        <setting name="mapUnderscoreToCamelCase" value="true" />
    </settings>
    <!--配置别名-->
    <typeAliases>
        <package name="com.entity"/>
    </typeAliases>
</configuration>

启动器

@SpringBootApplication
@EnableTransactionManagement //1.开始事物声明式注解处理
public class ApplicationRun {
    public static void main(String[] args) {
        SpringApplication.run(ApplicationRun.class, args);
    }
}

项目目录结构

使用SWagger,需要配置

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .pathMapping("/")
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.controller"))
                .paths(PathSelectors.any())
                .build().apiInfo(new ApiInfoBuilder()
                        .title("SpringBoot整合Swagger接口测试")
                        .description("SpringBoot整合Swagger,详细信息......")
                        .version("1.0")
                        //new Contact("昵称", "网址链接", "邮箱"))
                        .contact(new Contact("隔壁老郭", "https://www.cnblogs.com/gtnotgod/", "1054769749@qq.com"))
                        .license("The Apache License")
                        .licenseUrl("http://www.baidu.com")
                        .build());
    }
}

Swagger配置还没完,还要开启静态资源过滤

@Configuration
public class webMvcConfig implements WebMvcConfigurer {

    /**
     * Description: 静态资源过滤
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //ClassPath:/Static/** 静态资源释放
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
        //释放swagger
        registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
        //释放webjars
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
}

Controller:

@RestController
@Slf4j
@Api(value = "SpringBoot-CRUD-Demo", tags = "基于SpringBoot的增删改查示例Controller")
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * Description: http://localhost:8888/getList
     */
    @GetMapping(value = "/getList", name = "全部获取数据")
    @ApiOperation("全部获取数据-接口")
    public RespObject getListByNotLimit() {
        List<UserTable> users = userService.getUsers();
        return RespObject.respOk(users);
    }

    /**
     * Description: http://localhost:8888/getListById?id=1001
     */
    @GetMapping(value = "/getListById", name = "根据ID获取数据")
    @ApiOperation("根据ID获取数据-接口")
    public RespObject getUserTable(@RequestParam("id") int id) {
        UserTable user = userService.getUserById(id);
        if (user == null) {
            return RespObject.respNo("输入的ID无效");
        }
        return RespObject.respOk(user);
    }

    /**
     * Description:  http://localhost:8888/getUsersByLimit?currentPage=1&pageSize=10
     * select * from table limit (start-1)*pageSize,pageSize;
     * mysql 分页第一个参数是index{0-max},第二个参数是取的数量
     */
    @GetMapping(value = "/getUsersByLimit", name = "分页查询")
    @ApiOperation("分页查询-接口")
    public RespObject getUserTableByLimit(@RequestParam("currentPage") int currentPage, @RequestParam("pageSize") int pageSize) {
        log.info("每一页分:" + pageSize + "条" + ";请求的第:" + currentPage + "页");
        if (currentPage == 0) {
            return RespObject.respNo("首页坐标是从1开始");
        }
        List<UserTable> limitUsers = userService.getUserByLimit((currentPage - 1) * pageSize, pageSize);
        return RespObject.respOk(limitUsers);
    }

    /**
     * Description: 添加操作
     */
    @PostMapping(value = "/addUserTable", name = "添加表数据")
    @ApiOperation("添加表数据-接口")
    public RespObject addUserTableData(@RequestBody UserTable userTable) {
        System.out.println(userTable);
        int id = userTable.getId();
        //判断ID是否存在-{isPresent存在就返回true}
        if (id != 0) {
            return RespObject.respNo("添加操作不允许传递主键");
        }
        int rowChange = 0;
        try {
            rowChange = userService.insertInToUserTable(userTable);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return rowChange > 0 ? RespObject.respOk() : RespObject.respNo("添加失败");
    }

    /**
     * Description: 删除操作
     */
    @DeleteMapping(value = "/delUserTableById/{id}", name = "删除表数据")
    @ApiOperation("删除表数据-接口")
    public RespObject addUserTableData(@PathVariable("id") int id) {
        int rowChange = 0;
        try {
            rowChange = userService.deleteusertablebyid(id);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return rowChange > 0 ? RespObject.respOk() : RespObject.respNo("该ID无效-删除失败");
    }

    /**
     * Description: 修改操作
     */
    @PutMapping(value = "/updateUserTableById", name = "修改表数据")
    @ApiOperation("修改表数据-接口")
    public RespObject updateUserTableData(@RequestBody UserTable userTable) {
        int id = userTable.getId();
        //判断-{isPresent存在就返回true}
        if (id == 0) {
            return RespObject.respNo("修改操作必须传递主键");
        }
        int rowChange = 0;
        try {
            rowChange = userService.updateUserTableById(userTable);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return rowChange > 0 ? RespObject.respOk() : RespObject.respNo("修改失败");
    }
}

Service

public interface UserService {
    /**
     * Description: 无条件查询
     */
    List<UserTable> getUsers();

    /**
     * Description:条件查询
     */
    UserTable getUserById(int userId);

    /**
     * Description:分页查询
     */
    List<UserTable> getUserByLimit(int startIndex, int everyPageSize);

    /**
     * Description: 新增 返回的结果是影响行数
     */
    int insertInToUserTable(UserTable tableObj);

    /**
     * Description: 修改
     */
    int updateUserTableById(UserTable tableObj);

    /**
     * Description:删除
     */
    int deleteusertablebyid(int id);

    /**
    * Description:登录
    */
    UserTable queryForEntity(LoginUser user);

    /**
    * Description: 根据Email查询
    */
    UserTable getbyUserEmail(String userEmail);
}

ServiceImpl

@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;

    @Override
    public List<UserTable> getUsers() {

        return userMapper.getUsers();
    }

    @Override
    public UserTable getUserById(int userId) {
        return userMapper.getUserById(userId);
    }

    @Override
    public List<UserTable> getUserByLimit(int startIndex, int everyPageSize) {
        return userMapper.getUserByLimit(startIndex, everyPageSize);
    }

    @Override
    public int insertInToUserTable(UserTable tableObj) {
        return userMapper.insertInToUserTable(tableObj);
    }

    @Override
    public int updateUserTableById(UserTable tableObj) {
        return userMapper.updateUserTableById(tableObj);
    }

    @Override
    public int deleteusertablebyid(int id) {
        return userMapper.deleteusertablebyid(id);
    }

    @Override
    public UserTable queryForEntity(LoginUser user) {
        String username = user.getUsername();
        String password = user.getPassword();
        return userMapper.selectByUserNameAndPassword(username, password);
    }

    @Override
    public UserTable getbyUserEmail(String userEmail) {

        return userMapper.selectByEmail(userEmail);
    }
}

Mapper Interface

@Mapper
public interface UserMapper {

    /**
     * Description: 无条件查询
     */
    List<UserTable> getUsers();

    /**
     * Description:条件查询
     */
    UserTable getUserById(@Param("userId") int userId);

    /**
     * Description:分页查询
     */
    List<UserTable> getUserByLimit(@Param("startIndex") int startIndex, @Param("everyPageSize") int everyPageSize);

    /**
     * Description: 新增 返回的结果是影响行数
     */
    int insertInToUserTable(UserTable tableObj);

    /**
     * Description: 修改
     */
    int updateUserTableById(UserTable tableObj);

    /**
     * Description:删除
     */
    int deleteusertablebyid(@Param("id") int id);

    UserTable selectByUserNameAndPassword(@Param("username") String username, @Param("password") String password);

    UserTable selectByEmail(@Param("userEmail") String userEmail);
}

Mapper.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="com.mapper.UserMapper">

    <sql id="tableFields">
        id,name ,gender,age,address,qq,email
    </sql>

    <insert id="insertInToUserTable" parameterType="userTable" useGeneratedKeys="true" keyProperty="id">
        insert into user (name ,gender,age,address,qq,email) values (#{name},#{gender},#{age},#{address},#{qq},#{email})
    </insert>

    <update id="updateUserTableById">
        update user
        <set>
            <if test="name!=null">
                name=#{name},
            </if>
            <if test="gender!=null">
                gender=#{gender},
            </if>
            <if test="age!=null">
                age=#{age},
            </if>
            <if test="address!=null">
                address=#{address},
            </if>
            <if test="qq!=null">
                qq=#{qq},
            </if>
            <if test="email!=null">
                email=#{email},
            </if>
        </set>
        where id=#{id}
    </update>

    <delete id="deleteusertablebyid">
        delete from user where id=#{id}
    </delete>

    <!--全部查询-->
    <select id="getUsers" resultType="userTable">
        select
        <include refid="tableFields"/>
        from user;
    </select>
    <!--条件查询-->
    <select id="getUserById" resultType="userTable">
          select * from user where id=#{userId}
    </select>
    <!--分页查询-->
    <select id="getUserByLimit" resultType="com.entity.UserTable">
        select * from user limit #{startIndex},#{everyPageSize}
    </select>

    <select id="selectByUserNameAndPassword" resultType="com.entity.UserTable">
           select * from user where name=#{username} and address =#{password}

    </select>

    <select id="selectByEmail" resultType="com.entity.UserTable">
            select * from user where email =#{userEmail}
    </select>

</mapper>

整合JWT

自定义需要登录注解:不需要登录注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedTokenByJWT {
    boolean required() default true;
}

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SkipTokenByJWT {
    boolean required() default true;
}

JWT拦截器配合Redis记录状态,控制登录访问

public class AuthenticationInterceptor implements HandlerInterceptor {
    /**
     * Description: HandlerInterceptor接口主要定义了三个方法
     * 1.boolean preHandle ():
     * 预处理回调方法,实现处理器的预处理,第三个参数为响应的处理器,自定义Controller,返回值为true表示继续流程(如调用下一个拦截器或处理器)或者接着执行
     * postHandle()和afterCompletion();false表示流程中断,不会继续调用其他的拦截器或处理器,中断执行。
     * <p>
     * 2.void postHandle():
     * 后处理回调方法,实现处理器的后处理(DispatcherServlet进行视图返回渲染之前进行调用),此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null。
     * <p>
     * 3.void afterCompletion():
     * 整个请求处理完毕回调方法,该方法也是需要当前对应的Interceptor的preHandle()的返回值为true时才会执行,也就是在DispatcherServlet渲染了对应的视图之后执行。用于进行资源清理。整个请求处理完毕回调方法。如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中
     *
     * @author: GuoTong
     * @date: 2021-06-28 15:26:49
     * @param:
     * @return:
     */

    @Autowired
    UserService userService;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * Description:  主要流程:
     * <p>
     * 1.从 http 请求头中取出 token,
     * 2.判断是否映射到方法
     * 3.检查是否有SkipTokenByJWT注解注释,有则跳过认证
     * 4.检查有没有需要用户登录的注解NeedTokenByJWT,有则需要取出并验证
     * 5.认证通过则可以访问,不通过会报相关错误信息
     *
     * @author: GuoTong
     * @date: 2021-06-28 15:27:55
     * @param:
     * @return:
     */

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
        // 从 http 请求头中取出 token
        String token = httpServletRequest.getHeader("token");
        // 如果不是映射到方法直接通过
        if (!(object instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) object;
        Method method = handlerMethod.getMethod();
        //检查是否有SkipTokenByJWT注释,有则跳过认证
        if (method.isAnnotationPresent(SkipTokenByJWT.class)) {
            SkipTokenByJWT SkipTokenByJWT = method.getAnnotation(SkipTokenByJWT.class);
            if (SkipTokenByJWT.required()) {
                return true;
            }
        }
        //检查有没有需要用户权限的注解
        if (method.isAnnotationPresent(NeedTokenByJWT.class)) {
            NeedTokenByJWT NeedTokenByJWT = method.getAnnotation(NeedTokenByJWT.class);
            if (NeedTokenByJWT.required()) {
                // 执行认证
                if (token == null) {
                    throw new RuntimeException("无token,请重新登录");
                }
                // 获取 token 中的 user id
                String userEmail;
                try {
                    userEmail = JWT.decode(token).getAudience().get(0);
                } catch (JWTDecodeException j) {
                    throw new MyLoginException("401,请重新登录");
                }
                UserTable user = userService.getbyUserEmail(userEmail);
                if (user == null) {
                    throw new RuntimeException("用户不存在,请重新登录");
                }
                // 验证 token
                JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getAddress())).build();
                try {
                    jwtVerifier.verify(token);
                    //Redis如果存在就判断token是否一致
                    String redisToken = (String) redisTemplate.opsForValue().get(user.getEmail());
                    if (!StringUtils.equals(token, redisToken)) {
                        throw new RuntimeException("用户登录状态已过期");
                    }
                } catch (JWTVerificationException e) {
                    throw new RuntimeException("未检测到用户登录401");
                }
                return true;
            }
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest,
                           HttpServletResponse httpServletResponse,
                           Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest,
                                HttpServletResponse httpServletResponse,
                                Object o, Exception e) throws Exception {
    }
}

注册拦截器 、编写Redis序列化配置

@Configuration
public class webMvcConfig implements WebMvcConfigurer {

    /**
     * Description: 静态资源过滤
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //ClassPath:/Static/** 静态资源释放
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
        //释放swagger
        registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
        //释放webjars
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

    /**
     * Description:添加基于JWT认证的拦截器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 拦截所有请求,通过判断是否有 @SkipTokenByJWT 注解 决定是否需要登录
        registry.addInterceptor(getInterceptorByJwt()).addPathPatterns("/**");
    }

    @Bean
    public AuthenticationInterceptor getInterceptorByJwt() {
        return new AuthenticationInterceptor();
    }

    /*解决RedisTemplate往redis存入的数据是二进制文件(不管是key还是value都是二进制文件),自定义json序列化与反序列化规则*/

    /**
     * redisTemplate 序列化使用的jdkSerializeable, 存储二进制字节码, 所以自定义序列化类
     *
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        // 使用Jackson2JsonRedisSerialize 替换默认序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        // 设置value的序列化规则和 key的序列化规则
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //jackson2JsonRedisSerializer就是JSON序列号规则,
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

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

自定义异常

public class MyLoginException extends RuntimeException {

    public MyLoginException(String message) {
        super(message);
    }
}

全局异常处理

@RestControllerAdvice
public class GlobalExceptionHander {

    private ObjectMapper objectMapper = new ObjectMapper();
    private final int NO_LOGIN_CODE = 401;

    @ExceptionHandler(value = Exception.class)
    public RespObject exceptionHandler(Exception e) {
        e.printStackTrace();
        return RespObject.respNo(e.getMessage());
    }

    @ExceptionHandler(value = MyLoginException.class)
    public RespObject MyLoginException(Exception e) {
        e.printStackTrace();
        String message = e.getMessage();
        if (String.valueOf(NO_LOGIN_CODE).equals(message)) {
            return RespObject.respOk(NO_LOGIN_CODE, e.getMessage());
        }
        return RespObject.respNo(e.getMessage());
    }

}

编写整合JWTcontroller

@RestController
public class HelloController {

    @Autowired
    private UserService userService;

    @Autowired
    private RedisTemplate redisTemplate;

    private final static Map<String, Object> RESP;

    static {
        RESP = new HashMap<>();
        RESP.put("author", "郭童");
        RESP.put("since", "JDK1.8");
        RESP.put("createTime", "2021-06-28 09:28");
        RESP.put("backFrame", "SpringBoot2.5.1");
        RESP.put("htmlTemp", "Thymeleaf");
    }

    @RequestMapping(value = "/hello", name = "web项目测试")
    public String gotoHelloWorld() {
        return "hello world!";
    }

    @RequestMapping(value = "/", name = "设置默认访问页面")
    public ModelAndView gotoIndexPage() {
        ModelAndView view = new ModelAndView("index");
        view.addObject("initData", RESP);
        return view;
    }

    @SkipTokenByJWT
    @PostMapping(value = "/login", name = "登录")
    public Object loginUser(@RequestBody LoginUser user) {
        UserTable userTable = userService.queryForEntity(user);
        if (userTable == null) {
            return RespObject.respNo("用户名或者密码错误");
        }
        String token = user.getToken(userTable);
        //缓存登录状态
        String emailIsRedisKeyByLife = userTable.getEmail();

        String dataRedis = (String) redisTemplate.opsForValue().get(emailIsRedisKeyByLife);
        if (StringUtils.isEmpty(dataRedis)) {
            redisTemplate.opsForValue().set(emailIsRedisKeyByLife, token);
            //设置过期时间; TimeUnit.MILLISECONDS  毫秒:设置默认时间是SECONDS秒:60秒
            redisTemplate.expire(emailIsRedisKeyByLife, 60, TimeUnit.MINUTES);

        }
        return RespObject.respLogin(token, userTable);
    }

    @GetMapping(value = "/getUserById", name = "根据ID获取数据")
    @NeedTokenByJWT
    public RespObject getUserTable(@RequestParam("id") int id) {
        UserTable user = userService.getUserById(id);
        if (user == null) {
            return RespObject.respNo("输入的ID无效");
        }
        return RespObject.respOk(user);
    }

}

整合HTML首页

<!DOCTYPE html>
<!--suppress ALL-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:include="/common/common.html">
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>首页</title>
</head>
<body>
<h1 class="text-center">Hello</h1>

<table class="table table-hover indexTable" th:object="${initData}">
    <tr class="active">
        <td>项目编码作者</td>
        <td th:text="*{author}"></td>
    </tr>
    <tr class="success">
        <td>使用JAVA版本</td>
        <td th:text="*{since}"></td>
    </tr>
    <tr class="warning">
        <td>项目创建时间</td>
        <td th:text="*{createTime}"></td>
    </tr>
    <tr class="danger">
        <td>后端使用框架</td>
        <td th:text="*{backFrame}"></td>
    </tr>
    <tr class="info">
        <td>视图模板引擎</td>
        <td th:text="*{htmlTemp}"></td>
    </tr>
</table>
<form class="indexTable" id="formByThisPage" onsubmit="return false">
    <div class="form-group">
        <label for="exampleInputEmail1">ID:</label>
        <input type="text" class="form-control" id="userTableId"
               placeholder="请输入ID">
    </div>
    <button id="btnByThisA" type="button" class="btn btn-default">查询</button>
</form>
<table class="table table-hover indexTable">
    <tr>
        <td>查询结果</td>
        <td>
            <textarea class="form-control" rows="3" id="selectText"></textarea>
        </td>
    </tr>
</table>

<form class="indexTable" id="formByThisPage" th:action="@{/login}" method="post" onsubmit="return false">
    <div class="form-group">
        <label for="exampleInputEmail1">用户名:</label>
        <input type="text" name="username" class="form-control" id="exampleInputEmail1"
               placeholder="请输入用户名">
    </div>
    <div class="form-group">
        <label for="exampleInputPassword1">密码:</label>
        <input type="password" name="password" class="form-control" id="exampleInputPassword1" placeholder="请输入密码">
    </div>
    <button id="btnByThis" type="button" class="btn btn-default">登录</button>
</form>
<script type="text/javascript">
    $(function (ev) {
        $("#btnByThis").on('click', function () {
            window.localStorage.token = undefined;
            let sendLoginData = {username: $("#exampleInputEmail1").val(), password: $("#exampleInputPassword1").val()}
            $.ajax({
                url: "/login",
                type: "post",
                data: JSON.stringify(sendLoginData),
                dataType: "json",
                contentType: "application/json; charset=utf-8",
                success: function (resp) {
                    if (resp.code == 200) {
                        toastr.success("登录成功!");
                        window.localStorage.token = resp.token;
                    } else {
                        toastr.error(resp.msg);
                    }

                }
            });
        })
        $("#btnByThisA").on('click', function () {
            let sendLoginData = $("#userTableId").val();
            $.ajax({
                url: "/getUserById?id=" + sendLoginData,
                type: "get",
                beforeSend: function (XMLHttpRequest) {
                    XMLHttpRequest.setRequestHeader("token", window.localStorage.token);
                },
                success: function (resp) {
                    if (resp.code == 200) {
                        $("#selectText").val(JSON.stringify(resp.data));
                        toastr.success("查询成功!!");
                    } else if (resp.code) {
                        $("#selectText").val(JSON.stringify(resp.data));
                        toastr.error(resp.msg);
                    }

                }
            });
        })
    });

</script>
</body>
</html>

实体类

@Data
public class UserTable {
    private int id;
    private String name;
    private String address;
    private String gender;
    private String qq;
    private String email;
    private int age;

}

@Data
@Accessors(chain = true)
public class LoginUser {
    String Id;
    String username;
    String password;

    /**
    * Description: Algorithm.HMAC256():使用HS256生成token,密钥则是用户的密码,唯一密钥的话可以保存在服务端。
     * withAudience()存入需要保存在token的信息,这里我把用户getEmail存入token中
    * @author: GuoTong
    * @date: 2021-06-28 15:19:59
    * @param:
    * @return:
    */
    public String getToken(UserTable user) {
        String token = "";
        token = JWT.create().withAudience(user.getEmail())
                .sign(Algorithm.HMAC256(user.getAddress()));
        return token;
    }

}