AI陪伴机器人DTO参数校验-把脏数据拦在门口
07-DTO参数校验-把脏数据拦在门口系列AI 伙伴AI-Partner——具身智能陪伴机器人 · 数据接口部署与二次开发篇07/12一、为什么校验要放在门口先说个真实场景你在前端页面上提交一条健康记录手一抖type字段空了请求直接打到了 Service 层Service 里判空逻辑没写全于是数据库里多了一条类型为 null的脏数据。后面做趋势查询、做异常告警判定这条数据就是个地雷随时炸。这种问题的正解只有一个数据进门之前先验一遍不合格直接轰出去。在 Spring Boot 的体系里这个门口就是 DTO Bean Validation。先解释几个名词照顾零基础的朋友DTOData Transfer Object数据传输对象。前端传过来的 JSON先反序列化成一个 Java 对象这个对象就叫 DTO。它和数据库的 Entity 不是一回事——Entity 对应表结构DTO 对应接口契约。Bean ValidationJava 的参数校验标准老一辈叫 JSR-303/380Jakarta 时代就是 jakarta.validation 包。你在字段上贴注解框架替你做检查。Valid触发校验的开关。贴在 Controller 入参上进方法之前先校验 DTO。二、项目里的依赖validation starterAI 伙伴AI-Partner后端在 pom 里显式引入了校验依赖项目源码dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-validation/artifactId/dependency这里有个新手常踩的坑要提一句Spring Boot 2.3 之后web starter 里不再自带校验能力。你要是用校验注解却没引这个 starter运行时Valid会假装生效——不报错、也不校验脏数据照进不误。所以这个依赖别漏。三、5 个 DTO 逐个拆字段、注解、文案AI 伙伴AI-Partner的dto包里共有 5 个 DTO全部用 Lombok 的Data校验注解来自jakarta.validation.constraints。下面逐个过以下均为项目源码。3.1 ChatRequest —— 对话请求NotNull(messageuserId 不能为空)privateLonguserId;NotBlank(message消息内容不能为空)privateStringmessage;privateStringsessionTypetext;// text / voiceprivateBooleanneedTtsfalse;// 是否合成语音3.2 UserRequest —— 用户注册/登录NotBlank(messageopenId 不能为空)privateStringopenId;privateStringplatformweb;// wechat / alipay / webprivateStringnickname;3.3 ReminderRequest —— 创建提醒NotNull(messageuserId 不能为空)privateLonguserId;NotBlank(message提醒标题不能为空)privateStringtitle;privateStringcontent;privateLocalDateTimeremindTime;// 触发时间不填则用 cronprivateStringtypecustom;// medication/schedule/water/birthday/customprivateStringcron;// 重复表达式可选privateStringdeviceId;// 目标设备编码可选3.4 EmotionRequest —— 情绪记录NotNull(messageuserId 不能为空)privateLonguserId;NotBlank(message情绪类型不能为空)privateStringemotion;privateIntegerintensity5;// 强度 1-10privateStringcontext;// 触发场景privateStringsourcemanual;// agent / vision / manual3.5 HealthRequest —— 健康数据NotNull(messageuserId 不能为空)privateLonguserId;NotBlank(message记录类型不能为空)privateStringtype;privateStringvalue;privateStringunit;privateStringnote;privateStringdeviceId;// 设备编码可选四、NotNull 与 NotBlank一字之差天壤之别看上面 5 个 DTO只用到了两种注解但选得很有讲究注解对 String 的判定适用场景NotNull只检查不是 null空串能通过数字、对象、枚举 ID 等NotBlank必须非 null 且去掉首尾空格后还有内容一切用户输入的文本为什么userId用NotNull而message用NotBlank因为 userId 是Long类型的数字根本不存在空串概念null 就是唯一违法形态而 message 是文本和 在业务上都等于没说话必须拦。对照表收好这就是项目目前的字段 → 约束 → 错误文案全账DTO字段约束校验失败时的错误文案默认值ChatRequestuserIdNotNulluserId 不能为空—ChatRequestmessageNotBlank消息内容不能为空—ChatRequestsessionType无—textChatRequestneedTts无—falseUserRequestopenIdNotBlankopenId 不能为空—UserRequestplatform无—webUserRequestnickname无——ReminderRequestuserIdNotNulluserId 不能为空—ReminderRequesttitleNotBlank提醒标题不能为空—ReminderRequesttype无—customEmotionRequestuserIdNotNulluserId 不能为空—EmotionRequestemotionNotBlank情绪类型不能为空—EmotionRequestintensity无—5EmotionRequestsource无—manualHealthRequestuserIdNotNulluserId 不能为空—HealthRequesttypeNotBlank记录类型不能为空—看清楚了吗只有必填项做了校验格式校验长度、范围、枚举取值一个都没有。这是项目的现状也是可以改进的空间后面细说。五、Valid 触发时机与异常路径Controller 里是怎么用的以 ChatController 为例项目源码节选PostMappingpublicApiResponseChatService.ChatResultchat(ValidRequestBodyChatRequestrequest){returnApiResponse.ok(chatService.chat(request.getUserId(),request.getMessage(),request.getSessionType(),Boolean.TRUE.equals(request.getNeedTts())));}9 个 Controller 里所有带 DTO 的 POST 接口都挂了Valid RequestBody。执行顺序是请求进来先把 JSON 反序列化成 DTOValid触发按字段上的注解逐个检查全部通过 → 进入方法体任一失败 → 抛出MethodArgumentNotValidException方法体根本不会执行。那异常抛出来谁接项目在common/GlobalExceptionHandler里专门写了一个处理器项目源码ExceptionHandler(MethodArgumentNotValidException.class)publicResponseEntityApiResponseVoidhandleValidation(MethodArgumentNotValidExceptione){FieldErrorfieldErrore.getBindingResult().getFieldError();StringmessagefieldErrornull?参数校验失败:fieldError.getDefaultMessage();returnResponseEntity.badRequest().body(ApiResponse.fail(400,message));}这段代码有三个细节值得品只取第一个字段错误getFieldError()返回的就是一条。当多个字段同时非法时用户只会看到第一条。对陪伴机器人这种 C 端场景一次报一条比一股脑甩十条规定更友好但如果你做管理后台可能希望返回全部错误改成getAllErrors()循环拼接即可。HTTP 状态码 400 业务码 400 双重标识配合统一响应体ApiResponsecode/message/data 三段式。兜底文案“参数校验失败”。万一哪个注解忘了写 message也不至于把英文技术报错直接糊到用户脸上。实际效果演示curl-XPOST http://localhost:8080/api/chat\-HContent-Type: application/json\-d{userId:1,message:}# 响应{code:400,message:消息内容不能为空,data:null}注意校验失败时data是 null前端只需要盯code ! 0就行。六、进阶玩法示意嵌套、分组、自定义注解项目目前没用这些但零基础的朋友迟早会需要这里给出思路。嵌套对象校验DTO 里套 DTO 时在外层字段上加Valid才能让校验钻进去publicclassOrderRequest{NotNull(messageuserId 不能为空)privateLonguserId;Valid// 没有它里面的注解全部失效NotNull(message地址信息不能为空)privateAddressRequestaddress;// AddressRequest 内部再贴 NotBlank 等}分组校验同一个 DTO新增时不校验 id更新时必须校验 id。用groups区分publicclassUserSaveRequest{Null(groupsCreate.class,message新增时不能传 id)NotNull(groupsUpdate.class,message更新时 id 不能为空)privateLongid;}// Controller 上Validated(Create.class) 或 Validated(Update.class)自定义校验注解比如想让EmotionRequest.intensity强制 1~10。思路是定义一个注解IntensityRange再写一个实现ConstraintValidatorIntensityRange, Integer的校验器isValid里写判断逻辑最后在字段上贴注解。这是把魔法值判断从 Service 收编到 DTO 的标准姿势。当然最省事的现成方案是直接用Min(1) Max(10)——jakarta 自带何必手搓。七、边界讨论校验放 DTO 还是 Service这是老争论了我的观点两层都要但干不同的活。层职责例子失败表现DTO Valid格式与形式类型、必填、长度、范围、正则message 非空、intensity 在 1~10HTTP 400统一文案Service业务规则查库后才能判断的东西userId 是否存在、设备是否绑定、openId 是否重复BusinessException带业务语义AI 伙伴AI-Partner正是这么分的DTO 层拦必填Service 层管业务比如MemoryService.save里校验 importance 在 [1,5]、EmotionService.record里校验 intensity 在 [1,10]都是查上下文后的业务校验。一句话总结DTO 校验是门卫Service 校验是安检机缺一个都会漏人。八、现状盘点与改进清单如实说AI 伙伴AI-Partner的校验层目前只做了必填这一档结合前面那张对照表可以列出这些待改进点缺格式约束type、emotion、platform这类枚举语义字段是自由字符串传个abc也能进库。可以用Pattern(regexp ...)或自定义注解收口。缺长度约束title数据库限 128 字符、content限 512DTO 层没有Size超长内容会直接被 MySQL 报错变成一坨 500。缺数值范围intensity的 1~10 校验在 ServiceDTO 层加Min Max更早拦截。错误聚合目前只返回第一条错误多字段同时出错时前端体验一般。校验这东西做得早是挡脏数据的门做得晚是擦脏数据的墙。项目这条链路DTO 注解 → Valid → MethodArgumentNotValidException → 统一 400 响应已经把骨架搭好了剩下的就是往注解里填约束的体力活。动手吧让每一条进门的 JSON 都干干净净。

