工具类
这篇解决什么
集合转换、时间区间、JSON 读写、取客户端 IP、从容器拿 Bean,业务里天天会碰到。读完能改 yudao-common 的 util 包,并先用 Hutool,而不是再贴一份新工具类。
对象拷贝走 VO 对象转换、数据翻译 的 BeanUtils。入参校验和时间格式走 参数校验、时间传参。分页条件拼 SQL 走 MyBatis 数据库。
默认端口 48080,管理端前缀 /admin-api。
怎么选
package-info 写明:先查 Hutool;没有再封装,类名以 Utils 结尾,和 Hutool 的 Util 区分。依赖是 hutool-all 5.8.x,文档见 Hutool。
| 名称 | 说明 | 仓库路径 |
|---|---|---|
| Hutool | 主工具库:CollUtil、StrUtil、DateUtil、HttpUtil | yudao-common/pom.xml 的 hutool-all |
util 包 | 只补 Hutool 没有的能力 | ruoyi-office/yudao-framework/yudao-common/.../util/ |
| Lombok | 生成 getter / setter / 构造,少写样板 | 工程根 ruoyi-office/lombok.config |
本文只列 collection、date、json、servlet、spring 五个包里真实存在的类。BeanUtils、PageUtils 见 VO / 分页篇。
先复用,再封装
空集合、字符串裁剪、日期加减,Hutool 已有就直接调。框架已有 CollectionUtils.convertList 这类方法时,复制一份到业务模块只会分叉。真要补能力,改对应 Utils,并写单测。
根目录 lombok.config 打开链式 setter,并让 toString / equals / hashCode 走父类:
| 名称 | 说明 | 仓库路径 |
|---|---|---|
lombok.accessors.chain | setXxx 返回 this | ruoyi-office/lombok.config |
lombok.tostring.callsuper | toString 带上父字段 | 同上 |
lombok.equalsandhashcode.callsuper | 相等比较带上父字段 | 同上 |
config.stopBubbling | 不再向上找别的 Lombok 配置 | 同上 |
集合
四个类都在 cn.iocoder.yudao.framework.common.util.collection。空集合返回空 List / Map / Set,不返回 null。
| 名称 | 说明 | 仓库路径 |
|---|---|---|
CollectionUtils | convertList / convertSet / convertMap / convertMultiMap、过滤、求和、diffList | .../util/collection/CollectionUtils.java |
MapUtils | findAndThen、从 KeyValue 建 Map、取 BigDecimal | .../util/collection/MapUtils.java |
SetUtils | asSet,内部是 Hutool CollUtil.newHashSet | .../util/collection/SetUtils.java |
ArrayUtils | 数组合并、集合转数组、按下落标 | .../util/collection/ArrayUtils.java |
UserController 批量回填部门、角色、岗位时,先抽 ID 再查表:
Map<Long, DeptDO> deptMap = deptService.getDeptMap(
convertList(list, AdminUserDO::getDeptId));
Set<Long> userIds = convertSet(list, AdminUserDO::getId);
Map<Long, RoleDO> roleMap = convertMap(
CollUtil.removeNull(roleService.getRoleListFromCache(
convertSet(userRoles, UserRoleDO::getRoleId))),
RoleDO::getId);convertMap 默认冲突保留先出现的值。一对多用 convertMultiMap(值是 List)或 convertMultiMap2(值是 Set)。新旧列表对账用 diffList,返回「新增 / 修改 / 删除」三段。
MapUtils.findAndThen 在 key 或 value 为 null 时直接跳过,适合列表翻译:
MapUtils.findAndThen(userMap, customerVO.getOwnerUserId(), user -> {
customerVO.setOwnerUserName(user.getNickname());
});这段在 CRM 客户列表,完整拼接见 VO 对象转换、数据翻译。
时间
新代码用 LocalDate / LocalDateTime。和 Date 互转、默认时区常量放 DateUtils。
| 名称 | 说明 | 仓库路径 |
|---|---|---|
DateUtils | Date ↔ LocalDateTime、TIME_ZONE_DEFAULT、日期格式常量 | .../util/date/DateUtils.java |
LocalDateTimeUtils | 区间、日/月起止、重叠、分片;内嵌 TimeRange | .../util/date/LocalDateTimeUtils.java |
| 名称 | 说明 | 仓库路径 |
|---|---|---|
TIME_ZONE_DEFAULT | GMT+8 | DateUtils |
FORMAT_YEAR_MONTH_DAY | yyyy-MM-dd,VO 上 @JsonFormat 常用 | DateUtils |
FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND | yyyy-MM-dd HH:mm:ss | DateUtils |
EMPTY | 1970-01-01 零点,给唯一索引当非 null 占位 | LocalDateTimeUtils |
字典类型删除时把 deletedTime 写成 EMPTY,避免唯一索引里出现多个 null:
dictType.setDeletedTime(LocalDateTimeUtils.EMPTY);这段在 yudao-module-system-server 的 DictTypeServiceImpl。
判断「现在是否落在区间」用 isBetween;日/月边界用 beginOfDay、beginOfMonth、getDateTimeRange。会议室、考勤这类要算重叠或裁切时段,用 isOverlap、mergeTimeRanges、subtractTimeRanges。
接口上 LocalDate 必须带 @JsonFormat,LocalDateTime 默认毫秒。对照见 参数校验、时间传参。
JSON
业务里统一走 JsonUtils,不要再 new 一个 ObjectMapper。Web 启动后 YudaoJacksonAutoConfiguration 会 JsonUtils.init,和接口序列化共用同一套规则。
| 名称 | 说明 | 仓库路径 |
|---|---|---|
JsonUtils | toJsonString / parseObject / parseArray / convertObject | .../util/json/JsonUtils.java |
TimestampLocalDateTimeSerializer | 默认写成 epoch 毫秒;字段有 @JsonFormat 则按 pattern | .../util/json/databind/ |
TimestampLocalDateTimeDeserializer | 毫秒读回 LocalDateTime | 同上 |
NumberSerializer | 超出 JS 安全整数的 Long 改写成字符串 | 同上;注册在 YudaoJacksonAutoConfiguration |
String json = JsonUtils.toJsonString(snapshot);
UserRespVO vo = JsonUtils.parseObject(json, UserRespVO.class);
List<UserRespVO> list = JsonUtils.parseArray(json, UserRespVO.class);parseObject 失败会抛运行时异常并打日志。允许失败时用 parseObjectQuietly 或 parseMap,得到 null。Map / POJO 转目标类型用 convertObject,不要先 toJsonString 再 parse。
带 @JsonTypeInfo(use = Id.CLASS) 且 JSON 没有 class 字段时,用 parseObject2(Hutool JSONUtil)。
Servlet
ServletUtils 包一层 Hutool JakartaServletUtil,方便在非 Controller 参数里取当前请求。
| 名称 | 说明 | 仓库路径 |
|---|---|---|
ServletUtils | 写 JSON、UA、客户端 IP、读 Body、附件下载 | .../util/servlet/ServletUtils.java |
CacheRequestBodyFilter | 缓存 JSON Body,才能重复读 | yudao-spring-boot-starter-web/.../CacheRequestBodyFilter.java |
登录日志这样取 IP 和 UA,不必把 HttpServletRequest 一路往下传:
reqDTO.setUserAgent(ServletUtils.getUserAgent());
reqDTO.setUserIp(ServletUtils.getClientIP());这段在 yudao-module-system-server 的 AdminAuthServiceImpl。getRequest() 靠 RequestContextHolder,异步线程里拿不到,要先取出再往后传。
getBody / getBodyBytes 只处理 Content-Type 以 application/json 开头的请求;表单读不到。写回包用 writeJSON,下载用 writeAttachment。
Spring
| 名称 | 说明 | 仓库路径 |
|---|---|---|
SpringUtils | 继承 Hutool SpringUtil,多一个 isProd() | .../util/spring/SpringUtils.java |
SpringExpressionUtils | 解析 SpEL:切面参数,或容器里的 Bean | .../util/spring/SpringExpressionUtils.java |
getBean、getActiveProfile 来自父类。isProd() 判断当前 profile 是否等于 prod。静态方法、工具类里临时取 Bean 可以调它,常规 Service 仍用 @Resource。
if (SpringUtils.isProd()) {
// 生产环境收紧日志或开关
}
BillCodeUtils coder = SpringUtils.getBean(BillCodeUtils.class);切面上按方法参数算表达式(操作日志、租户忽略、脱敏开关)走 parseExpressions(joinPoint, ...)。例如 TenantIgnoreAspect 读 @TenantIgnore.enable()。和 Bean 工厂绑定时用 parseExpression(expr, variables)。
配置与操作
优先用 yudao-common 已有工具,不要复制一份日期/JSON 工具。单据号走 BillCodeUtils Bean。
开启见 框架层。
