跳转到正文

通用工具

本页入口均位于 com.own.constant.util。集合、文件和脱敏编码分别见集合与分页文件与图片脱敏与编码

从具体任务选择即可:整理字符串用文本与参数,计算报文校验值用CRC,关联任务日志用 TraceId,在单进程内同步通知监听器用进程内事件。时间、标识和静态 YAML 的兼容行为放在各自小节中供查询。

文本与参数

java
import com.own.constant.util.CharacterUtil;
import com.own.constant.util.NumberUtil;
import com.own.constant.util.ParamUtil;
import java.util.Map;

System.out.println(CharacterUtil.fullWidthToHalfWidth("ABC123")); // ABC123
System.out.println(NumberUtil.completeDigitsStart(12, 5));            // 00012
System.out.println(ParamUtil.sort(Map.of("b", "2", "a", "1")));     // a=1&b=2
入口行为与边界
CharacterUtil.fullWidthToHalfWidth(String/Character)转换全角 ASCII 字母、数字和标点,不转换全角空格;null 抛异常
EmojiUtil.filter(text)空白原样返回;按正则移除代理字符范围,可能删除非 Emoji 的补充字符,不是完整 Emoji 解析
DataUtil.getPrintLength(text)U+0000..U+00FF 计 1,其他 UTF-16 code unit 计 2;空白为 0,不是精确显示宽度
ParamUtil.sort(map)key 字典序拼接 key=value&...,不做 URL 编码;null value 拼为 null
ParamUtil.sortObject(map)对 value 调用 toString,null value 抛异常
ParamUtil.mergeParams(value, values)合并到新 HashSet,不保序;忽略单值 null,集合内的 null 保留
NumberUtil.checkNumberLimit(number, floor, ceil)限制数值,但不验证 floor 小于等于 ceil
NumberUtil.completeDigitsStart/End(content, length[, filler])左/右补位,默认 0;超过长度不截断,null content 抛异常

HTTP/URL 与表单格式

判断协议、编码参数和验证输入是不同操作:识别 HTTP/HTTPS scheme 用 HttpUtil.isHttpUrl,编码单个参数值用 UrlUtil.specialUrlEncode。这两者都不能证明一个地址适合被服务端下载。

HttpUtil.isHttp/isHttps/isUrl 只检查小写前缀,不 trim。isHttpUrl 则 trim 后通过 URI scheme 忽略大小写识别 HTTP/HTTPS,空值或非法 URI 返回 false;它不要求 host,http:test 也可返回 true,不能据此直接允许服务端下载。

UrlUtil.specialUrlEncode(text) 对整个文本执行 UTF-8 表单编码,再调整空格、星号和波浪号;适用于单个参数值,直接传完整 URL 会把 :/?& 等也编码。

FormUtil.isPhone 只要求 11 位数字且首位为 1;isEmail 使用简化邮箱正则。FormUtil.isUrl 当前把输入当作正则、固定表达式当作待匹配文本,普通 URL 常返回 false,非法正则还会抛异常,不应用作 URL 验证。只需识别协议可改用上面的 isHttpUrl;完整地址或下载来源校验还需应用自行实现。

十六进制与 CRC

DataUtil.hexStringToBytes 将偶数长度文本按两位转成一个字节;null/空字符串返回 null。它不校验非法字符,奇数长度会因读取末尾下一字符而抛 StringIndexOutOfBoundsException,不会忽略末位。调用前应自行校验长度和字符。

Crc8Util 则使用 CRC-8/MAXIM 的反射多项式 0x8C、初始值 0,提供明确的报文校验入口:

java
import com.own.constant.util.Crc8Util;

System.out.println(Crc8Util.calcCrcHex("313233343536373839")); // A1

calcCrc(byte[]/String) 返回 0..255,calcCrcHex 返回两位大写十六进制。字符串参数是十六进制文本,允许空白,非法字符抛 IllegalArgumentException;奇数长度将末位按低 4 位处理,例如 ABC 对应 AB 0C。null/空输入的 CRC 为 0。CRC 用于校验报文差错,不提供消息认证。

时间、随机值与标识

