三十二、Swagger介紹&使用
官網:https://swagger.io/
什么是swagger
Swagger是一個接口文檔生成工具,它可以幫助開發者自動生成接口文檔。當項目的接口發生變更時,Swagger可以實時更新文檔,確保文檔的準確性和時效性。Swagger還內置了測試功能,開發者可以直接在文檔中測試接口,無需編寫額外的測試代碼。
引入swagger
- 在oj-common下創建oj-common-swagger子module
- 導入依賴(放在這個模塊的pom文件就可以了)
-
<dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-starter-webmvc-ui</artifactId><version>2.2.0</version> </dependency>
- 錄入配置
-
@Configuration public class SwaggerConfig {@Beanpublic OpenAPI openAPI() {return new OpenAPI().info(new Info().title("在線oj系統").description("在線oj系統接??檔").version("v1"));} }
具體服務引?:
由于system模塊要用到這個swagger組件,
因此將剛剛封裝的swagger的這個組件也引入到要用的微服務中也就是引入到
system的pom文件中。
<dependency><groupId>com.qyy</groupId><artifactId>oj-common-swagger</artifactId><version>${oj-common-swagger.version}</version></dependency>
swagger基本注解
@Tag
- 介紹:用于給接口分組,用途類似于為接口文檔添加標簽。
- 用于:方法、類、接口。
- 常用屬性:
name
:分組的名稱@RestController @RequestMapping("/sysUser") @Tag(name = "管理員接口") public class SysUserController extends BaseController {
@Operation
- 介紹:用于描述接口的操作。
- 用于:方法。
- 常用屬性:
summary
:操作的摘要信息。description
:操作的詳細描述。? @Operation(summary = "管理員登錄", description = "根據賬號密碼進行管理員登錄")//controller層如果介紹的是body 參數 需要使用@RequestBody注解public R<String> login(@RequestBody LoginDTO loginDTO) {return sysUserService.login(loginDTO.getUserAccount(), loginDTO.getPassword());}?
@Parameters
- 介紹:用于指定
@Parameter
注解對象數組,描述操作的輸入參數。- 用于:方法。
@Parameters(value = {@Parameter(name = "userId", in = ParameterIn.PATH, description = "用戶ID")})public R<Void> delete(@PathVariable Long userId) {return null;}
@Parameter
- 介紹:用于描述輸入參數。
- 用于:方法。
- 常用屬性:
name
:參數的名稱。in
:參數的位置,可以是path
、query
、header
、cookie
中的一種。description
:參數的描述。@Parameters(value = {@Parameter(name = "userId", in = ParameterIn.PATH, description = "用戶ID")})public R<Void> delete(@PathVariable Long userId) {return null;}
@ApiResponse
- 介紹:用于描述操作的響應結果。
- 用于:方法。
- 常用屬性:
- responseCode:響應的狀態碼。
- description:響應的描述。
@ApiResponse(responseCode = "1000", description = "操作成功")@ApiResponse(responseCode = "2000", description = "服務繁忙請稍后重試")@ApiResponse(responseCode = "3102", description = "用戶不存在")@ApiResponse(responseCode = "3103", description = "用戶名或密碼錯誤")//controller層如果介紹的是body 參數 需要使用@RequestBody注解public R<String> login(@RequestBody LoginDTO loginDTO) {return sysUserService.login(loginDTO.getUserAccount(), loginDTO.getPassword());}
@Schema
- 介紹:用于描述數據模型的屬性。
- 用于:方法、類、接口。
- 常用屬性:
- description:響應的描述。
@Getter @Setter public class SysUserSaveDTO {@Schema(description = "用戶賬號")private String userAccount;@Schema(description = "用戶密碼")private String password; }
為了讓SwaggerConfig生效(外部bean讓Spring能掃描到)
在oj-common-swagger模塊下的 resources 下創建
META-INF.spring包
再創建org.springframework.boot.autoconfigure.AutoConfiguration.imports?件
在里面寫上路徑
com.qyy.swagger.SwaggerConfig;
生成當前接口文檔的地址
服務器運行之后,在瀏覽器輸入地址:例如我的地址就是
http://localhost:1208/swagger-ui/index.html
三十三、 管理員登錄-接口測試01
如何用swagger進行測試呢
我們使用login接口進行測試
測試內容如下:
1.登錄成功
2.賬號和密碼錯誤
controller層
代碼如下,傳入兩個參數
loginDTO.getUserAccount(), loginDTO.getPassword()
用戶名和密碼
DTO就代表是前端傳給服務器的數據
@PostMapping("/login") //安全@Operation(summary = "管理員登錄", description = "根據賬號密碼進行管理員登錄")@ApiResponse(responseCode = "1000", description = "操作成功")@ApiResponse(responseCode = "2000", description = "服務繁忙請稍后重試")@ApiResponse(responseCode = "3102", description = "用戶不存在")@ApiResponse(responseCode = "3103", description = "用戶名或密碼錯誤")//controller層如果介紹的是body 參數 需要使用@RequestBody注解public R<String> login(@RequestBody LoginDTO loginDTO) {return sysUserService.login(loginDTO.getUserAccount(), loginDTO.getPassword());}
Service層
ISysUserService類
這是接口
R<String> login(String userAccount, String password);
SysUserServiceImpl類
這是接口的實現
@Override//維護性、性能、安全public R<String> login(String userAccount, String password) {
// try {
// FileInputStream inputStream = new FileInputStream("a.txt");
// } catch (FileNotFoundException e) {
// throw new RuntimeException(e);
// }
// int a = 100 / 0;//通過賬號去數據庫中查詢,對應的用戶信息LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();//select password from tb_sys_user where user_account = #{userAccount}SysUser sysUser = sysUserMapper.selectOne(queryWrapper.select(SysUser::getUserId, SysUser::getPassword, SysUser::getNickName).eq(SysUser::getUserAccount, userAccount));
// R loginResult = new R();if (sysUser == null) {
// loginResult.setCode(ResultCode.FAILED_USER_NOT_EXISTS.getCode());
// loginResult.setMsg(ResultCode.FAILED_USER_NOT_EXISTS.getMsg());
// return loginResult;return R.fail(ResultCode.FAILED_USER_NOT_EXISTS);}if (BCryptUtils.matchesPassword(password, sysUser.getPassword())) {
// loginResult.setCode(ResultCode.SUCCESS.getCode());
// loginResult.setMsg(ResultCode.SUCCESS.getMsg());
// return loginResult;// jwttoken = 生產jwttoken的方法return R.ok(tokenService.createToken(sysUser.getUserId(),secret, UserIdentity.ADMIN.getValue(), sysUser.getNickName(), null));}
// loginResult.setCode(ResultCode.FAILED_LOGIN.getCode());
// loginResult.setMsg(ResultCode.FAILED_LOGIN.getMsg());
// return loginResult;return R.fail(ResultCode.FAILED_LOGIN);}
點擊try it out 就可以傳入用戶名和密碼
如圖返回結果是用戶名和密碼錯誤
這是因為數據庫中我們存儲的密碼并不是加密后的。因此密碼對應不上
三十四、Apifox介紹&使用
Apifox = Postman + Swagger + Mock + JMeter
Apifox 簡介
Apifox 是一款集 API 文檔管理、調試、Mock、自動化測試于一體的開發工具,旨在為開發者、測試人員和前端/后端工程師提供一站式 API 開發解決方案。它結合了類似 Postman 的 API 調試功能、Swagger 的文檔生成功能以及 Mock 數據服務,極大地提升了團隊協作效率和 API 開發體驗。
添加接口
設置環境
設置值
讀取變量
發送數據(測試成功)
設置響應數據模型
在響應中引入數據模型
隱藏字段
解除關聯
單個解除關聯:就可以對響應數據進行修改,但不能修改名稱
整體解除關聯:那么就都可以修改了
Apifox 的核心功能
1. API 文檔管理
- Apifox 支持基于 OpenAPI(原 Swagger)標準的 API 文檔定義。
- 提供直觀的可視化界面,方便開發者快速編寫和維護 API 文檔。
- 自動生成 API 請求示例代碼(如 cURL、JavaScript、Python 等),便于集成到不同開發環境。
- 支持團隊協作,多人可以同時編輯和查看文檔。
2. API 調試
- 類似于 Postman 的功能,允許用戶直接在 Apifox 中發送 HTTP 請求并查看響應結果。
- 支持多種請求方法(GET、POST、PUT、DELETE 等)和復雜的請求參數設置。
- 內置環境變量管理功能,方便切換不同的開發、測試和生產環境。
- 自動保存歷史記錄,便于回溯和復用請求。
3. Mock 數據
- 根據 API 文檔自動生成 Mock 數據,無需手動配置。
- 支持自定義 Mock 規則,滿足復雜的業務需求。
- Mock 服務支持動態生成數據,例如隨機字符串、時間戳、枚舉值等。
- 無需后端開發完成即可進行前端開發,提升開發效率。
4. 自動化測試
- 提供強大的 API 自動化測試功能,支持斷言、參數提取、全局變量等功能。
- 測試用例可按順序執行,支持批量運行。
- 測試結果以清晰的報告形式展示,便于分析和優化。
- 支持 CI/CD 集成,將自動化測試嵌入到持續集成流程中。
5. 團隊協作
- 支持多人在線協作,團隊成員可以共享 API 文檔、測試用例和環境配置。
- 提供權限管理功能,確保不同角色的用戶只能訪問其權限范圍內的資源。
- 版本控制功能,支持 API 文檔的歷史版本管理。
6. 插件與擴展
- Apifox 提供豐富的插件生態,用戶可以根據需求擴展功能。
- 支持與其他工具(如 Git、Jenkins 等)集成,進一步增強開發和測試能力。
Apifox 的優勢
-
一體化工具
Apifox 將 API 文檔管理、調試、Mock 和測試功能整合到一個平臺中,避免了在多個工具之間切換的麻煩。 -
易用性
提供直觀的圖形化界面,即使是初學者也能快速上手。 -
高效開發
Mock 數據和自動化測試功能顯著縮短了前后端聯調的時間,提高了開發效率。 -
團隊協作友好
強大的團隊協作功能使得跨部門溝通更加順暢,減少了信息不對稱的問題。 -
跨平臺支持
Apifox 支持 Windows、macOS 和 Linux,覆蓋主流操作系統。
適用場景
-
前后端分離項目
在前后端分離的開發模式下,Apifox 可以幫助前端開發者通過 Mock 數據快速啟動開發,而無需等待后端接口完成。 -
API 文檔維護
對于需要長期維護的 API 項目,Apifox 提供了一個集中化的平臺來管理所有 API 文檔。 -
接口測試與自動化
測試人員可以利用 Apifox 的自動化測試功能對 API 進行回歸測試,確保接口的穩定性和可靠性。 -
微服務架構
在微服務架構中,Apifox 可以幫助團隊統一管理多個服務的 API 接口,并提供高效的調試和測試能力。
Apifox vs 其他工具
總結
Apifox 是一款功能強大且易于使用的 API 工具,特別適合需要高效協作和全流程 API 管理的團隊。無論是開發、測試還是文檔維護,Apifox 都能夠為用戶提供極大的便利。如果你正在尋找一款能夠替代 Postman 和 Swagger 的工具,Apifox 無疑是一個值得嘗試的選擇。
三十五、管理員登錄-代碼優化-加密算法介紹
管理員登錄:還要考慮可維護性、性能、安全
這里重點討論
安全問題
數據庫中我們如果存儲密碼的明文,那么黑客如果拿到數據庫,就可以隨意登錄管理員頁面,或者即使是內部人員看到也是不可以的。
簡介
加密算法是一種將明文轉換為密文的過程,以保護數據的安全性和機密性。
作用
- 安全性:存儲明文密碼是非常不安全的。如果數據庫被非法訪問或泄露,攻擊者可以直接獲取所有用戶的密碼。
- 減少內部風險:即使企業內有不誠實的員工或管理員,他們也無法輕易獲取其他管理員的密碼,因為密碼是加密存儲的。
- 提升用戶信任度。密碼加密有助于提升用戶對網站或應用程序的信任感,使其更愿意使用加密保護的網站。
常見的加密算法:
- 可逆算法:一種可以將加密后的密文還原為原始明文的算法。
- 對稱算法:對稱加密(也叫私鑰加密)指加密和解密使用相同密鑰的加密算法。它要求發送方和接收方在安全通信之前,商定一個密鑰。對稱算法的安全性依賴于密鑰,泄漏密鑰就意味著任何人都可以對他們發送或接收的消息解密,所以密鑰的保密性對通信的安全性至關重要。
- 非對稱算法:非對稱加密是指需要兩個密鑰來進行加密和解密,這兩個秘鑰分別是公鑰(public key)和私鑰(private key),如果用公鑰對數據進行加密,只有用對應的私鑰才能解密。
- 不可逆算法:一種無法將加密后的密文還原為原始明文的算法。
- 單向散列(hash)加密:是指把任意長的輸入串變化成固定長的輸出串,并且由輸出串難以得到輸入串的加密方法。廣泛應用于對敏感數據加密,比如用戶密碼,請求參數,文件加密等。
BCrypt
Bcrypt是一種哈希加密算法,被廣泛應用于存儲密碼和進行身份驗證。并且Bcrypt算法包含一個重要特性即每次生成的哈希值都不同,這是由于Bcrypt算法在計算時會先生成一個隨機的鹽值與用戶密碼一起參與計算最終得到一個加密后的字符串。由于生成的鹽值是隨機的,所以即使每次使用相同的密碼得到結果也是不同的。這樣可以有效的防止攻擊者使用一些手段破解用戶密碼。
package com.qyy.system.utils;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;/*** 加密算法工具類*/
public class BCryptUtils {/*** 生成加密后密文** @param password 密碼* @return 加密字符串*/public static String encryptPassword(String password) {BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();return passwordEncoder.encode(password);// 加密}/*** 判斷密碼是否相同** @param rawPassword 真實密碼* @param encodedPassword 加密后密文* @return 結果*/public static boolean matchesPassword(String rawPassword, String encodedPassword) {BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();return passwordEncoder.matches(rawPassword, encodedPassword);// 密碼匹配}public static void main(String[] args) {System.out.println(encryptPassword("123"));System.out.println(matchesPassword("123", "$2a$10$Nm0bAesQKuPt5CWijLVbmOb5FXxRuoFUbHNwupVp.8DqbYQjf8iUW"));}}
}
三十六、管理員登錄-代碼優化-加密算法使用
在SysUserServiceImpl類中
修改login方法,借助BCryptUtils.matchesPassword()方法,來判斷
用戶輸入密碼是否正確
@Override//維護性、性能、安全public R<String> login(String userAccount, String password) {//通過賬號去數據庫中查詢,對應的用戶信息LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();//select password from tb_sys_user where user_account = #{userAccount}SysUser sysUser = sysUserMapper.selectOne(queryWrapper.select(SysUser::getUserId, SysUser::getPassword, SysUser::getNickName).eq(SysUser::getUserAccount, userAccount));return R.fail(ResultCode.FAILED_USER_NOT_EXISTS);}if (BCryptUtils.matchesPassword(password, sysUser.getPassword())) {return R.ok();}return R.fail(ResultCode.FAILED_LOGIN);}
測試成功
封裝返回結果
封裝返回方法
package com.qyy.common.core.domain;import com.qyy.common.core.enums.ResultCode;
import lombok.Getter;
import lombok.Setter;//接口文檔
@Getter
@Setter
public class R<T> {private int code; //定義一些固定的code,前后端商量好的 0 1 請求成功 常量 2 3 枚舉private String msg; //? 通常是code的輔助說明 一個code 對應一個msgprivate T data; //請求某個接口返回的數據list SysUser 泛型//將封裝result的封裝方法可以寫到R里面
// loginResult.setCode(ResultCode.FAILED_USER_NOT_EXISTS.getCode());
// loginResult.setMsg(ResultCode.FAILED_USER_NOT_EXISTS.getMsg());
// return loginResult;public static <T> R<T> ok() {return assembleResult(null, ResultCode.SUCCESS);}public static <T> R<T> ok(T data) {return assembleResult(data, ResultCode.SUCCESS);}public static <T> R<T> fail() {return assembleResult(null, ResultCode.FAILED);}public static <T> R<T> fail(int code, String msg) {return assembleResult(code, msg, null);}/*** 指定錯誤碼** @param resultCode 指定錯誤碼* @param <T>* @return*/public static <T> R<T> fail(ResultCode resultCode) {return assembleResult(null, resultCode);}private static <T> R<T> assembleResult(T data, ResultCode resultCode) {R<T> r = new R<>();r.setCode(resultCode.getCode());r.setData(data);r.setMsg(resultCode.getMsg());return r;}private static <T> R<T> assembleResult(int code, String msg, T data) {R<T> r = new R<>();r.setCode(code);r.setData(data);r.setMsg(msg);return r;}
}
三十七、全局異常處理
思考一下,前面寫的代碼還有啥問題:
就是我們沒有考慮異常出現的時候
例如:
發現代碼中有
int a = 100 / 0;
項目依然可以跑起來,但是實際上我們都知道,代碼執行到這里可以是會報錯的。
那么如果這個錯誤不明顯,不像int a = 100 / 0;
那么我們要找到這個錯誤是一件很困難的事情
注意:判斷密碼錯誤等不屬于異常,這只是邏輯錯誤的判斷
如果都加try catch會讓代碼變得很丑陋,因此我們要使用全局異常處理
異常:
- 編譯時異常
- 運行時異常
在oj-common下,創建oj-common-security子工程
創建GlobalExceptionHandler類
@RestControllerAdvice注解
:當拋出異常時,
@RestControllerAdvice
標注的類將被自動調用,并根據異常類型和處理程序的注解來決定如何處理該異常。這使得開發者可以在整個應用程序范圍內統一處理異常。
@ExceptionHandler注解
:
@ExceptionHandler
一般與@RestControllerAdvice
配合使用,使用其來捕獲和處理不同類型的異常。注意:
1.我們盡量將拋出的異常都使用自定義異常,這樣便于在異常處理處進行異常處理,比如統一返回json格式,或者統一進行日志記錄等。
2.對于其他微服務來講,它也是外部bean,因此要加上這個文件,才能將這個bean交給Spring容器去管理
3.為了讓用戶不看到我們的后臺具體出什么問題了,因此就返回一個服務器異常就行了,而我們程序員就看日志的錯誤就行了
package com.qyy.common.security.handler;import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.qyy.common.core.domain.R;
import com.qyy.common.core.enums.ResultCode;
import com.qyy.common.security.exception.ServiceException;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;import java.util.Collection;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;/*** 全局異常處理器*/
@RestControllerAdvice//全局異常處理器
@Slf4j
//我們盡量將拋出的異常都使用自定義異常,這樣便于在異常處理處進行異常處理,比如統一返回json格式,或者統一進行日志記錄等。
public class GlobalExceptionHandler {/*** 請求方式不支持*/@ExceptionHandler(HttpRequestMethodNotSupportedException.class)public R<?> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,HttpServletRequest request) {String requestURI = request.getRequestURI();log.error("請求地址'{}',不支持'{}'請求", requestURI, e.getMethod());return R.fail(ResultCode.ERROR);}//攔截業務異常@