First Commit
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
package quant.rich.emoney.util;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/* r2.b */
|
||||
public class EmoneyBytesResortUtils {
|
||||
|
||||
/* renamed from: a reason: collision with root package name */
|
||||
private static char[] f42522a = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
||||
|
||||
public static int a(int i9) {
|
||||
int i10 = 0;
|
||||
while (i9 != 0) {
|
||||
i10++;
|
||||
i9 &= i9 - 1;
|
||||
}
|
||||
return i10;
|
||||
}
|
||||
|
||||
private static int b() {
|
||||
return 26895494;
|
||||
}
|
||||
|
||||
private static int c() {
|
||||
return 35063394;
|
||||
}
|
||||
|
||||
public static void d(byte[] bArr) {
|
||||
int length = bArr.length;
|
||||
byte[] bytes = Integer.valueOf(b() + c()).toString().getBytes(Charset.forName("UTF-8"));
|
||||
int length2 = bytes.length;
|
||||
int i9 = 0;
|
||||
do {
|
||||
for (int i10 = 0; i10 < length2; i10++) {
|
||||
int i11 = i9 + i10;
|
||||
if (i11 >= length) {
|
||||
break;
|
||||
}
|
||||
bArr[i11] = (byte) (bArr[i11] ^ bytes[i10]);
|
||||
}
|
||||
i9 += length2;
|
||||
} while (i9 < length);
|
||||
}
|
||||
|
||||
public static String[] e(String str, int i9) {
|
||||
int i10;
|
||||
String str2;
|
||||
int length = str.length() / i9;
|
||||
int length2 = str.length() % i9;
|
||||
if (length2 != 0) {
|
||||
i10 = 1;
|
||||
} else {
|
||||
i10 = 0;
|
||||
}
|
||||
int i11 = length + i10;
|
||||
String[] strArr = new String[i11];
|
||||
for (int i12 = 0; i12 < i11; i12++) {
|
||||
if (i12 != i11 - 1 || length2 == 0) {
|
||||
int i13 = i12 * i9;
|
||||
str2 = str.substring(i13, i13 + i9);
|
||||
} else {
|
||||
int i14 = i12 * i9;
|
||||
str2 = str.substring(i14, i14 + length2);
|
||||
}
|
||||
strArr[i12] = str2;
|
||||
}
|
||||
return strArr;
|
||||
}
|
||||
|
||||
}
|
||||
309
src/main/java/quant/rich/emoney/util/EncryptUtils.java
Normal file
309
src/main/java/quant/rich/emoney/util/EncryptUtils.java
Normal file
@@ -0,0 +1,309 @@
|
||||
package quant.rich.emoney.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Instant;
|
||||
import java.util.Base64;
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bouncycastle.crypto.digests.SHA3Digest;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
|
||||
@Slf4j
|
||||
public class EncryptUtils {
|
||||
|
||||
private static final byte[] IV = "aesiv_emapp@2018".getBytes(StandardCharsets.UTF_8);
|
||||
private static final String PASSWORD_KEY_STRING_BASE64 = "5pxlF/NrBtxlBve0jQwGEw==";
|
||||
private static final String PASSWORD_CIPHER_INSTANCE = "AES/CBC/PKCS5Padding";
|
||||
private static final String AES_DECODE_KEY_STRING_BASE64 = "39KLC1etSfUXuiGxRm5ilDDtqapKXm0COuHxUrWSWlc=";
|
||||
private static final String EM_SIGN_MESS_1 = "23082404151001";
|
||||
private static final String EM_SIGN_MESS_2 = "994fec3c512f2f7756fd5e4403147f01";
|
||||
private static final String SLASH = "/";
|
||||
private static final String COLON = ":";
|
||||
|
||||
/**
|
||||
* 加密用于 Emoney 登录的密码
|
||||
* @param pwd 明文密码
|
||||
* @return 加密过的密码(Base64 字符串)
|
||||
*/
|
||||
public static String encryptAesForEmoneyPassword(String pwd) {
|
||||
try {
|
||||
byte[] keyBytes = Base64.getDecoder().decode(PASSWORD_KEY_STRING_BASE64);
|
||||
|
||||
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(IV);
|
||||
|
||||
Cipher cipher = Cipher.getInstance(PASSWORD_CIPHER_INSTANCE);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
|
||||
|
||||
// 执行加密
|
||||
byte[] encryptedBytes = cipher.doFinal(pwd.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// 将加密结果转换为Base64字符串
|
||||
String encryptedString = Base64.getEncoder().encodeToString(encryptedBytes);
|
||||
return encryptedString;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Encrypt aes for emoney-password failed", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密由 Emoney 进行加密过的密码
|
||||
* @param pwd 加密过的密码(Base64 字符串)
|
||||
* @return 明文密码
|
||||
*/
|
||||
public static String decryptAesForEmoneyPassword(String pwd) {
|
||||
try {
|
||||
byte[] keyBytes = Base64.getDecoder().decode(PASSWORD_KEY_STRING_BASE64);
|
||||
byte[] passBytes = Base64.getDecoder().decode(pwd);
|
||||
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(IV);
|
||||
|
||||
Cipher cipher = Cipher.getInstance(PASSWORD_CIPHER_INSTANCE);
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
|
||||
|
||||
// 执行解密
|
||||
byte[] decryptedBytes = cipher.doFinal(passBytes);
|
||||
|
||||
return new String(decryptedBytes, "UTF-8");
|
||||
|
||||
} catch (Exception e) {
|
||||
//log.error("Decrypt aes for emoney-password failed", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用益盟自带 AES key 对指定字节数组进行解密,一般而言该字节数组通过解码 Base64 字符串得到
|
||||
* @param bArr
|
||||
* @return
|
||||
*/
|
||||
private static String decryptAesForEmSignMess(byte[] bArr) {
|
||||
|
||||
try {
|
||||
byte[] keyBytes = Base64.getDecoder().decode(AES_DECODE_KEY_STRING_BASE64);
|
||||
EmoneyBytesResortUtils.d(keyBytes);
|
||||
|
||||
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES");
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec);
|
||||
|
||||
return new String(cipher.doFinal(bArr), "UTF-8");
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException
|
||||
| BadPaddingException | UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字符串转 32 位小写 md5 字符串
|
||||
*
|
||||
* @param str
|
||||
* @return
|
||||
*/
|
||||
public static String toMD5String(String str) {
|
||||
if (StringUtils.isEmpty(str)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return toMD5String(str.getBytes("UTF-8"));
|
||||
} catch (UnsupportedEncodingException e10) {
|
||||
e10.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 字节数组转 32 位小写 md5 字符串
|
||||
* @param bytes
|
||||
* @return
|
||||
*/
|
||||
public static String toMD5String(byte[] bytes) {
|
||||
if (bytes == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
MessageDigest instance = MessageDigest.getInstance("MD5");
|
||||
instance.update(bytes);
|
||||
return bytesToHex(instance.digest());
|
||||
} catch (NoSuchAlgorithmException e10) {
|
||||
e10.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String bytesToHex(byte[] bytes) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
// 将字节转换为无符号整数表示的16进制,并且保证每个字节输出两个字符
|
||||
sb.append(String.format("%02x", b & 0xFF));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 content、method 和 xProtocolId 生成 EM-Sign,随机字符串和时间戳自动生成
|
||||
* @param content 请求的字节数组
|
||||
* @param method 请求方法
|
||||
* @param xProtocolId 请求 X-Protocol-Id
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public static String getEMSign(
|
||||
byte[] content,
|
||||
String method,
|
||||
String xProtocolId) throws IOException {
|
||||
String randomString = TextUtils.randomString(10);
|
||||
long timestampFixed = Instant.now().toEpochMilli();
|
||||
return getEMSign(content, method, xProtocolId, randomString, timestampFixed);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 content、method、xProtocolId、指定的随机 10 位字符串、指定的时间戳生成 EM-Sign
|
||||
* @param content 请求的字节数组
|
||||
* @param method 请求方法
|
||||
* @param xProtocolId 请求 X-Protocol-Id
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public static String getEMSign(
|
||||
byte[] content,
|
||||
String method,
|
||||
String xProtocolId,
|
||||
String randomString, long timestampFixed) throws IOException {
|
||||
|
||||
// TODO: 空校验、时间戳校验、随机字符串长度校验等防呆操作
|
||||
|
||||
StringBuffer plainSignSb = new StringBuffer();
|
||||
plainSignSb.append(EM_SIGN_MESS_1);
|
||||
plainSignSb.append(method.toUpperCase());
|
||||
plainSignSb.append(EM_SIGN_MESS_2);
|
||||
plainSignSb.append(SLASH);
|
||||
plainSignSb.append(URLDecoder.decode(xProtocolId, "UTF-8"));
|
||||
plainSignSb.append(timestampFixed);
|
||||
plainSignSb.append(randomString);
|
||||
|
||||
if (content != null && content.length != 0) {
|
||||
plainSignSb.append(toMD5String(content));
|
||||
}
|
||||
|
||||
String md5Sign = EncryptUtils.toMD5String(plainSignSb.toString());
|
||||
|
||||
StringBuilder emSignSb = new StringBuilder();
|
||||
emSignSb.append(EM_SIGN_MESS_1);
|
||||
emSignSb.append(COLON);
|
||||
emSignSb.append(md5Sign);
|
||||
emSignSb.append(COLON);
|
||||
emSignSb.append(randomString);
|
||||
emSignSb.append(COLON);
|
||||
emSignSb.append(timestampFixed);
|
||||
|
||||
String emSign = emSignSb.toString();
|
||||
return emSign;
|
||||
}
|
||||
|
||||
public static byte[] sha3(byte[] bytes, int digit) {
|
||||
SHA3Digest digest = new SHA3Digest(digit);
|
||||
digest.update(bytes, 0, bytes.length);
|
||||
byte[] rsData = new byte[digest.getDigestSize()];
|
||||
digest.doFinal(rsData, 0);
|
||||
return rsData;
|
||||
}
|
||||
|
||||
public static String sha256(String input) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] hash = digest.digest(input.getBytes());
|
||||
StringBuilder hexString = new StringBuilder();
|
||||
for (byte b : hash) {
|
||||
String hex = Integer.toHexString(0xff & b);
|
||||
if (hex.length() == 1) {
|
||||
hexString.append('0');
|
||||
}
|
||||
hexString.append(hex);
|
||||
}
|
||||
return hexString.toString();
|
||||
} catch (Exception e) {
|
||||
log.error("SHA-256 Hash failed", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String sha3(String data, int digit) {
|
||||
return Hex.toHexString(sha3(data.getBytes(), digit));
|
||||
}
|
||||
|
||||
public static String toHexString(byte[] data) {
|
||||
return Hex.toHexString(data);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
// EM-Sign:
|
||||
// 23082404151001:356b7041c37848b4f73a8ad5c9416f48:nBZTuInqo1:1721439297485
|
||||
long timestampFixed = 1721439297485L;
|
||||
String randomString = "nBZTuInqo1";
|
||||
byte[] mess = Base64.getDecoder().decode("0JryhAshy7JXgP+5sLmWwaurGM3YHWaFbGaKH/mi7oKN+5mlTnEp5LTeFJVtJTTR");
|
||||
String f13443a = EncryptUtils.decryptAesForEmSignMess(mess);
|
||||
|
||||
String path = "C:\\Users\\Administrator\\Desktop\\Emoney\\login.request.body";
|
||||
|
||||
FileInputStream in = new FileInputStream(new File(path));
|
||||
byte[] requestBody = new byte[in.available()];
|
||||
in.read(requestBody);
|
||||
in.close();
|
||||
|
||||
String requestBodyStr = "{\"date\":0}";// new String(requestBody, "UTF-8") + '\0';
|
||||
requestBody = requestBodyStr.getBytes("UTF-8");
|
||||
|
||||
RequestBody body = RequestBody.create(requestBodyStr, MediaType.parse("application/json"));
|
||||
okio.Buffer buffer = new okio.Buffer();
|
||||
body.writeTo(buffer);
|
||||
requestBody = buffer.readByteArray();
|
||||
|
||||
String requestBodyMd5;
|
||||
if (requestBody == null || requestBody.length == 0) {
|
||||
requestBodyMd5 = "";
|
||||
} else {
|
||||
requestBodyMd5 = toMD5String(requestBody);
|
||||
}
|
||||
|
||||
String str2 = "23082404151001" + "POST" + f13443a + "/"
|
||||
+ URLDecoder.decode("SmartInvestment%2FIndex%2FTianYan", "UTF-8") + timestampFixed + randomString
|
||||
+ requestBodyMd5;
|
||||
|
||||
String md5 = EncryptUtils.toMD5String(str2);
|
||||
log.info("Handmade emsign md5: {}", md5);
|
||||
String emSign = getEMSign(requestBody, "POST", "SmartInvestment%2FIndex%2FTianYan", randomString, timestampFixed);
|
||||
log.info("Method emsign: {}", emSign);
|
||||
|
||||
// 测试密码加解密
|
||||
String encryptPass = encryptAesForEmoneyPassword("123456");
|
||||
log.info("Encrypt pass for 123456: {}", encryptPass);
|
||||
String decryptPass = decryptAesForEmoneyPassword(encryptPass);
|
||||
log.info("Decrypt pass for {}: {}", encryptPass, decryptPass);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package quant.rich.emoney.util;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.agent.ByteBuddyAgent;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.*;
|
||||
|
||||
public class PatchOkHttpUAStatic {
|
||||
|
||||
// 入口方法:只需调用一次
|
||||
public static void replace(String uaString) {
|
||||
try {
|
||||
ByteBuddyAgent.install();
|
||||
|
||||
// 传递目标 UA 值到 Advice
|
||||
UserAgentAdvice.overrideValue = uaString;
|
||||
|
||||
new ByteBuddy()
|
||||
.redefine(Class.forName("okhttp3.Request$Builder"))
|
||||
.visit(Advice.to(UserAgentAdvice.class)
|
||||
.on(named("header").and(takesArguments(String.class, String.class))))
|
||||
.make()
|
||||
.load(Class.forName("okhttp3.Request$Builder").getClassLoader(),
|
||||
ClassReloadingStrategy.fromInstalledAgent());
|
||||
|
||||
System.out.println("[PatchOkHttpUA] Successfully installed custom User-Agent hook.");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.err.println("[PatchOkHttpUA] Failed to patch OkHttp.");
|
||||
}
|
||||
}
|
||||
|
||||
// 内部类:修改 header(name, value) 方法逻辑
|
||||
public static class UserAgentAdvice {
|
||||
public static String overrideValue = "MySpoofed-UA";
|
||||
|
||||
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
|
||||
static boolean intercept(
|
||||
@Advice.This Object builder,
|
||||
@Advice.Argument(0) String name,
|
||||
@Advice.Argument(value = 1, readOnly = false) String value) {
|
||||
if ("User-Agent".equalsIgnoreCase(name)) {
|
||||
value = overrideValue;
|
||||
System.out.println("[PatchOkHttpUA] Replaced User-Agent with: " + value);
|
||||
}
|
||||
return false; // 不跳过原方法,继续执行但已修改参数
|
||||
}
|
||||
}
|
||||
}
|
||||
215
src/main/java/quant/rich/emoney/util/SpringContextHolder.java
Normal file
215
src/main/java/quant/rich/emoney/util/SpringContextHolder.java
Normal file
@@ -0,0 +1,215 @@
|
||||
package quant.rich.emoney.util;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.support.DefaultSingletonBeanRegistry;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.data.util.ProxyUtils;
|
||||
import org.springframework.jmx.export.annotation.ManagedAttribute;
|
||||
import org.springframework.jmx.export.annotation.ManagedOperation;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import quant.rich.emoney.config.ConstructionGuard;
|
||||
|
||||
/**
|
||||
* 以静态变量保存Spring ApplicationContext, 可在任何代码任何地方任何时候取出ApplicaitonContext.
|
||||
*
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
@Lazy(false)
|
||||
public class SpringContextHolder implements ApplicationContextAware, DisposableBean {
|
||||
|
||||
private static ApplicationContext applicationContext = null;
|
||||
|
||||
/**
|
||||
* 取得存储在静态变量中的ApplicationContext.
|
||||
*/
|
||||
public static ApplicationContext getApplicationContext() {
|
||||
assertContextInjected();
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T getBean(String name) {
|
||||
assertContextInjected();
|
||||
return (T) applicationContext.getBean(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
|
||||
*/
|
||||
public static <T> T getBean(Class<T> requiredType) {
|
||||
assertContextInjected();
|
||||
return applicationContext.getBean(requiredType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除SpringContextHolder中的ApplicationContext为Null.
|
||||
*/
|
||||
public static void clearHolder() {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext);
|
||||
}
|
||||
applicationContext = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实现ApplicationContextAware接口, 注入Context到静态变量中.
|
||||
*/
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) {
|
||||
SpringContextHolder.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实现DisposableBean接口, 在Context关闭时清理静态变量.
|
||||
*/
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
SpringContextHolder.clearHolder();
|
||||
}
|
||||
|
||||
public static boolean updateBean(String name, Object newBean) {
|
||||
log.debug("Enter updateBean for {}'s update", name);
|
||||
if (SpringContextHolder.applicationContext != null) {
|
||||
if (!applicationContext.containsBean(name)) {
|
||||
log.debug("updateBean is only for initialized bean's updating, but {} doesn't exist or hasn't been initialized yet");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
Class<?> beanType = SpringContextHolder.applicationContext.getType(name);
|
||||
if (ConstructionGuard.isConstructing(beanType)) {
|
||||
log.debug("updateBean is called but {} is initializing", name);
|
||||
return false;
|
||||
}
|
||||
Object bean = SpringContextHolder.applicationContext.getBean(name);
|
||||
Class<?> newBeanType = newBean.getClass();
|
||||
if (beanType != newBeanType) {
|
||||
log.warn("Cannot set new bean type {} to {}", newBeanType.getName(), name);
|
||||
return false;
|
||||
}
|
||||
if (bean != null) {
|
||||
if (AopUtils.isAopProxy(bean)) {
|
||||
bean = ProxyUtils.getUserClass(bean);
|
||||
}
|
||||
if (getNeverRefreshable().contains(bean.getClass().getName())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Field[] declaredFields = beanType.getDeclaredFields();
|
||||
for (Field field : declaredFields) {
|
||||
int modifiers = field.getModifiers();
|
||||
if (Modifier.isFinal(modifiers) || Modifier.isStatic(modifiers)) {
|
||||
continue;
|
||||
}
|
||||
field.setAccessible(true);
|
||||
try {
|
||||
setFieldData(field, bean, field.get(newBean));
|
||||
} catch (Exception e) {
|
||||
log.error("Change bean failed", e);
|
||||
}
|
||||
field.setAccessible(false);
|
||||
}
|
||||
rebindSingleton(name, bean);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("Cannot rebind to " + name, e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void setFieldData(Field field, Object bean, Object obj) throws Exception {
|
||||
Class<?> type = field.getType();
|
||||
if (obj == null) {
|
||||
field.set(bean, null);
|
||||
} else if (type.equals(String.class)) {
|
||||
field.set(bean, String.valueOf(obj));
|
||||
} else if (type.equals(Integer.class)) {
|
||||
field.set(bean, Integer.valueOf(String.valueOf(obj)));
|
||||
} else if (type.equals(Long.class)) {
|
||||
field.set(bean, Long.valueOf(String.valueOf(obj)));
|
||||
} else if (type.equals(Double.class)) {
|
||||
field.set(bean, Double.valueOf(String.valueOf(obj)));
|
||||
} else if (type.equals(Short.class)) {
|
||||
field.set(bean, Short.valueOf(String.valueOf(obj)));
|
||||
} else if (type.equals(Byte.class)) {
|
||||
field.set(bean, Byte.valueOf(String.valueOf(obj)));
|
||||
} else if (type.equals(Boolean.class)) {
|
||||
field.set(bean, Boolean.valueOf(String.valueOf(obj)));
|
||||
} else if (type.equals(Date.class)) {
|
||||
field.set(bean, new Date(Long.valueOf(String.valueOf(obj))));
|
||||
} else if (obj instanceof List) {
|
||||
field.set(bean, new ArrayList<>((List<?>) obj));
|
||||
} else {
|
||||
field.set(bean, obj);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重绑定单例模式 Bean
|
||||
*
|
||||
* @param name
|
||||
* @return
|
||||
* @see <a href=
|
||||
* "http://blog.gxitsky.com/2023/01/31/SpringBoot-63-Auto-Refresh-Environment-Config-Data/">
|
||||
* Spring Boot 2系列(六十三):动态刷新环境配置和Bean属性值</a>
|
||||
*/
|
||||
@ManagedOperation
|
||||
public static boolean rebindSingleton(String name, Object newBean) {
|
||||
if (SpringContextHolder.applicationContext != null) {
|
||||
try {
|
||||
Object bean = SpringContextHolder.applicationContext.getBean(name);
|
||||
|
||||
DefaultSingletonBeanRegistry registry = (DefaultSingletonBeanRegistry) SpringContextHolder.applicationContext
|
||||
.getAutowireCapableBeanFactory();
|
||||
if (bean != null) {
|
||||
if (AopUtils.isAopProxy(bean)) {
|
||||
bean = ProxyUtils.getUserClass(bean);
|
||||
}
|
||||
// TODO: determine a more general approach to fix this.
|
||||
// see https://github.com/spring-cloud/spring-cloud-commons/issues/571
|
||||
if (getNeverRefreshable().contains(bean.getClass().getName())) {
|
||||
return false; // ignore
|
||||
}
|
||||
registry.destroySingleton(name);
|
||||
}
|
||||
registry.registerSingleton(name, newBean);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("Cannot rebind to " + name, e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ManagedAttribute
|
||||
public static Set<String> getNeverRefreshable() {
|
||||
String neverRefresh = SpringContextHolder.applicationContext.getEnvironment()
|
||||
.getProperty("spring.cloud.refresh.never-refreshable", "com.zaxxer.hikari.HikariDataSource");
|
||||
return StringUtils.commaDelimitedListToSet(neverRefresh);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查ApplicationContext不为空.
|
||||
*/
|
||||
private static void assertContextInjected() {
|
||||
Validate.validState(applicationContext != null, "applicaitonContext 属性未注入,请检查程序是否未正常启动");
|
||||
}
|
||||
}
|
||||
27
src/main/java/quant/rich/emoney/util/TextUtils.java
Normal file
27
src/main/java/quant/rich/emoney/util/TextUtils.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package quant.rich.emoney.util;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class TextUtils {
|
||||
|
||||
private static final Random random = new Random();
|
||||
|
||||
/**
|
||||
* 提供 n 位的 A-Za-z0-9 的随机字串
|
||||
*
|
||||
* @param n
|
||||
* @return
|
||||
*/
|
||||
public static String randomString(int n) {
|
||||
return randomString("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", n);
|
||||
}
|
||||
|
||||
public static String randomString(String from, int n) {
|
||||
StringBuilder sb = new StringBuilder(n);
|
||||
for (int i = 0; i < n; i++) {
|
||||
sb.append(from.charAt(random.nextInt(from.length())));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
61
src/main/java/quant/rich/emoney/util/VersionComparator.java
Normal file
61
src/main/java/quant/rich/emoney/util/VersionComparator.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package quant.rich.emoney.util;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
public class VersionComparator implements Comparator<String> {
|
||||
|
||||
public static VersionComparator INSTANCE = new VersionComparator();
|
||||
|
||||
@Override
|
||||
public int compare(String version1, String version2) {
|
||||
// 将版本号拆分为数字部分(以 '.' 为分隔符)
|
||||
String[] version1Parts = version1.split("\\.");
|
||||
String[] version2Parts = version2.split("\\.");
|
||||
|
||||
// 找到较长的版本号长度
|
||||
int length = Math.max(version1Parts.length, version2Parts.length);
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
// 如果 version1Parts 或 version2Parts 长度不足,补齐为 0
|
||||
try {
|
||||
int v1 = (i < version1Parts.length) ? Integer.parseInt(version1Parts[i]) : 0;
|
||||
int v2 = (i < version2Parts.length) ? Integer.parseInt(version2Parts[i]) : 0;
|
||||
|
||||
// 比较版本号的每一部分
|
||||
if (v1 < v2) {
|
||||
return -1; // version1 小于 version2
|
||||
} else if (v1 > v2) {
|
||||
return 1; // version1 大于 version2
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
// 转换数字失败的直接调用 String 的 compare
|
||||
String v1 = (i < version1Parts.length) ? version1 : "";
|
||||
String v2 = (i < version2Parts.length) ? version2 : "";
|
||||
return v1.compareTo(v2);
|
||||
}
|
||||
}
|
||||
// 如果各部分都相等
|
||||
return 0; // version1 等于 version2
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
// 使用 Comparator 比较版本号
|
||||
VersionComparator versionComparator = new VersionComparator();
|
||||
|
||||
// 示例版本号
|
||||
String version1 = "1.2.3";
|
||||
String version2 = "1.10.5";
|
||||
|
||||
// 比较两个版本号
|
||||
int result = versionComparator.compare(version1, version2);
|
||||
|
||||
if (result == 0) {
|
||||
System.out.println(version1 + " is equal to " + version2);
|
||||
} else if (result < 0) {
|
||||
System.out.println(version1 + " is less than " + version2);
|
||||
} else {
|
||||
System.out.println(version1 + " is greater than " + version2);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user