工具当前规则
TimeUtil.unitConvert(time, unit)DAYS/HOURS/MINUTES/SECONDS 转为毫秒;其他单位原值返回,MICROSECONDS/NANOSECONDS 不会正确折算
TimeUtil.DEFAULT_ZONE_IDAsia/Shanghai,只是常量,不修改 JVM 默认时区
RandomUtil.getRandomString(n[, template])从模板随机取字符,默认数字加大小写字母;n 小于等于 0 为空串,null 模板抛异常
UUIDToLongUtil.to(String/UUID)UUID 高低 64 位异或后清除符号位,返回非负 long;有损映射,不能保证不碰撞

时间乘法不检查 long 溢出。随机字符使用 java.util.Random,不能作为 Token 或密钥生成器;空模板且 n 大于 0 会失败,模板中重复字符会影响概率。

TraceId 与线程边界

TraceIdUtil.get() 读取 SLF4J MDC 的 traceId,不存在或空白时生成 UUID 并写入;set(object) 写入 toString,传 null 时生成新 UUID。MDC 的实际存储依赖应用日志实现。

java
import com.own.constant.ConstantCommon;
import com.own.constant.util.TraceIdUtil;
import org.slf4j.MDC;

String previous = MDC.get(ConstantCommon.TRACE_ID);
try {
    TraceIdUtil.set("job-demo");
    System.out.println(TraceIdUtil.get());
} finally {
    if (previous == null) {
        MDC.remove(ConstantCommon.TRACE_ID);
    } else {
        MDC.put(ConstantCommon.TRACE_ID, previous);
    }
}

该工具不提供请求拦截器、线程间复制或自动清理。已有 Base 的 HTTP 和异步任务链路时,优先沿用其上下文生命周期,避免泄漏到线程池的下一项任务。

按 key 同步调用

SynchronizedUtil.exec(String/Integer, Runnable) 在进程内按 key 获取临时锁,两种 key 使用独立 Map。任务异常原样传播,finally 删除锁映射。

当前实现会在仍有线程等待旧锁时移除映射,后来的调用可能取得新锁,导致同 key 并行。因此它不能提供严格的串行执行保证;需要临界区一致性时使用生命周期明确的锁,或按场景接入 Store 锁

进程内事件

EventEmitsUtil<T> 收集可序列化 Consumer,以 Lambda 实现类和方法名作为监听标识。下面的完整类演示长期和一次性监听:

java
package example;

import com.own.constant.util.EventEmitsUtil;

public class EventExample {
    static void always(String value) {
        System.out.println("always:" + value);
    }

    static void first(String value) {
        System.out.println("once:" + value);
    }

    public static void main(String[] args) {
        var events = new EventEmitsUtil<String>();
        events.on(EventExample::always);
        events.once(EventExample::first);
        events.emit("A");
        events.emit("B");
        events.clear();
    }
}

输出依次为 always:Aonce:Aalways:Boff(consumer) 同时删除长期与一次性同标识项;clear() 删除全部监听。

执行是同步的:先长期,再执行未与长期同标识的一次性监听,最后清空一次性 Map。同一类同一方法的不同实例或捕获值可能被视为同一监听,相同标识会覆盖旧项;同一 Map 内没有执行顺序保证。

两个 Map 均无并发保护,emit 期间注册/删除可能触发并发修改异常。监听异常直接传播,并且可能阻止一次性 Map 清空,使已执行项在下次再次执行。该容器没有持久化、重试或跨进程投递。

监听器类型与标识解析

LambdaUtils.getLambdaMethodIdentity(Serializable) 依赖反射读取 writeReplace();普通对象或不可解析 Lambda 抛 BusinessException("无法解析 lambda")。注册方法接收 com.own.constant.function.SerializableConsumer<T>,不是任意 Consumer 实现。

读取静态 YAML

Spring 应用需要使用启动参数、外部配置或绑定对象时,优先使用 Spring Environment 或显式绑定的属性对象。下面的工具适用于维护已有的 classpath YAML 读取逻辑。

PropertiesYamlUtil.get(key) 在首次使用类时读取 classpath 的 application.yml,随后读取逗号分隔的 spring.profiles.include 文件和单个 spring.profiles.active 文件,后加载覆盖先加载。

缺少主文件或引用的 profile 文件可能导致类初始化失败;key 缺失时抛 NullPointerException。该工具不等价于 Spring Environment,不处理其完整配置来源或多 active profile 语义,也不支持刷新。

示例涉及的源码类型

ConstantCommon