1. 程式人生 > 其它 >快速構建一個簡單的Springboot-web專案

快速構建一個簡單的Springboot-web專案

web專案基本的核心成分

  • 資料落地 MYSQL資料庫
  • 登入標識 JWT :{Java web token }
  • 記錄有效登入狀態 以及快取常用資料: Redis
  • 資料庫與JAVA實體的快速自動對映ORM:mybatis
  • 資料庫連線池化技術:Druid
  • 檢視解析器模板引擎:Thymeleaf
  • 基於介面測試和介面文件工具:Swagger
  • 單元測試:Junit
  • 實體類簡化工具:lombok
  • SpringbootTest:

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/", "[email protected]"))
                        .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;
    }

}