SpringBoot3.x使用Swagger

SpringBoot3.x使用Swagger

  • 當(dāng)前開發(fā)主流是前后端分離,有完整文檔可以使團隊配合更加流暢
  • Spring生態(tài)中通常使用springfox抒钱,但是當(dāng)前springfox并不支持SpringBoot3.x版本
  • 使用替代產(chǎn)品:Springdoc.org
    • Springdoc在v1.7.0版本之后不支持SpringBoot2.x和1.xD北摇@俣睢诅蝶!
  • 項目啟動后调炬,Swagger默認(rèn)地址:http://localhost:8080/swagger-ui/index.html
  • 版本:
    • Java: 17.0.7
    • SpringBoot: 3.1.5
    • springdoc: 2.2.0
  • 同步發(fā)布BiliBili視頻

設(shè)置

  • Springdoc同時支持WebMvc和WebFlux
  • 因為沒用WebFlux實踐過缰泡,所以下面只介紹WebMvc中的應(yīng)用

依賴(pom.xml)

  • 實際開發(fā)中习柠,通常會和校驗搭配使用
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.2.0</version>
</dependency>
<!--  搭配校驗使用资溃,使用與SpringBoot相同的版本號  -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
    <version>3.1.5</version>
</dependency>
  • 示例中使用的其他依賴
<!--  SpringBoot  -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.1.5</version>
</dependency>
<!-- 讓響應(yīng)結(jié)果更美觀 -->
<dependency>
    <groupId>com.alibaba.cola</groupId>
    <artifactId>cola-component-dto</artifactId>
    <version>4.3.2</version>
</dependency>
<!--  lombok  -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.26</version>
    <optional>true</optional>
</dependency>
<!--  日志  -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.4.11</version>
</dependency>

環(huán)境配置(application.yml)

  • 開發(fā)環(huán)境
  • 開發(fā)環(huán)境通常會開啟Swagger文檔宝恶,方便前端查閱文檔
  • 如果使用微服務(wù)垫毙,為避免Swagger地址沖突拱绑,通常會加上前綴
    • 如鑒權(quán)服務(wù): "/auth-service/v3/api-docs"和"/auth-service/swagger-ui/index.html"
    • 如用戶服務(wù): "/user-service/v2/api-docs"和"/user-service/swagger-ui/index.html"
springdoc:
  api-docs:
    enabled: true # 開啟OpenApi接口
    path: /user-service/v3/api-docs  # 自定義路徑猎拨,默認(rèn)為 "/v3/api-docs"
  swagger-ui:
    enabled: true # 開啟swagger界面,依賴OpenApi国觉,需要OpenApi同時開啟
    path: /user-service/swagger-ui/index.html # 自定義路徑麻诀,默認(rèn)為"/swagger-ui/index.html"
  • 生產(chǎn)環(huán)境
  • 切記生產(chǎn)環(huán)境要關(guān)閉文檔
springdoc:
  api-docs:
    enabled: false # 關(guān)閉OpenApi接口
  swagger-ui:
    enabled: false # 關(guān)閉swagger界面

配置

  • 在SwaggerUI中增加描述
  • 項目中新建"SwaggerConfig.java"文件
@Configuration
public class SwaggerConfig {
    @Bean
    public OpenAPI swaggerOpenApi() {
        return new OpenAPI()
                .info(new Info().title("XXX平臺YYY微服務(wù)")
                        .description("描述平臺多牛逼")
                        .version("v1.0.0"))
                .externalDocs(new ExternalDocumentation()
                        .description("設(shè)計文檔")
                        .url("https://juejin.cn/user/254742430749736/posts"));
    }
}
  • 全局異常處理
  • 參數(shù)校驗錯誤返回提示信息
  • 捕捉Exception大類蝇闭,后面示例中補充更詳細(xì)的類
  • 響應(yīng)結(jié)果Response為"cola-component-dto"中的一個包裝類
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(Exception.class)
    public Response handleException(Exception e) {
        log.warn("未知異常", e);
        return Response.buildFailure("未知異常", e.getMessage());
    }
}

Restful接口

Post

  • Post用于新增
  • 通常使用body傳遞參數(shù)

