亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

SpringBoot使用Validation包進(jìn)行輸入?yún)?shù)校驗(yàn)

 更新時(shí)間:2024年05月16日 08:58:35   作者:磊磊落落  
Spring Boot 自帶的 spring-boot-starter-validation 包支持以標(biāo)準(zhǔn)注解的方式進(jìn)行輸入?yún)?shù)校驗(yàn),本文即關(guān)注 spring-boot-starter-validation 包所涵蓋的標(biāo)準(zhǔn)注解的使用、校驗(yàn)異常的捕獲與展示、分組校驗(yàn)功能的使用,以及自定義校驗(yàn)器的使用,需要的朋友可以參考下

Spring Boot 自帶的 spring-boot-starter-validation 包支持以標(biāo)準(zhǔn)注解的方式進(jìn)行輸入?yún)?shù)校驗(yàn)。spring-boot-starter-validation 包主要引用了 hibernate-validator 包,其參數(shù)校驗(yàn)功能就是 hibernate-validator 包所提供的。

本文即關(guān)注 spring-boot-starter-validation 包所涵蓋的標(biāo)準(zhǔn)注解的使用、校驗(yàn)異常的捕獲與展示、分組校驗(yàn)功能的使用,以及自定義校驗(yàn)器的使用。

本文示例工程使用 Maven 管理。

下面列出寫作本文時(shí)所使用的 JDK、Maven 與 Spring Boot 的版本:

JDK:Amazon Corretto 17.0.8
Maven:3.9.2
Spring Boot:3.2.1

本文以開發(fā)一個(gè) User 的 RESTful API 為例來(lái)演示 Validation 包的使用。

所以 pom.xml 文件除了需要引入 spring-boot-starter-validation 依賴外:

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

還需要引入 spring-boot-starter-web 依賴:

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

為了省去 Model 類 Getters 與 Setters 的編寫,本文還使用了 lombok 依賴:

<!-- pom.xml -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

依賴準(zhǔn)備好后,即可以嘗試對(duì) Validation 包進(jìn)行使用了。

1 Validation 標(biāo)準(zhǔn)注解的使用

下面列出 spring-boot-starter-validation 包中常用的幾個(gè)注解。

