1. 程式人生 > 程式設計 >SpringBoot整合jwt和mybatis-plus的腳手架專案

SpringBoot整合jwt和mybatis-plus的腳手架專案

近期給公司弄了個腳手架專案,打算以後後臺開發就用這個了, 求star~

點選跳轉到github地址

概述

Volcano是一個基於springboot後臺開發簡易腳手架。能實現當作一個單獨專案或者作為微服務的一個節點進行快速開發和部署。

更新日誌

2019-11-14 新增swagger異常解決方案

專案結構

javasea-volcano 父類,用於springcloud和springboot的版本管控,maven外掛,倉庫統一管控等.

|--javasea-volcano-base: 測試類和相關業務配置, 依賴common專案中的元件,開發的時候在該專案的基礎上新增新增業務程式碼即可。

|--javasea-volcano-common:常用元件和工具類

專案環境

中介軟體 版本 備註
JDK 1.8+ JDK1.8及以上
MySQL 5.6+ 5.6及以上
Redis 3.2+

基本功能

參考包com.zhirui.lmwy.wms.demo下的配置

引數校驗

JSR校驗

spring-boot-starter-web已經預設集成了JSR303校驗,只需要直接使用註解校驗即可。

參考測試類: com.zhirui.lmwy.wms.demo.web.controller.TestCheckParamController

手動校驗

Assert類定義了不滿足條件後快速斷言的方式,可以在校驗引數中使用。

還可以採用Spring的Assert進行校驗

//第一個引數為false則丟擲IllegalArgumentException異常
Assert.isTrue(concurrentConsumers > 0,"'concurrentConsumers' value must be at least 1 (one)");
Assert.isTrue(!this.exclusive || concurrentConsumers == 1,"When the consumer is exclusive,the concurrency must be 1");
複製程式碼

也可以採用Optional進行校驗

ZOrder order = this
.getOrderByOrderNum(orderNum); Optional.ofNullable(order).filter(o -> { return null != o && (0 == o.getStatus() || 3 == o.getStatus() || 9 == o.getStatus()); }).orElseThrow(() -> new ParamException("獲取資料異常,訂單號有誤或者訂單狀態異常!")); 複製程式碼

還可以通過guava的Preconditions類來進行引數檢查。有需要請自行百度。

引數轉換

參考測試類:TestDateConverterAndJson

URL方式傳值到後端轉換

URL傳參到後端包括 如下三種方式傳參:

@GetMapping("testDateConverter")
public Student testDateConverter(@RequestParam Student student){...}

@GetMapping("testDateConverter")
public Student testDateConverter(Student student){...}

@GetMapping("testDateConverter/{id}")
public Student testDateConverter(@PathVariable Integer id){...}
複製程式碼

com.zhirui.lmwy.common.converter包下定義了很多轉換類:

  • StringToDateConverter

    如果實體屬性是Date,通過該轉換器將String轉Date型別。效果和日期屬性上的註解@DatetimeFormat相同。但是啟用該轉換器的時候,實體屬性上的註解@DatetimeFormat不再生效。

  • StringToDoubleConverter

    如果實體屬性是Double,通過該轉換器將String轉Double型別。

  • StringToIntegerConverter

    如果實體屬性是Integer,通過該轉換器將String轉Integer型別。

請求體方式傳值到後端轉換

請求體方式傳參到後端 包括如下方式:

@PostMapping("testDateConverter2")
public Student testDateConverter2(@RequestBody Student student){...}
複製程式碼

com.zhirui.lmwy.common.json.jackson包下定義了JSON的序列化和反序列化方式:

  • deserializer 包下的序列化類

    在@RequestBody接參時候,會呼叫該包中的序列化類將JSON轉換成實體接參。

  • serializer 包下的序列化類

    用於後端傳值給前端。

後端傳值給前端

com.zhirui.lmwy.common.json.jackson.serializer包下的定義了序列化類 。

在Controller類上新增註解@ResponseBody或者@RestController,那麼後端傳值給前端是JSON方式。

如果是返回值是物件或者集合,會用序列化類進行引數型別轉換。

如下列子中,Student中的兩個屬性日期會轉換成serializer 定義的格式“yyyy-MM-dd HH:mm:ss”。