用戶類

  • 新增用戶模型丁眼,說明
    • @Data: Lombok的寫法苞七,可以省略get/set
    • @Schema: Swagger文檔的注解蹂风,用于說明類/字段
      • title: 類/字段說明
      • example: 示例惠啄,Swagger中會將這個字段作為示例
      • minLength/maxLength: 最小/最大長度任内,字段為String類型時生效(僅用于文檔說明死嗦,不會拋出異常)
      • minimum/maximum: 最小/最大值越除,字段為數(shù)字時有效(僅用于文檔說明摘盆,不會拋出異常)
    • @NotBlank: 校驗不能為空(String生效)孩擂,為空或空字符則拋出異常。文檔將該字段解析為必填項
    • @NotNull: 校驗不能為空(包裝類生效囤锉,如Integer/Long/Boolean)官地,為空將拋出異常驱入。文檔將該字段解析為必填項
    • @Range: 檢查數(shù)值范圍亏较,不符合將拋出異常
@Data
@Schema(title = "新增用戶模型")
public class UserAddCO {
    @Schema(title = "名字", example = "老王", minLength = 1, maxLength = 5)
    @NotBlank(message = "名字不能為空")
    private String name;

    @Schema(title = "年齡", example = "18", minimum = "0", maximum = "150")
    @NotNull(message = "年齡不能為空")
    @Range(min = 0, max = 150, message = "年齡在0~150之間")
    private Integer age;
//    private int age;
//    既然不能為空雪情,為什么不使用int巡通?
//    1. 因為int是基本類型不會為空舍哄,所以@NotNull校驗無效
//    2. 類初始化時生成默認(rèn)值(int默認(rèn)為0)表悬,@Range中最小值包含0蟆沫,所以沒有age參數(shù)校驗通過饭庞,不符合預(yù)期

    @Schema(title = "電話(可選)")
    private String phone;
}

請求方法

  • @Tag: 控制器說明
    • name: 名稱
    • description: 描述說明
  • @PostMapping: 使用post方法但绕,一般用于新增記錄
  • @Operation: 請求說明
    • summary: 說明捏顺,Swagger頁面在方法后面幅骄,不會被折疊
    • descirption: 描述拆座,會被折疊到方法說明中
  • @Validated: 校驗數(shù)據(jù)冠息,有了這個注解模型中的@NotBlank@NotNull@Range才會生效
  • @RequestBody: 從請求body中讀取數(shù)據(jù)
@RestController
@RequestMapping("/demo")
@Tag(name = "示例控制器", description = "演示Restful接口")
public class DemoController {
    @PostMapping("/")
    @Operation(summary = "Post方法示例", description = "Post通常用于新增")
    public SingleResponse<Long> add(@Validated @RequestBody UserAddCO user) {
        // TODO:添加到數(shù)據(jù)庫中逛艰,然后返回記錄id
        return SingleResponse.of(1L);
    }
}

異常處理

  • BindException: 如@NotBlank@NotNull@Range等校驗失敗時會報該異常
  • HttpMessageConversionException: 轉(zhuǎn)換失敗時散怖,如age參數(shù)傳入字符串會報該異常
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BindException.class)
    public Response handleBindException(BindException e) {
        // 拼接錯誤信息镇眷,用于多個校驗不通過的錯誤信息拼接
        List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
        String message = allErrors.stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.joining(";"));
        log.info("參數(shù)校驗不通過:{}", message);
        return Response.buildFailure("參數(shù)校驗不通過", message);
    }

    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpMessageConversionException.class)
    public Response handleHttpMessageConversionException(HttpMessageConversionException e) {
        log.info("參數(shù)轉(zhuǎn)換失斍范:{}", e.getMessage());
        return Response.buildFailure("參數(shù)轉(zhuǎn)換失敗", e.getMessage());
    }
}