注解作用字段類型說(shuō)明
@Null任意類型驗(yàn)證元素值為 null
@NotNull任意類型驗(yàn)證元素值不為 null,無(wú)法驗(yàn)證空字符串
@NotBlankCharSequence 子類型驗(yàn)證元素值不為空(不為 null 且不為空字符串)
@NotEmptyCharSequence 子類型、CollectionMap、數(shù)組驗(yàn)證元素值不為 null 且不為空(字符串長(zhǎng)度、集合大小不為 0
@Min任何 Number 類型驗(yàn)證元素值大于等于 @Min 指定的值
@Max任何 Number 類型驗(yàn)證元素值小于等于 @Max 指定的值
@Digits任何 Number 類型驗(yàn)證元素值的整數(shù)位數(shù)和小數(shù)位數(shù)上限
@Size字符串、Collection、Map、數(shù)組等驗(yàn)證元素值的在指定區(qū)間之內(nèi),如字符長(zhǎng)度、集合大小
@Range數(shù)值類型驗(yàn)證元素值在最小值和最大值之間
@EmailCharSequence 子類型驗(yàn)證元素值是電子郵件格式
@PatternCharSequence 子類型驗(yàn)證元素值與指定的正則表達(dá)式匹配
@Valid任何非原子類型指定遞歸驗(yàn)證關(guān)聯(lián)的對(duì)象

下面就看一下如何使用這些注解。

假設(shè)我們想編寫一個(gè)創(chuàng)建 User 的 RESTful API,而創(chuàng)建 User 時(shí),其中有一些字段是有校驗(yàn)規(guī)則的(如:必填、滿足字符串長(zhǎng)度要求、滿足電子郵件格式、滿足正則表達(dá)式等)。

下面即看一下使用了 Validation 注解的 User Model 代碼:

// src/main/java/com/example/demo/model/User.java
package com.example.demo.model;

import jakarta.validation.constraints.*;
import lombok.Data;

@Data
public class User {

    @NotNull(message = "name can not be null")
    @Size(min = 2, max = 20, message = "name length should be in the range [2, 20]")
    private String name;

    @NotNull(message = "age can not be null")
    @Range(min = 18, max = 100, message = "age should be in the range [18, 100]")
    private Integer age;

    @NotNull(message = "email can not be null")
    @Email(message = "email invalid")
    private String email;

    @NotNull(message = "phone can not be null")
    @Pattern(regexp = "^1[3-9][0-9]{9}$", message = "phone number invalid")
    private String phone;

}

下面淺析一下 User Model 中每個(gè)字段的校驗(yàn)規(guī)則:

  • name

    其為字符串類型,使用了 @NotNull、@Size 注解,表示這個(gè)字段為必填,且字符串長(zhǎng)度應(yīng)屬于區(qū)間 [2, 20]。

  • age

    其為整數(shù)類型,使用了 @NotNull、@Range 注解,表示這個(gè)字段為必填,且數(shù)值應(yīng)屬于區(qū)間 [2, 20]。

  • email

    其為字符串類型,使用了 @NotNull、@Email 注解,表示這個(gè)字段為必填,且為 Email 格式。

  • phone

    其為字符串類型,使用了 @NotNull、@Pattern 注解,表示這個(gè)字段為必填,且為合法的國(guó)內(nèi)手機(jī)號(hào)格式。

下面看一下統(tǒng)一的錯(cuò)誤返回 Model 類 ErrorMessage 的代碼:

// src/main/java/com/example/demo/model/ErrorMessage.java
package com.example.demo.model;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class ErrorMessage {

    private String code;
    private String description;

}

最后看一下 UserController 的代碼:

// src/main/java/com/example/demo/controller/UserController.java
package com.example.demo.controller;

import com.example.demo.model.ErrorMessage;
import com.example.demo.model.User;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/users")
public class UserController {

    @PostMapping("")
    public ResponseEntity<?> addUser(@RequestBody @Valid User user, BindingResult result) {
        if (result.hasErrors()) {
            List<ObjectError> allErrors = result.getAllErrors();
            if (!allErrors.isEmpty()) {
                ObjectError error = allErrors.get(0);
                String description = error.getDefaultMessage();
                return ResponseEntity.badRequest().body(new ErrorMessage("validation_failed", description));
            }
        }

        // userService.addUser(user);

        return ResponseEntity.status(HttpStatus.CREATED).build();
    }

}

可以看到,UserControlleraddUser 方法使用了 User Model 來(lái)接收請(qǐng)求體,User Model 前使用了 @Valid 注解,該注解會(huì)對(duì) User Model 中的字段根據(jù)注解設(shè)定的規(guī)則自動(dòng)進(jìn)行校驗(yàn)。此外,addUser 方法還有另外一個(gè)參數(shù) BindingResult,該參數(shù)會(huì)捕獲所有的字段校驗(yàn)錯(cuò)誤信息,本文僅是將其中的第一個(gè)錯(cuò)誤按照 ErrorMessage 格式返回了出來(lái),沒有任何錯(cuò)誤信息則會(huì)返回 201 狀態(tài)碼。

下面使用 CURL 命令測(cè)試一下這個(gè)接口:

curl -L \
  -X POST \
  -H "Content-Type: application/json" \
  http://localhost:8080/users \
  -d '{"name": "Larry", "age": 18, "email": "larry@qq.com"}'
// 400
{ "code": "validation_failed", "description": "phone can not be null" }

可以看到,如果有字段不滿足校驗(yàn)規(guī)則時(shí),會(huì)返回設(shè)定的錯(cuò)誤信息。

如果 Model 類中有嵌套對(duì)象,該怎么做驗(yàn)證呢?只需要在對(duì)應(yīng)的字段上加上 @Valid 注解就可以了。

比如,User Model 中有一個(gè)字段為 address,其為 Address 對(duì)象,Address 類的代碼如下:

// src/main/java/com/example/demo/model/Address.java
package com.example.demo.model;

import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;

@Data
public class Address {

    @NotNull(message = "province can not be null")
    @Size(min = 2, max = 100, message = "province length should be in the range [10, 100]")
    private String province;

    @NotNull(message = "city can not be null")
    @Size(min = 2, max = 100, message = "city length should be in the range [10, 100]")
    private String city;

    @NotNull(message = "street can not be null")
    @Size(min = 10, max = 1000, message = "street length should be in the range [10, 1000]")
    private String street;

}

則 User Model 中,若想對(duì) address 字段應(yīng)用校驗(yàn)規(guī)則,則需要額外在該字段上加一個(gè) @Valid 注解:

// src/main/java/com/example/demo/model/User.java
package com.example.demo.model;

import jakarta.validation.constraints.*;
import lombok.Data;

@Data
public class User {

    ...

    @Valid
    @NotNull(message = "address can not be null")
    private Address address;

}

了解了 Validation 包中常用注解的使用方式,下面看一下校驗(yàn)錯(cuò)誤的異常捕獲與展示。

2 校驗(yàn)錯(cuò)誤的異常捕獲與展示

我們注意到,上面的例子中 UserControlleraddUser 方法使用一個(gè)額外的參數(shù) BindingResult 來(lái)接收校驗(yàn)錯(cuò)誤信息,然后根據(jù)需要展示給調(diào)用者。但這種處理方式有點(diǎn)太冗余了,每個(gè)請(qǐng)求方法都需要加這么一個(gè)參數(shù)并重新寫一遍錯(cuò)誤返回的邏輯。

其實(shí)不加這個(gè)參數(shù)的話,若有校驗(yàn)錯(cuò)誤,Spring Boot 框架會(huì)拋出一個(gè) MethodArgumentNotValidException。所以簡(jiǎn)單一點(diǎn)的處理方式是:使用 @RestControllerAdvice 注解來(lái)將一個(gè)類標(biāo)記為全局的異常處理類,針對(duì) MethodArgumentNotValidException,只需要在這個(gè)異常處理類中進(jìn)行統(tǒng)一捕獲、統(tǒng)一處理就可以了。

異常處理類 MyExceptionHandler 的代碼如下:

// src/main/java/com/example/demo/exception/MyExceptionHandler.java
package com.example.demo.exception;

import com.example.demo.model.ErrorMessage;
import org.springframework.http.HttpStatus;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.List;

@RestControllerAdvice
public class MyExceptionHandler {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ErrorMessage handleValidationExceptions(
            MethodArgumentNotValidException ex) {
        List<ObjectError> allErrors = ex.getBindingResult().getAllErrors();
        if (!allErrors.isEmpty()) {
            ObjectError error = allErrors.get(0);
            String description = error.getDefaultMessage();
            return new ErrorMessage("validation_failed", description);
        }
        return new ErrorMessage("validation_failed", "validation failed");
    }

}

有了該異常處理類后,UserController 的代碼即可以變得很純凈:

// src/main/java/com/example/demo/controller/UserController.java
package com.example.demo.controller;

...

@RestController
@RequestMapping("/users")
public class UserController {

    @PostMapping("")
    public ResponseEntity<?> addUser(@RequestBody @Valid User user) {
        // userService.addUser(user);

        return ResponseEntity.status(HttpStatus.CREATED).build();
    }

}

使用該種方式后,對(duì)于調(diào)用方來(lái)說(shuō),有校驗(yàn)錯(cuò)誤時(shí),效果與之前是一樣的:

# 使用 CURL 命令新建一個(gè) User(未提供 phone 參數(shù))
curl -L \
  -X POST \
  -H "Content-Type: application/json" \
  http://localhost:8080/users \
  -d '{"name": "Larry", "age": 18, "email": "larry@qq.com"}'
// 會(huì)返回 400 狀態(tài)碼,以及如下錯(cuò)誤信息
{ "code": "validation_failed", "description": "phone can not be null" }

學(xué)會(huì)如何以統(tǒng)一的異常處理類來(lái)處理校驗(yàn)錯(cuò)誤后,下面看一下如何使用分組校驗(yàn)功能。

3 分組校驗(yàn)功能的使用

分組校驗(yàn)功能可以針對(duì)同一個(gè) Model,為不同的場(chǎng)景應(yīng)用不同的校驗(yàn)規(guī)則。

下面我們嘗試使用同一個(gè) User Model 來(lái)同時(shí)接收新增和更新的請(qǐng)求數(shù)據(jù),但為各個(gè)字段指定不同的分組來(lái)區(qū)別新增和更新時(shí)校驗(yàn)規(guī)則的不同。

User Model 的代碼如下:

// src/main/java/com/example/demo/model/User.java
package com.example.demo.model;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import jakarta.validation.groups.Default;
import lombok.Data;
import org.hibernate.validator.constraints.Range;

@Data
public class User {

    @NotNull(message = "id can not be null", groups = Update.class)
    private Long id;

    @NotNull(message = "name can not be null", groups = Add.class)
    @Size(min = 2, max = 20, message = "name length should be in the range [2, 20]")
    private String name;

    @NotNull(message = "age can not be null", groups = Add.class)
    @Range(min = 18, max = 100, message = "age should be in the range [18, 100]")
    private Integer age;

    @NotNull(message = "email can not be null", groups = Add.class)
    @Email(message = "email invalid")
    private String email;

    @NotNull(message = "phone can not be null", groups = Add.class)
    @Pattern(regexp = "^1[3-9][0-9]{9}$", message = "phone number invalid")
    private String phone;

    public interface Add extends Default {
    }

    public interface Update extends Default {
    }

}

可以看到,我們?cè)?User Model 中定義了兩個(gè)分組:AddUpdate。每個(gè)字段上都有一個(gè) @NotNull 注解,但 id 字段的分組是 Update.class,其它字段的分組是 Add.class,其余注解則未指定分組(表示均適用)。意思是要求:在新增時(shí),name、age、emailphone 為必填字段;在更新時(shí),id 為必填字段;而且不論新增還是更新,只要提供了對(duì)應(yīng)的字段,就需要滿足對(duì)應(yīng)字段的校驗(yàn)規(guī)則。

