ValidX 订单数据验证:从购物车到支付的完整验证链路

发布时间:2026/10/13 20:49:36
ValidX 订单数据验证:从购物车到支付的完整验证链路 订单数据验证从购物车到支付的完整验证链路 目录引言一、订单数据验证全景四段式链路二、购物车阶段商品项验证三、收货信息验证四、订单信息验证五、支付信息验证六、注解与链式 API同一规则两种写法七、分组验证草稿 / 提交 / 支付三阶段八、源码解读TradeOrderNumberValidator 支持哪些格式九、边界与坑总结项目地址引言一个订单从产生到支付要经过四个环节购物车结算 → 填写收货信息 → 生成订单 → 发起支付。每个环节都有不同的数据要校验环节典型数据校验重点购物车商品 ID、数量、单价数量范围、金额精度收货信息姓名、手机号、地址、邮编格式合法性中文姓名/手机号/邮编订单信息订单号、支付方式、订单状态订单号格式、枚举合法性支付信息银行卡号、CVVLuhn 算法、安全码格式手写每一处的校验四个环节加起来是几十段重复的if-else 正则。而 ValidX 恰好把每一类都封装成了现成能力——本文用一条完整链路演示如何用注解 链式 API 把购物车到支付的校验全部串起来。一、订单数据验证全景四段式链路先看整条链路的校验点分布购物车结算 ──► 填写收货信息 ──► 生成订单 ──► 发起支付 ① 商品项 ② 收货信息 ③ 订单信息 ④ 支付信息 · 数量 1-99 · ChineseName · 订单号 TradeOrderNumber · 卡号 BankCardLuhn · 单价 Digits(8,2) · ChinesePhone · 支付方式 In/Enum · 安全码 CVV · 金额上限见服务层 · ChineseZipCode · 状态 Enum(targetOrderStatus) · 有效期 MM/YY Pattern · Email四个环节每一类数据都能在 ValidX 找到对应注解。下面逐段实现。二、购物车阶段商品项验证购物车结算时前端传来的是一组商品项。每个商品项要验证数量和金额publicclassCartItem{NotBlankprivateStringskuId;// 商品 SKU 必填Min(value1,message商品数量至少为1)Max(value99,message单件商品最多购买99件)privateintquantity;// 数量1-99NotNullDigits(integer8,fraction2)// 单价最多8位整数 2位小数privateBigDecimalprice;// 单价}注意金额BigDecimal用标准 Bean ValidationJSR-380的Digits/Min/Max校验而不是正则——金额是数值语义正则只适合字符串格式。ValidX 的注解聚焦格式类校验手机号、卡号、身份证……数值范围类交给标准 Bean Validation 注解两者各司其职。quantity和price的下限判断必须放在服务层做汇总校验购物车为空、总价超限因为那是业务规则不是字段格式if(cartItems.isEmpty()){thrownewBusinessException(购物车不能为空);}BigDecimaltotalcartItems.stream().map(i-i.getPrice().multiply(BigDecimal.valueOf(i.getQuantity()))).reduce(BigDecimal.ZERO,BigDecimal::add);if(total.compareTo(newBigDecimal(50000))0){thrownewBusinessException(单笔订单金额不能超过5万元);}三、收货信息验证结算后的下一步是收货信息。这一段的字段几乎全是中国业务格式正好是 ValidX 的主场publicclassShippingInfo{NotBlankChineseName// 中文姓名2-50个汉字支持少数民族姓名间隔号·privateStringreceiverName;NotBlankChinesePhone// 中国大陆手机号11位1[3-9]开头privateStringreceiverPhone;Email// 电子邮箱privateStringemail;NotBlankChineseZipCode// 中国邮政编码6位数字privateStringzipCode;NotBlankprivateStringaddress;// 详细地址自由文本长度校验即可}五个字段四类格式注解全部声明式搞定。每个注解都对应一个独立的ConstraintValidator例如ChinesePhone底层是预编译的手机号格式正则11 位、1[3-9]开头不枚举具体号段避免新放号段被误杀——把最容易写错的手机号正则从业务代码里彻底移除。四、订单信息验证订单从创建到支付请求里反复出现的两类字段是订单号和枚举类字段支付渠道、订单状态。注意一个前提订单号由后端在创建订单时生成生成策略见第八节创建请求本身不含它需要校验格式的是客户端回传订单号的操作查询、支付、取消、确认收货publicclassOrderSubmitDTO{// 提交/流转已生成订单orderNo 为创建接口返回后回传NotBlankTradeOrderNumber// 订单号T18位数字 / 18位数字 / UUID含32位紧凑版privateStringorderNo;NotNullIn(value{WECHAT,ALIPAY,BANK_CARD,BALANCE},message不支持的支付方式)privateStringpayChannel;// 支付方式白名单校验NotBlankEnum(targetOrderStatus.class,fieldcode)privateStringstatus;// 订单状态必须是枚举 code 之一}两个枚举校验各有侧重In给字符串白名单支付方式列表固定直接枚举Enum对齐 Java 枚举类适合经常增删的状态机值代码改动一处校验自动同步订单号这一行值得展开——TradeOrderNumber是 ValidX 金融分类下的专属注解支持的格式在后面第八节详细拆解。订单号不该由客户端生成也不该出现在创建请求里。创建订单成功后订单号由后端生成并随响应下发TradeOrderNumber的真正用武之地是回传校验——客户端拿之前下发的订单号来查询、支付、取消、确认收货时格式校验能拦截手误与伪造。这也是为什么第五节PayRequest、第七节OrderDTO里的orderNo校验都成立它们都是后端已生成、客户端回传的语义。若某接口本应自己生成订单号却去校验客户端传入的订单号说明接口设计就有问题——订单号字段应直接不进 DTO。补充回传订单号通常走 URL 路径而不是 body。订单号是资源标识符RESTful 风格下操作既有订单如发起支付应写成POST /orders/{orderNo}/pay由PathVariable承载DTO 里并不需要该字段。仅三种情况才需要把它放进 body 用注解校验非 REST 风格接口串单防护路径与 body 各带一份比对一致防止换成他人订单TradeOrderNumber先过格式关、再与路径参数equals服务端回调支付网关把订单号作为回调参数传回。上面OrderSubmitDTO属于教学演示实际项目中更推荐路径参数方案。五、支付信息验证支付环节是安全敏感区银行卡号既要格式对Luhn也要能用发卡行支持。ValidX 负责前一半publicclassPaymentInfo{NotBlankBankCard// 银行卡号13-19位 Luhn 算法privateStringcardNo;NotBlankCVV// CVV/CVC 安全码3或4位数字privateStringcvv;NotBlankPattern(regexp(0[1-9]|1[0-2])/\\d{2},message有效期格式必须为 MM/YY)privateStringexpiry;// 有效期MM/YY用标准注解写正则}BankCard背后是 Luhn 算法——它会自动拦截手滑输错一位的卡号。BankCardValidator的处理流程去空格/连字符 → 纯数字 → 13-19 位 → Luhn 校验位publicbooleanisValid(Stringvalue,ConstraintValidatorContextcontext){if(valuenull||value.isEmpty()){returntrue;// 空值处理交给NotNull等其他注解处理}// 移除所有空格和连字符StringcleanValuevalue.replaceAll([\\s-],);// 检查是否全部为数字if(!cleanValue.matches(\\d)){returnfalse;}// 检查长度是否符合银行卡号规范通常为13-19位if(cleanValue.length()13||cleanValue.length()19){returnfalse;}// 使用Luhn算法验证银行卡号returnisLuhnValid(cleanValue);}卡号格式合法 ≠ “卡可用”。真实支付前还需要 BIN 识别发卡行、调用银行接口验证——Luhn 只是第一道闸。BIN 识别可参考本站文章 22《银行卡BIN码识别》。六、注解与链式 API同一规则两种写法同一个订单场景注解用于DTO 声明式校验链式 API 用于方法内即时校验适合没有 DTO 的简单接口或预处理。两种写法等价① 注解方式推荐用于入参 DTOpublicclassPayRequest{TradeOrderNumberprivateStringorderNo;BankCardprivateStringcardNo;CVVprivateStringcvv;}// 使用SetConstraintViolationPayRequestviolationsvalidator.validate(req);② 链式 API适合没有 DTO 的场景ValidXvValidX.init();v.isTradeOrderNumber(orderNo).isBankCard(cardNo).isCVV(cvv);if(v.passed()){payService.pay(orderNo,cardNo,cvv);}else{// v.getErrors() 返回所有失败信息thrownewBusinessException(v.getErrors().get(0));}链式 API 的优势是一次调用链完成多字段校验并收集全部错误而不是第一处失败就 return。isTradeOrderNumber→isBankCard→isCVV的书写顺序就是支付接口的执行顺序可读性极强。七、分组验证草稿 / 提交 / 支付三阶段电商最常见的需求同一张订单不同阶段校验不同字段。草稿阶段只要求订单号存在其它都允许空提交阶段收货信息必填、支付方式合法支付阶段银行卡信息必填Bean ValidationJSR-380的groups机制正好解决。定义两个分组接口publicinterfaceSubmitGroup{}// 提交时校验publicinterfacePayGroup{}// 支付时校验然后给字段挂上分组publicclassOrderDTO{TradeOrderNumber(groups{SubmitGroup.class,PayGroup.class})privateStringorderNo;NotBlank(groupsSubmitGroup.class)ChineseName(groupsSubmitGroup.class)privateStringreceiverName;// 提交时才校验NotBlank(groupsSubmitGroup.class)ChinesePhone(groupsSubmitGroup.class)privateStringreceiverPhone;NotBlank(groupsPayGroup.class)BankCard(groupsPayGroup.class)privateStringcardNo;// 支付时才校验NotBlank(groupsPayGroup.class)CVV(groupsPayGroup.class)privateStringcvv;}使用时分组校验// 提交订单只校验 SubmitGroupvalidator.validate(orderDTO,SubmitGroup.class);// 发起支付只校验 PayGroupvalidator.validate(orderDTO,PayGroup.class);执行效果阶段校验的字段未声明的字段提交订单订单号、收货人、手机号银行卡、CVV跳过发起支付订单号、银行卡、CVV收货人、手机号跳过这就是从购物车到支付完整链路中一条链路、两种校验强度的标准解法。八、源码解读TradeOrderNumberValidator 支持哪些格式订单号是订单链路最核心的字段TradeOrderNumber到底接受什么看源码TradeOrderNumberValidator.java// T开头18位数字格式privatestaticfinalPatternPREFIX_T_PATTERNPattern.compile(^T\\d{18}$);// 纯18位数字格式privatestaticfinalPatternDIGITS_18_PATTERNPattern.compile(^\\d{18}$);// UUID格式带连字符privatestaticfinalPatternUUID_PATTERNPattern.compile(^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$);// UUID格式不带连字符privatestaticfinalPatternUUID_NO_HYPHEN_PATTERNPattern.compile(^[0-9a-fA-F]{32}$);publicbooleanisValid(Stringvalue,ConstraintValidatorContextcontext){if(valuenull||value.isEmpty()){returntrue;// 空值处理交给NotNull等其他注解处理}// 移除所有空格和连字符StringcleanValuevalue.replaceAll([\\s-],);// 验证是否匹配任一支持的格式returnPREFIX_T_PATTERN.matcher(cleanValue).matches()||DIGITS_18_PATTERN.matcher(cleanValue).matches()||UUID_PATTERN.matcher(cleanValue).matches()||UUID_NO_HYPHEN_PATTERN.matcher(cleanValue).matches();}四个格式覆盖三类订单号生成策略生成策略匹配格式示例业务前缀 时间戳T 18 位数字T202510171234567890纯时间戳/流水18 位纯数字202510171234567890UUID带连字符550e8400-e29b-41d4-a716-446655440000UUID 紧凑版32 位十六进制550e8400e29b41d4a716446655440000几个值得注意的实现细节正则全部预编译static final Pattern高并发下单场景没有重复编译开销replaceAll([\\s-], )先归一化——容忍前端传来的2025-10-17-1234-5678-90之类带分隔符的写法去连字符后恰为 18 位数字命中纯数字格式一个实现瑕疵值得留意连字符在归一化时已被删除因此第 3 个带连字符 UUID正则实际不可达——带连字符 UUID 之所以能通过靠的是删连字符后命中32 位紧凑 UUID分支。若要保留该分支应先匹配原始串、再归一化null/空串放行JSR-380 惯例——是否必填由NotBlank决定职责分离四选一用||短路——前一个匹配成功就不再匹配后面的正则九、边界与坑1. 金额绝不能用 Double 比较0.1 0.2 ! 0.3。订单金额一律BigDecimal构造用字符串new BigDecimal(19.90)而不是new BigDecimal(19.9)校验用Digits控精度比较用compareTo。2. 校验顺序格式校验先于业务校验先跑注解校验格式再跑服务层业务校验库存、余额、限购。把手机号格式错和库存不足混在一个异常里前端没法精准提示。3. 支付接口必须防重TradeOrderNumber只保证格式不保证唯一。幂等键 数据库唯一索引缺一不可Luhn 和订单号正则都防不了重复提交。4. CVV 不要落库CVV 校验通过后立即丢弃任何日志、数据库、缓存都不许存——PCI-DSS 合规红线。文章里PaymentInfo只做校验 DTO不持久化。5. 分组校验记得处理未分组字段validator.validate(dto, PayGroup.class)时没有声明groups的字段默认属于Default组不会被执行。如果你的约束如Email没指定 groups支付阶段不会校验它——按需给每个约束显式分组别留默认。6. 链式 API 的语义isTradeOrderNumber(null)会放行null 合法所以链式调用前若订单号必填要自己先判空或用NotBlank语义的检查。ValidX 每个链式方法都遵循空值放行、由必填注解兜底的 JSR-380 惯例。7. 卡号归一化BankCard容忍空格和连字符5425-2334-3010-9903合法但存库前必须统一格式去分隔符否则同一张卡在库里可能有三份记录。总结阶段关键字段ValidX 能力标准注解兜底购物车数量、单价—金额非格式校验Min/Max/Digits收货信息姓名/手机/邮编/邮箱ChineseName/ChinesePhone/ChineseZipCode/EmailNotBlank订单信息订单号/支付方式/状态TradeOrderNumber/In/EnumNotNull支付信息卡号/CVV/有效期BankCardLuhn/CVVPatternMM/YY从购物车到支付的完整验证链路本质是四层职责分离格式层ValidX 注解正则/Luhn声明式零散代码清零数值层标准 Bean ValidationMin/Digits金额与数量业务层服务代码库存、余额、防重、汇总阶段层groups分组草稿/提交/支付一套 DTO 两种强度ValidX 的意义在于把最容易写错、最容易遗漏的格式层全部标准化——手机号正则、银行卡 Luhn、订单号格式、身份证校验码……这些都是订单系统的地基地基稳了上面三层才谈得上可靠。项目地址GitHubhttps://github.com/vipxieliang/ValidXGiteehttps://gitee.com/vipxieliang/ValidXMaven Centralhttps://central.sonatype.com/artifact/io.github.vipxieliang/validx

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询