新增(移动)一些应该放在“管理”形成列表管理,而非放在“设置”形成单一配置的内容

This commit is contained in:
2025-07-03 15:58:27 +08:00
parent 04cf470ead
commit 148583cdaa
52 changed files with 2433 additions and 362 deletions

View File

@@ -0,0 +1,79 @@
package quant.rich.emoney.entity.sqlite;
import java.net.InetSocketAddress;
import java.net.Proxy;
import org.apache.commons.lang3.ObjectUtils;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.annotation.Nonnull;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import quant.rich.emoney.validator.ProxySettingValid;
@Data
@Accessors(chain = true)
@NoArgsConstructor
@TableName(value = "proxy_setting", autoResultMap = true)
@ProxySettingValid
public class ProxySetting {
@TableId(value="id", type=IdType.AUTO)
private Integer id;
@Nonnull
private String proxyName;
private Proxy.Type proxyType = Proxy.Type.DIRECT;
private String proxyHost = "";
private Integer proxyPort = 1;
private Boolean ignoreHttpsVerification = false;
/**
* 根据配置获取 java.net.Proxy
* @return
*/
@JsonIgnore
public Proxy getProxy() {
if (getProxyType() != null && getProxyType() != Proxy.Type.DIRECT) {
return new Proxy(getProxyType(),
new InetSocketAddress(getProxyHost(), getProxyPort()));
}
return Proxy.NO_PROXY;
}
/**
* 根据当前配置获取 ProxyUrl(String)
* <p>如:
* <li>socks5://127.0.0.1:8888</li>
* <li>http://10.0.0.1:7890</li>
* </p>
* @return
*/
public String getProxyUrl() {
if (ObjectUtils.anyNull(getProxyType(), getProxyHost(), getProxyPort())) {
return null;
}
StringBuilder sb = new StringBuilder();
if (getProxyType() == Proxy.Type.SOCKS) {
sb.append("socks5://");
}
else if (getProxyType() == Proxy.Type.HTTP) {
sb.append("http://");
}
else {
return null;
}
sb.append(getProxyHost()).append(':').append(getProxyPort());
return sb.toString();
}
}

View File