下面看一下 UserController 的代碼:

// src/main/java/com/example/demo/controller/UserController.java
package com.example.demo.controller;

...

@RestController
@RequestMapping("/users")
public class UserController {

    @PostMapping("")
    public ResponseEntity<?> addUser(@RequestBody @Validated(User.Add.class) User user) {
        // userService.addUser(user);

        return ResponseEntity.status(HttpStatus.CREATED).build();
    }

    @PatchMapping("")
    public ResponseEntity<?> updateUser(@RequestBody @Validated(User.Update.class) User user) {
        // userService.updateUser(user);

        return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
    }

}

可以看到,新增 User 接口與更新 User 接口使用了同一個(gè) User Model;但新增使用的分組是 User.Add.class,更新使用的分組是 User.Update.class。

注意:這里指定分組時(shí)用到了 @Validated 注解,而前面用到的是 @Valid 注解,這里簡(jiǎn)單解釋一下兩者的不同。@Validated 注解是 Spring 框架自帶的,而 @Valid 注解是 jakarta.validation 包下的,@Validated 注解可以指定分組,而 @Valid 注解則沒有這個(gè)功能。

下面嘗試在不提供 id 字段的情況下更新一下 User:

curl -L \
  -X PATCH \
  -H "Content-Type: application/json" \
  http://localhost:8080/users \
  -d '{"name": "Larry", "age": 18, "email": "larry@qq.com"}'