相关新闻

海信电视秋季新品打出王炸,双7旗舰首发,RGB-Mini LED电视体验常用常新

海信电视秋季新品打出王炸,双7旗舰首发,RGB-Mini LED电视体验常用常新

9月23日,2026海信电视召开秋季新品发布会,正式推出全新一代RGB-Mini LED影像旗舰U7T Pro、性能旗舰E7S Pro两大双7旗舰新品,一次性落地六大行业首发技术,从背光硬件、屏幕素质、专属画质到智能系统全方位革新,彻底改写…

2026/9/24 3:53:48 阅读更多 →
Storm 拓扑测试与调试:单元测试、集成测试与拓扑调试技巧

Storm 拓扑测试与调试:单元测试、集成测试与拓扑调试技巧

Storm 拓扑测试基础Storm是一个开源的分布式实时计算系统,用于处理大规模数据流。拓扑(Topology)是Storm应用的基本执行单元,由Spout(数据源)和Bolt(处理单元)组成。由于拓扑运行在分布式环境中,测试和调试变得尤为重要。正确的测试策略可以确…

2026/9/24 3:52:48 阅读更多 →
建议收藏|盘点2026年备受追捧的AI论文写作软件

建议收藏|盘点2026年备受追捧的AI论文写作软件

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂的AI论文写作软件正在席卷学术圈,覆盖选题构思、文献综述、数据整理、格式排版等全流程,实测提速超300%,高效搞定论文不再是梦。 一、全流程王者:一站式搞定论文全链路&am…