Post(FormData)

  • 上傳通常使用FormData上傳數(shù)據(jù)
  • @PostMapping: 上傳使用Post
    • consumes: 這個值一定要設(shè)置成"MediaType.MULTIPART_FORM_DATA_VALUE"具伍,否則Swagger將錯誤識別為json格式(file字段錯誤識別為string)沿猜,而不是FormData
  • @RequestPart: 比較少用到啼肩,就是用于FormData
  • MultipartFile: 接收文件的對象祈坠,可以將流保存到本地
    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @Operation(summary = "上傳文件示例")
    public SingleResponse<String> upload(@RequestPart("file") MultipartFile file) {
        log.info("接收到文件:{}, 大惺妇ⅰ:{}", file.getOriginalFilename(), file.getSize());
        try {
            // 保存到本地
            File localTempFile = new File(file.getOriginalFilename());
            localTempFile.createNewFile();
            file.transferTo(localTempFile);
            return SingleResponse.of(localTempFile.getAbsolutePath());
        } catch (IOException e) {
            throw new RuntimeException("文件上傳失敗");
        }
    }

    @PostMapping(value = "/upload/multi", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @Operation(summary = "上傳多個文件示例")
    public MultiResponse<String> uploadMulti(@RequestPart("files") MultipartFile[] files) {
        List<String> result = new LinkedList<>();
        for (MultipartFile file : files) {
            log.info("接收到文件:{}, 大刑赏:{}", file.getOriginalFilename(), file.getSize());
            result.add(file.getOriginalFilename());
            // TODO:如上保存到本地
        }
        return MultiResponse.of(result);
    }

Get(分頁)

  • Get常用于獲取分頁數(shù)據(jù)

用戶詳情類(響應(yīng)結(jié)果)

  • 用戶詳情
  • @Schema: 字段/類的Swagger描述注解
    • title: 字段/類說明蹋艺,實際開發(fā)中捎谨,這個是必填涛救,其他為可選
    • 如example/minLength/maxLength/minimum/maximum等為可選項,部分HTTP工具可以利用這些描述生成Mock數(shù)據(jù)
@Data
@Schema(title = "用戶詳情")
public class UserDetailCO {
    @Schema(title = "用戶id", minimum = "1")
    private long id;

    @Schema(title = "用戶名", minLength = 1, maxLength = 5, example = "言午日堯耳總")
    private String name;

    @Schema(title = "手機")
    private String phone;
}

請求分頁類

  • @Parameter: QueryString的參數(shù)定義
    • required: 是否必填
    • description: 描述
    • example: 示例值,部分HTTP工具請求時會使用這個當(dāng)做默認(rèn)值(開發(fā)自測調(diào)試逆甜,不用每次寫參數(shù))
@Data
public class UserPageQry {
    @Parameter(required = true, description = "頁碼,從1開始", example = "1")
    @Min(value = 1, message = "pageIndex必須大于1")
    private int pageIndex;
//    @NotNull(message="pageIndex參數(shù)必須填寫")
//    private Integer pageIndex;
//    1. 此處可以使用int類型斟或,因為限制最小值為1萝挤,不填寫默認(rèn)賦值0怜珍,@Min校驗不通過會拋出異常
//    2. 也可以寫成Integer+@NotNull的組合(如下pageSize)酥泛,更加語義化

    @Parameter(required = true, description = "頁面大小", example = "20")
    @NotNull(message = "pageSize參數(shù)必須填寫")
    @Range(min = 1, max = 100, message = "pageSize必須在1-100之間")
    private Integer pageSize;

    @Parameter(description = "搜索名字(模糊搜索)柔袁,不搜索就傳null或空字符")
    private String phone;
}

請求方法1(推薦)

  • Operation: 請求描述
  • @Validated: 啟用校驗
  • @ParameterObject: 參數(shù)對象捶索,這個注解可以將對象字段解析為QueryString參數(shù)
  • PageResponse: 分頁響應(yīng)結(jié)果腥例,Swagger中解析響應(yīng)字段
    @GetMapping("/")
    @Operation(summary = "獲取分頁列表")
    public PageResponse<UserDetailCO> getPageList(@Validated @ParameterObject UserPageQry qry) {
        log.info("{}", qry);
        List<UserDetailCO> result = List.of(new UserDetailCO());
        return PageResponse.of(result, 1, qry.getPageSize(), qry.getPageIndex());
    }

請求方法2(不推薦燎竖,沒有方法1優(yōu)雅)

  • 字段單獨設(shè)置
  • @Validated: 控制器上的@Validated必須設(shè)置底瓣,否則校驗注解無效
  • @Operation: 方法說明
  • @Parameter: 對每一個字段進行描述
    • name: 與字段名一一對應(yīng)捐凭,才能正確解析
    • description: 描述
    • example: 示例值茁肠,部分HTTP工具請求時會使用這個當(dāng)做默認(rèn)值(開發(fā)自測調(diào)試,不用每次寫參數(shù))
@Slf4j
@Validated // 控制器加上改注解到才能進行校驗
@RestController
@RequestMapping("/demo")
@Tag(name = "示例控制器", description = "演示Restful接口")
public class DemoController {
    @GetMapping("/other/")
    @Operation(summary = "另一種獲取分頁列表方式")
    @Parameter(name = "pageIndex", description = "頁碼,從1開始", example = "1")
    @Parameter(name = "pageSize", description = "數(shù)量印蓖,不小于1", example = "20")
    @Parameter(name = "name", description = "搜索名字(模糊搜索)赦肃,不搜索就傳null或空字符")
    public PageResponse<UserDetailCO> getPage(@NotNull(message = "pageIndex不能為空") @Min(value = 1, message = "pageIndex必須大于1") Integer pageIndex,
                                              @NotNull(message = "pageIndex不能為空") @Range(min = 1, max = 100, message = "pageSize必須在1-100之間") Integer pageSize,
                                              @Nullable String name) {
        log.info("{} {} {}", pageIndex, pageSize, name);
        List<UserDetailCO> result = List.of(new UserDetailCO());
        return PageResponse.of(result, 1, pageSize, pageIndex);
    }
}
  • 異常處理
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ValidationException.class)
    public Response handleValidationException(ValidationException e) {
        log.info("參數(shù)驗證失敗: {}", e.getMessage());
        return Response.buildFailure("參數(shù)驗證失敗", e.getMessage());
    }

