前言
为什么会有这种功能出现呢!
各位可能会有疑问,为什么不使用 @Valid
注解呢!各位兄弟我也想用啊!但是没办法啊!项目性质导致的。项目会对接各种渠道方,但是所有渠道方都是用同一个实体进行传递的(通用性),但是呢,每个渠道方对字段必传的效验又不一样(用户是上帝)。比如公用实体里有 a、b、c 属性。AA 渠道方只传递 a、b 属性,c 属性没办法给,那我们对 AA 渠道只能给 a、b 属性做必传效验。然后 BB 渠道方给 b、c 属性,那我们对 BB 渠道只能给 b、c 属性做必传效验。也不可能用 if/else一个一个去判断的,字段属性一多,会凉的。基于这种场景就出现了使用 反射 做效验。
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Objects;
@Slf4j
public class CheckUtil {
/**
* 效验属性类型为 Object
* @param sourceObject 源对象
* @param checkFields 需要效验的字段集合
* @param sourceObject
* @param checkFields
* @return
*/
public static String checkObjNull(Object sourceObject, List<String> checkFields){
if(Objects.isNull(sourceObject)){
return "请输入正确源对象";
}
if(CollectionUtils.isEmpty(checkFields)){
return "效验字段不能为空";
}
Class<?> objClass = sourceObject.getClass();
// 获得本类所有属性对象
Field[] declaredFields = objClass.getDeclaredFields();
if(null == declaredFields || declaredFields.length == 0){
return "源对象中没有属性";
}
StringBuilder sb = new StringBuilder();
for (String checkField : checkFields) {
for (Field field : declaredFields) {
//关闭程序的安全检测
field.setAccessible(true);
// 效验字段与 属性名称相等 则进行效验
String fieldName = field.getName();
if(checkField.equals(fieldName)){
try {
// 获得字段属性值
Object fieldValue = field.get(sourceObject);
// 如果字段属性值为 null 或者 “” 字符串 则认为该字段没有传值
if(Objects.isNull(fieldValue) || StringUtils.isEmpty(fieldValue.toString())){
sb = sb.append("[").
append(fieldName).
append("]").
append("为必传字段");
return sb.toString();
}
} catch (IllegalAccessException e) {
log.error("获取属性值异常",e);
return "请检查传递的源对象";
}
}
}
}
// 通过效验
return null;
}
/**
* 效验属性类型为 List
* @param sourceObject 源对象
* @param checkFields 需要效验的字段集合
* @param listPropertyName 源对象的属性名称
* @return
*/
public static String checkArrayNull(Object sourceObject, List<String> checkFields,String listPropertyName){
if(Objects.isNull(sourceObject)){
return "请输入正确源对象";
}
if(CollectionUtils.isEmpty(checkFields)){
return "效验字段不能为空";
}
if(sourceObject instanceof List){
List list = (List) sourceObject;
if(CollectionUtils.isEmpty(list)){
return listPropertyName + "为必传字段";
}
for (Object obj : list) {
String resultStr = checkObjNull(obj, checkFields);
return resultStr;
}
}
return null;
}
}
好了,工具类已经编写好了。那怎么进行使用呢!
使用
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
DTO
创建三个类,分别是 UserInfoDTO
、BankCardDTO
、IdCardDTO
- UserInfoDTO:用户信息
@Data
@ApiModel("用户信息")
public class UserInfoDTO {
@ApiModelProperty("用户唯一号")
private Long userId;
@ApiModelProperty("姓名")
private String name;
@ApiModelProperty("银行卡列表信息")
private List<BankCardDTO> cardDTOList;
@ApiModelProperty("身份证信息")
private IdCardDTO idCardDTO;
}
- BankCardDTO:银行卡列表信息
@Data
@ApiModel("银行卡列表")
public class BankCardDTO {
@ApiModelProperty("卡号")
private String cardNumber;
@ApiModelProperty("银行名称")
private String bankName;
}
- IdCardDTO:身份证信息
@Data
@ApiModel("身份证信息")
public class IdCardDTO {
@ApiModelProperty("证件号")
private String idNumber;
@ApiModelProperty("有效期")
private String validity;
}
Controller
然后再创建一个名为TestController
的类,提供一个saveUser
方法,方法中调用编写的工具类。
@PostMapping(value = "/saveUser")
private String saveUser(@RequestBody UserInfoDTO req){
//效验 用户信息
String check = CheckUtil.checkObjNull(req, Arrays.asList("userId", "name", "cardDTOList", "idCardDTO"));
if(StringUtils.hasLength(check)){
return check;
}
//效验 用户信息内的 银行卡列表
List<BankCardDTO> cardDTOList = req.getCardDTOList();
String check2 = CheckUtil.checkArrayNull(cardDTOList, Arrays.asList("cardNumber", "bankName"),"cardDTOList");
if(StringUtils.hasLength(check2)){
return check2;
}
//效验 用户信息内的 身份证信息
IdCardDTO idCardDTO = req.getIdCardDTO();
String check3 = CheckUtil.checkObjNull(idCardDTO, Arrays.asList("idNumber", "validity"));
if(StringUtils.hasLength(check3)){
return check3;
}
return "成功";
}
调用方法
请求地址:localhost:8080/api/saveUser
{
"userId": "1",
"name": "1",
"cardDTOList": [
{
"cardNumber": "5",
"bankName": ""
}
],
"idCardDTO": {
"idNumber": "55",
"validity": "4"
}
}
响应:
[bankName]为必传字段
在我们项目组这效果以及足够使用了。因为不直接对接前端,我们只需要将响应给对接方就好了。有些小伙伴可能会直接对接前端,将响应信息展示在前端了。那肯定不行,用户怎么知道 [bankName]为必传字段
这是个啥!肯定是需要提示中文的,例如:[银行名称]为必传字段
。
提示信息优化
如果要提示中文呢!也是有办法的。各位小伙伴可能注意到了,我导入了swagger
的依赖,其中有个注解为 @ApiModelProperty
,这注解写在了每个属性上,为每个属性提供了描述信息,所以我们可以取这个注解的值。
public static String checkObjNull(Object sourceObject, List<String> checkFields){
if(Objects.isNull(sourceObject)){
return "请输入正确源对象";
}
if(CollectionUtils.isEmpty(checkFields)){
return "效验字段不能为空";
}
Class<?> objClass = sourceObject.getClass();
// 获得本类所有属性对象
Field[] declaredFields = objClass.getDeclaredFields();
if(null == declaredFields || declaredFields.length == 0){
return "源对象中没有属性";
}
StringBuilder sb = new StringBuilder();
for (String checkField : checkFields) {
for (Field field : declaredFields) {
//关闭程序的安全检测
field.setAccessible(true);
// 效验字段与 属性名称相等 则进行效验
String fieldName = field.getName();
// 获得指定注解
ApiModelProperty apiModelProperty = field.getAnnotation(ApiModelProperty.class);
// 获得 value属性 的值
String value = apiModelProperty.value();
if(checkField.equals(fieldName)){
try {
// 获得字段属性值
Object fieldValue = field.get(sourceObject);
// 如果字段属性值为 null 或者 “” 字符串 则认为该字段没有传值
if(Objects.isNull(fieldValue) || StringUtils.isEmpty(fieldValue.toString())){
sb = sb.append("[").
append(value).
append("]").
append("为必传字段");
return sb.toString();
}
} catch (IllegalAccessException e) {
log.error("获取属性值异常",e);
return "请检查传递的源对象";
}
}
}
}
// 通过效验
return null;
}
再次调用方法
请求地址:localhost:8080/api/saveUser
{
"userId": "1",
"name": "1",
"cardDTOList": [
{
"cardNumber": "5",
"bankName": ""
}
],
"idCardDTO": {
"idNumber": "55",
"validity": "4"
}
}
响应:
[银行名称]为必传字段
- 如你对本文有疑问或本文有错误之处,欢迎评论留言指出。如觉得本文对你有所帮助,欢迎点赞和关注。