美团CPS分销接口开发中Java请求参数校验的优雅实现技巧
在对接美团CPS(Cost Per Sale)分销接口时,上游系统或前端传入的参数可能存在缺失、格式错误或业务逻辑不合法等问题。若未做严格校验,轻则导致接口异常,重则引发佣金计算错误或数据污染。本文结合JSR-380(Bean Validation 2.0)、自定义注解与分组校验,提供一套高内聚、低侵入的参数校验方案。
1. 基础DTO与标准注解使用
首先定义接收美团回调或调用其OpenAPI的DTO,并使用javax.validation注解约束字段:
packagebaodanbao.com.cn.cps.dto;importjavax.validation.constraints.*;importjava.math.BigDecimal;publicclassMeituanOrderCreateRequest{@NotBlank(message="商户ID不能为空")privateStringmerchantId;@Pattern(regexp="^[A-Za-z0-9]{1,32}$",message="订单号仅支持字母数字,长度1-32")privateStringorderId;@NotNull(message="用户ID不可为空")@Min(value=10000,message="用户ID需大于10000")privateLonguserId;@NotNull(message="订单金额不可为空")@DecimalMin(value="0.01",message="订单金额至少0.01元")@DecimalMax(value="99999.99",message="单笔订单金额不得超过99999.99元")privateBigDecimalorderAmount;@Email(message="通知邮箱格式不正确")privateStringnotifyEmail;// getters & setters}2. 分组校验应对多场景复用
同一DTO可能用于“创建订单”和“查询订单”两种接口,所需字段不同。通过分组实现差异化校验:
packagebaodanbao.com.cn.cps.groups;publicinterfaceCreateGroup{}publicinterfaceQueryGroup{}在DTO中指定分组:
publicclassMeituanOrderRequest{@NotBlank(groups={CreateGroup.class,QueryGroup.class})privateStringorderId;@NotNull(groups=CreateGroup.class)@DecimalMin(value="0.01",groups=CreateGroup.class)privateBigDecimalamount;@Min(value=1,groups=QueryGroup.class)privateIntegerpageNum;// ...}Controller中触发对应分组校验:
packagebaodanbao.com.cn.cps.controller;importbaodanbao.com.cn.cps.dto.MeituanOrderRequest;importbaodanbao.com.cn.cps.groups.CreateGroup;importbaodanbao.com.cn.cps.groups.QueryGroup;importorg.springframework.validation.annotation.Validated;importorg.springframework.web.bind.annotation.*;@RestController@RequestMapping("/meituan/cps")publicclassMeituanCpsController{@PostMapping("/order/create")publicStringcreateOrder(@Validated(CreateGroup.class)@RequestBodyMeituanOrderRequestreq){return"success";}@GetMapping("/order/list")publicStringlistOrders(@Validated(QueryGroup.class)MeituanOrderRequestreq){return"success";}}3. 自定义业务校验注解
美团CPS要求某些字段组合满足特定规则,例如“当订单类型为团购时,必须包含团购券码”。此时需自定义校验器:
packagebaodanbao.com.cn.cps.validation;importjavax.validation.Constraint;importjavax.validation.Payload;importjava.lang.annotation.*;@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Constraint(validatedBy=CouponCodeValidator.class)@Documentedpublic@interfaceValidCouponIfGroupBuy{Stringmessage()default"团购订单必须提供有效券码";Class<?>[]groups()default{};Class<?extendsPayload>[]payload()default{};}实现校验逻辑:
packagebaodanbao.com.cn.cps.validation;importbaodanbao.com.cn.cps.dto.MeituanOrderCreateRequest;importjavax.validation.ConstraintValidator;importjavax.validation.ConstraintValidatorContext;publicclassCouponCodeValidatorimplementsConstraintValidator<ValidCouponIfGroupBuy,MeituanOrderCreateRequest>{@OverridepublicbooleanisValid(MeituanOrderCreateRequestrequest,ConstraintValidatorContextcontext){if("GROUP_BUY".equals(request.getOrderType())){returnrequest.getCouponCode()!=null&&!request.getCouponCode().trim().isEmpty();}returntrue;// 非团购订单无需校验}}在DTO上使用:
@ValidCouponIfGroupBuypublicclassMeituanOrderCreateRequest{privateStringorderType;// NORMAL / GROUP_BUYprivateStringcouponCode;// other fields...}4. 全局异常处理统一返回格式
捕获MethodArgumentNotValidException并返回结构化错误信息:
packagebaodanbao.com.cn.cps.exception;importorg.springframework.http.HttpStatus;importorg.springframework.validation.FieldError;importorg.springframework.web.bind.MethodArgumentNotValidException;importorg.springframework.web.bind.annotation.ExceptionHandler;importorg.springframework.web.bind.annotation.ResponseStatus;importorg.springframework.web.bind.annotation.RestControllerAdvice;importjava.util.HashMap;importjava.util.Map;@RestControllerAdvicepublicclassValidationExceptionHandler{@ExceptionHandler(MethodArgumentNotValidException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)publicMap<String,String>handleValidationExceptions(MethodArgumentNotValidExceptionex){Map<String,String>errors=newHashMap<>();ex.getBindingResult().getAllErrors().forEach((error)->{StringfieldName=((FieldError)error).getField();StringerrorMessage=error.getDefaultMessage();errors.put(fieldName,errorMessage);});returnerrors;}}5. 手动触发校验(适用于非Controller场景)
在Service层或消息消费中,可手动校验对象:
packagebaodanbao.com.cn.cps.service;importbaodanbao.com.cn.cps.dto.MeituanOrderCreateRequest;importorg.springframework.stereotype.Service;importjavax.validation.ConstraintViolation;importjavax.validation.Validation;importjavax.validation.Validator;importjavax.validation.ValidatorFactory;importjava.util.Set;@ServicepublicclassOrderValidationService{privatefinalValidatorvalidator;publicOrderValidationService(){ValidatorFactoryfactory=Validation.buildDefaultValidatorFactory();this.validator=factory.getValidator();}publicvoidvalidate(MeituanOrderCreateRequestrequest){Set<ConstraintViolation<MeituanOrderCreateRequest>>violations=validator.validate(request);if(!violations.isEmpty()){StringBuildersb=newStringBuilder();for(ConstraintViolation<MeituanOrderCreateRequest>violation:violations){sb.append(violation.getPropertyPath()).append(": ").append(violation.getMessage()).append("; ");}thrownewIllegalArgumentException(sb.toString());}}}本文著作权归 俱美开放平台 ,转载请注明出处!