基于注解实现请求参数校验

1、定义参数注解,作用于提交对象属性上,指定该属性描述,是否非空,长度等

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * @ClassName:  FieldDesc   
 * @Description:用于提交参数校验  
 * @author: Ccl
 * @date:   2022年4月29日 上午11:51:54      
 * @Copyright:
 */
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldDesc {
    String desc();
    boolean isNotNull();
    int length();
}

2、定义方法注解

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @ClassName:  FieldInterceptor   
 * @Description:作用于方法上,标识对该方法参数进行校验
 * @author: Ccl
 * @date:   2022年4月29日 上午11:51:54      
 * @Copyright:
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface FieldInterceptor {
    
    boolean isCheck() default true;
}

3、定义注解切面实现

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer;
import com.ccl.order.conf.exception.OperationDeniedException;
import com.ccl.order.util.DataUtils;
import com.ccl.order.util.MapUtils;

/**
 * @ClassName:  FieldInterceptorAspect   
 * @Description:方法请求参数的属性,是否非空,长度进行校验,方法入口校验的参数名必须为param
 * @author: Ccl
 * @date:   2022年5月1日 下午6:32:08      
 * @Copyright:
 */
@Aspect
@Order(value = 3)
@Component
public class FieldInterceptorAspect {
    
    private static final Logger log = LoggerFactory.getLogger(FieldInterceptorAspect.class);
    
    //切入点
    @Pointcut(value = "@annotation(com.ccl.order.aop.FieldInterceptor)")
    private void pointcut() {
    }
    
    @Around(value="pointcut() && @annotation(fieldInterceptor)")
    public Object around(ProceedingJoinPoint joinPoint,FieldInterceptor fieldInterceptor){
        Map<String,Object> map = changMap(joinPoint);
        boolean is_update = fieldInterceptor.isCheck();//是否校验参数
        if(is_update) {
            Object param = map.get("param");//获取请求参数对象
            check_obj_value(MapUtils.objectToMap(param),param.getClass());//开始校验参数
        }
        try {
            return joinPoint.proceed();//校验正常,继续往下执行
        } catch (Throwable e) {
            //自定义异常按照正常格式返回
            e.printStackTrace();
            throw new OperationDeniedException(e.getMessage());
        }
    }
    
    /**
     * 
     * @Title: check_obj_value   
     * @Description: 对象参数校验
     * @param: @param param
     * @param: @param clazz      
     * @return: void      
     * @throws
     */
    public void check_obj_value(Map<String,Object> param,Class clazz) {
        Field[] fields = DataUtils.getFields(clazz);//获取类所有字段
        for(Field field : fields) {
            field.setAccessible(true);
            String typeName = field.getType().getSimpleName();//获取类名
            if(typeName.contains("List")) {//如果为列表,则需判断是否为列表对象,如果为一般数据类型,则不用循环判断每个对象
                List list = (List) param.get(field.getName());//获取列表对象
                if(list == null || list.isEmpty()) continue;
                Object first_obj = list.get(0);
                if(isJavaBeanForClass(first_obj.getClass())) {//判断列表元素是否为javabean对象
                    for(int i = 0;i < list.size(); i++) {//循环判断每一个对象参数
                        Object sub_obj = list.get(i);
                        check_obj_value(MapUtils.objectToMap(sub_obj),sub_obj.getClass());
                    }
                }else {
                    checkSubmitParamIsCurrent(field,get_emp_str_if_null(param.get(field.getName())));//判断属性值是否满足要求
                }
            }else {
                checkSubmitParamIsCurrent(field,get_emp_str_if_null(param.get(field.getName())));//判断属性值是否满足要求
            }
        }
    }
    
    /**
     * @Title: isJavaBean   
     * @Description: 判断类型是非为java bean
     * @param: @param type
     * @param: @return      
     * @return: boolean      
     * @throws
     */
    public static final boolean isJavaBean(Type type){
        if(null == type ) return false;
        return ParserConfig.global.getDeserializer(type) instanceof JavaBeanDeserializer;
    }
    
    /**
     * @Title: isJavaBeanForClass   
     * @Description: 判断类是否为java bean   
     * @param: @param clazz
     * @param: @return      
     * @return: boolean      
     * @throws
     */
    public static final boolean isJavaBeanForClass(Class clazz){
        if(null == clazz ) return false;
        return isJavaBean(((Type)clazz));
    }
    
    /**
     * 从切点中中获取已经映射好的参数,因为不确定request的类型,所以只能够通过映射封装好的参数来获取参数值,参数一定要用方法@RequestParam接收
     * @Title: changMap   
     * @Description: 获取切入点方法请求参数   
     * @param: @param pjp
     * @param: @return      
     * @return: Map<String,Object>      
     * @throws
     */
    private static Map<String, Object> changMap(ProceedingJoinPoint pjp) {
        Signature signature = pjp.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        String[] strings = methodSignature.getParameterNames();//获取该方法所有参数
        Object[] args = pjp.getArgs();
        List<String> list = Arrays.asList(strings);
        Map<String, Object> map = new HashMap<String, Object>();
        for (int i = 0; i < args.length; i++) {
            if(!(args[i] instanceof HttpServletRequest) && !(args[i] instanceof HttpServletResponse)){ //有request参数时忽略
                map.put(list.get(i), args[i]);
            }
        }
        return map;
    }
    
    public String get_emp_str_if_null(Object obj) {
        if(obj == null) return "";
        return obj.toString();
    }
    
