构建可持续交付的SaaS平台(5)——谷雨开源SaaS接口规范之数据模型和接口
本文详细介绍了G2rain微服务Java体系中的数据模型设计与接口规范。系统采用分层数据模型设计,包括持久化层(BasePo)、展示层(BaseVo)和业务层(BaseDto)等核心基础类,实现标准化约束。通过MapStruct转换器解决不同层级数据转换问题,并提供通用时间转换器。接口响应采用Result和PageData统一封装格式,包含状态码、错误信息和业务数据等核心内容。该规范基于JDK21

在SaaS平台研发体系中,数据模型是业务落地的核心载体,接口是服务间交互的关键桥梁。规范、统一的数据模型与接口设计,是保障平台可扩展性、可维护性及可持续交付的基础。G2rain作为面向企业级场景的SaaS开源平台,围绕认证与授权、微前端框架等核心能力构建解决方案,其Java服务层设计严格遵循统一规范。本文将聚焦G2rain微服务Java体系中的数据模型设计与接口规范,拆解核心设计思路与实现细节。
首先明确G2rain Java服务的基础技术选型:所有Java服务均基于JDK21构建,核心原因是JDK21的虚拟线程特性可轻松支撑十万甚至百万级并发请求,大幅提升高并发处理能力。基于JDK21,Spring生态组件统一采用Spring Boot 4.0.x版本(对应Spring核心版本7.0.x),该版本对JDK21的虚拟线程及其他新特性提供完善适配与升级支持,为规范落地奠定稳定技术基座。
一、基础模型类概述
G2rain倡导“表为系统设计核心”理念,开发初期需完成所有核心表设计,且表名、字段名必须遵循统一规范。Java层面功能与表一一对应:下划线分隔的表名转换为驼峰命名的Java类并添加用途后缀;表字段的下划线命名同样转换为Java类的驼峰属性。
为实现数据模型标准化,G2rain在 com.g2rain.common.model 包中提供统一方案,覆盖持久化层、业务层及接口响应层,核心基础类包括:
- 持久化层:BasePo(定义ID、创建时间、更新时间、版本号等通用约束);
- 业务层:BaseVo(出参基础类)、BaseDto(新增/编辑入参基础类)、BaseSelectListDto(列表查询入参基础类)、BasePageSelectListDto(分页查询入参基础类),适配不同业务场景的参数传递;
- 统一响应:Result(通用接口出参包装)、PageData(分页数据包装),实现接口响应标准化。
二、核心基础类详细说明
2.1 数据模型分层设计逻辑
G2rain采用分层数据模型设计,明确区分持久化层、展示层、业务层职责,避免跨层数据混乱,提升代码复用性与可维护性。各层级核心类设计如下:
2.1.1 BasePo - 持久化层核心类(Persistence Object)
所有数据库实体类必须继承BasePo,该类定义数据库表通用核心字段,实现持久化层标准化约束。核心代码与设计说明:
@Data
public class BasePo {
private Long id;
private LocalDateTime updateTime;
private LocalDateTime createTime;
/**
* 版本号,支持乐观锁机制,数据更新时自增1
*/
private Integer version;
}
Java
- 核心用途:作为数据库表与Java实体的直接映射载体,精准对应表结构。
- 命名规范:下划线表名转驼峰并加Po后缀,如 sys_user 对应 SysUserPo 。
- 关键设计:内置version字段实现乐观锁,解决并发更新冲突,每次更新自增1。
2.1.2 BaseVo - 展示层核心类(View Object)
所有Controller层返回前端的查询数据需基于BaseVo扩展,是接口响应数据的基础载体,聚焦前端展示需求。核心代码:
@Data
public class BaseVo {
private Long id;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private String updateTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private String createTime;
}
Java
- 核心用途:封装接口响应数据,为前端提供标准化展示格式。
- 核心特点:① 时间适配:将BasePo的LocalDateTime转为String,避免前端解析异常;② 统一格式化:通过 @JsonFormat 指定格式为“yyyy-MM-dd HH:mm:ss”、时区GMT+8,确保跨时区一致性;③ 精简体积:移除前端无需关注的version字段。
2.1.3 BaseDto - 编辑入参核心类(Data Transfer Object)
用于封装创建、更新接口的请求参数,所有新增/编辑接口入参类需继承BaseDto,设计与BaseVo高度一致,便于数据复用与转换。核心代码:
@Data
public class BaseDto {
private Long id;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private String updateTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private String createTime;
}
Java
- 核心用途:作为创建/更新接口的请求参数载体,接收前端业务数据。
- 核心特点:① 时间适配:用String接收前端时间字符串,通过 @JsonFormat 标准化解析;② 结构复用:与BaseVo结构一致,减少跨层转换复杂度;③ 扩展性强:业务层可扩展字段适配不同场景。
2.1.4 BaseSelectListDto - 列表查询入参基类
针对列表查询场景设计,封装通用查询条件,所有列表查询接口入参类需继承该类。核心代码:
@Data
public class BaseSelectListDto {
private Long id; // 单个ID查询
private Set<Long> ids; // 批量ID查询
private List<String> updateTime; // 更新时间段:[开始时间, 结束时间]
private List<String> createTime; // 创建时间段:[开始时间, 结束时间]
// 向ids集合添加元素
public void addId(Long id) {
if (ids == null) {
ids = new HashSet<>();
}
ids.add(id);
}
}
Java
- 核心用途:封装列表查询通用条件,支持单个ID、批量ID及时间段查询。
- 核心特点:① 多维度查询:支持单个ID精准查询(详情关联)、批量ID查询(批量加载);② 灵活时间段:通过 createTime 和 updateTime 的List字段实现“大于/小于/介于某时间”的查询;③ 便捷方法:提供 addId 简化批量ID参数组装。
2.1.5 BasePageSelectListDto - 分页查询入参基类
继承BaseSelectListDto,在通用查询条件基础上新增分页参数及计算逻辑,适配分页列表查询。核心代码:
@EqualsAndHashCode(callSuper = true)
@Setter
public class BasePageSelectListDto extends BaseSelectListDto {
public static final int DEFAULT_PAGE_SIZE = 10;
private int pageNum; // 当前页码,最小为1
private int pageSize; // 每页条数,默认10
// 自动校正pageNum≥1
public int getPageNum() {
return Math.max(pageNum, 1);
}
// 自动校正pageSize默认10
public int getPageSize() {
return pageSize <= 0 ? DEFAULT_PAGE_SIZE : pageSize;
}
// 计算数据库查询偏移量(适配MySQL LIMIT语法)
public int getOffset() {
return (getPageNum() - 1) * getLimit();
}
// 获取每页查询条数
public int getLimit() {
return getPageSize();
}
}
Java
- 核心用途:封装分页列表查询的所有参数(通用条件+分页参数)。
- 核心特点:① 继承性:复用BaseSelectListDto的通用查询能力;② 参数校验:自动校正pageNum≥1、pageSize默认10,避免非法参数;③ 分页计算:内置 getOffset (偏移量)和 getLimit (每页条数),直接适配数据库分页语法。
2.2 MapStruct 转换支持
G2rain采用分层数据模型设计,不同层级(如Po→Vo、Dto→Po)存在大量数据转换需求。为避免手动编写繁琐代码,统一采用MapStruct实现转换,并结合自定义通用转换器解决时间类型(LocalDateTime与String)转换问题。
结合前文的代码生成器,每个数据库表的分层模型类(Po、Vo、Dto等)生成时,会自动生成对应的MapStruct转换器,核心聚焦时间转换——G2rain在common包中提供 CommonConverter 通用转换器,供所有自动生成的转换器复用。
2.2.1 CommonConverter - 通用时间转换器
提供LocalDateTime与String的双向转换方法,通过MapStruct的 @Named 注解标记,便于具体转换器引用。核心代码:
public class CommonConverter {
/**
* LocalDateTime → String,格式:yyyy-MM-dd HH:mm:ss
*/
@Named("localDateTimeToString")
public String localDateTimeToString(LocalDateTime time) {
return Moments.format(time);
}
/**
* String → LocalDateTime,解析格式:yyyy-MM-dd HH:mm:ss
*/
@Named("stringToLocalDateTime")
public LocalDateTime stringToLocalDateTime(String time) {
return Moments.parse(time);
}
}
Java
注: Moments 是G2rain封装的时间工具类,内部实现指定格式的时间格式化与解析,确保转换一致性。
2.2.2 MapStruct 转换器示例
以Test模块为例,自动生成的 TestConverter 实现 TestPo 、 TestVo 、 TestDto 间的转换,核心代码:
@Mapper(uses = CommonConverter.class)
public interface TestConverter {
/**
* 单例实例,通过 {@link Mappers#getMapper(Class)} 获取 MapStruct 自动生成的实现。
*/
TestConverter INSTANCE = Mappers.getMapper(TestConverter.class);
/**
* Po -> Vo
* 自动将 createTime 和 updateTime 从 {@link LocalDateTime} 转换为 {@link String}
*/
@Mapping(target = "createTime", source = "createTime", qualifiedByName = "localDateTimeToString")
@Mapping(target = "updateTime", source = "updateTime", qualifiedByName = "localDateTimeToString")
TestVo po2vo(TestPo po);
/**
* Dto -> Po
* 自动将 createTime 和 updateTime 从 {@link String} 转换为 {@link LocalDateTime}
* 忽略 version 字段
* 忽略 deleteFlag 字段
*/
@Mapping(target = "version", ignore = true)
@Mapping(target = "deleteFlag", ignore = true)
@Mapping(target = "createTime", source = "createTime", qualifiedByName = "stringToLocalDateTime")
@Mapping(target = "updateTime", source = "updateTime", qualifiedByName = "stringToLocalDateTime")
TestPo dto2po(TestDto dto);
}
Java
- 组件化配置:通过 @Mapper(componentModel = "spring") 指定为Spring组件,可直接依赖注入使用;
- 通用转换器复用:通过 uses = CommonConverter.class 引入通用时间转换器,实现时间字段自动转换;
- 场景化映射:针对创建、更新场景,通过 @Mapping(ignore = true) 忽略无需前端传递的字段(如主键、版本号、系统填充时间),确保数据安全与一致性;
- 批量转换支持:提供 toVoList 等方法,适配批量数据查询与响应。
2.3 统一响应格式
为实现接口响应标准化,便于前端统一处理数据、后端统一处理异常日志,G2rain定义 Result (通用响应)和 PageData (分页响应)两套格式,所有Controller接口返回值需基于此封装。
2.3.1 Result - 通用响应结果封装
适用于所有接口,封装响应状态、错误信息、业务数据等核心内容。核心代码:
@Data
public class Result<T> {
private int status; // 200成功,500失败(可扩展其他状态码)
private String errorCode; // 错误码,status≠200时有效
private String errorMessage; // 错误信息,status≠200时展示
private Map<String, Object> keyArgs; // 键值参数,填充errorMessage占位符
private Object[] indexArgs; // 索引参数,填充errorMessage占位符
private List<FieldError> fieldErrors; // 字段错误列表,适配参数校验失败
private T data; // 业务数据:单个为Vo对象,多个为Vo列表
private String requestId; // 请求ID,用于链路追踪与问题排查
private String requestTime; // 请求时间,格式:yyyy-MM-dd HH:mm:ss
}
Java
- 状态码标准化:采用HTTP风格状态码(200成功、500失败),便于前端快速判断结果;
- 错误信息精细化:通过 errorCode 和 errorMessage 区分错误类型,结合 keyArgs 、 indexArgs 支持动态占位符填充(如“参数{paramName}不能为空”);
- 参数校验支持: fieldErrors 封装参数校验失败详情(如字段名、错误原因),适配JSR-380规范;
- 链路追踪: requestId 记录请求唯一标识,结合日志系统实现全链路排查;
- 通用性强:泛型设计( <T> )适配不同类型业务数据。
2.3.2 PageData - 分页数据封装
适用于分页列表查询接口,封装分页核心参数与数据列表,通常作为 Result 的 data 字段值。核心代码:
@Data
public class PageData<T> {
private int pageNum; // 当前页码
private int pageSize; // 每页条数
private long total; // 总记录数
private int totalPages; // 总页数(自动计算:total%pageSize==0? total/pageSize : total/pageSize+1)
private List<T> records; // 当前页数据列表(Vo对象集合)
}
Java
- 分页参数完整:包含 pageNum (当前页)、 pageSize (每页条数)、 total (总记录数)、 totalPages (总页数)四大核心参数,满足前端分页控件需求;
- 总页数自动计算: totalPages 通过总记录数与每页条数自动计算,无需前端额外处理;
- 数据通用性:泛型设计适配不同业务模块的分页数据(如用户列表、角色列表)。
分页接口响应示例:
{
"status": 200,
"errorCode": null,
"errorMessage": null,
"data": {
"pageNum": 1,
"pageSize": 10,
"total": 100,
"totalPages": 10,
"records": [
{"id": 1, "userName": "admin", "createTime": "2024-01-01 10:00:00"},
// 更多用户数据...
]
},
"requestId": "req-20240520123456-1234",
"requestTime": "2024-05-20 12:34:56"
}
JSON
三、规范落地价值与总结
G2rain的数据模型与接口规范,基于企业级SaaS平台研发实践总结,核心价值是“标准化”与“高效率”,具体体现在:
- 提升研发效率:通过统一基础类(BasePo、BaseVo等)避免重复定义通用字段;结合代码生成器,可快速生成分层模型类、MapStruct转换器及基础CRUD接口,开发者聚焦核心业务逻辑,大幅缩短周期。
- 降低维护成本:所有模块遵循统一命名、数据转换及响应格式规范,新开发者可快速上手;通过 requestId 等标准化字段实现全链路追踪,降低跨团队协作与长期维护成本。
- 保障平台稳定性:通过乐观锁(version字段)、参数自动校验、非法参数校正等设计,从源头规避并发冲突、数据异常;统一响应格式便于前端统一处理异常,提升用户体验。
- 支撑可持续交付:标准化设计使平台具备良好扩展性,新增业务模块可复用现有规范与工具,无需重构基础架构;规范代码结构便于自动化测试、持续集成与部署,支撑可持续迭代。
综上,数据模型与接口规范是G2rain微服务体系的核心基石。实际研发中,需严格遵循“表为核心”理念,基于统一基础类扩展业务模型,借助MapStruct实现高效转换,通过标准化响应保障接口一致性。后续,我们将围绕G2rain的微服务治理、安全防护等核心能力,持续分享企业级SaaS平台的构建规范与实践经验。
更多推荐

所有评论(0)