在电商系统里,数据就像流淌的血液 —— 用户填的手机号、下单的商品数量、支付的金额,每一个数字、每一段文字都得靠谱。要是数据出了错,轻则订单下不了,重则钱货两空。ZKmall 开源商城作为一个分布式电商系统,每天要处理成百上千的用户输入、第三方接口数据和服务间调用,怎么保证这些数据 "干干净净" 地流转?他们用了 Jakarta Validation 这套规范,搭起了一道看不见的数据防线,既省了写代码的功夫,又让系统少出乱子。
Jakarta Validation:让校验规则 "贴" 在数据上
以前写数据校验,程序员得写一堆 if-else:"用户名不能为空"、"密码长度得在 6 到 20 位之间"、"手机号格式不对"…… 这些代码混在业务逻辑里,看起来就像一锅大杂烩。后来出了 Jakarta Validation(以前叫 Java EE Validation),这事儿就简单了 —— 用注解把校验规则直接 "贴" 在数据上,代码一下子清爽多了。
这套规范最妙的是解耦。比如用户注册时要填用户名,以前得写if (username == null || username.trim().isEmpty()) \{ ... \}
,现在只要在字段上标个@NotBlank(message = "用户名不能为空")
,校验逻辑和业务代码彻底分开。新来的开发者一看注解就知道这个字段有啥要求,不用在一堆代码里扒逻辑。
复用性也特别好。手机号格式校验这事儿,用户注册、改绑手机、下单填收货信息时都得用。按以前的做法,可能在三个地方各写一遍正则判断,改个规则得改三处。现在把@Pattern(regexp = "^1[3-9]\\\\d\{9\}$")
定义在手机号字段上,哪儿用这个字段,哪儿就自动生效,改规则也只改一处。
还有错误信息处理,也不用手动攒了。校验失败时,系统会自动收集所有问题,比如 "用户名不能为空"、"手机号格式不对",直接转成用户能看懂的提示。有次运营说用户反馈报错信息太乱,技术团队用这套规范统一了格式,用户投诉一下子少了一半。
ZKmall 选了 3.0 版本,主要是因为它跟 Spring Boot 3 能无缝衔接,还支持 Java 8 以后的新特性。比如处理 Optional 类型的数据时,注解能自动识别里面的值,不用额外写代码判断是否为空。
注解在手,常见校验不用愁
Jakarta Validation 带了一堆现成的注解,像工具箱里的扳手螺丝刀,对付日常校验绰绰有余。ZKmall 把它们分了四类,用到的时候信手拈来。
基础约束注解管的是 "有没有" 的问题。@NotNull
说的是这个字段不能是 null,比如订单里的用户 ID,没它根本不知道给谁发货;@NotBlank
专门盯字符串,不光不能是 null,还不能全是空格,像收货地址填一堆空格肯定不行;@NotEmpty
是给集合用的,购物车提交时至少得有一件商品吧?这三个注解组合起来,就能把必填项管得明明白白。
数值约束注解专盯数字。@Positive
保证是正数,商品单价总不能是负数;@Min
和@Max
划定范围,比如限购商品每人最多买 100 件;@DecimalMin
和@DecimalMax
对付高精度的价格计算,避免用 float、double 时出现 "0.01+0.02=0.0300000004" 这种坑。有次财务对账发现几分钱的差异,就是因为没用好这些注解,后来全换成 BigDecimal 加注解校验,再也没出过岔子。
字符串约束注解处理文字格式。@Email
能自动识别邮箱,哪怕用户写成 "USER@EXAMPLE.COM" 也能过,不用自己写复杂的正则;@Size
管长度,密码太短不安全,太长记不住,设个 6 到 20 位就挺合适;@Pattern
最灵活,啥特殊格式都能用正则搞定,比如身份证号、银行卡号这些有固定规则的。
集合约束注解里,@Valid
是个狠角色,能让校验 "钻" 到对象里面去。比如一个订单里有多个订单项,给集合标个@Valid
,系统就会挨个检查每个订单项的商品 ID、数量对不对,不用手动循环校验。有个新人不知道这个注解,自己写了个 for 循环遍历订单项,结果漏了空指针判断,上线后出了 bug,后来全换成@Valid
,省心多了。
在 Spring Boot 里用这些注解也简单,Controller 的参数前加个@Validated
,系统就会自动触发校验。失败了会抛异常,全局异常处理器接住后,转成统一的错误格式返回给前端,用户能清楚地看到哪个字段出了问题。
自定义校验:把业务规则变成 "注解"
现成的注解虽然好用,但电商业务总有特殊要求。比如密码得同时有字母和数字,促销活动的结束时间不能比开始时间早,这些规则用内置注解搞不定,就得自己造注解
ZKmall 里有个@PasswordStrength
注解,专门管密码强度。实现这东西分三步:先定义个注解,指定它能标在哪些地方,用哪个类来校验;然后写个校验器,里面放具体的判断逻辑 —— 长度够不够 8 到 20 位,有没有字母,有没有数字;最后在密码字段上标一下这个注解,齐活。
有次安全审计说密码太简单,容易被破解,安全团队提了新要求:得包含大小写字母、数字和特殊符号。开发们没改多少代码,就在校验器里加了几行判断,新规则第二天就上线了,要是换以前改一堆 if-else,没三天搞不完。
更复杂点的跨字段校验也能搞定。比如促销活动 DTO 里的startTime
和endTime
,得保证结束时间在后头。这种时候,校验器可以拿到整个对象,对比两个字段的值。ZKmall 的做法是自定义一个@ValidEndTime
注解,校验器里通过反射拿到startTime
,再跟endTime
比较,只要endTime
不在startTime
之后,就报错。
他们还把常用的自定义校验打包成了一个模块,里面有身份证号、银行卡号、商品编码这些电商特有的校验规则。各业务团队要用的时候,直接引入依赖,标个注解就行,不用重复造轮子。有个做生鲜业务的团队,要校验 "配送时间必须在商品保质期内",就是在这个模块基础上扩展的,一周就搞定了。
分组校验:不同场景,不同规矩
同一个数据在不同场景下,规矩可能不一样。比如商品 ID,创建商品的时候不能填(系统自动生成),更新的时候必须填;用户昵称,注册时可以不填(用手机号代替),但第一次修改资料时必须填。这种情况,分组校验就能派上用场。
做法是先定义几个空接口当 "分组标识",比如CreateGroup
、UpdateGroup
,然后在注解里指定这个规则属于哪个组。创建商品时,id
字段标@Null(groups = CreateGroup.class)
;更新时,标@NotNull(groups = UpdateGroup.class)
。Controller 方法里用@Validated(CreateGroup.class)
指定用哪个组的规则,系统就只会按这个组的规则来校验。
分组还能继承,比如AdminCreateGroup
继承CreateGroup
,这样管理员创建商品时,既会校验普通用户创建时的规则(如商品名称不能为空),还会额外校验只有管理员才需要的规则(如审核状态)。有个运营团队想搞个 "内部商品",不需要填那么多字段,技术团队就新建了个InternalGroup
,复用了大部分规则,只改了几个字段的校验要求,三天就上线了。
订单系统里这招用得最多。普通订单要校验收货地址,自提订单就不用;用优惠券的订单要校验优惠券是否有效,不用优惠券的就跳过。以前为每种订单搞一套 DTO,代码冗余得不行,现在用分组校验,一个 DTO 就能应对多种场景,维护起来方便多了。
性能优化:校验也得 "轻装上阵"
数据校验虽然重要,但太耗时也不行。特别是大促的时候,每秒几万次请求,每次校验都慢吞吞的,系统肯定扛不住。ZKmall 琢磨了不少办法,让校验又快又准。
少校验是最直接的。查询接口一般只需要校验页码、每页条数这些简单参数,不用把整个复杂对象从头到尾查一遍;内部服务调用时,如果上游已经校验过了,下游就可以跳过,省点力气。比如商品服务传给订单服务的商品信息,商品服务已经确认过价格、库存没问题了,订单服务就不用再校验一遍, trust but verify 在这里不太适用,效率更重要。
校验逻辑要轻。自定义校验器里千万别搞复杂操作,像查数据库、调接口这种事儿,绝对不能放进去。有个开发者图省事,在校验器里查了下商品库存,结果大促时库存服务一卡,校验也跟着卡,整个下单流程都慢了。后来把库存校验挪到了业务逻辑层,校验器只负责格式,速度一下子提上来了。
批量处理比逐条校验快。批量导入商品的时候,要是一条一条校验,1000 条数据得循环 1000 次,改成一次性把所有数据扔给校验器,性能能提升一半以上。ZKmall 的商品批量导入功能,就因为改了这个,从每次最多导 500 条,变成能导 2000 条,运营们高兴坏了。
还有些小技巧也挺管用。比如@NotBlank
已经包含了非 null 的判断,就别再画蛇添足加个@NotNull
;错误消息别用技术术语,用户看不懂 "Pattern 约束违例",换成 "手机号格式不对" 就明白多了;校验和业务逻辑要分清,"数量必须为正数" 是校验,"库存不够" 是业务逻辑,别混在一起。
他们还给所有校验规则写了单元测试,特别是自定义的那些。比如测试密码校验时,空字符串、纯字母、纯数字、符合要求的情况都测一遍,上线前跑一遍,心里踏实。有次一个新注解没测全,漏了对 null 值的处理,结果线上报错,后来测试覆盖率提到 100%,这种问题就再也没出过。
数据校验:电商系统的 "第一道防线"
用了 Jakarta Validation 之后,ZKmall 的变化挺明显:手动写的校验代码少了七成,开发者不用再跟 if-else 较劲,能专心琢磨业务;数据相关的线上故障降了一半多,以前总有人因为填错格式、漏填字段投诉,现在系统提前就拦住了;新功能开发速度快了三成,加个校验规则只要加个注解,不用改一大片代码。
随着系统往微服务方向发展,他们还把校验规则放进了 API 契约里,服务之间调用的时候,网关会先按契约校验一遍,省得无效请求跑到服务里去。有次支付服务升级,不小心改了参数格式,网关的校验立马报错,开发们及时回滚,没影响到用户。
说到底,电商系统里的数据就像积木,一块错了,搭起来的房子可能就塌了。Jakarta Validation 这套规范,就像个细心的质检员,把不合格的积木早早挑出来,让整个系统能稳稳当当地跑。对于其他电商项目来说,这套思路也挺值得借鉴 —— 用规范的力量简化校验,既省心又靠谱,何乐而不为呢?