尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
SpringBoot 三层架构里 DTO 该放哪一层:从鸿蒙登录接口返回密码说起
答辩前一周我给鸿蒙 App 配的后端lifeApp0110已经跑通了登录、待办和图标三个模块。技术栈很普通SpringBoot 2.3.4、MyBatis 2.1.4、MySQL 5.x包名com.qf.lifeapp0110用Controller ResponseBody把对象直接转成 JSON 回给客户端。答辩前一天我打开 Swagger 核对接口/user/one的响应示例里赫然列着password。再看代码这个登录接口连密码比对都没写只按用户名查了一次就把整行数据交了出去。我一开始以为补一行密码判断就完了真正改起来才发现问题不在那一行if而在接口的形状是由数据库表决定的表里有什么列客户端就能收到什么字段。这篇把那次改造完整写一遍重点不是背分层口诀而是说清每一类对象为什么只能待在一个地方。一、实体为什么不能直接出网关实体Entity的定义很简单一个类对应一张表一个对象对应一行记录。项目里user表只有id、username、password三列MyBatis 的queryByName返回的就是这个实体。问题在于实体一旦被ResponseBody序列化它就从数据库的投影变成了对外契约。当时的代码大致是这样RequestMapping(/one) ResponseBody public Map queryOne(RequestBody User user1) { Map map new HashMap(); User user userMapper.queryByName(user1.getUsername()); if (user ! null) { map.put(user, user); // 整个实体进了 JSON map.put(message, ok); } else { map.put(user, new User()); // 失败也用空对象兜底 map.put(message, error); } return map; }这段代码有三个隐患而且都不是改一行能解决的。第一返回体里带着password。只要字段在实体上任何返回实体的接口都会带上它今天是登录明天是查个人资料后天是管理员看用户列表。靠每个接口手动置空迟早漏一个而漏的那次通常没人发现。第二表结构和 API 绑死。以后给user加phone、is_deleted客户端收到的 JSON 立刻多出字段反过来客户端想要的昵称 头像如果不在表里接口就拼不出来。接口本应描述业务能力现在却成了 DDL 的镜像。第三new User()把没查到编码成了一个 id 为 0 的空对象。鸿蒙端必须知道id 为 0 就表示失败这个隐含约定编译器不检查Swagger 也写不出来。这类约定一旦散落在客户端代码里后端想改错误码都改不动。二、三类对象三种生命周期改造时我先把对象按职责分开而不是急着建一堆类对象所在包由谁创建字段从哪来能不能出网关Entity / POentityMyBatis 映射数据库表的列不能DTO入参dtoSpring MVC 反序列化客户端请求体只在入方向VO出参voService 组装业务需要展示的字段可以且只暴露必要字段这张表里最关键的是最后一列。实体不能出网关不是因为它层次低而是它的字段集合由 DDL 决定对外字段集合应该由接口设计决定这两个集合的变更节奏根本不同。表可以为了查询性能加冗余列接口却不能因为这个就把冗余列抖给客户端。还要说明一点不是每个接口都得配三个类。这个项目里我只有LoginRequest和UserVO两个够用了。真正不能破的是方向——Controller 可以依赖 DTO 和 ServiceService 不能依赖HttpServletRequestMapper 更不能认识 VO。三、入参 DTO校验注解跟着契约走登录接口的入参单独抽出来校验注解放这里public class LoginRequest { NotBlank(message 用户名不能为空) private String username; NotBlank(message 密码不能为空) private String password; // getter / setter 省略 } PostMapping(/login) public UserVO login(Valid RequestBody LoginRequest req) { return userService.login(req); }为什么校验不写在User实体上因为这个实体同时被注册、改资料、后台编辑复用三处对字段的要求并不一样注册时用户名密码都必填改昵称时密码根本不该出现。注解写在实体上就得引入 validation groups 按场景分组分组一多注解比字段还长后来接手的人也不敢删。更重要的是校验规则属于接口契约接口改规则就改 DTO不该逼着表一起动。入参 DTO 还只放客户端能决定的东西。id、create_time这类字段不要出现在请求对象里它们要么由数据库生成要么由服务端赋值。请求对象里没有这个字段客户端就没法伪造这比在 Service 里补一堆if (id ! null) throw省事得多。四、出参 VO转换放在 Service出参单独定义并且把哪些字段可以出门这条规则收在一处public class UserVO { private Integer id; private String username; public static UserVO from(User user) { UserVO vo new UserVO(); vo.setId(user.getId()); vo.setUsername(user.getUsername()); return vo; } } Service public class UserService { Autowired private UserMapper userMapper; Autowired private PasswordEncoder passwordEncoder; public UserVO login(LoginRequest req) { User user userMapper.queryByName(req.getUsername()); if (user null || !passwordEncoder.matches(req.getPassword(), user.getPassword())) { throw new BizException(用户名或密码错误); } return UserVO.from(user); } }为什么转换放在 Service 而不是 Controller因为 Controller 的职责是把 HTTP 翻译成方法调用它不该知道哪个字段敏感。让 Controller 转换每加一个接口就要重新判断一次该脱哪些字段漏一次就是一次事故放在 Service规则只有一份登录、资料页、后台列表都走同一个UserVO.from。这里有个我踩过的细节一开始图省事用了BeanUtils.copyProperties(user, vo)后来 VO 的id写成Long实体是Integer类型不匹配时它直接静默跳过id一直是 null。接口返回 200前端拿到空值排查了半天。字段少的时候手写from()最稳字段多了上 MapStruct它是编译期生成代码源字段对不上会给出警告配置成 ERROR 就直接编译失败比运行时才发现强。顺带说密码。表里存的还是明文列定义是varchar(32)这次我只把接口改造和密码存储拆成两件事接口先改成不返回密码、比对走PasswordEncoder.matches引入spring-security-crypto这个独立包就够不必把整个 Spring Security 拖进来哈希迁移单独排期。两件事混在一个提交里出问题很难判断是哪一步导致的。五、包结构和依赖方向改造后的目录是这样的每个包只做一件事com.qf.lifeapp0110 ├── controller UserController 只依赖 dto 和 service ├── service UserService 业务规则、实体转 VO ├── dao UserMapper 只进出 Entity ├── entity User 与表一一对应 ├── dto LoginRequest 入参 校验注解 ├── vo UserVO 出参 └── common BizException 统一异常依赖方向只有一条controller → service → dao。dto和vo被上层依赖自己不反过来依赖 mapper。只要在vo包里看到import com.qf.lifeapp0110.dao.UserMapper分层就已经破了而且这种破坏往往是从顺手查一下开始的。DAO 的入参也要守住。queryByName(String username)是对的query(LoginRequest req)就不对——DAO 一旦认识 HTTP 契约将来接口从 REST 换成内部 RPC 或者定时任务调用DAO 都得跟着改。常见放错位置的现象我整理成了这样一张表现象根因后果Controller 直接返回 User 实体出参用了 PO表字段即 APIpassword 顺手带出校验注解写在实体上契约规则和表约束混在一起改单个字段也要整对象校验Service 返回Map没有出参类型前端靠猜字段Swagger 生成不出来DTO 放进dao包层间依赖反了换调用方式要连 DAO 一起改VO 里塞UserMapper出参层偷偷查库循环依赖、事务边界混乱六、改造顺序先加再切最后删这次改造最怕的不是写错是鸿蒙端的老版本还在读user.password判断登录成功。所以我没做一把梭Service 新增login返回UserVO旧接口原样保留先让新接口在 Swagger 里跑通鸿蒙端LoginPage.ets切到新接口用message字段判断结果不再看id是否为 0观察一段时间旧路径的调用日志确认没有存量版本还在用再删旧接口最后打开/swagger-ui.html搜一遍password响应示例里搜不到才算收尾。兼容老客户端的原则是加字段可以改语义要慢。给 VO 新增一个字段老版本无感把user从对象换成字符串老版本立刻解析失败。判据不是我这边编译通过而是用户手机上的那个版本还认不认。小结实体是表的影子DTO 是接口的契约VO 是业务的输出。三者混用时不会立刻报错但每加一个字段、每改一次表、每上一个接口风险都会沿着这条缝扩散。最小可行的做法就三条Mapper 只碰实体Controller 只收 DTO、只返回 VOService 负责把实体翻译成 VO顺手把不该出门的字段留在里面。至于密码它从始至终都不该出现在任何返回对象里——这一点靠纪律不如靠类型因为类型不会忘。
RELATED

相关推荐

SpringBoot2.7+MyBatis-Plus+MySQL5.7校园小程序实战

SpringBoot2.7+MyBatis-Plus+MySQL5.7校园小程序实战

简介:这是一套面向高校开发者与Java全栈学习者的校园生活服务类微信小程序完整源码,聚焦学生群体高频需求——跑腿代办、快递代取与校园社交脱单,基于SpringBootMybatis-PlusMySQL技术栈实现前后端分离架构。资源包共1792个文件,涵…

📅 2026/10/27 16:06:22
W5500 DHCP实战:状态机控制、realnde PHY协同与保底机制

W5500 DHCP实战:状态机控制、realnde PHY协同与保底机制

简介:本资源是面向嵌入式开发工程师与物联网初学者的W5500以太网控制器DHCP功能实践代码包,解决设备在局域网中自动获取IP地址、子网掩码、网关及DNS等网络参数的核心需求,适用于基于STM32、ESP32等MCU平台接入W5500的网络通信项目。压缩包为…

📅 2026/10/27 16:01:20
Spring Boot智慧社区毕设全解析:从权限设计到部署避坑

Spring Boot智慧社区毕设全解析:从权限设计到部署避坑

简介:面向Java毕业设计场景的智慧社区管理系统源码包,基于Spring Boot框架实现,覆盖用户、商品分类、动物信息、车位、便民服务、缴费、房屋等十余个管理模块,适合需要完整项目参考的本科生及初级开发者用于课程设计或毕业答辩。包…

📅 2026/10/27 16:01:20
MORE NEWS

更多资讯

📰

RV32I五级流水线处理器设计:数据前推、冒险检测与分支预测实践

简介:一套基于RISC-V RV32I指令集的五级流水线处理器设计资料,完整支持42条用户级指令,面向计算机体系结构学习者、硬件课程设计学生及入门开发者,覆盖取指、译码、执行、访存、写回五个阶段,并采用数据前推、冒险检测…

📰

本地部署声音克隆+工作流引擎:开源语音工作室实操指南

最近我在一台旧笔记本上折腾出了一个完全本地运行的开源语音工作室,核心是声音克隆加工作流引擎,全套流程全部在本地跑完,不联网、不烧API费用,也不会把自己的声纹数据交给任何在线服务。简单说,你录一小段自己的声音丢…

📰

SpringBoot与小程序开发睡眠健康管理系统实践

1. 项目概述:睡眠健康管理系统的现实需求当代社会快节奏生活带来的睡眠问题日益严重。根据中国睡眠研究会发布的《2023中国睡眠质量报告》,超过3亿人存在不同程度的睡眠障碍,其中年轻人失眠率高达38%。这种背景下,基于SpringBoot和…

📰

智能物流系统:AGV与数字孪生技术驱动制造业转型

1. 项目背景:智能物流集成商的逆袭之路去年还深陷亏损泥潭的某工厂智能物流集成商,最新财报却亮出了净利润暴增529%的惊人数据。这种教科书级的"V型反转"背后,是传统制造业向智能化转型浪潮下的一个典型样本。作为深耕工业自动化领…

📰

IDEA 2025.3新特性解析:AI编程助手与性能优化

1. IDEA 2025.3 新特性深度解析 JetBrains 在 2025.3 版本中带来了多项突破性更新,其中最引人注目的是深度集成的 AI 编程助手系统。这个版本不再只是简单整合第三方 AI 插件,而是构建了原生的智能开发环境。我在实际使用中发现,新的 AI 辅助…

📰

Django+MySQL银行信贷系统实战源码:多角色审批与风控流程落地

简介:本资源是一套基于Python Django框架与MySQL数据库开发的银行信贷管理系统毕业设计源码,面向计算机专业本科生及Web开发初学者,适用于课程设计、毕业设计实践与全栈开发能力训练。系统涵盖用户管理、贷款申请、审批流程、信用评估、还款计…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