序列化類``JacksonDateSerializer的作用相當於在屬性上添加了@JsonFormat(pattern="yyyy-MM-dd"),實現將Date型別轉換成String,但是新增JacksonDateSerializer@JsonFormat(pattern="yyyy-MM-dd")失效。

    @PostMapping("testDateConverter2")
    public Student testDateConverter2(@RequestBody Student student){
        System.out.println(student);
        Student s = new Student();
        s.setBirth(new Date());
        s.setCreateTime(LocalDateTime.now());
        return s;
    }
複製程式碼

異常處理

com.zhirui.lmwy.common.exception.impl中定義了三大類異常:

BusinessException: 通用業務異常
ParamException:引數校驗異常
AuthenticationException:認證失敗異常
複製程式碼

Exceptions類定義了快捷丟擲異常的一些通用方法,在需要丟擲異常時請不要去throw new XXXException(),而是用Exceptions類的方法進行操作。

已經定義了全域性異常處理器GlobalExceptionHandler對各種異常可以進行處理,請不要在controller和service中try..catch

返回值處理

controller的返回前端使用ResultModel類進行封裝,裡面有codemsgdata等欄位。

各種場景下的ResultModel返回:

    //插入後返回方式, msg:插入成功;插入失敗
    public ResultModel resultInsert(){
        boolean flag = false;
        return ResultModel.resultInsert(flag);
    }

    //更新後返回方式, msg:更新成功;更新失敗
    public ResultModel resultUpdate(){
        boolean flag = false;
        return ResultModel.resultUpdate(flag);
    }

    //刪除後返回方式, msg:刪除成功;刪除失敗
    public ResultModel resultDelete(){
        boolean flag = false;
        return ResultModel.resultDelete(flag);
    }

    //刪除後返回方式, msg:操作成功;操作失敗
    public ResultModel result(){
        boolean flag = false;
        return ResultModel.result(flag);
    }

    //認證失敗返回方式, msg:認證資訊異常
    public ResultModel errorTokenMsg(){
        boolean flag = false;
        //如果msg引數為null,那麼是預設的msg:認證資訊異常
        return ResultModel.errorTokenMsg(null);
    }
複製程式碼

推薦優先上面的放回方式,如果不能滿足,還可以使用如下通用的失敗成功的返回方式:

ResultModel.error();  //失敗返回方式
ResultModel.ok();       //成功返回方式
複製程式碼

AOP 日誌輸出

  1. 新增座標
<!-- AOP -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- console彩色日誌 -->
<dependency>
    <groupId>org.fusesource.jansi</groupId>
    <artifactId>jansi</artifactId>
    <version>1.18</version>
</dependency>
複製程式碼
  1. 定義AOP類實現彩色日誌輸出

參考:com.zhirui.lmwy.common.aop.LogAop

整合 絲襪哥

在common專案中集成了swagger

  1. pom中新增註解
        <!-- swagger start -->
        <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>
        <!-- swagger end -->
複製程式碼
  1. 新增配置

    兩個配置類:

    com.zhirui.lmwy.common.swagger.SwaggerConfiguration

    com.zhirui.lmwy.common.swagger.SwaggerProperties

  2. 設定自定義顯示引數

    在base專案的application.yaml中新增如下配置進行設定:

swagger:
  #  open: true              #是否開啟swagger,在生產環境下需要關閉
  protocol: http          #協議,http或https
  base-package: com.zhirui.lmwy.wms   #一定要寫對,會在這個路徑下掃描controller定義
  title: volcano-base專案
  version: 1.0
  description: volcano-base專案絲襪哥測試
