本文將從如何創建Spring Boot項目開始講解如何搭建一個較爲通用的快速開發模板,方便在以後的開發中可以快速的應用,做一個高效的cv
工程師,避免每次寫都要去以前的項目裏翻工具類和通用配置。
做高效cv
工程師,從搭建自己的常用通用開發模板開始吧,本文的項目源碼都放在我的github
上了,有用就給上一個Star
吧
文章目錄
創建一個Spring Boot項目
開發工具使用idea
採用idea中的Spring Initializr創建項目(如果第一次使用下載依賴會比較慢)
- 選擇 Spring Initializr方式構建
- 配合項目名包路徑
- 依賴就選擇一個web的就可以,需要什麼依賴之後手動加入
- 設置項目存放位置,選擇自己的工作空間
- 點擊finish開始創建,第一次可能會比較久
- 創建完成後的目錄結構
- 修改maven倉庫位置,默認創建的是在C盤中的倉庫,重新定位到自己的倉庫(可不改)
file - > settings ->搜索maven
選中自己的本地倉庫地址,點擊apply
一個Spring Boot的項目就創建好了
基礎配置編寫
- 引入常用Maven依賴
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<!--mysql驅動-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1.tmp</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.3.1.tmp</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.30</version>
</dependency>
<!--模板引擎-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.1</version>
</dependency>
<!--guava-->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
</dependency>
<!-- pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.5</version>
</dependency>
<!--通用工具包commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
<!--數據源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!--swagger2-->
<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>
<!-- 引入swagger-bootstrap-ui包 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.8.5</version>
</dependency>
<!--熱部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<!--依賴不傳遞-->
<optional>true</optional>
</dependency>
<!--jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>
-
創建常用的包
-
配置BasedemoApplication
BasedemoApplication是整個web項目的啓動類
配置一下mapper接口的掃描
@SpringBootApplication
@MapperScan("com.example.basedemo.dao")
public class BasedemoApplication {
public static void main(String[] args) {
SpringApplication.run(BasedemoApplication.class, args);
}
}
-
創建配置文件
默認創建了application.properties
手動創建開發環境配置:application-dev.properties
手動創建生產環境配置:application-prov.properties
application.properties內容:spring.profiles.active=dev
application-dev.properties和application-prov.properties根據需求自己配置
#端口
server.port = 3036
#xml文件路徑
mybatis.mapper-locations=classpath:mapper/*.xml
#數據類連接
spring.datasource.url=jdbc:mysql://localhost:3306/crm?useUnicode=true&characterEncoding=UTF-8&sessionVariables=time_zone='%2B8:00'
spring.datasource.username=root
spring.datasource.password=root
#使用druid數據源
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#連接驅動
spring.datasource.driver-class-name= com.mysql.cj.jdbc.Driver
#時間格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
日誌配置
Spring Boot默認採用的是SLF4J + LogBack,這也是比較優的一種搭配了,就直接用這個
引入配置文件logback-spring.xml
日誌寫入根目錄下的log目錄,每天生成一個日誌文件
具體配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定義日誌文件的存儲地址 勿在 LogBack 的配置中使用相對路徑-->
<property name="LOG_HOME" value="log" />
<!--控制檯日誌, 控制檯輸出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化輸出:%d表示日期,%thread表示線程名,%-5level:級別從左顯示5個字符寬度,%msg:日誌消息,%n是換行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!--文件日誌, 按照每天生成日誌文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日誌文件輸出的文件名-->
<FileNamePattern>${LOG_HOME}/klchen_summary.log.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日誌文件保留天數-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化輸出:%d表示日期,%thread表示線程名,%-5level:級別從左顯示5個字符寬度%msg:日誌消息,%n是換行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
<!--日誌文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!--myibatis log configure-->
<logger name="com.apache.ibatis" level="TRACE"/>
<logger name="java.sql.Connection" level="DEBUG"/>
<logger name="java.sql.Statement" level="DEBUG"/>
<logger name="java.sql.PreparedStatement" level="DEBUG"/>
<!-- 日誌輸出級別 -->
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE"/>
</root>
</configuration>
接口文檔Swagger2配置
在utils目錄下創建Swagger2類,在裏面編寫配置文件,內容如下:
必須要修改的是basePackage("com.klchen.hos.controller"))
修改爲自己的接口包的路徑,才能被掃描到
apiInfo內配置的是文檔上顯示的參數
@Configuration
@EnableSwagger2
public class Swagger2 {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.klchen.hos.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("存儲系統API文檔")
.description("基於HBase的文件存儲系統API文檔")
.contact(new Contact("klChen","https://blog.csdn.net/qq_41170102","[email protected]"))
.version("2.0.1")
.build();
}
}
啓動我們的項目,可以查看一下文檔
官方的ui的路徑是:http://localhost:3036/swagger-ui.html
,其中3036是配置的項目的port,swagger-ui.html是路徑
在前面引包裏面我們引入了一個包,如下
<!-- 引入swagger-bootstrap-ui包 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.8.5</version>
</dependency>
這個包是一個比較簡潔的ui,訪問路由是http://localhost:3036/doc.html
swagger2常用註解:
- @Api()用於類;
表示標識這個類是swagger的資源 - @ApiOperation()用於方法;
表示一個http請求的操作 - @ApiParam()用於方法,參數,字段說明;
表示對參數的添加元數據(說明或是否必填等) - @ApiModel()用於類
表示對類進行說明,用於參數用實體類接收 - @ApiModelProperty()用於方法,字段
表示對model屬性的說明或者數據操作更改 - @ApiIgnore()用於類,方法,方法參數
表示這個方法或者類被忽略 - @ApiImplicitParam() 用於方法
表示單獨的請求參數 - @ApiImplicitParams() 用於方法,包含多個 @ApiImplicitParam
自定義全局異常處理配置
首先定義一個接口CommonError,裏面定義三個方法,分別是設置錯誤信息,獲取錯誤信息,獲取錯誤編碼的方法
public interface CommonError {
public int getErrCode();
public String getErrMsg();
public CommonError setErrMsg(String errMsg);
}
定義一個枚舉類,在裏面可以定義我們常用的異常,並實現自定義的CommonError 接口,方便通過枚舉類來獲取錯誤編碼和錯誤信息
public enum EmBusinssError implements CommonError{
//通用錯誤類型
PARAMETER_VALIDATION_ERROR(10001,"參數不合法"),
UNKNOWN_ERROR(10002,"未知錯誤"),
NOTFIND(10003,"查詢結果爲空"),
//以20000開頭爲用戶信息相關錯誤定義
USER_NOT_EXIST(20001,"用戶不存在"),
PRIMARY_KEY_EXIST(20002,"主鍵衝突"),
USER_Login_Fail(20003,"用戶賬號或密碼不正確"),
LOGINOUTTIME(30001,"登錄狀態過期,請重新登錄"),
ERROR_PERMISSION_DENIED(403,"權限錯誤"),
ERROR_FILE_NOT_FOUND(404,"未能找到"),
ERROR_HBASE(500,"hbase發生錯誤"),
ERROR_HDFS(501,"hdfs發出錯誤")
;
private int errCode;
private String errMsg;
EmBusinssError(int errCode, String errMsg) {
this.errCode = errCode;
this.errMsg = errMsg;
}
@Override
public int getErrCode() {
return this.errCode;
}
@Override
public String getErrMsg() {
return this.errMsg;
}
@Override
public CommonError setErrMsg(String errMsg) {
this.errMsg = errMsg;
return this;
}
}
創建一個包裝器業務異常類,用來包裝異常信息,拋出異常時可以統一使用BusinessException 就可以了
//包裝器業務異常類實現
public class BusinessException extends Exception implements CommonError {
private CommonError commonError;
//直接接收embussinesserror的傳參,用於構造業務異常
public BusinessException(CommonError commonError) {
super();
this.commonError = commonError;
}
//接收自定義errMsg的方式構造業務異常
public BusinessException(CommonError commonError ,String errMsg){
super();
this.commonError = commonError;
this.commonError.setErrMsg(errMsg);
}
@Override
public int getErrCode() {
return this.commonError.getErrCode();
}
@Override
public String getErrMsg() {
return this.commonError.getErrMsg();
}
@Override
public CommonError setErrMsg(String errMsg) {
this.commonError.setErrMsg(errMsg);
return this;
}
}
項目中把他們放在error目錄下
爲了捕獲異常,還需要定義個一個頂層的捕獲器 ,將其定義在BaseController中,所有的Controller都繼承BaseController,所有業務拋出的異常最終都會到BaseController進行處理,然後將錯誤信息返回
public class BaseController {
//定義exceptionhandler解決未被controller層吸收的exception
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public Object handlerException(HttpServletRequest request, Exception ex){
Map<String, Object> responseData = new HashMap<>();
if (ex instanceof BusinessException){
BusinessException businessException = (BusinessException)ex;
responseData.put("errCode",businessException.getErrCode());
responseData.put("errMsg",businessException.getErrMsg());
}else {
responseData.put("errCode", EmBusinssError.UNKNOWN_ERROR.getErrCode());
responseData.put("errMsg",EmBusinssError.UNKNOWN_ERROR.getErrMsg());
}
return CommonreturnType.create(responseData,"fail");
}
}
通用返回包裝類配置
爲了統一返回的JSON數據的格式,方便使用,需要定義一個通用的返回包裝類
包裝類中
- 定義了兩個字段,請求狀態和返回數據
如果是正常的請求,狀態碼爲success,如果是拋出了異常,狀態碼是fail - 定義了兩個構造方法,一個是傳入參數爲一個Object的方法,默認爲成功的請求返回,請求狀態是success;一個是傳入兩個參數的方法,可指定請求的狀態
這裏的@Data註解是lambok中的,可以生成所有屬性的getter,setter,tostring方法,使用時需要安裝lambok插件並導入包,後面專門寫一篇lambok使用的文章
@Data
@ApiModel(value = "通用返回類型")
public class CommonreturnType<T> {
//表明請求的返回處理結果“success”或“fail”
@ApiModelProperty(value = "狀態碼")
private String status;
//若status爲success,返回前端需要的json數據
//若status爲fail,返回data內使用通用的錯誤格式碼
@ApiModelProperty(value = "返回數據")
private T data;
//定義一個通用的創建方法
public static CommonreturnType create(Object result){
return CommonreturnType.create(result,"success");
}
public static CommonreturnType create(Object result, String status){
CommonreturnType type = new CommonreturnType();
type.setStatus(status);
type.setData(result);
return type;
}
}
PageHelper分頁插件配置
我們主要是使用Mybatis Plus來實現CRUD,並且Mybatis Plus已經存在了只需要配置就能用的分頁插件,但是Mybatis Plus只針對單表操作很方便,多表聯查的場景還是需要自己實現,出於編碼習慣,多表聯查的分頁查詢還是使用pagehelper的分頁插件
首先要導入對應的依賴,前面也已經導入過了
<!-- pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.5</version>
</dependency>
編寫一個自定義的PageInfo用於包裝返回數據,如果不自己設置就是使用默認的,參數會比較多而且名字不能變
爲了方便接口調用者,我們把PageInfo內的參數名儘量和Mybatis Plus中的分頁的參數名一致
@SuppressWarnings({"rawtypes", "unchecked"})
@Data
public class PageInfo<T> implements Serializable {
private static final long serialVersionUID = 1L;
//當前頁
private int current;
//每頁的數量
private int size;
//總記錄數
private long total;
//總頁數
private int pages;
//結果集
private List<T> records;
//是否爲第一頁
private boolean isFirstPage = false;
//是否爲最後一頁
private boolean isLastPage = false;
public PageInfo() {
}
/**
* 包裝Page對象
*
* @param records
*/
public PageInfo(List<T> records) {
if (records instanceof Page) {
Page page = (Page) records;
this.current = page.getPageNum();
this.size = page.getPageSize();
this.pages = page.getPages();
this.records = page;
this.total = page.getTotal();
} else if (records instanceof Collection) {
this.current = 1;
this.size = records.size();
this.pages = 1;
this.records = records;
this.total = records.size();
}
if (records instanceof Collection) {
//判斷頁面邊界
judgePageBoudary();
}
}
/**
* 判定頁面邊界
*/
private void judgePageBoudary() {
isFirstPage = current == 1;
isLastPage = current == pages;
}
}
經過以上兩步匹配,之後就可以使用了
Mybatis Plus分頁配置
這是Mybatis Plus自帶的分頁插件,用起來非常方便,只需要注入一個bean就可以了
@EnableTransactionManagement
@Configuration
public class MybatisPlusConfig {
/**
* 分頁插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
Mybatis Plus自動生成模板與配置
Mybatis Plus之所以強大,一方面就是他的自動生成模板,只需少量配置,就可以生成可以實現幾乎所有單表查詢的dao層和server層,讓程序猿可以專注於業務邏輯而不是繁瑣的編碼
在test下的根目錄下添加如下的文件,文件可以在我的github
中下載
templates下的模板都是配置好的,基本不需要修改,我覺得是非常好用
CodeGenerator就是我們的代碼生成器了,配置好了之後運行就可以生成我們的通用dao層和通用server層,只需要學會他的調用方法就可以了
如果只是簡單使用,需要配置的內容如下
將數據庫連接和項目根路徑修改爲自己的
private static final String dbUrl = "jdbc:mysql://localhost:3306/crm?useUnicode=true&characterEncoding=UTF-8&sessionVariables=time_zone='%2B8:00'";//數據庫連接url
private static final String dbDriver = "com.mysql.cj.jdbc.Driver";//數據庫驅動
private static final String dbUsername = "root";//數據庫賬號
private static final String dbPassword = "root";//數據庫密碼
private static final String basePackage = "cn.chenkl";//項目包路徑
在最下面找到這一行代碼,將需要自動生成的表名加入到裏面,以逗號隔開
完成上面兩步配置就可以啓動CodeGenerator了,處於測試,我就只生成一張表tn_user_info的。啓動後,生成了如下的五個文件,對應了實體類,dao層和service層的接口和實現類
自動生成就配置完畢,後面的實際操作中會講解具體的用法
相比之前用過的Mybatis-generator和通用mapper,針對單表操作,Mybatis-Plus真的太強大了
從別的博主處搬來一張對比圖,感興趣的可以去看看對比的博文
圖片出處https://blog.csdn.net/m0_37524586/article/details/88351833
JWT加密解密配置
JSON Web Token(JWT)是目前最流行的無狀態跨域身份驗證解決方案。
JWT又三部分組成,第一部分我們稱它爲頭部(header),第二部分我們稱其爲載荷(payload, 類似於飛機上承載的物品),第三部分是簽證(signature).
- header
頭部承載了兩部分信息,分別是令牌類型和簽名算法
{
'typ': 'JWT',
'alg': 'HS256'
}
頭部進行base64加密形式第一部分的密文
- playload
載荷就是存放有效信息的地方。分爲標準中註冊的聲明,公共的聲明,私有的聲明
-
註冊中的聲明包括以下七種
iss: jwt簽發者
sub: jwt所面向的用戶
aud: 接收jwt的一方
exp: jwt的過期時間,這個過期時間必須要大於簽發時間
nbf: 定義在什麼時間之前,該jwt都是不可用的.
iat: jwt的簽發時間
jti: jwt的唯一身份標識,主要用來作爲一次性token,從而回避重放攻擊。
不強制被要求使用
-
公共的聲明 :
公共的聲明可以添加任何的信息,一般添加用戶的相關信息或其他業務需要的必要信息.但不建議添加敏感信息,因爲該部分在客戶端可解密. -
私有的聲明 :
私有聲明是提供者和消費者所共同定義的聲明,一般不建議存放敏感信息,因爲base64是對稱解密的,意味着該部分信息可以歸類爲明文信息。
將所有的聲明進行base64加密生成第二部分密文
-
signature
jwt的第三部分是一個簽證信息,這個簽證信息由三部分組成:header (base64後的)
payload (base64後的)
secret
將加密後的header和加密後的payload進行字符串拼接,之後通過頭部聲明的算法進行再次加密,加密時加鹽secret
secret是服務器端私有的一個密匙,不能泄露,因爲base64加密是對稱加密,有了secret之後可以將token破解,導致信息泄露
實踐一下:
創建一個playload實體用於存儲我們的聲明
@Data
public class Payload {
private String issuer;//發佈者
private String subject;//主題
private List<String> audience;//簽名的觀衆 也可以理解誰接受簽名的
private Date issuedAt;//發佈時間
private Date expiresAt;//過期時間
private Date notBefore;//開始使用時間
private Map<String, String> claims;
//重載這個方法
public void setAudience(List<String> audience) {
this.audience = audience;
}
public void setAudience(String... audienceStr) {
List<String> audiences = new ArrayList<String>();
for (String string : audienceStr) {
audiences.add(string);
}
this.audience = audiences;
}
}
聲明參數採用的是註冊中的聲明,claims中可以自定義存放數據,通常是業務相關的一些非重要信息,如用戶名,用戶角色等
創建一個加密解密業務類JWTService,在裏面創建生成簽名和翻譯簽名的方法方便調用
生成簽名的過程:封裝Payload 屬性,對頭部加密,對負荷加密,對簽名加密
/**
* 創建 hour小時後過期的Token
*
* @param claims
* @param hour
* @return
*/
public String createToken(Map<String, String> claims, int hour) throws UnsupportedEncodingException {
Payload createPayload = this.createPayload(hour);//封裝公有負載
createPayload.setClaims(claims);//封裝私有負載
Algorithm hmac256 = Algorithm.HMAC256(this.getSecret());//構建密匙信息
return createToken(createPayload, hmac256);
}
/**
* 根據負載和算法創建Token
*
* @param payload
* @param algorithm
* @return
*/
public String createToken(Payload payload, Algorithm algorithm) {
Builder headBuilder = createHeaderBuilder(algorithm);//頭部加密
Builder publicClaimbuilder = addPublicClaimBuilder(headBuilder, payload);//公有負載加密
Builder privateClaimbuilder = addPrivateClaimbuilder(publicClaimbuilder, payload);//私有負載加密
String token = privateClaimbuilder.sign(algorithm);//生成簽名
return token;
}
校驗(解密)token的過程:解碼簽名獲得payload,解析payload獲取對應的負荷信息封裝到payload對象
/**
* 校驗Token
*
* @param token
* @return
*/
public Payload verifyToken(String token) throws UnsupportedEncodingException {
DecodedJWT jwt = null;
Payload payload = null;
try {
jwt = getDecodedJWT(token);//解碼token獲得jwt
payload = getPublicClaim(jwt);//jwt解析出公有負載
payload = getPrivateClaim(jwt, payload);//jwt解析出私有負載
} catch (AlgorithmMismatchException e) {
throw e;
}
return payload;//返回解析對象
}
具體的方法實現參照我的github
登錄攔截器配置
定義一個登陸攔截器LoginInterceptor實現HandlerInterceptor接口
只需要具體實現下preHandle方法
從header中獲取token的信息
String token = request.getHeader("X-Auth-Token");
如果token爲空重定向到一個返回沒有登錄的信息的接口
//token is null
if (StringUtils.isEmpty(token)) {
String url = "/toLogin";
response.sendRedirect(url);
return false;
}
爲了實現退出登錄功能,登錄完成後需要將token存放到ServletContext中,所以我們還需要看一下ServletContext中有沒有數據,如果沒有,說明調用過退出登錄的接口,當前token就邏輯上失效了
String tokenInServletContext = (String)request.getServletContext().getAttribute(token);
//未登錄或者過期 ServletContext中找不到這個token
if(StringUtils.isEmpty(tokenInServletContext)) {
String url = "/toLogin";
response.sendRedirect(url);
return false;
}
通過上面兩層校驗之後,調用JWTServer中的解密的方法將JWT解密,並捕獲異常,如果發生異常說明解密失敗。token不正確,如果正確解密,返回一個Payload對象,可以存入ThreadLocal中,方便調用其中的屬性
//校驗token,如果無誤放行
Payload payload = jwtService.verifyToken(token);
ContextUtil.setCurrentplayLoad(payload);
定義一個攔截器配置類InterceptorConfig,實現WebMvcConfigurer接口
首先定義一個Map存放免驗證的url
將不需要jwt驗證的接口放在裏面
//請求無需驗證登錄的url
List<String> passingUrl = new ArrayList<String>(){
{
//登錄
add("/login");
//未登錄跳轉提醒
add("/toLogin");
//swagger 相關
add("/swagger-ui.html");
add("/webjars/**");
add("/v2/**");
add("/swagger-resources/**");
add("/doc.html/**");
}
};
注入一個LoginInterceptor實例
@Autowired
private LoginInterceptor loginInterceptor ;
添加一個攔截器將LoginInterceptor 和passingUrl注入進去
/**
* 攔截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor).
addPathPatterns("/**").
excludePathPatterns(passingUrl);
}
同時,還有兩個常用的攔截配置需要加入
- SpringBoot中默認只支持"GET", “POST”, “PUT”, "DELETE"中的Get和Post請求,想要實現Restful的Api,並解決跨域問題,所有需要配置下addCorsMappings
- 系統中常用到靜態資源映射,映射之後可以通過url訪問服務器上映射的資源,
/**
* 配置跨域和支持restoful接口
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowCredentials(true)
.allowedMethods("GET", "POST", "PUT", "DELETE");
}
/**
* 設置圖片映射
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//addResourceHandler是指你想在url請求的路徑
//addResourceLocations是圖片存放的真實路徑
registry.addResourceHandler("/image/**").addResourceLocations("file:"+System.getProperty("user.dir")+"/image/");
}
線程局部變量ThreadLocal使用
還不瞭解ThreadLocal的可以看一看我寫過的一篇關於ThreadLocal的博文
使用時,創建一個工具類ContextUtil操作ThreadLocal
/**
* ThreadLocal工具類,用來獲取請求用戶的信息
*/
public class ContextUtil {
//線程局部變量
private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
public static String getCurrentUser() {
return threadLocal.get();
}
public static void setCurrentUser(String openId) {
threadLocal.set(openId);
}
public static void clear() {
threadLocal.remove();
}
}
當jwt驗證通過後,將Payload對象存入到ThreadLocal中,在一次請求調用到的代碼的任何地方都可以獲取到Payload對象,從而獲取到封裝的業務屬性
熱部署
使用熱部署可以在代碼修改後不用重啓服務器,只需要保存代碼就會自動的重新加載
首先 導入依賴
<!--熱部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<!--依賴不傳遞-->
<optional>true</optional>
</dependency>
在配置文件中添加配置
#熱部署生效
spring.devtools.restart.enabled=true
#設置重啓的目錄,添加那個目錄的文件需要restart
spring.devtools.restart.additional-paths=src/main/java
修改idea配置
File-Settings-Build-Compiler-Build Project automatically
ctrl + shift + alt + / ,選擇Registry,勾上 Compiler autoMake allow when app running
ssl證書配置
(這個需要https時才配)
寫了一篇使用博客
登錄接口實現
從這裏開始就要將之前配置的所有東西都用起來了
定義一個LoginController繼承自BaseController
注入JWTService方便調用
@Autowired
private JWTService jwtService;
編寫登錄的接口,首先校驗是否爲空,然後調用方法校驗密碼,如果不滿足,就直接拋出BusinessException
驗證通過後,將自定義參數加入到map中,調用createToken方法生成token,並存儲到ServletContext 中,並返回給接口調用方token
@ApiOperation("登錄")
@PostMapping("/login")
public CommonreturnType login(@RequestParam(value = "username") String username,
@RequestParam(value = "password") String password,
HttpServletRequest request) throws BusinessException, UnsupportedEncodingException {
if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
throw new BusinessException(EmBusinssError.USER_Login_Fail);
}
if (!checked(username,password)){
throw new BusinessException(EmBusinssError.USER_Login_Fail);
}
Map<String, String> userInfo = new HashMap<String, String>() {
{
put("username", username);
//自定義參數
}
};
String token = jwtService.createToken(userInfo, 1);
ServletContext context = request.getServletContext();
context.setAttribute(token, token);
return CommonreturnType.create(token);
}
退出登錄方法
只需要清除掉ServletContext 中的token,就可以使token邏輯上失效,也可以存入到redis中,這裏就不演示了
@ApiOperation("退出登錄")
@GetMapping("/logout")
public CommonreturnType logout(String token, HttpServletRequest request) {
ServletContext context = request.getServletContext();
context.removeAttribute(token);
return CommonreturnType.create("logout");
}
token驗證不通過重定向方法
@ApiIgnore
@GetMapping("/toLogin")
public CommonreturnType toLogin() throws BusinessException {
throw new BusinessException(EmBusinssError.LOGINOUTTIME);
}
單表CRUD實現
Mybatis Plus的常用操作可以去看官方的文檔
慕課網上也有一個不錯的免費課程,可以跟着熟悉下API,MyBatis-Plus入門
以用戶表爲例,對用戶表進行增刪改查和分頁查詢,代碼如下
@RestController
@RequestMapping("/api")
public class BaseDataController extends BaseController {
@Autowired
private TnUserInfoService tnUserInfoService;
@ApiOperation(value = "insert", notes = "")
@PostMapping("/user")
public CommonreturnType insertUser(@RequestBody TnUserInfoPo tnUserInfoPo) throws BusinessException {
Integer count = tnUserInfoService.lambdaQuery().
eq(TnUserInfoPo::getUserCode, tnUserInfoPo.getUserCode()).
count();//判斷主鍵是否已經存在
if (count > 0) {//主鍵衝突
throw new BusinessException(EmBusinssError.PRIMARY_KEY_EXIST);
}
boolean save = tnUserInfoService.save(tnUserInfoPo);
return CommonreturnType.create(save);
}
@ApiOperation(value = "update", notes = "")
@PutMapping("/user")
public CommonreturnType updateUser(@RequestBody TnUserInfoPo tnUserInfoPo) {
// 通過傳入實體進行修改,默認策略會排除空字段
// boolean update = tnUserInfoService.updateById(tnUserInfoPo);
// 通過條件修改指定的字段 lambda表達式
boolean update = tnUserInfoService.lambdaUpdate().
eq(TnUserInfoPo::getUserCode, tnUserInfoPo.getUserCode()).
set(TnUserInfoPo::getUserName, tnUserInfoPo.getUserName()).update();
return CommonreturnType.create(update);
}
@ApiOperation(value = "delete", notes = "")
@DeleteMapping("/user")
public CommonreturnType deleteUser(@RequestParam(value = "userCode") String userCode) {
boolean remove = tnUserInfoService.lambdaUpdate().
eq(TnUserInfoPo::getUserCode, userCode).
remove();
return CommonreturnType.create(remove);
}
@ApiOperation(value = "get", notes = "")
@GetMapping("/user/{userCode}")
public CommonreturnType getUser(@PathVariable String userCode) {
TnUserInfoPo tnUserInfoPo = tnUserInfoService.
getOne(Wrappers.<TnUserInfoPo>lambdaQuery().
eq(TnUserInfoPo::getUserCode, userCode));
return CommonreturnType.create(tnUserInfoPo);
}
@ApiOperation(value = "getPage", notes = "")
@GetMapping(value = "/user/pageNum/{pageNum}/pageSize/{pageSize}")
public CommonreturnType getUserByPage(@PathVariable Integer pageNum,
@PathVariable Integer pageSize) {
IPage<TnUserInfoPo> page = new Page<>(pageNum, pageSize);
IPage<TnUserInfoPo> pageInfo = tnUserInfoService.lambdaQuery().
select(TnUserInfoPo.class,info->!info.getColumn().equals("PASSWORD")).//排除密碼被查詢
page(page);
return CommonreturnType.create(pageInfo);
}
}
多表CRUD實現
用戶表的字段如下:
@TableId("USER_CODE")
private String userCode;
@TableField("USER_NAME")
private String userName;
@TableField("UNIT_CODE")
private String unitCode;
@TableField("ChineseName")
private String ChineseName;
@TableField("PASSWORD")
private String password;
@TableField("CREATE_TIME")
private Date createTime;
其中的UNIT_CODE對應部門表的部門id,查詢用戶信息時應該把部門名稱也一同查詢出來,所以查詢需要關聯上部門表
聯表查詢的Sql如下,不僅要查出來,我們還需要分頁查詢
SELECT
u.USER_CODE,
u.USER_NAME,
u.ChineseName,
u.UNIT_CODE,
unit.NAME unitName,
u.CREATE_TIME
FROM
`tn_user_info` u
LEFT JOIN tn_unit unit ON unit.UNIT_CODE = u.UNIT_CODE
多表查詢可以使用wrapper輔助使用,習慣了原生的Mybatis使用,所以就採用原生的開發
添加一個實體類封裝返回數據
@Data
@ApiModel(value="User多表聯查結果集映射對象", description="")
public class UserVo implements Serializable {
private String userCode;
private String userName;
private String unitCode;
private String unitName;
private String ChineseName;
private Date createTime;
}
Controller層
@ApiOperation(value = "get2", notes = "多表關聯查詢")
@GetMapping("/user2/{userCode}")
public CommonreturnType getUser2(@PathVariable String userCode) {
UserVo user = tnUserInfoService.getUser(userCode);
return CommonreturnType.create(user);
}
@ApiOperation(value = "getPage2", notes = "多表關聯分頁查詢")
@GetMapping(value = "/user2/pageNum/{pageNum}/pageSize/{pageSize}")
public CommonreturnType getUserByPage2(@PathVariable Integer pageNum,
@PathVariable Integer pageSize) {
com.github.pagehelper.Page<UserVo> userVos = tnUserInfoService.getUserByPage(pageNum, pageSize);
PageInfo<UserVo> pageInfo = new PageInfo<>(userVos);
return CommonreturnType.create(pageInfo);
}
Server層實現類
@Autowired
private TnUserInfoDao tnUserInfoDao;
@Override
public UserVo getUser(String userCode) {
return tnUserInfoDao.getUser(userCode);
}
@Override
public Page<UserVo> getUserByPage(Integer pageNum, Integer pageSize) {
PageHelper.startPage(pageNum, pageSize);
return tnUserInfoDao.getUserByPage();
}
dao層xml文件
<resultMap id="MyResultMap" type="com.example.basedemo.entity.vo.UserVo">
<id column="USER_CODE" property="userCode" />
<result column="USER_NAME" property="userName" />
<result column="UNIT_CODE" property="unitCode" />
<result column="unitName" property="unitName" />
<result column="ChineseName" property="ChineseName" />
<result column="CREATE_TIME" property="createTime" />
</resultMap>
<select id="getUser" resultMap="MyResultMap">
SELECT
u.USER_CODE,
u.USER_NAME,
u.ChineseName,
u.UNIT_CODE,
unit.NAME unitName,
u.CREATE_TIME
FROM
`tn_user_info` u
LEFT JOIN tn_unit unit ON unit.UNIT_CODE = u.UNIT_CODE
WHERE u.USER_CODE = #{userCode}
</select>
<select id="getUserByPage" resultMap="MyResultMap">
SELECT
u.USER_CODE,
u.USER_NAME,
u.ChineseName,
u.UNIT_CODE,
unit.NAME unitName,
u.CREATE_TIME
FROM
`tn_user_info` u
LEFT JOIN tn_unit unit ON unit.UNIT_CODE = u.UNIT_CODE
</select>
Swagger2測試
測試前可以設置是否啓用JWT驗證
在登錄攔截器中注入當前的配置文件環境
/**
* 當前激活的配置文件
*/
@Value("${spring.profiles.active}")
private String env;
在前置方法裏面加上放行判斷
//認證驗證
if ("dev".equals(env)) { //開發環境忽略簽名認證
return true;
}
如果是開發環境下不啓動登錄攔截器
沒有攔截器時swagger-ui.htm的測試文件進行測試,針對上傳下載會比較友好,而加上了攔截器需要傳token時可以採用doc.html的接口文檔,可以設置全局的header,方便測試
登錄測試,成功生成token
分頁查詢,查詢多表關聯查詢的
單表的分頁查詢
新增用戶
PostMan測試
相比自動生成的測試接口,使用Postman會更加的靈活
針對一個項目的接口測試,可以創建一個Collection,然後所有的接口都放在裏面
點擊new Collection創建一個Collection
然後再我們剛纔編寫完測試的頁面點擊Save
選中我們新建的Collection
接口就保存好了
點擊右鍵可以修改請求的名稱,方便管理
當啓用jwt驗證時,可以通過給Collection配置全局的header,在裏面攜帶我們的token
點擊
選中Api key
進行一次登錄,然後把token存入header中,保存後再去訪問其他接口就會自動攜帶jwt
一個較爲完善的測試接口文檔如下
項目部署
前後端的開發通常都不是一個人能完成的,爲了方便接口調用者使用,接口寫好後需要部署到服務器
如果是高校學生還沒有云服務器的可以去這裏看看,免費領12個月
最簡單方便的就是打成一個jar包拖服務器上啓動就好了,Spring Boot支持打成Jar包,內置有tomcat,服務器只需要java運行環境就可以了
點擊maven-package打包
在target目錄下找到jar包
將這個jar上傳到服務器上
輸入nohup java -jar basedemo-0.0.1-SNAPSHOT.jar &
將jar包在後臺掛起
爲了方便使用,可以編寫啓動和停止的腳本,或者使用docker進行部署
有時間了再來慢慢完善
更新 2020年5月4日14:57:34