有了分組驗證其實還是有可能不夠的,所以必須要使用自定義驗證,來確保參數的正確性
要能夠自己定義註解,必須使用@interface,在common專案中建立,然後參考原始碼,將自定義的部份弄一個類似的驗證註解,其中參數多了一個values,這是要確保開關只會有數字0或1,另外message的部份也另外在common專案中建立一個設定檔,這部份也是參照原始碼的作法
@Documented
@Constraint(validatedBy = {})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListValue {
String message() default "{com.cheng.common.valid.ListValue.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int[] values() default {};
}
在resource資料夾裡面建立一個設定檔ValidationMessages.properties,然後內容訊息指定給ListValue註解使用
com.cheng.common.valid.ListValue.message=開關參數有誤
然後參考這個註解@Constraint(validatedBy = {})
在參考@Constraint可以看到原始碼是給什麼樣參數,這裡給的是一個陣列,而型態是一個泛型,所以就仿照這部份,建立一個自己的驗證器

然後再從這個介面驗證器點進去看,可以知道的兩個參數,一個是放入註解,另一個是放型態,而剛新增的驗證器需要放入的註解就是@ListValue而另一個參數則是數字型態Integer

在common專案valid資料夾中,新增Class檔案ListValueConstraintValidator,並實作上述的介面
public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> {
private final Set<Integer> set = new HashSet<>();
@Override
public void initialize(ListValue constraintAnnotation) {
int[] values = constraintAnnotation.values();
for (int value : values) {
set.add(value);
}
}
@Override
public boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) {
return set.contains(integer);
}
}
第一個方法是初始化,將參數中允許的值放入一個容器中,第二個方法則表示是否已驗證,當參數的數字不包含在容器中則是失敗
然後再將這個驗證規則和註解綁在一起,在validateBy中指定剛剛寫的驗證器,這樣的自定義註解才會有效果

並且在參數上寫上分組規則

另外上一篇建立全域的捕獲異常類沒有加上log記錄,所以這裡也順便補上去

然後重新啟動服務並測試

這樣就完成自定義註解和訊息了
另外可以寫多個驗證器,並綁定在同個驗證註解上,因為如果今天要驗證別的型態,那麼就要再新增一個驗證器,並且是驗證該型態,如果遇到的話再新增即可
從前端測試時發現一個問題

明明有寫上單個字母卻出現參數驗證失敗,此時檢查發現正則表達式有寫錯,這邊也順便記錄一下

原本前後有加上斜線/,但這在後端是不需要的,所以拿掉即可
然後更改狀態開關的時候是觸發更新的部分,但因為名稱沒改變等於為空,所以還是會出現錯誤,因此這部份也需要調整
在BrandController中新增一個方法專門給修改狀態使用

並且新增一個分組驗證是更新狀態使用的,所以要在common/valid中新增這個介面,然後再更改為更新狀態時才使用這個分組,同時也加上@NotNull註解

因後端有修改過API所以前端部分也要修改,在brand.vue檔案中要改成使用更新狀態的API

這樣測試就OK了