前几篇我们走过了 HTTP 通信通道、WebSocket 长连接、服务端引导流程和客户端重连机制。在这些数据流动的背后,始终有一个角色在默默守护——加解密体系。第二篇中我们见过
MsgPayloadUtils的加密压缩流程,第三篇中提到 WebSocket 通道上的数据“同样是加密传输的”。但加密到底是怎么做的?密钥从哪来?三种算法之间怎么切换?今天我们就来拆解 Phoenix 的数据加解密架构——一个用接口、枚举和继承搭建的“密码学抽象层”。
一、为什么要做统一抽象?
Phoenix 支持三种对称加密算法:AES、DES 和 SM4(国密)。用户可以根据自己的合规要求和安全策略,在配置文件中选择其中一种。
这里有一个很现实的工程问题:如果加密逻辑散落在各个调用点——HTTP 发送时调一下 AesEncryptUtils.encrypt(),WebSocket 发送时再调一下——那当用户切换加密算法时,所有调用点都要改。更糟糕的是,如果某个调用点忘了改,就会出现“一半数据用 AES 加密,一半用 DES 加密”的灾难。
Phoenix 的解法很经典:用一层抽象,隔离算法选择和算法实现。调用方只需要说“给我加密”,具体用哪种算法,由配置决定、由抽象层分发。调用方不需要、也不应该关心底层用的是 AES 还是 SM4。
二、四层架构:从接口到配置
整个加解密体系可以分为四层,从上到下依次是:
┌─────────────────────────────────┐
│ 调用层(业务代码) │ SecureUtils.encrypt() / decrypt()
├─────────────────────────────────┤
│ 路由层(算法分发) │ SecurerEnums.valueOf(SECRET_TYPE)
├─────────────────────────────────┤
│ 实现层(具体算法) │ AesEncryptUtils / DesEncryptUtils / Sm4EncryptUtils
├─────────────────────────────────┤
│ 初始化层(配置加载) │ InitSecure(静态块 + 反射)
└─────────────────────────────────┘
每一层只依赖它下面那一层,职责清晰、互不干扰。接下来我们从底层开始,逐层往上拆解。
三、ISecurer:加解密的契约
一切从一个接口开始:
public interface ISecurer {
String encrypt(String str, Charset charset);
String encrypt(byte[] arry);
String decrypt(String str, Charset charset);
byte[] decrypt(String str);
}
四个方法,两对对称操作:
| 方法 | 输入 | 输出 | 用途 |
|---|---|---|---|
encrypt(String, Charset) |
明文字符串 + 字符集 | 密文字符串(Base64) | 常规文本加密 |
encrypt(byte[]) |
原始字节数组 | 密文字符串(Base64) | 压缩后的二进制数据加密 |
decrypt(String, Charset) |
密文字符串 + 字符集 | 明文字符串 | 常规文本解密 |
decrypt(String) |
密文字符串 | 原始字节数组 | 需要解压的数据解密 |
为什么需要两组重载?回忆一下第二篇博客中 MsgPayloadUtils 的逻辑:小数据直接加密(字符串进、字符串出),大数据先 Gzip 压缩成字节数组再加密(字节数组进、字符串出)。对称地,解密时也需要两条路径——直接解密成字符串,或者解密成字节数组再解压。
这个接口虽然只有四个方法,但它定义了整个加解密体系的契约——任何加密算法,只要实现了这四个方法,就可以无缝接入 Phoenix 的通信管线。
四、SecurerEnums:枚举实现接口的精妙设计
在 Java 中,枚举可以实现接口——这是一个很多人知道但不常用的特性。Phoenix 正是利用这个特性,把三种算法的路由做得异常简洁:
public enum SecurerEnums implements ISecurer {
DES {
@Override
public String encrypt(String str, Charset charset) {
return DesEncryptUtils.encrypt(str, charset);
}
@Override
public String encrypt(byte[] arry) {
return DesEncryptUtils.encrypt(arry);
}
@Override
public String decrypt(String str, Charset charset) {
return DesEncryptUtils.decrypt(str, charset);
}
@Override
public byte[] decrypt(String str) {
return DesEncryptUtils.decrypt(str);
}
},
AES {
@Override
public String encrypt(String str, Charset charset) {
return AesEncryptUtils.encrypt(str, charset);
}
// ... 同样的四个方法,委托给 AesEncryptUtils
},
SM4 {
@Override
public String encrypt(String str, Charset charset) {
return Sm4EncryptUtils.encrypt(str, charset);
}
// ... 同样的四个方法,委托给 Sm4EncryptUtils
}
}
每个枚举常量都是 ISecurer 的一个完整实现——它不直接做加解密,而是委托给对应的工具类。这种设计有几个值得玩味的点:
第一,枚举天然是单例。SecurerEnums.AES 在 JVM 中只存在一份,不需要 static final 变量,不需要单例模式,Java 语言规范直接保证。
第二,枚举名即配置值。用户在配置文件里写 monitoring.secure.encryption-algorithm-type=aes,代码里用 SecurerEnums.valueOf("AES") 就能拿到对应的实现。枚举名和配置值之间的映射是零代码的——不需要 if-else,不需要 Map,valueOf() 天然完成了这件事。
第三,编译期安全。如果有人在配置文件里写了 monitoring.secure.encryption-algorithm-type=rsa,SecurerEnums.valueOf("RSA") 会抛出 IllegalArgumentException——在运行的最早时机暴露配置错误。
为什么不直接在枚举里写加解密逻辑,而要委托给独立的工具类?因为关注点分离。枚举负责“路由”——根据算法名字找到对应的实现;工具类负责“执行”——真正的加解密操作。如果把所有逻辑堆在枚举里,SecurerEnums 会变成一个几百行的巨型文件,可读性和可维护性都会下降。
五、三个工具类:殊途同归的实现
5.1 AesEncryptUtils
public class AesEncryptUtils extends InitSecure {
public static String encrypt(String str, Charset charset) {
byte[] key = Base64.getDecoder().decode(SECRET_KEY_AES);
return SecureUtil.aes(key).encryptBase64(str, charset);
}
public static String encrypt(byte[] arry) {
byte[] key = Base64.getDecoder().decode(SECRET_KEY_AES);
return SecureUtil.aes(key).encryptBase64(arry);
}
public static String decrypt(String str, Charset charset) {
byte[] key = Base64.getDecoder().decode(SECRET_KEY_AES);
return SecureUtil.aes(key).decryptStr(str, charset);
}
public static byte[] decrypt(String str) {
byte[] key = Base64.getDecoder().decode(SECRET_KEY_AES);
return SecureUtil.aes(key).decrypt(str);
}
}
5.2 DesEncryptUtils
public class DesEncryptUtils extends InitSecure {
public static String encrypt(String str, Charset charset) {
byte[] key = Base64.getDecoder().decode(SECRET_KEY_DES);
return SecureUtil.des(key).encryptBase64(str, charset);
}
public static String decrypt(String str, Charset charset) {
byte[] key = Base64.getDecoder().decode(SECRET_KEY_DES);
return SecureUtil.des(key).decryptStr(str, charset);
}
// ... 其余两个方法结构相同
}
5.3 Sm4EncryptUtils
public class Sm4EncryptUtils extends InitSecure {
public static String encrypt(String str, Charset charset) {
byte[] key = Base64.getDecoder().decode(SECRET_KEY_SM4);
return SmUtil.sm4(key).encryptBase64(str, charset);
}
public static String decrypt(String str, Charset charset) {
byte[] key = Base64.getDecoder().decode(SECRET_KEY_SM4);
return SmUtil.sm4(key).decryptStr(str, charset);
}
// ... 其余两个方法结构相同
}
三个工具类的代码结构几乎一模一样,差异只在三个地方:
| AES | DES | SM4 | |
|---|---|---|---|
| 密钥字段 | SECRET_KEY_AES |
SECRET_KEY_DES |
SECRET_KEY_SM4 |
| Hutool 工厂方法 | SecureUtil.aes(key) |
SecureUtil.des(key) |
SmUtil.sm4(key) |
| 底层算法 | AES/ECB/PKCS5Padding | DES/ECB/PKCS5Padding | SM4/ECB/PKCS5Padding |
有几个共性值得关注:
第一,全部继承 InitSecure。三个工具类都 extends InitSecure,这意味着它们可以直接访问父类的 static final 字段——SECRET_KEY_AES、SECRET_KEY_DES、SECRET_KEY_SM4。继承在这里不是为了多态,而是为了共享静态常量。
第二,密钥是 Base64 编码存储的。配置文件中的密钥是 Base64 字符串(如 pqdOWnEkA3AQKyb7L2ewAA==),使用时先 Base64.getDecoder().decode() 还原成原始字节数组。为什么不直接存二进制?因为配置文件是文本格式,不适合存二进制数据。Base64 是将二进制安全地编码为可打印 ASCII 字符的标准方式。
第三,底层全部委托给 Hutool。Phoenix 没有自己实现任何加密算法——AES 和 DES 用 Hutool 的 SecureUtil,SM4 用 Hutool 的 SmUtil。密码学算法的实现极其容易出错(时序攻击、填充预言攻击、不安全的随机数……),能用久经考验的开源库就绝不自己造轮子。这是一种非常务实的工程态度。
第四,输出统一为 Base64。所有加密方法的返回值都是 Base64 字符串(encryptBase64),而不是原始字节数组。这确保了加密后的数据可以安全地放入 JSON 字段中传输——JSON 不支持二进制数据,但 Base64 字符串没问题。
六、InitSecure:反射驱动的静态初始化
三个工具类都继承了 InitSecure,那密钥到底是从哪来的?答案藏在 InitSecure 的静态初始化块中。
6.1 四个静态常量
public class InitSecure {
public static final String SECRET_TYPE; // 加解密类型,如 "AES"
public static final String SECRET_KEY_DES; // DES 密钥(Base64)
public static final String SECRET_KEY_AES; // AES 密钥(Base64)
public static final String SECRET_KEY_SM4; // SM4 密钥(Base64)
}
四个 public static final 字段,在 static 块中完成赋值。final 意味着一旦初始化完成就不可更改——加密算法和密钥在运行期间是固定的,不允许中途切换。这是合理的——如果加密方式在运行时改变,已经用旧算法加密的数据就解不出来了。
6.2 反射加载配置
static {
TimeInterval timer = DateUtil.timer();
try {
// 通过反射加载 MonitoringSecureProperties 实例
Object secureProps = loadSecureProperties();
if (secureProps != null) {
SECRET_TYPE = getSecretType(secureProps);
if (StringUtils.equalsIgnoreCase(SecurerEnums.DES.name(), SECRET_TYPE)) {
SECRET_KEY_DES = getSecretKey(secureProps, "getDes");
} else {
SECRET_KEY_DES = null;
}
if (StringUtils.equalsIgnoreCase(SecurerEnums.AES.name(), SECRET_TYPE)) {
SECRET_KEY_AES = getSecretKey(secureProps, "getAes");
} else {
SECRET_KEY_AES = null;
}
if (StringUtils.equalsIgnoreCase(SecurerEnums.SM4.name(), SECRET_TYPE)) {
SECRET_KEY_SM4 = getSecretKey(secureProps, "getSm4");
} else {
SECRET_KEY_SM4 = null;
}
log.info("初始化加解密配置成功!加解密类型:{}", StringUtils.defaultIfBlank(SECRET_TYPE, "无"));
} else {
SECRET_TYPE = null;
SECRET_KEY_DES = null;
SECRET_KEY_AES = null;
SECRET_KEY_SM4 = null;
log.warn("未加载到 MonitoringSecureProperties 配置,加解密功能将被禁用!");
}
} finally {
String betweenDay = timer.intervalPretty();
log.info("初始化加解密配置耗时:{}", betweenDay);
}
}
这段代码有一个非常重要的设计决策:只加载当前选中算法的密钥。如果配置了 encryption-algorithm-type=aes,那么只有 SECRET_KEY_AES 会被赋值,SECRET_KEY_DES 和 SECRET_KEY_SM4 都是 null。
为什么不把三个密钥都加载进来?两个原因:
- 安全性:内存中持有的密钥越少越好。只加载用得到的那个,即使发生内存转储(heap dump),攻击者也只能看到一个密钥。
- Fail-Fast:如果代码因为 bug 不小心调用了错误算法的工具类(比如配置的是 AES,但代码调了
DesEncryptUtils),会立即因为SECRET_KEY_DES为null而抛异常——而不是悄悄用错误的密钥加密出一堆垃圾数据。
6.3 为什么用反射?
loadSecureProperties() 方法的实现非常有意思:
private static Object loadSecureProperties() {
try {
Class<?> configLoader = Class.forName("com.gitee.pifeng.monitoring.plug.core.ConfigLoader");
Method getMonitoringProperties = configLoader.getMethod("getMonitoringProperties");
setAccessibleQuietly(getMonitoringProperties);
Object monitoringProps = getMonitoringProperties.invoke(null);
if (monitoringProps == null) {
return null;
}
Method getSecure = monitoringProps.getClass().getMethod("getSecure");
setAccessibleQuietly(getSecure);
return getSecure.invoke(monitoringProps);
} catch (ClassNotFoundException e) {
log.error("未找到 ConfigLoader 类!");
return null;
} catch (Exception e) {
log.error("通过反射加载 MonitoringSecureProperties 实例时发生异常!", e);
return null;
}
}
为什么要用反射而不是直接 ConfigLoader.getMonitoringProperties().getSecure()?
这是一个模块解耦的问题。InitSecure 位于 phoenix-common-core 模块,ConfigLoader 位于 phoenix-client-core 模块。在 Maven 的依赖关系中,phoenix-client-core 依赖 phoenix-common-core,反过来不行——如果 phoenix-common-core 直接引用 ConfigLoader,就会形成循环依赖。
反射打破了编译期的依赖关系——InitSecure 在编译时不需要知道 ConfigLoader 的存在,只在运行时通过字符串类名去查找它。如果 ConfigLoader 不在 classpath 上,Class.forName() 会抛 ClassNotFoundException,被 catch 后安静地返回 null——加密功能被禁用,但程序不会崩溃。
6.4 declare():提前触发初始化
public static void declare() {
// 仅用于触发 static 块执行,无需任何逻辑
log.trace("触发 InitSecure 静态初始化(若尚未执行)!");
}
这个“空方法”看似多余,实则意义重大。Java 的类加载机制是懒加载的——一个类只有在被首次主动使用时才会被加载和初始化。如果不调用 declare(),InitSecure 的静态块会在第一次加密操作时才执行。
问题在于,第一次加密操作通常发生在某个定时任务线程中——心跳包要发送了,需要加密,触发 InitSecure 类加载,然后反射加载配置、初始化密钥……这一系列操作会让第一次心跳发送变得异常缓慢。
所以 Monitor.start() 在启动流程的第 4 步就主动调用了 InitSecure.declare():
// 4.初始化加解密配置
InitSecure.declare();
// 5.运行双向数据交换器
DataExchanger.run();
在任何数据交换发生之前,就把加解密配置初始化好——密钥已经在内存中就位,后续的加密操作不会有“冷启动”延迟。这种“提前预热”的思想,在对延迟敏感的系统中非常常见。
七、SecureUtils:面向调用方的统一门面
业务代码不直接和 SecurerEnums、AesEncryptUtils 打交道——它们通过 SecureUtils 这个门面(Facade)完成所有加解密操作:
public class SecureUtils extends InitSecure {
public static String encrypt(String str, Charset charset) {
// 没选择加解密类型,则不加密
if (StringUtils.isBlank(SECRET_TYPE)) {
return str;
}
return SecurerEnums.valueOf(StringUtils.upperCase(SECRET_TYPE)).encrypt(str, charset);
}
public static String encrypt(byte[] arry) {
if (StringUtils.isBlank(SECRET_TYPE)) {
return Base64.getEncoder().encodeToString(arry);
}
return SecurerEnums.valueOf(StringUtils.upperCase(SECRET_TYPE)).encrypt(arry);
}
public static String decrypt(String str, Charset charset) {
if (StringUtils.isBlank(SECRET_TYPE)) {
return str;
}
return SecurerEnums.valueOf(StringUtils.upperCase(SECRET_TYPE)).decrypt(str, charset);
}
public static byte[] decrypt(String str) {
if (StringUtils.isBlank(SECRET_TYPE)) {
return Base64.getDecoder().decode(str);
}
return SecurerEnums.valueOf(StringUtils.upperCase(SECRET_TYPE)).decrypt(str);
}
}
SecureUtils 做了两件事:
第一,优雅降级。如果 SECRET_TYPE 为空(用户没有配置加密算法),加密方法直接返回原文,解密方法也直接返回原文。这意味着加密是可选的——不配置就不加密,数据以明文传输。这种设计在开发和调试阶段非常友好——你可以在开发环境关闭加密,方便用抓包工具观察数据内容。
注意字节数组的降级处理有所不同:加密时返回 Base64.getEncoder().encodeToString(arry)——因为调用方期望得到的是一个字符串,直接返回字节数组类型不对。解密时返回 Base64.getDecoder().decode(str)——对称地,把 Base64 字符串还原回字节数组。即使不加密,数据格式的转换仍然要保持一致。
第二,算法路由。SecurerEnums.valueOf(StringUtils.upperCase(SECRET_TYPE)) 这一行完成了从配置字符串到算法实现的映射。valueOf() 是枚举的标准方法——传入枚举常量的名字,返回对应的枚举实例。由于 SecurerEnums 实现了 ISecurer 接口,拿到枚举实例后直接调用 encrypt() 或 decrypt() 即可。
整个调用链条如下:
SecureUtils.encrypt("hello", UTF-8)
→ SECRET_TYPE = "AES"
→ SecurerEnums.valueOf("AES") // 拿到 SecurerEnums.AES 枚举实例
→ SecurerEnums.AES.encrypt("hello", UTF-8) // 接口调用
→ AesEncryptUtils.encrypt("hello", UTF-8) // 委托给工具类
→ SecureUtil.aes(key).encryptBase64("hello", UTF-8) // Hutool 底层实现
→ "9FUrFVEs..." // 返回 Base64 密文
五次方法调用,从抽象层层穿透到底层实现,每一层都只做一件事——职责单一,层次分明。
八、密钥生成:SecureUtils.generateKey()
Phoenix 还提供了密钥生成能力:
public static String generateKey(String algorithm, int keySize) {
return Base64.getEncoder().encodeToString(KeyUtil.generateKey(algorithm, keySize).getEncoded());
}
底层用 Hutool 的 KeyUtil.generateKey() 调用 JDK 的 KeyGenerator 生成指定算法和长度的密钥,再 Base64 编码成字符串。生成的密钥可以直接填入配置文件的 monitoring.secure.aes.key 等字段中。
九、配置体系:从 properties 到内存
用户通过配置文件选择加密算法和密钥。Phoenix 支持两种配置方式——properties 文件和 Spring Boot 的 YAML/properties 配置。
9.1 Properties 文件配置
# 加密算法类型:aes / des / sm4,不填写则不加密
monitoring.secure.encryption-algorithm-type=aes
# AES 密钥(Base64 编码)
monitoring.secure.aes.key=pqdOWnEkA3AQKyb7L2ewAA==
# DES 密钥(Base64 编码)
monitoring.secure.des.key=W21AB4W2nmg=
# SM4 密钥(Base64 编码)
monitoring.secure.sm4.key=UKW5kIkKmBHmL+yLV54tMg==
9.2 Spring Boot Starter 配置
phoenix:
monitoring:
secure:
encryption-algorithm-type: aes
aes:
key: pqdOWnEkA3AQKyb7L2ewAA==
9.3 配置属性类
配置值被加载到一组简洁的属性类中:
public class MonitoringSecureProperties {
private SecurerEnums encryptionAlgorithmType; // 加密算法类型
private MonitoringSecureDesProperties des; // DES 配置
private MonitoringSecureAesProperties aes; // AES 配置
private MonitoringSecureSm4Properties sm4; // SM4 配置
}
每个算法的配置属性类只有一个字段——key(密钥):
public class MonitoringSecureAesProperties {
// 注意:这个属性名称不能随意改动,因为在 InitSecure 用了反射来获取这个属性
private String key;
}
注释里那行提醒很重要——因为 InitSecure 通过反射调用 getKey() 方法获取密钥,属性名改了反射就找不到了。这是反射解耦的代价:编译器不再替你检查字段名的一致性。
注意 encryptionAlgorithmType 的类型是 SecurerEnums——不是 String,而是枚举本身。这意味着配置值在加载阶段就会被校验——如果写了一个不存在的算法名,Spring Boot 在启动时就会报错,而不是等到第一次加密时才发现。
十、调用全景:一条数据的加密之旅
让我们把所有组件串起来,看看一条心跳数据从明文到密文的完整旅程。以 HTTP 通道为例:
心跳包明文 JSON(约 200 字节)
│
▼
MsgPayloadUtils.encryptPayload(json)
│
├── ZipUtils.isNeedGzip(json) → 200B < 64KB → 不压缩
│
├── SecureUtils.encrypt(json, UTF-8)
│ │
│ ├── SECRET_TYPE = "AES"(非空,走加密路径)
│ │
│ ├── SecurerEnums.valueOf("AES")
│ │ │
│ │ └── SecurerEnums.AES.encrypt(json, UTF-8)
│ │ │
│ │ └── AesEncryptUtils.encrypt(json, UTF-8)
│ │ │
│ │ ├── Base64.decode("pqdOWnEkA3AQKyb7L2ewAA==") → 密钥字节数组
│ │ │
│ │ └── SecureUtil.aes(key).encryptBase64(json, UTF-8)
│ │ │
│ │ └── "9FUrFVEsBR3Mjw..." ← AES/ECB/PKCS5Padding + Base64
│ │
│ └── 返回密文字符串
│
└── new CiphertextPackage("9FUrFVEsBR3Mjw...", false)
│
└── .toJsonString() → {"ciphertext":"9FUrFVEsBR3Mjw...","isUnGzipEnabled":false}
接收方的解密是完全对称的逆操作——解析 JSON、取出密文、解密、返回明文。发送方和接收方必须使用相同的算法和密钥,否则解密失败。这也是为什么 Phoenix 的所有节点(客户端、代理端、服务端、UI 端)的配置文件中都写着同样的加密配置。
十一、设计复盘
“接口 + 枚举”的策略模式
经典的策略模式需要一个接口、多个实现类、一个上下文持有策略引用。Phoenix 用枚举替代了实现类和上下文——枚举既是策略的容器(SecurerEnums.AES),又是策略的注册表(SecurerEnums.valueOf("AES")),还是策略的单例保证。一个枚举干了三件事,代码量极小,但功能完整。
如果将来需要添加一种新算法(比如 SM3),只需要:
- 在
SecurerEnums中加一个枚举常量 - 写一个
Sm3EncryptUtils工具类 - 在
InitSecure中加一个SECRET_KEY_SM3字段和对应的加载逻辑 - 在配置属性类中加一个
MonitoringSecureSm3Properties
核心的调用链——SecureUtils → SecurerEnums → 工具类——完全不需要修改。这就是抽象的价值。
继承用于共享常量,而非多态
AesEncryptUtils extends InitSecure 这种继承关系,不是为了实现多态(你不会把 AesEncryptUtils 的引用赋给 InitSecure 类型的变量),而是为了让子类能直接访问父类的 protected/public static final 常量。这是一种实用但略显“非正统”的继承用法——在正统的 OOP 设计中,这种场景应该用组合或静态导入。但在这个具体场景中,继承让代码更简洁——工具类直接写 SECRET_KEY_AES 而不是 InitSecure.SECRET_KEY_AES,读起来更自然。
加密可选:零配置即零加密
SecureUtils 的降级逻辑让加密变成了一个完全可选的功能——不填 encryption-algorithm-type,数据就以明文传输。这种设计让 Phoenix 在不同环境下有不同的表现:
- 开发环境:不配加密,方便调试和抓包
- 测试环境:配 DES(速度快,强度够用)
- 生产环境:配 AES 或 SM4(强度高,满足合规要求)
用户不需要改一行代码,只需要修改一行配置,就能在三种安全级别之间切换。
十二、小结
本篇拆解了 Phoenix 的数据加解密体系。回顾核心要点:
- ISecurer 接口:定义了四个方法(字符串/字节数组 × 加密/解密),是所有算法的统一契约
- SecurerEnums 枚举:实现
ISecurer接口,每个枚举常量(DES、AES、SM4)委托给对应的工具类,用valueOf()实现配置到实现的零代码映射 - 三个工具类:
AesEncryptUtils、DesEncryptUtils、Sm4EncryptUtils,继承InitSecure获取密钥,底层委托 Hutool 完成真正的加解密 - InitSecure:通过反射从
ConfigLoader加载配置,在静态块中初始化密钥;反射解决了模块间的循环依赖问题;declare()方法实现提前预热 - SecureUtils:面向调用方的统一门面,实现算法路由和优雅降级(不配置则不加密)
- 配置体系:支持 properties 和 Spring Boot 两种配置方式,
MonitoringSecureProperties用枚举类型字段实现编译期校验
整个体系的设计哲学可以用一句话概括:算法可切换,调用不感知。无论底层用的是 AES、DES 还是 SM4,上层的 MsgPayloadUtils、Sender、DataExchanger 看到的始终是同一个 SecureUtils.encrypt() 接口——它们不知道也不需要知道数据到底是怎么被加密的。这种“信息隐藏”正是面向对象设计的精髓所在。
下一篇,我们将深入 Phoenix 的消息压缩策略——看看 Gzip 压缩是如何与加密协同工作的,以及那个 64KB 阈值背后的设计考量。
项目地址:
https://gitcode.com/monitoring-platform/phoenix
https://gitee.com/monitoring-platform/phoenix
https://github.com/709343767/phoenix
欢迎关注微信公众号获取更多技术干货
评论