![]()
在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 包中提供统一方案,覆盖持久化层、业务层及接口响应层,核心基础类包括:
G2rain采用分层数据模型设计,明确区分持久化层、展示层、业务层职责,避免跨层数据混乱,提升代码复用性与可维护性。各层级核心类设计如下:
所有数据库实体类必须继承BasePo,该类定义数据库表通用核心字段,实现持久化层标准化约束。核心代码与设计说明:
@Datapublic class BasePo { private Long id; private LocalDateTime updateTime; private LocalDateTime createTime; /** * 版本号,支持乐观锁机制,数据更新时自增1 */ private Integer version;}
Java
所有Controller层返回前端的查询数据需基于BaseVo扩展,是接口响应数据的基础载体,聚焦前端展示需求。核心代码:
@Datapublic 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
用于封装创建、更新接口的请求参数,所有新增/编辑接口入参类需继承BaseDto,设计与BaseVo高度一致,便于数据复用与转换。核心代码:
@Datapublic 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
针对列表查询场景设计,封装通用查询条件,所有列表查询接口入参类需继承该类。核心代码:
@Datapublic 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
继承BaseSelectListDto,在通用查询条件基础上新增分页参数及计算逻辑,适配分页列表查询。核心代码:
@EqualsAndHashCode(callSuper = true)@Setterpublic 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
G2rain采用分层数据模型设计,不同层级(如Po→Vo、Dto→Po)存在大量数据转换需求。为避免手动编写繁琐代码,统一采用MapStruct实现转换,并结合自定义通用转换器解决时间类型(LocalDateTime与String)转换问题。
结合前文的代码生成器,每个数据库表的分层模型类(Po、Vo、Dto等)生成时,会自动生成对应的MapStruct转换器,核心聚焦时间转换——G2rain在common包中提供 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封装的时间工具类,内部实现指定格式的时间格式化与解析,确保转换一致性。
以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
为实现接口响应标准化,便于前端统一处理数据、后端统一处理异常日志,G2rain定义 Result (通用响应)和 PageData (分页响应)两套格式,所有Controller接口返回值需基于此封装。
适用于所有接口,封装响应状态、错误信息、业务数据等核心内容。核心代码:
@Datapublic 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
)适配不同类型业务数据。
适用于分页列表查询接口,封装分页核心参数与数据列表,通常作为 Result 的 data 字段值。核心代码:
@Datapublic 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
分页接口响应示例 :
{ "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平台研发实践总结,核心价值是“标准化”与“高效率”,具体体现在:
综上,数据模型与接口规范是G2rain微服务体系的核心基石。实际研发中,需严格遵循“表为核心”理念,基于统一基础类扩展业务模型,借助MapStruct实现高效转换,通过标准化响应保障接口一致性。后续,我们将围绕G2rain的微服务治理、安全防护等核心能力,持续分享企业级SaaS平台的构建规范与实践经验。