Java Stream API 本身不直接处理加解密,但可将确定性、无副作用的加解密逻辑封装为Function类型纯函数,通过map或flatMap注入流中实现字段级自动化脱敏加密;需预共享密钥、线程安全工具类及前置校验,避免流内初始化与异常中断。

Java Stream API 本身不直接处理加解密,但可以将加解密逻辑封装为纯函数,再通过 map 注入到数据流中,实现敏感字段的自动化脱敏。关键不是让 Stream 做加密,而是让它“安全地调度加密”——即把加密行为变成无状态、可复用、类型明确的映射操作。
把加解密逻辑抽象成 map 的输入函数
加解密必须是确定性、幂等、无副作用的操作,才能放进 map。推荐封装为 Function<string string></string> 类型的 Lambda 或方法引用:
- 手机号加密(AES):用预共享密钥对原始值加密,返回 Base64 字符串
- 身份证脱敏(非加密):用规则替换(如
s → s.replaceAll("^(\d{6})\d{8}(\d{4})$", "$1********$2"))——这是脱敏,不是加密 - 邮箱加密(AES):同手机号,但注意长度限制和填充策略
- 解密仅在出参/日志等下游环节按需触发,
map中一般只做加密或单向脱敏
在 map 中调用 AES 工具类完成字段级加密
假设已有线程安全的 AESUtil.encrypt(data, key),且密钥已注入 Spring 上下文或配置中心。可这样写:
List<User> safeUsers = users.stream()
.map(u -> {
try {
User copy = new User();
copy.setId(u.getId());
copy.setName(u.getName()); // 非敏感字段直赋
copy.setPhone(AESUtil.encrypt(u.getPhone(), "aes-key-256")); // 加密
copy.setEmail(AESUtil.encrypt(u.getEmail(), "aes-key-256"));
return copy;
} catch (Exception e) {
throw new RuntimeException("加密失败", e); // 或记录告警后返回 null + filter 掉
}
})
.collect(Collectors.toList());
结合 Desensitizer 接口统一管理不同字段策略
避免每个字段硬编码 AESUtil.encrypt。定义接口并注册策略:
立即学习“Java免费学习笔记(深入)”;
@FunctionalInterface
interface FieldProcessor<T> {
T process(T raw);
}
Map<String, FieldProcessor<String>> processors = Map.of(
"phone", s -> AESUtil.encrypt(s, PHONE_KEY),
"email", s -> AESUtil.encrypt(s, EMAIL_KEY),
"idCard", s -> s.replaceAll("^(\d{6})\d{8}(\d{4})$", "$1********$2")
);
// 使用时
user.setPhone(processors.get("phone").process(user.getPhone()));
再嵌入 Stream:.map(u -> applyProcessors(u, processors)),实现字段级可插拔脱敏/加密。
注意 map 的边界与陷阱
map 是一一映射,不能丢数据;若某字段加密失败,map 会抛异常中断整个流。正确做法是:
- 用
flatMap替代map实现“成功才保留”,例如返回Stream.of(encryptedUser)或Stream.empty() - 敏感字段为空时,
map不应尝试加密,需前置filter(u -> u.getPhone() != null && !u.getPhone().trim().isEmpty()) - AES 加密结果长度不可控,入库前需确认字段长度限制(如 MySQL VARCHAR(255) 是否足够存 Base64 密文)
- 不要在
map里生成新密钥或读配置文件——这些属于初始化逻辑,应在流外完成


