Get(一條記錄詳情)

  • @GetMapping: Restful風(fēng)格他宛,在請求路徑中放id
  • @PathVariable: 讀取路徑中的id
    • 接收參數(shù)可以使用long這樣的基本類型厅各,id必定不為空(如果為空队塘,路徑錯誤不會進入該方法)
    @GetMapping("/{id}/")
    @Operation(summary = "獲取用戶詳情")
    public SingleResponse<UserDetailCO> getUser(@PathVariable("id") long id) {
        log.info("{}", id);
        return SingleResponse.of(new UserDetailCO());
    }

Put/Patch

  • 區(qū)別
    • Put: 修改對象的所有參數(shù)
    • Patch: 修改對象的部分參數(shù)
  • id從路徑中讀取人灼,遵循Restful風(fēng)格
  • 值從body中讀取
    • body接收對象UserPutCO包含User的所有字段投放,UserPatchCO只包含一部分字段
    @PutMapping("/{id}/")
    @Operation(summary = "修改參數(shù)")
    public Response putUser(@PathVariable("id") long id, @Validated @RequestBody UserPutCO user) {
        // TODO:使用id在數(shù)據(jù)查找到用戶灸芳,然后修改值并保存
        return Response.buildSuccess();
    }

    @PatchMapping("/{id}/")
    @Operation(summary = "修改部分參數(shù)")
    public Response patchUser(@PathVariable("id") long id, @Validated @RequestBody UserPatchCO user) {
        // TODO:使用id在數(shù)據(jù)查找到用戶烙样,然后修改值并保存
        return Response.buildSuccess();
    }

Delete

    @DeleteMapping("/{id}/")
    @Operation(summary = "刪除")
    public Response deleteUser(@PathVariable("id") long id) {
        // TODO:根據(jù)id直接刪除用戶
        return Response.buildSuccess();
    }

其他

屏蔽過濾器

  • 用于前后端分離項目
  • 有時候為了鑒權(quán)方便谒获,會使用全局過濾器過濾權(quán)限
  • 將Swagger相關(guān)接口排除掉