2026/9/24 3:52:48 阅读更多 →

最新新闻

LPC2388实战指南:AMBA总线与ARM7嵌入式开发深度解析

LPC2388实战指南:AMBA总线与ARM7嵌入式开发深度解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/24 4:28:13 阅读更多 →
定制柜背板 5 毫米、9 毫米、18 毫米,各用在哪

定制柜背板 5 毫米、9 毫米、18 毫米,各用在哪

背板用 5 毫米、9 毫米还是 18 毫米,先看柜子挂在哪个房间、柜深多少、跨度多长,不是越厚越合适。这是做海口全屋定制时容易被一句话带过去的构件,也容易被"加厚就是升级"的直觉带偏。欧派大家居在海口是有实体门店的连锁体系&…

2026/9/24 4:28:13 阅读更多 →
nginx-ui MCP 配置管理工具详解:让 AI Agent 安全读写 Nginx 配置文件

nginx-ui MCP 配置管理工具详解:让 AI Agent 安全读写 Nginx 配置文件

后端前端运维MCP 服务 【免费下载链接】nginx-ui Yet another WebUI for Nginx 项目地址: https://gitcode.com/gh_mirrors/ngi/nginx-ui 点击查看 免费下载 导读 本文聚焦 nginx-ui 内置的 MCP(Model Context Protocol)配置管理模块&#…

2026/9/24 4:28:13 阅读更多 →
Talos Linux ResolverConfig 配置指南:nameservers、searchDomains 与 hostDNS 全解析

Talos Linux ResolverConfig 配置指南:nameservers、searchDomains 与 hostDNS 全解析

云原生操作系统容器编排 【免费下载链接】talos Talos Linux is a modern Linux distribution built for Kubernetes. 项目地址: https://gitcode.com/gh_mirrors/ta/talos 点击查看 免费下载 本文基于 Talos Linux(v1.15 参考文档与源码)系…

2026/9/24 4:28:13 阅读更多 →
Storm 与机器学习:在线模型更新、实时预测与特征工程管道

Storm 与机器学习:在线模型更新、实时预测与特征工程管道

Storm 与机器学习:在线模型更新、实时预测与特征工程管道本文探讨了如何利用 Apache Storm 构建机器学习在线模型更新、实时预测与特征工程管道。从基础架构到具体实现,详细介绍了 Storm 与机器学习系统的集成方案,包括在线模型更新机制、实时…

2026/9/24 4:28:13 阅读更多 →
高通骁龙865救砖指南:QPST与9008模式底层刷机实战

高通骁龙865救砖指南:QPST与9008模式底层刷机实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/24 4:27:12 阅读更多 →

日新闻

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为…

2026/9/24 0:00:19 阅读更多 →
单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

简介:一份基于单细胞RNA测序数据的细胞类型注释算法研究Python毕业设计源码,针对计算机相关专业正在做毕设或需要项目实战的学习者,可用于课程设计与期末大作业。项目代码完整、经导师指导评审通过,可直接运行,覆盖数据…

2026/9/24 0:00:19 阅读更多 →
C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

第一次在项目里被反射卡住,是在一个老旧的WinForms模块里:几十个类依赖PropertyChanged通知,运行时反射读属性、发通知,每次启动慢半拍不说,一上.NET Native/AOT裁剪模式几乎全面崩盘。后来我把这段逻辑全部改成C#源生…

2026/9/24 0:00:19 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/23 4:55:02 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/23 4:49:06 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/23 9:53:41 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/23 9:53:40 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/23 9:53:40 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/23 9:53:40 阅读更多 →