    //校验提交参数是否正确
      public void checkSubmitParamIsCurrent(Field field,String value){
          String fieldDesc = "";
          boolean isNotNull = false;
          int length = -1;
          Annotation[] annotation = field.getAnnotations();
          for (Annotation tag : annotation) {
              if (tag instanceof FieldDesc) {
                  isNotNull = ((FieldDesc) tag).isNotNull();
                  fieldDesc = ((FieldDesc) tag).desc();
                  length = ((FieldDesc) tag).length();
                  break;
              }
          }
          if(StringUtils.isBlank(fieldDesc)) return;
          if(!isCanConvert(field,value)){
              throw new OperationDeniedException("提交参数["+fieldDesc+"],包含值["+value+"]类型错误,请提交正确值!");
          }else if(isNotNull && StringUtils.isBlank(value)){
              throw new OperationDeniedException("提交参数["+fieldDesc+"],不可为空,请提交正确值!");
          }else if(length != -1 && StringUtils.isNotBlank(value) && value.length() > length){
              throw new OperationDeniedException("提交参数["+fieldDesc+"]字数为["+value.length()+"],限制字数为["+length+"],请提交正确值!");
          }
      }
      
      public boolean isCanConvert(Field field,String valueStr){
        if(StringUtils.isBlank(valueStr) || "null".equalsIgnoreCase(valueStr))
            return true;
        return isCanConvert(field.getType(),valueStr);
    }

    public boolean isCanConvert(Type type,String valueStr){
        if(StringUtils.isBlank(valueStr) || "null".equalsIgnoreCase(valueStr))
            return true;
        String typeName = ((Class)type).getName();
        String[] types = {"java.lang.Long","java.lang.Integer","java.lang.Float","java.lang.Double","java.math.BigDecimal","java.lang.Byte","java.lang.Short"};
        if(Arrays.asList(types).contains(typeName)){
            valueStr = valueStr.trim();
            if(!NumberUtils.isDigits(valueStr)){
                return false;
            }
        }else if(typeName.contains("Date")){//时间格式至少为 年月日格式
            Date date = null;
            try {
                date = DateUtils.parseDate(valueStr, new String[] {"yyyy-MM-dd HH:mm:ss","yyyy-MM-dd HH:mm","yyyy-MM-dd HH","yyyy-MM-dd"});
            } catch (ParseException e) {
            }
            return date != null;
        }
        return true;
    }
    
}

4、应用,将参数注解加入到实体对象里面

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ccl.order.aop.FieldDesc;
import com.ccl.order.api.base.ExtendDataDTO;
import com.ccl.order.excel.annotation.Excel;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

/**
 * @ClassName:  OrderEntity   
 * @Description: 订单实体类
 * @author: Ccl
 * @date:   2022年5月3日 下午3:25:27      
 * @Copyright:
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = false)
@TableName("sc_order")
public class OrderEntity extends ExtendDataDTO implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 订单id
     */
    @TableId
    private String id;
    /**
     * 公司id
     */
    @Excel(name = "公司id")
    private String companyId;
    /**
     * 订单号
     */
    @FieldDesc(desc = "订单号",isNotNull = true,length = 50)
    @Excel(name = "订单号")
    private String orderNumber;
    /**
     * 订单名称
     */
    @FieldDesc(desc = "订单名称",isNotNull = true,length = 10)
    @Excel(name = "订单名称")
    private String orderName;
    /**
     * 订单类型
     */
    @FieldDesc(desc = "订单类型",isNotNull = false,length = -1)
    @Excel(name = "订单类型")
    private String orderType;
    /**
     * 订单价格
     */
    @FieldDesc(desc = "订单价格",isNotNull = true,length = 10)
    @Excel(name = "订单价格")
    private BigDecimal orderPrice;
    /**
     * 发货地址
     */
    @FieldDesc(desc = "发货地址",isNotNull = true,length = 255)
    @Excel(name = "发货地址")
    private String orderSendAddr;
    /**
     * 收货地址
     */
    @FieldDesc(desc = "收货地址",isNotNull = true,length = 255)
    @Excel(name = "收货地址")
    private String orderReceiAddr;
    /**
     * 下单时间
     */
    @FieldDesc(desc = "下单时间",isNotNull = true,length = -1)
    @Excel(name = "下单时间")
    private Date orderTime;
    /**
     * 创建人
     */
    @Excel(name = "创建人")
    private String createUserId;
    /**
     * 创建时间
     */
    @Excel(name = "创建时间")
    private Date createTime;
    /**
     * 修改人
     */
    @Excel(name = "修改人")
    private String updateUserId;
    /**
     * 修改时间
     */
    @Excel(name = "修改时间")
    private Date updateTime;

}

5、请求入口方法处加上方法注解,标识对该方法进行参数校验

@RestController
@RequestMapping("order")
@Validated
public class OrderController extends BaseController<OrderEntity,OrderService>{
    @Autowired
    private OrderService orderService;

   @Auth
    @FieldInterceptor
    @PostMapping("/saveOrUpdate")
    public ApiResponse saveOrUpdate(ScuserTicket ticket,@RequestBody OrderEntityDTO param){
        orderService.saveOrUpdateOrder(ticket,param);

        return ApiResponse.buildSuccess();
    }
}

6、效果

 

posted @ 2022-05-03 15:31  Change你所当然  阅读(125)  评论(0)    收藏  举报