複製程式碼
  1. 在controller和model中使用swagger

    controller類中使用參考:com.zhirui.lmwy.wms.demo.web.controller.TestCurdController

    model類中使用參考:com.zhirui.lmwy.wms.demo.web.entity.User

  2. 通過swagger進行http請求

    • http://localhost:8080/swagger-ui.html#/

      絲襪哥預設的訪問方式

    • http://localhost:8080/docs

      通過controller 重定向後的訪問方式

      swagger在2.9.2可能會報錯如下:

      Illegal DefaultValue null for parameter type integer
      複製程式碼

      根據上面這句報錯資訊,點進去AbstractSerializableParameter.java:412可以看到

      if(BaseIntegerProperty.TYPE.equals(type)){
          return Long.valueOf(example);
      }
      複製程式碼

      就是說如果實體屬性型別是Integer,就把example轉為Long型別,而example預設為"",導致轉換錯誤。

      解決辦法

      方法一: 實體類中,Integer型別的屬性加@ApiModelProperty時,必須要給example引數賦值,且值必須為數字型別。

      @ApiModelProperty(value = "試卷ID",example = "1")
      private int pageId;
      複製程式碼

      方法二:

      將版本改成2.8.0

      <!-- swagger start -->
      <dependency>
          <groupId>io.springfox</groupId>
          <artifactId>springfox-swagger2</artifactId>
          <version>2.8.0</version>
      </dependency>
      <dependency>
          <groupId>io.springfox</groupId>
          <artifactId>springfox-swagger-ui</artifactId>
          <version>2.8.0</version>
      </dependency>
      複製程式碼

整合 mybatis-plus(下文中稱為MP)

ORM框架使用mybatis-plus,簡便了CURD操作

  1. 新增pom

            <!-- mybatis-plus -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.2.0</version>
            </dependency>
            <!-- 程式碼生成器  start -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-generator</artifactId>
                <version>3.2.0</version>
            </dependency>
            <!-- 程式碼生成器需要的引擎模板 -->
            <dependency>
                <groupId>org.apache.velocity</groupId>
                <artifactId>velocity-engine-core</artifactId>
                <version>2.0</version>
            </dependency>
            <!-- 程式碼生成器  end -->
    複製程式碼
  2. 在yml中進行配置

    mybatis-plus:
      mapper-locations: classpath:mapper/**/*.xml
      global-config:
        db-config:
          id-type: AUTO  #主鍵自增長
    複製程式碼
  3. 新增配置類配置

    下列程式碼中使用了租戶模式,如果只是單純需要新增分頁外掛,只需要如下方式即可:

    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        return paginationInterceptor;
    }
    複製程式碼
    /** 分頁外掛: 實現物理分頁 */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        ArrayList<ISqlParser> iSqlParsers = new ArrayList<>();
        TenantSqlParser tenantSqlParser = new TenantSqlParser();
        tenantSqlParser.setTenantHandler(new TenantHandler() {
            @Override
            public Expression getTenantId(boolean where) {
                return new StringValue(tenantId);
            }
    
            @Override
            public String getTenantIdColumn() {
                //指定表中的租戶列
                return "tenant_id";
            }
            @Override
            public boolean doTableFilter(String tableName) {
                return false;
            }
        });
        iSqlParsers.add(tenantSqlParser);
        paginationInterceptor.setSqlParserList(iSqlParsers);
        return paginationInterceptor;
    }
    複製程式碼

通過MP進行CRUD操作

參考測試類和官網com.zhirui.lmwy.wms.demo.web.controller.TestCurdController

程式碼生成器

採用了MP的程式碼生成器,實現了兩個程式碼生成器。

javasea-volcano-base專案的src/test/java目錄的 com.zhirui.lmwy.wms包下,按照註釋修改為自己需要的配置執行即可。看個人習慣,推薦用PrimaryCodeGenerator

  • SencondCodeGenerator

    通過資料庫表生成基本的entity,mapper,controller和service類等基本類。

  • PrimaryCodeGenerator

    SencondCodeGenerator 功能的基礎上,controller,entity中生成swagger的註解。controller、service生成常用的crud方法。

整合redis

  1. 新增pom座標
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

複製程式碼
  1. yml中進行配置
spring:
    redis:
      host: 127.0.0.1
      port: 6379
      password: zhirui888
      timeout: 2000
      database: 6
      lettuce:
        pool:
          max-active: 8
          max-wait: 1
          max-idle: 8
          min-idle: 0
複製程式碼
  1. 配置類中進行配置

    • 定義redis的RedisTemplate

      詳見:com.zhirui.lmwy.common.redis.RedisTemplateConfig

    • 開啟springcache

      詳見:com.zhirui.lmwy.common.redis.RedisCacheConfig

    • redis工具類

      詳見:com.zhirui.lmwy.common.redis.RedisUtils

整合 JWT

新增jwt座標

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
    <scope>compile</scope>
</dependency>
複製程式碼