@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Bean
    public JwtInterceptor jwtInterceptor() {
        return new JwtInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 除了swagger和登錄批狱,其他全部攔截驗證jwt
        registry.addInterceptor(jwtInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns(
                        "/**/*.html",
                        "/**/*.js",
                        "/**/*.css",
                        "/**/*.woff",
                        "/**/*.ttf",
                        "/**/*.js",
                        "/**/*.map",
                        "/**/*.png",
                        "/v3/api-docs", // 如果配置里改了赔硫,這里也記得修改
                        "/v3/api-docs/swagger-config",
                        "/auth/login"); // 登錄接口
    }
}

不同風(fēng)格注解

  • 將所有配置寫在控制器中
@Tag(name = "User", description = "用戶管理")
@RestController
@RequestMapping("/user")
public class UserController {
    // 這個樣式看起來更整齊
    @Operation(summary = "獲取用戶信息", description = "用戶詳情")
    @Parameter(name = "id", description = "用戶id")
    @ApiResponse(responseCode = "200", description = "用戶信息")
    @GetMapping("/{id}")
    public Resp<?> detail(@PathVariable("id") Integer id) {
        User user = new User();
        return new Resp<>(user);
    }

    @Operation(summary = "新增", description = "新增用戶")
    @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "用戶")
    @ApiResponse(responseCode = "201", description = "成功")
    @PostMapping("/")
    public Resp<User> add(@RequestBody User user) {
        return new Resp<>(user);
    }

    // 寫到一起的示例
    @Operation(summary = "獲取列表",
            description = "獲取用戶列表",
            parameters = {@Parameter(name = "page", description = "頁碼"),
                    @Parameter(name = "size", description = "每頁數(shù)量")},
            responses = {@ApiResponse(responseCode = "200", description = "用戶列表")})
    @GetMapping("/")
    public Resp<List<User>> list(@PathParam("page") Integer page, @PathParam("size") Integer size) {
        return new Resp<>(new ArrayList<>());
    }
}

使用Apifox自測

  • HTTP請求工具可以讀取OpenApi(Swagger)生成HTTP請求
  • 以Apifox為例
    • 打開Apifox权悟,新建項目
    • 項目設(shè)置 > 數(shù)據(jù)管理 > 導(dǎo)入數(shù)據(jù) > URL方式導(dǎo)入
    • 填入:http://localhost:8080/v3/api-docs
    • 提交峦阁,即可直接導(dǎo)入API接口
    • 就會根據(jù)Swagger生成HTTP文檔及請求

完整示例代碼

  • Swagger設(shè)置
@Configuration
public class SwaggerConfig {
    @Bean
    public OpenAPI swaggerOpenApi() {
        return new OpenAPI()
                .info(new Info().title("XXX平臺YYY服務(wù)")
                        .description("描述平臺多牛逼")
                        .version("v0.0.1"))
                .externalDocs(new ExternalDocumentation()
                        .description("設(shè)計文檔")
                        .url("https://juejin.cn/user/254742430749736/posts"));
    }
}
  • 異常處理
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(Exception.class)
    public Response handleException(Exception e) {
        log.warn("未知異常", e);
        return Response.buildFailure("未知異常", e.getMessage());
    }

    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BindException.class)
    public Response handleBindException(BindException e) {
        // 拼接錯誤信息,用于多個校驗不通過的錯誤信息拼接
        List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
        String message = allErrors.stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.joining(";"));
        log.info("參數(shù)校驗不通過:{}", message);
        return Response.buildFailure("參數(shù)校驗不通過", message);
    }

    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpMessageConversionException.class)
    public Response handleHttpMessageConversionException(HttpMessageConversionException e) {
        log.info("參數(shù)轉(zhuǎn)換失敶健:{}", e.getMessage());
        return Response.buildFailure("參數(shù)轉(zhuǎn)換失敗", e.getMessage());
    }

    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ValidationException.class)
    public Response handleValidationException(ValidationException e) {
        log.info("參數(shù)驗證失敗: {}", e.getMessage());
        return Response.buildFailure("參數(shù)驗證失敗", e.getMessage());
    }
}
  • 控制器
