Constraint注解
在spring boot进行开发的时候,有时需要对请求的参数进行校验, 而@Constraint可以实现自定义的校验注解。
Spring boot自定义参数校验注解
1. 如果Spring boot是2.3.0之后的版本,需要引入新的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2. 自定义注解
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MultipartFileValidator.class)
public @interface MultipartFileUploadPreCheck {
String DEFAULT_MAXSIZE = "-1";
/**
* AliasFor("endsWith")
*/
// FileUtils.Type[] value() default {};
/**
* 支持的文件后缀类型,默认全部,AliasFor("value")
*/
FileUtils.Type[] allowFileType() default {};
/**
* 文件后缀是否区分大小写
*/
boolean ignoreCase() default true;
/**
* 上传的文件是否允许为空
*/
boolean allowEmpty() default false;
/**
* Max file size. Values can use the suffixes "MB" or "KB" to indicate megabytes or
* kilobytes respectively.<br/>
* 默认不限制但必须小于等于SpringMVC中文件上传配置
*/
String maxSize() default DEFAULT_MAXSIZE;
/**
* Min file size. Values can use the suffixes "MB" or "KB" to indicate megabytes or
* kilobytes respectively. default byte
*/
String minSize() default "0MB";
String message() default "The uploaded file is not verified.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
3. FileUtils.Type文件
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public static enum Type {
/**
* json
*/
JSON("json"),
/**
* JEPG.
*/
JPEG("FFD8FF"),
JPG("FFD8FF"),
/**
* PNG.
*/
PNG("89504E47"),
/**
* GIF.
*/
GIF("47494638"),
/**
* TIFF.
*/
TIFF("49492A00"),
/**
* Windows Bitmap.
*/
BMP("424D"),
/**
* CAD.
*/
DWG("41433130"),
/**
* Adobe Photoshop.
*/
PSD("38425053"),
/**
* Rich Text Format.
*/
RTF("7B5C727466"),
/**
* XML.
*/
XML("3C3F786D6C"),
/**
* HTML.
*/
HTML("68746D6C3E"),
/**
* CSS.
*/
CSS("48544D4C207B0D0A0942"),
/**
* JS.
*/
JS("696B2E71623D696B2E71"),
/**
* Email [thorough only].
*/
EML("44656C69766572792D646174653A"),
/**
* Outlook Express.
*/
DBX("CFAD12FEC5FD746F"),
/**
* Outlook (pst).
*/
PST("2142444E"),
/**
* MS Word/Excel.
* XLS_DOC:ppt,doc,xls
* XLSX_DOCX:xlsx
*/
XLS("D0CF11E0"), XLSX("504B0304"),
DOC("D0CF11E0"), DOCX("504B0304"),
/**
* Visio
*/
VSD("d0cf11e0a1b11ae10000"),
/**
* MS Access.
*/
MDB("5374616E64617264204A"),
/**
* WPS文字wps、表格et、演示dps都是一样的
*/
WPS("d0cf11e0a1b11ae10000"),
/**
* torrent
*/
TORRENT("6431303A637265617465"),
/**
* WordPerfect.
*/
WPD("FF575043"),
/**
* Postscript.
*/
EPS("252150532D41646F6265"),
/**
* Adobe Acrobat.
*/
PDF("255044462D312E"),
/**
* Quicken.
*/
QDF("AC9EBD8F"),
/**
* Windows Password.
*/
PWL("E3828596"),
/**
* ZIP Archive.
*/
// ZIP("504b0304140000000800"),
/**
* RAR Archive.
*/
RAR("52617221"),
/**
* JSP Archive.
*/
JSP("3C2540207061676520"),
/**
* JAVA Archive.
*/
JAVA("7061636B61676520"),
/**
* CLASS Archive.
*/
CLASS("CAFEBABE0000002E00"),
// /**
// * JAR Archive.
// */
// JAR("504b03040a0000000000"),
/**
* MF Archive.
*/
MF("4D616E69666573742D56"),
/**
* EXE Archive.
*/
EXE("4D5A9000030000000400"),
/**
* CHM Archive.
*/
CHM("49545346030000006000"),
/**
* Wave.
*/
WAV("57415645"),
/**
* AVI.
*/
AVI("41564920"),
/**
* Real Audio.
*/
RAM("2E7261FD"),
/**
* Real Media.
*/
RM("2E524D46"),
/**
* MPEG (mpg).
*/
MPG("000001BA"),
/**
* Quicktime.
*/
MOV("6D6F6F76"),
/**
* Windows Media.
*/
ASF("3026B2758E66CF11"),
/**
* MIDI.
*/
MID("4D546864"),
/**
* MP4.
*/
MP4("00000020667479706d70"),
/**
* MP3.
*/
MP3("49443303000000002176"),
/**
* FLV.
*/
FLV("464C5601050000000900"),
/**
* TXT:txt,docx
*/
TXT("0000000000000000000000000000");
@Getter
private final String value;
private static HashMap<String, Type> codeValueMap = new HashMap<>(47);
static {
for (FileUtils.Type currentOne : FileUtils.Type.values()) {
codeValueMap.put(currentOne.getValue(), currentOne);
}
}
public static FileUtils.Type getInstance(int code) {
return codeValueMap.get(code);
}
public static boolean exists(int code) {
return codeValueMap.containsKey(code);
}
}
3. 编写自定义注解处理文件
public class MultipartFileValidator implements ConstraintValidator<MultipartFileUploadPreCheck, MultipartFile> {
@Autowired
private MultipartProperties multipartProperties;
private long maxSize = -1;
private long minSize = 0;
private MultipartFileUploadPreCheck multipartFileUploadPreCheck;
private final ArrayList<FileUtils.Type> extension = new ArrayList<>();
@Override
public void initialize(MultipartFileUploadPreCheck constraintAnnotation) {
this.multipartFileUploadPreCheck = constraintAnnotation;
//支持的文件扩展名集合
// Collections.addAll(extension, multipartFileUploadPreCheck.value());
Collections.addAll(extension, multipartFileUploadPreCheck.allowFileType());
//文件上传的最大值
if (constraintAnnotation.maxSize().equals(MultipartFileUploadPreCheck.DEFAULT_MAXSIZE)) {
//默认最大值采用Spring中配置的单文件大小
DataSize maxFileSize = multipartProperties.getMaxFileSize();
this.maxSize = parseSize(maxFileSize.toString());
} else {
this.maxSize = parseSize(constraintAnnotation.maxSize());
}
//文件上传的最小值
this.minSize = parseSize(constraintAnnotation.minSize());
}
private long parseSize(String size) {
Assert.hasLength(size, "Size must not be empty");
size = size.toUpperCase();
long length = Long.parseLong(size.substring(0, size.length() - 2));
if (size.endsWith("KB")) {
return length * 1024;
}
if (size.endsWith("MB")) {
return length * 1024 * 1024;
}
return Long.parseLong(size);
}
/**
* 多个文件也可以 效验
* @param multipartFiles
* @param cvc
* @return
*/
@SneakyThrows
public boolean isValids(MultipartFile[] multipartFiles, ConstraintValidatorContext cvc) {
//上传的文件是空的情况
if (Objects.isNull(multipartFiles)) {
if (multipartFileUploadPreCheck.allowEmpty()) {
return true;
}
validMessage("上传文件不能为空", cvc);
return false;
}
for (MultipartFile multipartFile : multipartFiles) {
boolean valid = isValid(multipartFile, cvc);
if (!valid) {
return false;
}
}
return true;
}
@SneakyThrows
@Override
public boolean isValid(MultipartFile multipartFile, ConstraintValidatorContext cvc) {
String fieldName = multipartFile.getName();
//上传的文件是空的情况
if (multipartFile.isEmpty()) {
if (multipartFileUploadPreCheck.allowEmpty()) {
return true;
}
validMessage("上传文件不能为空" + ",参数名:" + fieldName, cvc);
return false;
}
//上传的文件不是空的情况,验证其他条件是否成立
//获取文件名,如果上传文件后缀名不区分大小写则统一转成小写
String originalFilename = multipartFile.getOriginalFilename();
if (multipartFileUploadPreCheck.ignoreCase()) {
originalFilename = originalFilename.toLowerCase();
}
if (StringUtils.isBlank(originalFilename)){
validMessage("上传文件名不能为空", cvc);
return false;
}
// 先通过后缀名过滤一部分明显错误的, 因为读取文件流获取文件头信息 比较消耗资源
// String fileSuffixName = originalFilename.substring(originalFilename.lastIndexOf('.') + 1);
// List<String> allowedFileSuffixNames = extension.stream().map(e -> e.name().toLowerCase(Locale.ROOT)).collect(Collectors.toList());
// if (StringUtils.isNotBlank(fileSuffixName) && allowedFileSuffixNames.stream().noneMatch(e -> e.equals(fileSuffixName))) {
// validMessage("上传文件类型不符合要求" + ",参数名:" + fieldName, cvc);
// return false;
// }
FileUtils.Type type = FileUtils.getType(multipartFile.getInputStream());
if (extension.size() > 0 && extension.stream().noneMatch(e -> e.equals(type))) {
validMessage("上传文件类型不符合要求" + ",参数名:" + fieldName, cvc);
return false;
}
//上传文件字节数
long size = multipartFile.getSize();
if (size < this.minSize) {
validMessage("上传文件不能小于指定最小值" + ",参数名:" + fieldName, cvc);
return false;
}
if (size > this.maxSize) {
validMessage("上传文件不能大于指定最大值" + ",参数名:" + fieldName, cvc);
return false;
}
return true;
}
private void validMessage(String message, ConstraintValidatorContext cvc) {
cvc.disableDefaultConstraintViolation();
cvc.buildConstraintViolationWithTemplate(message)
.addConstraintViolation();
}
}