@@ -0,0 +1,433 @@
package quant.rich.emoney.entity.sqlite;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.AccessLevel;
import lombok.Data;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import quant.rich.emoney.entity.config.AndroidSdkLevelConfig;
import quant.rich.emoney.entity.config.ChromeVersionsConfig;
import quant.rich.emoney.entity.config.DeviceInfoConfig;
import quant.rich.emoney.entity.config.DeviceInfoConfig.DeviceInfo;
import quant.rich.emoney.entity.config.EmoneyRequestConfig;
import quant.rich.emoney.util.EncryptUtils;
import quant.rich.emoney.util.SpringContextHolder;
import quant.rich.emoney.util.TextUtils;
@Data
@Accessors(chain = true)
@Slf4j
@TableName(value = "request_info")
public class RequestInfo {
private static AndroidSdkLevelConfig androidSdkLevelConfig = SpringContextHolder.getBean(AndroidSdkLevelConfig.class);
private static DeviceInfoConfig deviceInfoConfig = SpringContextHolder.getBean(DeviceInfoConfig.class);
private static ChromeVersionsConfig chromeVersionsConfig = SpringContextHolder.getBean(ChromeVersionsConfig.class);
public RequestInfo() {
DeviceInfo deviceInfo = deviceInfoConfig.getRandomDeviceInfo();
setRelativeFieldsFromDeviceInfo(deviceInfo);
}
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 该请求信息配置的名称,助记用
*/
private String name = "";
/**
* 是否匿名登录
*/
private Boolean isAnonymous = true;
/**
* 非匿名登录时的用户名
*/
private String username = "";
/**
* 非匿名登录时的密码
*/
private String password = "";
/**
* 鉴权信息
*/
private String authorization = "";
/**
* UID
*/
private Integer uid = -1;
/**
* <b>用于:</b><ul>
* <li>益盟登录接口 <code><i>guid</i> = MD5(<b>androidId</b>)</code></li>
* <li>益盟登录接口 <code><i>exIdentify.AndroidID</i> = <b>androidId</b></code></li>
* </ul>
* <b>来源:</b><br>本例随机生成并管理,需要符合 16 位
*
*/
private String androidId = TextUtils.randomString("abcdef0123456789", 16);
/**
* <b>用于:</b><ul>
* <li>Webview <code><i>User-Agent</i></li>
* <li>Non-Webview Image <code><i>User-Agent</i></code></li>
* </ul>
* <b>来源:</b><code><b>DeviceInfoConfig</b></code>
* @see DeviceInfoConfig
*/
@Setter(AccessLevel.PRIVATE)
@TableField(exist=false)
private String androidVersion;
/**
* <b>用于:</b><ul>
* <li>益盟通讯接口请求头 <code><i>X-Android-Agent</i> = EMAPP/{<b>emoneyVersion</b>}(Android;{<b>androidSdkLevel</b>})</code></li>
* <li>益盟登录接口 <code><i>osVersion</i> = <b>androidSdkLevel</b></code></li>
* </ul>
* <b>来源:</b><code><b>DeviceInfoConfig</b>, 经由 <code><b>AndroidSdkLevelConfig</b></code> 转换,由本例代管</code>
* @see DeviceInfoConfig
* @see AndroidSdkLevelConfig
*/
@Setter(AccessLevel.PRIVATE)
@TableField(exist=false)
private String androidSdkLevel;
/**
* <b>用于:</b><ul>
* <li>益盟登录接口 <code><i>softwareType</i> = <b>softwareType</b></code></li>
* </ul>
* <b>来源:</b><code><b>DeviceInfoConfig</b>,由本例代管</code>
* @see DeviceInfoConfig
*/
private String softwareType;
/**
* <b>用于:</b><ul>
* <li>益盟通讯接口请求头 <code><i>User-Agent</i> = <b>okHttpUserAgent</b></code></li>
* </ul>
* 一般由程序所使用的 OkHttp 版本决定<br>
* <b>来源:</b>本例管理
*/
private String okHttpUserAgent = "okhttp/3.12.2";
/**
* 对应 build.prop 中 Build.MODEL, <b>用于:</b><ul>
* <li>WebView <code><i>User-Agent</i></code></li>
* <li>非 WebView 图片<code><i>User-Agent</i></code></li>
* </ul>
* <b>来源:</b><code><b>DeviceInfoConfig</b>, 由本例代为管理
* @see DeviceInfoConfig
*/
private String deviceName;
/**
* 对应 build.prop 中 Build.FINGERPRINT, <b>用于:</b><ul>
* <li>益盟登录接口 <code><i>hardware</i> = MD5(<b>fingerprint</b>)</code></li>
* <li>益盟登录接口 <code><i>exIdentify.OSFingerPrint</i> = <b>fingerprint</b></code></li>
* </ul>
* <font color="red">注意最终生成的 exIdentify 是 jsonString, 且有对斜杠的转义</font><br>
* <b>来源:</b><code><b>DeviceInfoConfig</b>, 由本例代为管理
* @see DeviceInfoConfig
*
*/
private String fingerprint;
/**
* 对应 build.prop 中 Build.ID, <b>用于:</b><ul>
* <li>WebView <code><i>User-Agent</i></code></li>
* <li>非 WebView 图片<code><i>User-Agent</i></code></li>
* </ul>
* <b>来源:</b><code><b>DeviceInfoConfig</b>, 由本例代为管理
* @see DeviceInfoConfig
*
*/
@TableField(exist=false)
private String buildId;
/**
* <b>用于:</b><ul>
* <li>WebView <code><i>User-Agent</i></code></li>
* </ul>
* <b>来源:</b><code><b>ChromeVersionsConfig</b>, 由本例代为管理
* @see ChromeVersionsConfig
*/
private String chromeVersion = chromeVersionsConfig.getRandomChromeVersion();
/**
* <b>用于:</b><ul>
* <li>益盟通讯接口请求头 <code><i>X-Android-Agent</i> =
* EMAPP/{<b>emoneyVersion</b>}(Android;{androidSdkLevel})</code></li>
* </ul>
* 由程序版本决定<br>
* <b>来源:</b>本例管理
* @see EmoneyRequestConfig.androidSdkLevel
*/
private String emoneyVersion = "5.8.1";
/**
* <b>用于:</b><ul>
* <li>益盟通讯接口请求头 <code><i>Emapp-ViewMode</i> = <b>emappViewMode</b></code></li>
* </ul>
* 由程序决定, 一般默认为 "1"<br>
* <b>来源:</b>本例管理
*/
private String emappViewMode = "1";
/**
* 从 deviceInfo 设置相关字段
* @param deviceInfo
* @return
*/
public RequestInfo setRelativeFieldsFromDeviceInfo(DeviceInfo deviceInfo) {
if (deviceInfo == null) {
throw new NullPointerException("deviceInfo is null");
}
deviceName = deviceInfo.getModel();
androidVersion = deviceInfo.getVersionRelease();
androidSdkLevel = String.valueOf(androidSdkLevelConfig.getSdkLevel(androidVersion));
softwareType = deviceInfo.getDeviceType();
fingerprint = deviceInfo.getFingerprint();
buildId = deviceInfo.getBuildId();
return this;
}
/**
* 设置密码:<ul>
* <li>null or empty保存空字符串</li>
* <li>尝试解密成功,说明是密文,直接保存</li>
* <li>尝试解密失败,说明是明文,加密保存</li>
* </ul>
* @param password
* @return
*/
public RequestInfo setPassword(String password) {
if (StringUtils.isEmpty(password)) {
this.password = "";
return this;
}
String tryDecryptPassword = EncryptUtils.decryptAesForEmoneyPassword(password);
if (tryDecryptPassword != null) {
this.password = password;
}
else {
this.password = EncryptUtils.encryptAesForEmoneyPassword(password);
}
return this;
}
/**
* 设置 fingerprint该设置可能会影响相关字段
* @param fingerprint
* @return
* @see RequestInfo#setRelativeFieldsFromDeviceInfo
*/
public RequestInfo setFingerprint(String fingerprint) {
if (ObjectUtils.allNotNull(deviceName, softwareType, fingerprint, androidSdkLevelConfig)) {
DeviceInfo deviceInfo = DeviceInfo.from(deviceName, fingerprint, softwareType);
setRelativeFieldsFromDeviceInfo(deviceInfo);
}
else {
this.fingerprint = fingerprint;
}
return this;
}
/**
* 根据当前配置获取 guid用于益盟登录接口
* @return
*/
@JsonIgnore
public String getGuid() {
return EncryptUtils.toMD5String(androidId);
}
/**
* 一般 Protobuf 请求 X-Android-Agent 头,由 emoneyVersion 和 androidSdkLevel 组成
* @return
*/
@JsonIgnore
public String getXAndroidAgent() {
// EMAPP/{emoneyVersion}(Android;{androidSdkLevel})
return
new StringBuilder()
.append("EMAPP/")
.append(getEmoneyVersion())
.append("(Android;")
.append(getAndroidSdkLevel())
.append(")").toString();
}
/**
* 用于 App 内用到 Webview 的地方
* @return
*/
@JsonIgnore
public String getWebviewUserAgent() {
return new StringBuilder()
.append("Mozilla/5.0 (Linux; Android ")
.append(getAndroidVersion())
.append("; ")
.append(getDeviceName())
.append(" Build/")
.append(getBuildId())
.append("; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/")
.append(getChromeVersion())
.append(" Mobile Safari/537.36")
.toString();
}
/**
* 用于 App 内少量未用到 Webview 的地方,如首页获取图片等
* @return
*/
@JsonIgnore
public String getNonWebviewResourceUserAgent() {
// Dalvik/2.1.0 (Linux; U; Android {安卓版本};{Build.DEVICE} Build/{Build.ID})
return new StringBuilder()
.append("Dalvik/2.1.0 (Linux; U; Android ")
.append(getAndroidVersion())
.append(";")
.append(getDeviceName())
.append(" Build/")
.append(getBuildId())
.append(")")
.toString();
}
/**
* 根据当前配置获取 hardware用于益盟登录接口
* @return
*/
@JsonIgnore
public String getHardware() {
return EncryptUtils.toMD5String(getFingerprint());
}
/**
* 根据本例信息(包括保存的用户名和密码)生成一个用于登录的 ObjectNode
* @return
*/
@JsonIgnore
public ObjectNode getUsernamePasswordLoginObject() {
return getUsernamePasswordLoginObject(username, password);
}
/**
* 根据指定用户名、密码和本例信息生成一个用于登录的 ObjectNode
* @param username 用户名
* @param password 密码(可以是加密过的,也可以是明文)
* @return
*/
public ObjectNode getUsernamePasswordLoginObject(String username, String password) {
if (StringUtils.isAnyBlank(username, password)) {
throw new RuntimeException("Try to generate a emoney login object but username and/or password is blank");
}
ObjectNode node = getAnonymousLoginObject();
node.put("accId", username);
node.put("accType", 1);
// 尝试解密 password 看是否成功,如果成功说明原本就已经是加密了的
String tryDecryptPassword = EncryptUtils.decryptAesForEmoneyPassword(password);
node.put("pwd",
tryDecryptPassword != null ? password :
EncryptUtils.encryptAesForEmoneyPassword(password)
);
return node;
}
/**
* 根据本例信息生成一个用于匿名登录的 ObjectNode
* @return
*/
@JsonIgnore
public ObjectNode getAnonymousLoginObject() {
ObjectMapper mapper = new ObjectMapper();
ObjectNode node = mapper.createObjectNode();
ObjectNode exIdentify = mapper.createObjectNode();
exIdentify.put("IMEI", "");
exIdentify.put("AndroidID", getAndroidId());
exIdentify.put("MAC", "");
exIdentify.put("OSFingerPrint", getFingerprint());
String exIdentifyString = exIdentify.toString().replace("/", "\\/");
// 这玩意最好按照顺序来,当前这个顺序是 5.8.1 的顺序
String guid = getGuid();
node.put("appVersion", getEmoneyVersion());
node.put("productId", 4);
node.put("softwareType", getSoftwareType());
node.put("deviceName", getDeviceName());
node.put("ssid", "0");
node.put("platform", "android");
node.put("exIdentify", exIdentifyString);
node.put("osVersion", getAndroidSdkLevel());
node.put("accId", guid);
node.put("guid", guid);
node.put("accType", 4);
node.put("pwd", "");
node.put("channelId", "1711");
node.put("hardware", getHardware());
return node;
}
/**
* 根据本例信息获取 Relogin ObjectNode
* @return 如果 authorization 和 uid 任意 null 则本例返回 null
*/
@JsonIgnore
public ObjectNode getReloginObject() {
if (ObjectUtils.anyNull(getAuthorization(), getUid())) {
return null;
}
ObjectMapper mapper = new ObjectMapper();
ObjectNode node = mapper.createObjectNode();
ObjectNode exIdentify = mapper.createObjectNode();
exIdentify.put("IMEI", "");
exIdentify.put("AndroidID", getAndroidId());
exIdentify.put("MAC", "");
exIdentify.put("OSFingerPrint", getFingerprint());
String exIdentifyString = exIdentify.toString().replace("/", "\\/");
// 这玩意最好按照顺序来,当前这个顺序是 5.8.1 的顺序
String guid = getGuid();
node.put("appVersion", getEmoneyVersion());
node.put("productId", 4);
node.put("softwareType", getSoftwareType());
node.put("deviceName", getDeviceName());
node.put("ssid", "0");
node.put("platform", "android");
node.put("token", getAuthorization()); // 和登录不同的地方: token
node.put("exIdentify", exIdentifyString);
node.put("uid", getUid()); // 和登录不同的地方: uid
node.put("osVersion", getAndroidSdkLevel());
node.put("guid", guid);
node.put("channelId", "1711");
node.put("hardware", getHardware());
return node;
}
}