@Slf4j
@Validated
@RestController
@RequestMapping("/demo")
@Tag(name = "示例控制器", description = "演示Restful接口")
public class DemoController {
    @PostMapping("/")
    @Operation(summary = "Post方法示例", description = "Post通常用于新增")
    public SingleResponse<Long> add(@Validated @RequestBody UserAddCO user) {
        // TODO:添加到數(shù)據(jù)庫中骡显,然后返回記錄id
        return SingleResponse.of(1L);
    }

    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @Operation(summary = "上傳文件示例")
    public SingleResponse<String> upload(@RequestPart("file") MultipartFile file) {
        log.info("接收到文件:{}, 大斜拱:{}", file.getOriginalFilename(), file.getSize());
        try {
            // 保存到本地
            File localTempFile = new File(file.getOriginalFilename());
            localTempFile.createNewFile();
            file.transferTo(localTempFile);
            return SingleResponse.of(localTempFile.getAbsolutePath());
        } catch (IOException e) {
            throw new RuntimeException("文件上傳失敗");
        }
    }

    @PostMapping(value = "/upload/multi", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @Operation(summary = "上傳多個文件示例")
    public MultiResponse<String> uploadMulti(@RequestPart("files") MultipartFile[] files) {
        List<String> result = new LinkedList<>();
        for (MultipartFile file : files) {
            log.info("接收到文件:{}, 大腥糇ā:{}", file.getOriginalFilename(), file.getSize());
            result.add(file.getOriginalFilename());
            // TODO:如上保存到本地
        }
        return MultiResponse.of(result);
    }

    @GetMapping("/")
    @Operation(summary = "獲取分頁列表")
    public PageResponse<UserDetailCO> getPageList(@Validated @ParameterObject UserPageQry qry) {
        log.info("{}", qry);
        List<UserDetailCO> result = List.of(new UserDetailCO());
        return PageResponse.of(result, 1, qry.getPageSize(), qry.getPageIndex());
    }

    @GetMapping("/other/")
    @Operation(summary = "另一種獲取分頁列表方式")
    @Parameter(name = "pageIndex", description = "頁碼调衰,從1開始", example = "1")
    @Parameter(name = "pageSize", description = "數(shù)量嚎莉,不小于1", example = "20")
    @Parameter(name = "name", description = "搜索名字(模糊搜索)趋箩,不搜索就傳null或空字符")
    public PageResponse<UserDetailCO> getPage(@NotNull(message = "pageIndex不能為空") @Min(value = 1, message = "pageIndex必須大于1") Integer pageIndex,
                                              @NotNull(message = "pageIndex不能為空") @Range(min = 1, max = 100, message = "pageSize必須在1-100之間") Integer pageSize,
                                              @Nullable String name) {
        log.info("{} {} {}", pageIndex, pageSize, name);
        List<UserDetailCO> result = List.of(new UserDetailCO());
        return PageResponse.of(result, 1, pageSize, pageIndex);
    }

    @GetMapping("/{id}/")
    @Operation(summary = "獲取用戶詳情")
    public SingleResponse<UserDetailCO> getUser(@PathVariable("id") long id) {
        // TODO:從數(shù)據(jù)庫查詢
        return SingleResponse.of(new UserDetailCO());
    }

    @PutMapping("/{id}/")
    @Operation(summary = "修改參數(shù)")
    public Response putUser(@PathVariable("id") long id, @Validated @RequestBody UserPutCO user) {
        // TODO:使用id在數(shù)據(jù)查找到用戶叫确,然后修改值并保存
        return Response.buildSuccess();
    }

    @PatchMapping("/{id}/")
    @Operation(summary = "修改部分參數(shù)")
    public Response patchUser(@PathVariable("id") long id, @Validated @RequestBody UserPatchCO user) {
        // TODO:使用id在數(shù)據(jù)查找到用戶启妹,然后修改值并保存
        return Response.buildSuccess();
    }

    @DeleteMapping("/{id}/")
    @Operation(summary = "刪除")
    public Response deleteUser(@PathVariable("id") long id) {
        // TODO:根據(jù)id直接刪除用戶
        return Response.buildSuccess();
    }
}
  • 用戶新增
@Data
@Schema(title = "新增用戶模型")
public class UserAddCO {
    @Schema(title = "名字", example = "老王", minLength = 1, maxLength = 5)
    @NotBlank(message = "名字不能為空")
    private String name;

