结论先行:
前端统一 snake_case,后端统一 camelCase,由 Jackson 全局自动完成双向映射。
这是Jackson 层面的统一协议问题,不应该在 DTO/VO 上“手工处理”。
在 大项目体量里,必须做到全局自动转换、零侵入、零重复注解。
一、核心机制:Jackson 命名策略(双向自动)
Jackson 原生支持序列化 & 反序列化同时生效。
| 场景 | 行为 |
|---|---|
| Request JSON → Java DTO | access_token→accessToken |
| Java VO → Response JSON | accessToken→access_token |
无需在字段上写任何注解。
二、推荐方案(全局配置,唯一入口)
1. application.yml(首选,最干净)
spring:jackson:property-naming-strategy:SNAKE_CASE这一行就够了。
2. 等价 Java 配置(如果你偏代码化)
@ConfigurationpublicclassJacksonConfig{@BeanpublicObjectMapperobjectMapper(){ObjectMappermapper=newObjectMapper();mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);returnmapper;}}建议用yml,避免 ObjectMapper 多实例污染。
三、示例:LoginRequest / LoginVO
1. LoginRequest(后端代码,驼峰)
publicclassLoginRequest{privateStringaccount;privateStringpassword;privateStringloginType;}前端请求 JSON(下划线)
{"account":"13800138000","password":"123456","login_type":"PASSWORD"}Jackson 自动映射为:
loginRequest.getLoginType();// PASSWORD2. LoginVO(后端代码,驼峰)
publicclassLoginVO{privateStringaccessToken;privateStringrefreshToken;privateLongexpiresIn;}返回给前端 JSON(自动下划线)
{"access_token":"xxx","refresh_token":"yyy","expires_in":7200}四、为什么这是大项目必须采用的方式
1. 前端世界就是 snake_case
- iOS / Android / 小程序 / OpenAPI
- 多语言 SDK 生成器默认 snake_case
- JSON Schema / OpenAPI 更友好
2. Java 世界必须 camelCase
- Lombok / Bean 规范
- MyBatis / MapStruct
- IDE 自动补全
3. 中间层自动转换,才是工业级解法
五、禁止的“反模式”(踩一个就会后悔)
❌ 每个字段写 @JsonProperty
@JsonProperty("access_token")privateStringaccessToken;问题:
- DTO 爆炸
- 重构成本极高
- 其他开发人员一定骂你
❌ Request / Response 分两套字段名
privateStringaccess_token;问题:
- Java 规范被破坏
- IDE / Lombok / Mapper 全部痛苦
六、特殊情况如何处理(白名单)
1. 单字段例外
@JsonProperty("openid")privateStringopenId;只在:
- 第三方强绑定字段
- 历史兼容接口
2. 禁止自动转换的字段
@JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy.class)publicclassThirdPartyCallbackRequest{}七、与 Swagger / OpenAPI 的一致性
启用后:
- Swagger 显示字段名:
access_token - 后端字段:
accessToken - 文档 = 实际接口
八、最终统一规范(一句话版)
后端代码只写驼峰;
API 协议只暴露下划线;
Jackson 全局负责翻译。