前言:
对于Oracle数据库varchar2类型的字段设计的字段,存储中文时一个汉字占3个字符,而数字、字母、符号占1个字符。导致接口校验字段内容长度时不方便,因此编写自定义注解用于校验这类字段的内容长度。
举个栗子:
类型 | 说明 |
---|---|
VARCHAR2(50 CHAR) | 按字符计数,可存储50个汉字 |
VARCHAR2(50) | 按字节计数,根据字符集不同,gbk可存25个汉字,utf8可存16个汉字 |
函数 | 说明 |
---|---|
lengthb(string) | 计算string所占的字节长度:返回字符串的长度,单位是字节 |
length(string) | 计算string所占的字符长度:返回字符串的长度,单位是字符 |
我们可以建表测试两种字段类型的区别:
create table tb_test_length(
char1 varchar2(20),
char2 varchar2(20 char)
);
所建表的长度均为20,不过char1最多只能存储6个汉字,而char2能存储最多20个汉字:
insert into tb_test_length values ('一二三四五六', '一二三四五六');
insert into tb_test_length values ('一二三四五六', '一二三四五六七');
insert into tb_test_length values ('一二三四五六七', '一二三四五六七'); -- 插入这条数据会报错
使用lengthb函数查看成功插入数据char2的字节数:
select t.CHAR2, LENGTHB (t.CHAR2) from tb_test_length t where CHAR1 = '一二三四五六';
JSR-303定义的长度检查注解
注解 | 说明 |
---|---|
@Size(min=, max=) | 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内 |
@Length(min=, max=) | Validates that the annotated string is between min and max included. |
以上两个注解 @Size ,@Length均可用于String类型的字段长度校验,不过都是字符级别的长度检查。针对VARCHAR2(50)这种类型的数据表字段值的长度校验是有问题的。
于是我们写一个自定义注解 @ValiStringByte :
import javax.validation.Constraint;
import java.lang.annotation.*;
@Constraint(validatedBy = {ValiStringByteValidator.class})
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValiStringByte {
// 默认提示信息
String message() default "{Character bytes are extremely long!}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
// 最大字节数,可以自行调整
int max() default 2147483647;
}
还要为 @ValiStringByte 注解实现校验逻辑,实现ConstraintValidator接口,重写isValid方法:
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class ValiStringByteValidator implements ConstraintValidator<ValiStringByte, String> {
private int max = 0;
public void initialize(ValiStringByte valiStringByte) {
max = valiStringByte.max();
}
/**
* 重写校验逻辑
*
* @param string
* @param constraintValidatorContext
* @return
*/
@Override
public boolean isValid(String string, ConstraintValidatorContext constraintValidatorContext) {
if (null != string) {
int total = count(string);
return total <= max;
} else {
return true;
}
}
/**
* 统计参数的字节数
*
* @param str
* @return
*/
private static Integer count(String str) {
int total = 0; // 字节总数
if (null == str || str.equals("")) {
return total;
}
for (int i = 0; i < str.length(); i++) {
char tmp = str.charAt(i);
if ((tmp >= 'A' && tmp <= 'Z') || (tmp >= 'a' && tmp <= 'z')) {
total++;
} else if ((tmp >= '0') && (tmp <= '9')) {
total++;
} else if (tmp == ' ') {
total++;
} else if (isChinese(tmp)) {
total += 3;
} else {
total++;
}
}
return total;
}
/**
* 判断字符是否为汉字字符
*
* @param ch
* @return
*/
private static boolean isChinese(char ch) {
Character.UnicodeBlock ub = Character.UnicodeBlock.of(ch);
return ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
|| ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
|| ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A
|| ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B
|| ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION
|| ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS
|| ub == Character.UnicodeBlock.GENERAL_PUNCTUATION;
}
}
测试:
@Data
public class ParamBean {
// 校验字段值字节数最长为20
@ValiStringByte(max = 20)
private String text;
}
@PostMapping(path = "/test")
public void test(@RequestBody @Validated ParamBean bean) {
logger.info(bean.toString());
}
调用接口:
POST 127.0.0.1:8080/test
param: {"text": "一二三四五六12" } 一共20个Byte
控制台正常打印:
POST 127.0.0.1:8080/test
param: {"text": "一二三四五六123" } 一共21个Byte
控制台打印报错,说明能成功校验:
org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument at index 0 in method: public void TestController.test(ParamBean), with 1 error(s): [Field error in object ‘paramBean’ on field ‘text’: rejected value [一二三四五六123]; codes [ValiStringByte.paramBean.text,ValiStringByte.text,ValiStringByte.java.lang.String,ValiStringByte]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [paramBean.text,text]; arguments []; default message [text],20]; default message [{Character bytes are extremely long!}]] …