會(huì)返回如下錯(cuò)誤:

// 400
{ "code": "validation_failed", "description": "id can not be null" }

介紹完分組校驗(yàn)功能的使用,下面看一下自定義校驗(yàn)器的使用。

4 自定義校驗(yàn)器的使用

如果 Validation 包中自帶的注解未能滿足您的校驗(yàn)需求,則可以自定義一個(gè)注解并實(shí)現(xiàn)對(duì)應(yīng)的校驗(yàn)邏輯。

下面自定義了一個(gè)注解 CustomValidation,其代碼如下:

package com.example.demo.validation;

...

@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CustomValidator.class)
public @interface CustomValidation {
    String message() default "Invalid value";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

如上代碼中,@Target 指定了該注解的作用域,本例中,表示該注解可應(yīng)用在方法或字段上;@Retention 指定該注解的存活期限,本例中,表示該注解在運(yùn)行時(shí)可以使用;@Constraint 指定該注解的處理類。

處理類 CustomValidator 用于編寫自定義校驗(yàn)邏輯,其代碼如下:

package com.example.demo.validation;

...

public class CustomValidator
        implements ConstraintValidator<CustomValidation, String> {
    @Override
    public void initialize(CustomValidation constraintAnnotation) {
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return null != value && value.startsWith("ABC");
    }
}

可以看到,CustomValidator 實(shí)現(xiàn)了 ConstraintValidator<CustomValidation, String>,表示被標(biāo)記字段是一個(gè) String 類型;initialize() 方法用于校驗(yàn)器的初始化,可以根據(jù)需要訪問(wèn)注解上的各種屬性;isValid() 方法可以拿到被校驗(yàn)的字段值,用于編寫真正的校驗(yàn)邏輯。

下面即在 User Model 中使用一下這個(gè)自定義注解:

package com.example.demo.model;

...

@Data
public class User {