    @Schema(title = "年齡", example = "18", minimum = "0", maximum = "150")
    @NotNull(message = "年齡不能為空")
    @Range(min = 0, max = 150, message = "年齡在0~150之間")
    private Integer age;
//    private int age;
//    既然不能為空桨啃,為什么不使用int照瘾?
//    1. 因為int是基本類型不會為空丧慈,所以@NotNull校驗無效
//    2. 類初始化時生成默認(rèn)值(int默認(rèn)為0)逃默,@Range中最小值包含0完域,所以沒有age參數(shù)校驗通過吟税,不符合預(yù)期

    @Schema(title = "電話(可選)")
    private String phone;
}
  • 用戶詳情
@Data
@Schema(title = "用戶詳情")
public class UserDetailCO {
    @Schema(title = "用戶id", minimum = "1")
    private long id;

    @Schema(title = "用戶名", minLength = 1, maxLength = 5, example = "言午日堯耳總")
    private String name;

    @Schema(title = "手機")
    private String phone;
}
  • 分頁請求
@Data
public class UserPageQry {
    @Parameter(required = true, description = "頁碼肠仪,從1開始", example = "1")
    @Min(value = 1, message = "pageIndex必須大于1")
    private int pageIndex;
//    @NotNull(message="pageIndex參數(shù)必須填寫")
//    private Integer pageIndex;
//    1. 此處可以使用int類型异旧,因為限制最小值為1吮蛹,不填寫默認(rèn)賦值0,@Min校驗不通過會拋出異常
//    2. 也可以寫成Integer+@NotNull的組合(如下pageSize)匹涮,更加語義化

    @Parameter(required = true, description = "頁面大小", example = "20")
    @NotNull(message = "pageSize參數(shù)必須填寫")
    @Range(min = 1, max = 100, message = "pageSize必須在1-100之間")
    private Integer pageSize;

    @Parameter(description = "搜索名字(模糊搜索)天试,不搜索就傳null或空字符")
    private String phone;
}
  • Patch修改用戶
@Data
@Schema(title = "新增用戶模型")
public class UserPatchCO {
    @Schema(title = "電話(可選)")
    private String phone;
}
  • Put修改用戶
@Data
@Schema(title = "新增用戶模型")
public class UserPutCO {
    @Schema(title = "名字", example = "老王", minLength = 1, maxLength = 5)
    @NotBlank(message = "名字不能為空")
    private String name;

    @Schema(title = "年齡", example = "18", minimum = "0", maximum = "150")
    @NotNull(message = "年齡不能為空")
    @Range(min = 0, max = 150, message = "年齡在0~150之間")
    private Integer age;

    @Schema(title = "電話(可選)")
    private String phone;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市然低,隨后出現(xiàn)的幾起案子喜每,更是在濱河造成了極大的恐慌务唐,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件带兜,死亡現(xiàn)場離奇詭異,居然都是意外死亡刚照,警方通過查閱死者的電腦和手機刑巧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來无畔,“玉大人啊楚,你說我怎么就攤上這事』胝茫” “怎么了恭理?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長郭变。 經(jīng)常有香客問我颜价,道長,這世上最難降的妖魔是什么诉濒? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任周伦,我火速辦了婚禮,結(jié)果婚禮上未荒,老公的妹妹穿的比我還像新娘横辆。我一直安慰自己,他們只是感情好茄猫,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著困肩,像睡著了一般划纽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上锌畸,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天勇劣,我揣著相機與錄音,去河邊找鬼潭枣。 笑死比默,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的盆犁。 我是一名探鬼主播命咐,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼谐岁!你這毒婦竟也來了醋奠?” 一聲冷哼從身側(cè)響起榛臼,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎窜司,沒想到半個月后沛善,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡塞祈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年金刁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片议薪。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡尤蛮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出笙蒙,到底是詐尸還是另有隱情抵屿,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布捅位,位于F島的核電站轧葛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏艇搀。R本人自食惡果不足惜尿扯,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望焰雕。 院中可真熱鬧衷笋,春花似錦、人聲如沸矩屁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吝秕。三九已至泊脐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間烁峭,已是汗流浹背容客。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留约郁,地道東北人缩挑。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像鬓梅,于是被迫代替她去往敵國和親供置。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容