对接企业微信

This commit is contained in:
2026-04-24 20:10:51 +08:00
parent 03ba0c9090
commit d1ff7bde35
16 changed files with 1097 additions and 3 deletions

View File

@@ -0,0 +1,13 @@
package com.rj.common;
/**
* dict_item 相关常量
*/
public final class DictItemConstants {
private DictItemConstants() {
}
public static final String QIWEI_CONFIG = "qiwei_config";
}

View File

@@ -2,6 +2,15 @@ package com.rj.common;
import org.springframework.util.DigestUtils;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Base64;
/**
* 密码工具类
*
@@ -15,6 +24,13 @@ public class PasswordUtil {
*/
public static final String DEFAULT_SALT = "rj_system_2025";
/**
* 可逆加密标记,避免重复加密
*/
private static final String ENC_PREFIX = "ENC$";
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
/**
* MD5加密密码
*
@@ -47,6 +63,79 @@ public class PasswordUtil {
return encryptPassword(password, DEFAULT_SALT);
}
/**
* 可逆加密AES-GCM输出格式ENC$base64(iv).base64(cipherText)
* <p>
* 说明:此方法用于需要可解密的配置值(例如企微配置),不要用于用户密码。
*/
public static String encryptReversibleWithDefaultSalt(String plainText) {
if (plainText == null) {
plainText = "";
}
if (isEncryptedReversible(plainText)) {
return plainText;
}
try {
byte[] iv = new byte[12]; // GCM recommended IV length
SECURE_RANDOM.nextBytes(iv);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.ENCRYPT_MODE, deriveAesKey(DEFAULT_SALT), spec);
byte[] cipherBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
String ivB64 = Base64.getEncoder().encodeToString(iv);
String ctB64 = Base64.getEncoder().encodeToString(cipherBytes);
return ENC_PREFIX + ivB64 + "." + ctB64;
} catch (Exception e) {
throw new IllegalStateException("encrypt reversible failed: " + e.getMessage(), e);
}
}
/**
* 可逆解密AES-GCM支持未加密值直接原样返回。
*/
public static String decryptReversibleWithDefaultSalt(String encryptedOrPlain) {
if (encryptedOrPlain == null) {
return null;
}
if (!isEncryptedReversible(encryptedOrPlain)) {
return encryptedOrPlain;
}
try {
String payload = encryptedOrPlain.substring(ENC_PREFIX.length());
int dot = payload.indexOf('.');
if (dot <= 0 || dot >= payload.length() - 1) {
throw new IllegalArgumentException("bad encrypted format");
}
byte[] iv = Base64.getDecoder().decode(payload.substring(0, dot));
byte[] cipherBytes = Base64.getDecoder().decode(payload.substring(dot + 1));
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.DECRYPT_MODE, deriveAesKey(DEFAULT_SALT), spec);
byte[] plain = cipher.doFinal(cipherBytes);
return new String(plain, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new IllegalStateException("decrypt reversible failed: " + e.getMessage(), e);
}
}
public static boolean isEncryptedReversible(String value) {
return value != null && value.startsWith(ENC_PREFIX);
}
private static SecretKey deriveAesKey(String salt) {
try {
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
byte[] keyBytes = sha256.digest(String.valueOf(salt).getBytes(StandardCharsets.UTF_8));
// 256-bit AES key
return new SecretKeySpec(keyBytes, "AES");
} catch (Exception e) {
throw new IllegalStateException("derive key failed: " + e.getMessage(), e);
}
}
/**
* 验证密码
*