yml中配置jwt

custom:
    jwt:
      header: token
      secret: 666666          #密碼,用於生成簽名
      issuer: volcano         #簽發人
      subject: volcano-jwt    #主題
      audience: web           #簽發的目標
      expire-minutes: 20      #過期時間
    interceptor:
      jwt:
        exclude:
          path: /swagger-resources/**,/api-docs/**,/v2/api-docs/**,/login,/verificationCode,/doc/**,/error/**,/docs,/test/**
      permission:
        exclude:
          path: /swagger-resources/**,/adminLogin,/sysLogin,/login.html,/docs
      token-timeout:
        exclude:
          path: /swagger-resources/**,/docs
複製程式碼

新增登陸介面

登陸後會將token資訊儲存到redis

詳見登陸controller:com.zhirui.lmwy.wms.security.controller.LoginController

新增jwt攔截器

攔截到請求後會進行校驗,校驗方式如下:

// 驗證token是否有效
Jws<Claims> jws = JwtUtil.verify(token);
複製程式碼

token未過期且合法的才能校驗通過,否則丟擲401異常。

詳見jwt攔截器:com.zhirui.lmwy.wms.security.interceptor.JwtInterceptor

在swagger進行token測試

  1. 執行登陸操作,token見返回值中

返回值為:

{
  "code": 200,"msg": "操作成功","data": {
    "loginSysUser": {
      "id": "1","userName": "admin"
    },"token": "eyJjdHkiOiJjdHkiLCJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ2b2xjYW5vLWp3dCIsImF1ZCI6IndlYiIsImlzcyI6InZvbGNhbm8iLCJleHAiOjE1Njg3MTI0MDcsImlhdCI6MTU2ODcxMDYwNywianRpIjoiNDM4NjMwMDAwOTlmNDFkNDk1Y2FlOWVkNWUzNDZhOTgifQ.wVZOx-J2knqUxdnRjRXZqr2nf1S-Qwmap2-0nGXJDXM"
  },"time": "2019-09-17 16:56:51"
}
複製程式碼
  1. 點選 Authorize按鈕

  1. 將token設定到value中,後面絲襪哥所有的請求都會帶一個叫做“Authorization”的請求頭。

    在jwt攔截器JwtInterceptor中會獲取該請求頭進行校驗。

專案啟動和部署

單獨作為專案使用

javasea-volcano預設是一個獨立的springboot專案,可以直接啟動javasea-volcano-base專案,在base專案的基礎上,直接編寫業務程式碼即可。

整合到現有的springcloud中

  1. 啟用bootstrap.yml的配置(放開註釋)

    #spring:
    #  cloud:
    #    config:
    #      uri: ${WMS_CONFIG_SERVER_URL}
    #      name: zhirui-lmwy2-wms${WMS_DEVELOPER_NAME:}
    #      profile: ${config.profile:dev}
    #
    複製程式碼
  2. base專案的pom中開啟對應eureka client的座標,啟動類WmsApplication通過註解@EnableDiscoveryClient啟用eureka client。

    <!-- springboot 1.X -->
    <!--<dependency>-->
       <!--<groupId>org.springframework.cloud</groupId>-->
       <!--<artifactId>spring-cloud-starter-eureka</artifactId>-->
    <!--</dependency>-->
    <!-- springboot 2.X -->
    <!--<dependency>-->
       <!--<groupId>org.springframework.cloud</groupId>-->
       <!--<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>-->
    <!--</dependency>-->
    複製程式碼
  3. IDE中配置vm引數,使用已經已經存在的eureka註冊中心配置中心

    在IDE中配置VM引數只能是作為測試,如果需要部署後生效,那麼需要配置到伺服器的環境變數或者在啟動的java -jar後新增配置。之後我會新增關於服務部署的文章。

    -Dmultipart-location=D:/wms/temp
    -DDiskLocation=D:/wms/
    -DWMS_CONFIG_SERVER_URL=http://192.168.1.230:8861
    -DWMS_EUREKA_SERVER_URL=http://192.168.1.230:8090/eureka/
    -DWMS_DEVELOPER_NAME=-longxiaonan
    -Dconfig.profile=dev
    複製程式碼

Maven方式打包

mvn clean package -Dmaven.test.skip=true
複製程式碼