    @CustomValidation(message = "testField invalid")
    private String testField;

}

這樣,當(dāng)這個(gè)字段值不滿足自定義校驗(yàn)規(guī)則時(shí),就會(huì)拋出對(duì)應(yīng)的錯(cuò)誤:

// 400
{ "code": "validation_failed", "description": "testField invalid" }

綜上,本文以示例代碼的方式詳細(xì)介紹了 spring-boot-starter-validation 包的使用。

到此這篇關(guān)于SpringBoot使用Validation包進(jìn)行輸入?yún)?shù)校驗(yàn)的文章就介紹到這了,更多相關(guān)SpringBoot Validation參數(shù)校驗(yàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解JAVA 強(qiáng)引用

    詳解JAVA 強(qiáng)引用

    這篇文章主要介紹了JAVA 強(qiáng)引用的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-08-08
  • 詳解如何使用MongoDB+Springboot實(shí)現(xiàn)分布式ID的方法

    詳解如何使用MongoDB+Springboot實(shí)現(xiàn)分布式ID的方法

    這篇文章主要介紹了詳解如何使用MongoDB+Springboot實(shí)現(xiàn)分布式ID的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • IDEA啟動(dòng)報(bào)錯(cuò)Internal?error.?Please?refer?to?https://jb.gg/ide/critical-startup-errors解決辦法

    IDEA啟動(dòng)報(bào)錯(cuò)Internal?error.?Please?refer?to?https://jb.gg/i

    這篇文章主要介紹了IDEA啟動(dòng)報(bào)錯(cuò)Internal?error.?Please?refer?to?https://jb.gg/ide/critical-startup-errors解決辦法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-04-04
  • Java中File類方法詳解以及實(shí)踐

    Java中File類方法詳解以及實(shí)踐

    Java File類的功能非常強(qiáng)大,利用java基本上可以對(duì)文件進(jìn)行所有操作,下面這篇文章主要給大家介紹了關(guān)于Java中File類方法以及實(shí)踐的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-04-04
  • java GUI編程之布局控制器(Layout)實(shí)例分析

    java GUI編程之布局控制器(Layout)實(shí)例分析

    這篇文章主要介紹了java GUI編程之布局控制器(Layout),結(jié)合實(shí)例形式分析了java GUI編程中布局控制器(Layout)具體功能、用法及相關(guān)操作注意事項(xiàng),需要的朋友可以參考下
    2020-01-01
  • Java創(chuàng)建對(duì)象之顯示創(chuàng)建與隱式創(chuàng)建

    Java創(chuàng)建對(duì)象之顯示創(chuàng)建與隱式創(chuàng)建

    在本篇文章中,小編會(huì)帶大家學(xué)習(xí)面向?qū)ο笾嘘P(guān)于對(duì)象的創(chuàng)建之顯示創(chuàng)建和隱式創(chuàng)建,其實(shí)類和對(duì)象作為面向?qū)ο笾凶罨镜?,也是最重要?需要的朋友可以參考下
    2023-05-05
  • java中Calendar與Date類型互相轉(zhuǎn)換的方法

    java中Calendar與Date類型互相轉(zhuǎn)換的方法

    這篇文章主要介紹了java中Calendar與Date類型互相轉(zhuǎn)換的方法,Calendar與Date類型是我們?nèi)粘i_發(fā)中常用的兩種數(shù)據(jù)類型,它們用于不同的場(chǎng)景,兩者具有不同的方法,接下來(lái)通過(guò)實(shí)例給大家詳解,需要的朋友可以參考下
    2022-09-09
  • MyBatis如何調(diào)用存儲(chǔ)過(guò)程與存儲(chǔ)函數(shù)

    MyBatis如何調(diào)用存儲(chǔ)過(guò)程與存儲(chǔ)函數(shù)

    這篇文章主要介紹了MyBatis如何調(diào)用存儲(chǔ)過(guò)程與存儲(chǔ)函數(shù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • 徹底理解Java中的ThreadLocal

    徹底理解Java中的ThreadLocal

     ThreadLocal翻譯成中文比較準(zhǔn)確的叫法應(yīng)該是:線程局部變量。使用這個(gè)工具類可以很簡(jiǎn)潔地編寫出優(yōu)美的多線程程序。 接下來(lái)通過(guò)本文給大家介紹Java中的ThreadLocal,需要的朋友可以參考下
    2017-03-03
  • Spring MVC創(chuàng)建項(xiàng)目踩過(guò)的bug

    Spring MVC創(chuàng)建項(xiàng)目踩過(guò)的bug

    這篇文章主要介紹了Spring MVC創(chuàng)建項(xiàng)目踩過(guò)的bug,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11

最新評(píng)論