2.1.引入庫
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>2.0.1.Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>8.0.1.Final</version> </dependency>
代碼如下(示例):
@Data
public class BrandEntity implements Serializable {
/**
* 品牌ID,用于標識品牌。
*
* 此字段通過注解進行了不同的驗證邏輯配置,以適應不同的業務場景。
* 在更新操作(UpdateGroup)中,要求此字段不為空,確保了更新操作有明確的目標品牌ID。
* 在新增操作(AddGroup)中,要求此字段為空,因為新增品牌時不應該預先指定ID。
* 這種通過注解進行驗證的方式,提高了代碼的靈活性和可維護性,避免了在業務邏輯中硬編碼驗證邏輯。
*/
@NotNull(message = "修改必須指定品牌id",groups = {UpdateGroup.class})
@Null(message = "新增不能指定id",groups = {AddGroup.class})
private Long brandId;
/**
* 品牌名稱字段。
*
* 該字段是必填的,不允許為空字符串,這在添加和更新品牌信息時都必須遵守。
* 使用@NotBlank注解來強制驗證品牌名的非空性,如果為空,則會觸發驗證失敗,
* 返回相應的錯誤消息。
*/
@NotBlank(message = "品牌名必須提交",groups = {AddGroup.class,UpdateGroup.class})
private String name;
/**
* 品牌logo地址
*
* 此字段在添加(AddGroup)和更新(UpdateGroup)時都必須是一個合法的URL地址,
* 以確保公司徽標的鏈接是有效和可訪問的。使用@URL注解進行驗證,
* 如果不符合URL格式,則會提示指定的錯誤信息。
*
* 使用@NotBlank注解確保在添加時該字段不為空,為空則認為是無效的輸入。
* 這是因為在更新時,如果用戶沒有提供新的徽標URL,可以保留舊的URL,
* 所以在更新組(UpdateGroup)中,@NotBlank約束被移除,允許為空。
*/
@NotBlank(groups = {AddGroup.class})
@URL(message = "logo必須是一個合法的url地址",groups={AddGroup.class,UpdateGroup.class})
private String logo;
/**
* 展示狀態字段,用于標記對象的展示狀態。
* 顯示狀態[0-不顯示;1-顯示]
*
* 此字段受到兩個驗證組(AddGroup, UpdateStatusGroup)的約束。
* 在這兩個組中,該字段不能為空(@NotNull)且其值必須在預定義的列表中(@ListValue)。
* 這樣的設計確保了在添加對象和更新狀態操作時,展示狀態的值是有效且受控的。
*
* @NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
* 表明在AddGroup和UpdateStatusGroup驗證組中,此字段不能為空。
* @ListValue(vals={0,1},groups = {AddGroup.class, UpdateStatusGroup.class})
* 表明在AddGroup和UpdateStatusGroup驗證組中,此字段的值必須是0或1。
*/
@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
@ListValue(vals={0,1},groups = {AddGroup.class, UpdateStatusGroup.class})
private Integer showStatus;
/**
* 字段firstLetter用于存儲實體的首字母。
* 該字段的驗證有以下規則:
* 1. 在添加(AddGroup)時,不能為空,確保數據完整性。
* 2. 在添加(AddGroup)和更新(UpdateGroup)時,必須是一個字母,確保數據的格式符合預期。
* 這些驗證規則通過注解的方式進行聲明,以在運行時對數據進行校驗。
*/
@NotEmpty(groups={AddGroup.class})
@Pattern(regexp="^[a-zA-Z]$",message = "檢索首字母必須是一個字母",groups={AddGroup.class,UpdateGroup.class})
private String firstLetter;
/**
* 排序字段,用于控制元素的顯示順序。
*
* @NotNull 標注指示該字段在添加(AddGroup)時不能為空,確保了排序值的有效性。
* @Min 標注指定了排序值必須大于等于0,適用于添加(AddGroup)和更新(UpdateGroup)操作,保證了排序的邏輯正確性。
*/
@NotNull(groups={AddGroup.class})
@Min(value = 0,message = "排序必須大于等于0",groups={AddGroup.class,UpdateGroup.class})
private Integer sort;
}
通過上面的代碼可以看出,如果要指定注解作用的范圍,就要自己添加分組。
AddGroup
public interface AddGroup { }
UpdateGroup
public interface UpdateGroup { }
UpdateStatusGroup
public interface UpdateStatusGroup { }
上述代碼中為了對狀態取值進行驗證,我們采用了自定義驗證器的方式
ListValue
@Documented @Constraint(validatedBy = { ListValueConstraintValidator.class }) @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) @Retention(RUNTIME) public @interface ListValue { String message() default "{com.xunqi.common.valid.ListValue.message}"; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { }; int[] vals() default { }; }
/** * <p> * 商品品牌 前端控制器 * </p> * * @author shiqi * @version 1.0.0 * @createTime 2024-06-26 */ @RestController @RequestMapping("product/brand") public class BrandController { /** * 保存品牌信息。 * <p> * 該方法通過@RequestMapping注解映射了"/save"的HTTP請求,用于保存BrandEntity對象。 * 使用@Validated注解對brandEntity參數進行驗證,確保添加或修改品牌時數據的合法性。 * BindingResult參數用于接收驗證后的錯誤信息,可以進一步處理和反饋給前端。 * <p> * 方法返回一個R對象,通常表示操作的成功或失敗狀態。 * * @param brandEntity 品牌實體對象,包含待保存的品牌信息。 * @param bindingResult 驗證結果對象,用于存儲brandEntity驗證過程中產生的錯誤信息。 * @return 返回一個表示操作結果的對象,通常是一個包含成功狀態和相關消息的R對象。 */ @RequestMapping("/save") public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brandEntity, BindingResult bindingResult) { // 方法體中應包含保存品牌信息的具體邏輯,此處省略。 return R.ok(); } }
/** * <p> * 描述:業務異常枚舉類 * </p> * * @author shiqi * @version 1.0.0 * @createTime 2024-06-26 */ public enum BizCodeEnum { UNKNOWN_EXCEPTION(10000,"系統未知異常"), VALID_EXCEPTION(10001,"參數格式校驗失敗"), TO_MANY_REQUEST(10002,"請求流量過大,請稍后再試"), SMS_CODE_EXCEPTION(10002,"驗證碼獲取頻率太高,請稍后再試"), PRODUCT_UP_EXCEPTION(11000,"商品上架異常"), USER_EXIST_EXCEPTION(15001,"存在相同的用戶"), PHONE_EXIST_EXCEPTION(15002,"存在相同的手機號"), NO_STOCK_EXCEPTION(21000,"商品庫存不足"), LOGIN_ACCOUNT_PASSWORD_EXCEPTION(15003,"賬號或密碼錯誤"); private int code; private String message; BizCodeEnum(int code, String message) { this.code = code; this.message = message; } public int getCode() { return code; } public String getMessage() { return message; } }
/** * <p> * 統一返回結果 * </p> * * @author shiqi * @version 1.0.0 * @createTime 2024-06-26 */ public class R extends HashMap<String, Object> { private static final long serialVersionUID = 1L; public R setData(Object data) { put("data",data); return this; } //利用fastjson進行反序列化 public <T> T getData(TypeReference<T> typeReference) { Object data = get("data"); //默認是map String jsonString = JSON.toJSONString(data); T t = JSON.parseObject(jsonString, typeReference); return t; } //利用fastjson進行反序列化 public <T> T getData(String key,TypeReference<T> typeReference) { Object data = get(key); //默認是map String jsonString = JSON.toJSONString(data); T t = JSON.parseObject(jsonString, typeReference); return t; } public R() { put("code", 0); put("msg", "success"); } public static R error() { return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知異常,請聯系管理員"); } public static R error(String msg) { return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg); } public static R error(int code, String msg) { R r = new R(); r.put("code", code); r.put("msg", msg); return r; } public static R ok(String msg) { R r = new R(); r.put("msg", msg); return r; } public static R ok(Map<String, Object> map) { R r = new R(); r.putAll(map); return r; } public static R ok() { return new R(); } public R put(String key, Object value) { super.put(key, value); return this; } public Integer getCode() { return (Integer) this.get("code"); } }
自定義校驗異常處理器
/** * <p> * 集中處理所有異常 * </p> * * @author shiqi * @version 1.0.0 * @createTime 2024-06-26 */ @Slf4j @RestControllerAdvice(basePackages = {"com.shiqi.jsr303demo"}) public class CustomExceptionControllerAdvice { /** * 處理方法參數不合法異常。 * 當方法參數不滿足驗證條件時,Spring MVC會拋出MethodArgumentNotValidException異常。 * 該異常處理器專門捕獲此類異常,以統一的方式處理參數驗證失敗的情況。 * * @param e MethodArgumentNotValidException異常實例,包含驗證失敗的詳細信息。 * @return 返回一個包含錯誤信息的響應對象。 */ @ExceptionHandler(value = MethodArgumentNotValidException.class) public R handleMethodArgumentNotValidException(MethodArgumentNotValidException e){ // 獲取驗證結果對象,其中包含了具體的驗證錯誤信息。 BindingResult bindingResult = e.getBindingResult(); // 初始化一個映射,用于存儲字段名和對應的錯誤信息。 HashMap<String,String> errMap=new HashMap<String,String>(); // 檢查是否有驗證錯誤,如果有,則遍歷所有字段錯誤,并將字段名和錯誤信息添加到errMap中。 if (bindingResult.hasErrors()){ bindingResult.getFieldErrors().forEach((item)->{ errMap.put(item.getField(),item.getDefaultMessage()); }); } // 返回一個包含錯誤代碼、錯誤消息和具體錯誤詳情的響應對象。 // 錯誤代碼為400,表示客戶端請求錯誤,錯誤消息為"參數校驗不合法"。 // errMap作為數據部分的一部分,包含了所有驗證失敗的字段和對應的錯誤信息。 return R.error(400,"參數校驗不合法").put("data",errMap); } /** * 處理所有異常的控制器異常處理器。 * <p> * 該方法旨在捕獲控制器層拋出的任何異常,無論是預期的業務異常還是未預期的運行時異常。 * 它的目的是統一異常的處理方式,向客戶端返回一個標準的響應體,而不是直接暴露服務器內部錯誤信息。 * * @param throwable 拋出的異常對象,無論異常類型為何。 * @return 返回一個表示錯誤響應的R對象。這個響應體可以幫助客戶端識別請求處理過程中發生了什么錯誤。 */ @ExceptionHandler(value = Throwable.class) private R handleValidException(Throwable throwable) { // 記錄異常信息到日志系統,以便后續的問題排查和分析。 log.error("出現異常{},異常類型{}", throwable.getMessage(), throwable.getClass()); // 返回一個通用的錯誤響應體,通知客戶端請求處理過程中發生了錯誤。 return R.error(); } }
5. 實際業務中的應用
表單校驗:確保用戶輸入的數據合法,如用戶注冊、登錄、表單提交等。
數據傳輸對象(DTO)校驗:在進行數據傳輸時,確保傳輸的數據符合預期,如API請求和響應。
領域對象校驗:確保業務邏輯中的對象狀態合法,如訂單處理、支付處理等。
使用JSR 303可以有效減少手動校驗代碼,簡化代碼結構,提高代碼可讀性和維護性。在實際應用中,常結合Spring框架和Hibernate Validator一起使用。
原文鏈接:https://blog.csdn.net/Hi_alan/article/details/139997617