删除 EmoneyRequestConfig 和 ProxyConfig 设置,改为数据库(SQLite)配置。默认配置的设置和删除逻辑由
SQLite 触发器配置。
This commit is contained in:
@@ -19,6 +19,7 @@ public class AndroidSdkLevelConfig implements IConfig<AndroidSdkLevelConfig> {
|
||||
|
||||
public AndroidSdkLevelConfig() {
|
||||
androidVerToSdk = new HashMap<>();
|
||||
androidVerToSdk.put("15", 35);
|
||||
androidVerToSdk.put("14", 34);
|
||||
androidVerToSdk.put("13", 33);
|
||||
androidVerToSdk.put("12L", 32);
|
||||
|
||||
@@ -49,6 +49,11 @@ public class DeviceInfoConfig implements IConfig<DeviceInfoConfig> {
|
||||
@Slf4j
|
||||
public static class DeviceInfo {
|
||||
|
||||
// 持久化在本地的 DeviceInfo 只有三个字段:
|
||||
// model、deviceType 和 fingerprint
|
||||
// 其中除 model 和 deviceType 外,其他字段全部从 fingerprint 派生
|
||||
// 也就是说只要提供 model、deviceType 和 fingerprint 就能创建一个 DeviceInfo 实例
|
||||
|
||||
@JsonView(IConfig.Views.Persistence.class)
|
||||
private String model;
|
||||
private String brand;
|
||||
@@ -62,11 +67,12 @@ public class DeviceInfoConfig implements IConfig<DeviceInfoConfig> {
|
||||
private String buildType;
|
||||
private String buildTags;
|
||||
|
||||
/**
|
||||
* 用以匹配 fingerprint 的正则表达式
|
||||
*/
|
||||
public static final Pattern PATTERN = Pattern.compile("^(?<brand>.*?)/(?<product>.*?)/(?<device>.*?):(?<versionRelease>.*?)/(?<buildId>.*?)/(?<buildNumber>.*?):(?<buildType>.*?)/(?<buildTags>.*?)$");
|
||||
|
||||
|
||||
private DeviceInfo() {
|
||||
}
|
||||
private DeviceInfo() {}
|
||||
|
||||
public DeviceInfo setFingerprint(String fingerprint) {
|
||||
Matcher m = PATTERN.matcher(fingerprint);
|
||||
@@ -126,8 +132,8 @@ public class DeviceInfoConfig implements IConfig<DeviceInfoConfig> {
|
||||
}
|
||||
|
||||
public final String toString() {
|
||||
return String.format("Model: %s, Fingerprint: %s",
|
||||
getModel(), getFingerprint()
|
||||
return String.format("Model: %s, DeviceType: %s, Fingerprint: %s",
|
||||
getModel(), getDeviceType(), getFingerprint()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -139,7 +145,7 @@ public class DeviceInfoConfig implements IConfig<DeviceInfoConfig> {
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return Objects.hash(getModel(), getFingerprint());
|
||||
return Objects.hash(getModel(), getDeviceType(), getFingerprint());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,539 +0,0 @@
|
||||
package quant.rich.emoney.entity.config;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
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.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import quant.rich.emoney.entity.config.DeviceInfoConfig.DeviceInfo;
|
||||
import quant.rich.emoney.interfaces.ConfigInfo;
|
||||
import quant.rich.emoney.interfaces.IConfig;
|
||||
import quant.rich.emoney.patch.okhttp.PatchOkHttp;
|
||||
import quant.rich.emoney.patch.okhttp.PatchOkHttpRule;
|
||||
import quant.rich.emoney.util.EncryptUtils;
|
||||
import quant.rich.emoney.util.SpringContextHolder;
|
||||
import quant.rich.emoney.util.TextUtils;
|
||||
import quant.rich.emoney.validator.EmoneyRequestConfigValid;
|
||||
|
||||
/**
|
||||
* 用于配置请求时的请求行为,一般而言,请求头与安卓系统的信息有关(build.prop)
|
||||
* 虽然部分请求对应服务器可能不进行审核,但合理的请求头能尽可能模仿真机行为,避免风险
|
||||
* @see DeviceInfoConfig
|
||||
* @see AndroidSdkLevelConfig
|
||||
* @see ChromeVersionsConfig
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Slf4j
|
||||
@EmoneyRequestConfigValid
|
||||
@ConfigInfo(field = "emoneyRequest", name = "益盟请求设置", initDefault = true)
|
||||
public class EmoneyRequestConfig implements IConfig<EmoneyRequestConfig> {
|
||||
|
||||
/**
|
||||
* 是否匿名登录
|
||||
*/
|
||||
private Boolean isAnonymous = true;
|
||||
|
||||
/**
|
||||
* 非匿名登录时的用户名
|
||||
*/
|
||||
private String username = "";
|
||||
|
||||
/**
|
||||
* 非匿名登录时的密码
|
||||
*/
|
||||
private String password = "";
|
||||
|
||||
/**
|
||||
* 鉴权信息
|
||||
*/
|
||||
private String authorization;
|
||||
|
||||
/**
|
||||
* UID
|
||||
*/
|
||||
private Integer uid;
|
||||
|
||||
/**
|
||||
* <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)
|
||||
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)
|
||||
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
|
||||
*
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* <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";
|
||||
|
||||
/**
|
||||
* OkHttp 用于注入 User-Agent 规则的 id
|
||||
*/
|
||||
@JsonIgnore
|
||||
private Integer userAgentPatchRuleId;
|
||||
|
||||
@Getter(AccessLevel.PRIVATE)
|
||||
@Autowired
|
||||
private AndroidSdkLevelConfig androidSdkLevelConfig;
|
||||
|
||||
@Getter(AccessLevel.PRIVATE)
|
||||
@Autowired
|
||||
private DeviceInfoConfig deviceInfoConfig;
|
||||
|
||||
@Getter(AccessLevel.PRIVATE)
|
||||
@Autowired
|
||||
private ChromeVersionsConfig chromeVersionsConfig;
|
||||
|
||||
public void afterBeanInit() {
|
||||
|
||||
try {
|
||||
androidSdkLevelConfig = Objects.requireNonNullElseGet(androidSdkLevelConfig, () -> SpringContextHolder.getBean(AndroidSdkLevelConfig.class));
|
||||
deviceInfoConfig = Objects.requireNonNullElseGet(deviceInfoConfig, () -> SpringContextHolder.getBean(DeviceInfoConfig.class));
|
||||
chromeVersionsConfig = Objects.requireNonNullElseGet(chromeVersionsConfig, () -> SpringContextHolder.getBean(ChromeVersionsConfig.class));
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
log.debug("试图从 SpringContextHolder 初始化 androidSdkLevelConfig, deviceInfoConfig 和 chromeVersionConfig, 但 SpringContextHolder 未准备好");
|
||||
}
|
||||
|
||||
if (ObjectUtils.anyNull(fingerprint, buildId, deviceName, androidVersion, androidSdkLevel, softwareType)) {
|
||||
// 任意是 null 的都要统一由 deviceInfo 进行设置
|
||||
initFromRandomDeviceInfo();
|
||||
}
|
||||
else {
|
||||
// 都不是 null,则由 fingerprint 来检查各项。
|
||||
// model 和 softwareType 本应交由 deviceInfoConfig 检查以
|
||||
// 应对可能的通过修改本地 json 来进行攻击的方式,可是本身
|
||||
// deviceInfoConfig 对 model 和 softwareType 的信息也来源
|
||||
// 于本地,万一本地的 deviceInfo(.fallback).json 也不值得信任?
|
||||
// 所以只检查 fingerprint
|
||||
|
||||
DeviceInfo deviceInfo;
|
||||
boolean valid = true;
|
||||
try {
|
||||
deviceInfo = DeviceInfo.from(null, fingerprint);
|
||||
Validate.validState(androidVersion.equals(
|
||||
deviceInfo.getVersionRelease()),
|
||||
"androidVersion(versionRelease) 与预设 fingerprint 不匹配");
|
||||
Validate.validState(androidSdkLevel.equals(
|
||||
String.valueOf(androidSdkLevelConfig.getSdkLevel(deviceInfo.getVersionRelease()))),
|
||||
"androidSdkLevel 与预设 fingerprint 不匹配");
|
||||
Validate.validState(buildId.equals(deviceInfo.getBuildId()),
|
||||
"buildId 与预设 fingerprint 不匹配");
|
||||
}
|
||||
catch (Exception e) {
|
||||
valid = false;
|
||||
}
|
||||
if (!valid) {
|
||||
initFromRandomDeviceInfo();
|
||||
}
|
||||
}
|
||||
|
||||
if (chromeVersion == null) {
|
||||
chromeVersion = chromeVersionsConfig.getRandomChromeVersion();
|
||||
}
|
||||
|
||||
// 注入 OkHttp
|
||||
patchOkHttp();
|
||||
}
|
||||
|
||||
/**
|
||||
* 注入 User-Agent patch 规则
|
||||
*/
|
||||
private EmoneyRequestConfig patchOkHttp() {
|
||||
userAgentPatchRuleId = PatchOkHttp.apply(
|
||||
PatchOkHttpRule.when()
|
||||
.hostEndsWith("emoney.cn")
|
||||
.not(r -> r.hostMatches("appstatic"))
|
||||
.or(a -> a.hostContains("emapp"))
|
||||
.or(b -> b.hasHeaderName("X-Protocol-Id"))
|
||||
.overrideIf("User-Agent", getOkHttpUserAgent()).build()
|
||||
.setId(userAgentPatchRuleId));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从随机 deviceInfo 填充本例相关字段
|
||||
* @return
|
||||
*/
|
||||
private EmoneyRequestConfig initFromRandomDeviceInfo() {
|
||||
DeviceInfo deviceInfo = deviceInfoConfig.getRandomDeviceInfo();
|
||||
// 更新 deviceInfo 后对应 androidId 也要修改,哪怕原来非空
|
||||
androidId = TextUtils.randomString("abcdef0123456789", 16);
|
||||
return initFromDeviceInfo(deviceInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从指定 deviceInfo 填充本例相关字段
|
||||
* @param deviceInfo
|
||||
* @return
|
||||
*/
|
||||
private EmoneyRequestConfig initFromDeviceInfo(DeviceInfo deviceInfo) {
|
||||
if (deviceInfo == null) {
|
||||
log.error("deviceInfo is null");
|
||||
RuntimeException e = new RuntimeException("deviceInfo is null");
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
deviceName = deviceInfo.getModel();
|
||||
androidVersion = deviceInfo.getVersionRelease();
|
||||
androidSdkLevel = String.valueOf(androidSdkLevelConfig.getSdkLevel(androidVersion));
|
||||
softwareType = deviceInfo.getDeviceType();
|
||||
fingerprint = deviceInfo.getFingerprint();
|
||||
buildId = deviceInfo.getBuildId();
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmoneyRequestConfig() {}
|
||||
|
||||
public EmoneyRequestConfig setFingerprint(String fingerprint) {
|
||||
// 进入前即便 androidSdkLevelConfig 为 null 也要尝试获取一下
|
||||
// 因为为 null 时不一定是程序初始化时,也有可能是从前端 Post 而来的
|
||||
try {
|
||||
androidSdkLevelConfig = Objects.requireNonNullElseGet(androidSdkLevelConfig, () -> SpringContextHolder.getBean(AndroidSdkLevelConfig.class));
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
log.debug("SpringContext not ready");
|
||||
}
|
||||
|
||||
if (ObjectUtils.allNotNull(deviceName, softwareType, fingerprint, androidSdkLevelConfig)) {
|
||||
DeviceInfo deviceInfo = DeviceInfo.from(deviceName, fingerprint, softwareType);
|
||||
initFromDeviceInfo(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 (getUid() == null || StringUtils.isBlank(getAuthorization())) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置密码:<ul>
|
||||
* <li>null or empty,保存空字符串</li>
|
||||
* <li>尝试解密成功,说明是密文,直接保存</li>
|
||||
* <li>尝试解密失败,说明是明文,加密保存</li>
|
||||
* </ul>
|
||||
* @param password
|
||||
* @return
|
||||
*/
|
||||
public EmoneyRequestConfig 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保 androidVersion/androidSdkLevel 不为 null
|
||||
*/
|
||||
public EmoneyRequestConfig beforeSaving() {
|
||||
setFingerprint(this.fingerprint);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmoneyRequestConfig afterSaving() {
|
||||
patchOkHttp();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package quant.rich.emoney.entity.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import quant.rich.emoney.enums.StockSpan;
|
||||
|
||||
@Data
|
||||
@Accessors(chain=true)
|
||||
public class IndexInfo {
|
||||
|
||||
private List<ParamInfo> paramInfoList = new ArrayList<>();
|
||||
|
||||
private String code;
|
||||
|
||||
private String name;
|
||||
|
||||
private Boolean isCalc;
|
||||
|
||||
private List<StockSpan> supportPeriod = new ArrayList<>();
|
||||
|
||||
@Data
|
||||
@Accessors(chain=true)
|
||||
public static class ParamInfo {
|
||||
|
||||
private String name;
|
||||
|
||||
private Integer max;
|
||||
|
||||
private Integer min;
|
||||
|
||||
private Integer defaultValue;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,25 +1,21 @@
|
||||
package quant.rich.emoney.entity.config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.experimental.Accessors;
|
||||
import okhttp3.ConnectionPool;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import quant.rich.emoney.annotation.LockByCaller;
|
||||
import quant.rich.emoney.client.OkHttpClientProvider;
|
||||
import quant.rich.emoney.component.CallerLockAspect.LockByCaller;
|
||||
import quant.rich.emoney.interfaces.ConfigInfo;
|
||||
import quant.rich.emoney.interfaces.IConfig;
|
||||
import quant.rich.emoney.service.sqlite.RequestInfoService;
|
||||
import quant.rich.emoney.util.SpringContextHolder;
|
||||
|
||||
/**
|
||||
* 指标信息配置,只做运行时管理,不做保存
|
||||
@@ -36,11 +32,6 @@ public class IndexInfoConfig implements IConfig<IndexInfoConfig> {
|
||||
@JsonView(IConfig.Views.Persistence.class)
|
||||
private JsonNode configIndOnline;
|
||||
|
||||
@Autowired
|
||||
@JsonIgnore
|
||||
@Getter(AccessLevel.PRIVATE)
|
||||
private EmoneyRequestConfig emoneyRequestConfig;
|
||||
|
||||
public IndexInfoConfig() {}
|
||||
|
||||
public String getConfigIndOnlineStr() {
|
||||
@@ -49,10 +40,13 @@ public class IndexInfoConfig implements IConfig<IndexInfoConfig> {
|
||||
|
||||
@LockByCaller
|
||||
@JsonIgnore
|
||||
public String getOnlineConfigByUrl() throws IOException {
|
||||
public String getOnlineConfigByUrl(String url) throws IOException {
|
||||
synchronized (this) {
|
||||
if (SpringContextHolder.getBean(RequestInfoService.class).getDefaultRequestInfo() == null) {
|
||||
throw new RuntimeException("请先新增请求配置并作为默认配置");
|
||||
}
|
||||
Request request = new Request.Builder()
|
||||
.url(configIndOnlineUrl)
|
||||
.url(url)
|
||||
.header("Cache-Control", "no-cache")
|
||||
.get()
|
||||
.build();
|
||||
@@ -68,7 +62,5 @@ public class IndexInfoConfig implements IConfig<IndexInfoConfig> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ConnectionPool connectionPool = new ConnectionPool(10, 5, TimeUnit.MINUTES);
|
||||
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ public class PlatformConfig implements IConfig<PlatformConfig> {
|
||||
private String password;
|
||||
|
||||
private String email;
|
||||
|
||||
private String apiToken;
|
||||
|
||||
private Boolean isInited;
|
||||
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
package quant.rich.emoney.entity.config;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import quant.rich.emoney.interceptor.EnumOptionsInterceptor.EnumOptions;
|
||||
import quant.rich.emoney.interfaces.ConfigInfo;
|
||||
import quant.rich.emoney.interfaces.IConfig;
|
||||
import quant.rich.emoney.pojo.dto.IpInfo;
|
||||
import quant.rich.emoney.util.GeoIPUtil;
|
||||
import quant.rich.emoney.validator.ProxyConfigValid;
|
||||
|
||||
/**
|
||||
* 独立出来一个代理设置的原因是后续可能需要做一个代理池,这样的话独立配置比较适合后续扩展
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Slf4j
|
||||
@ProxyConfigValid
|
||||
@ConfigInfo(field = "proxy", name = "代理设置", initDefault = true)
|
||||
public class ProxyConfig implements IConfig<ProxyConfig> {
|
||||
|
||||
/**
|
||||
* 代理类型
|
||||
*/
|
||||
@EnumOptions("ProxyTypeEnum")
|
||||
@JsonView(IConfig.Views.Persistence.class)
|
||||
private Proxy.Type proxyType = Proxy.Type.DIRECT;
|
||||
|
||||
/**
|
||||
* 代理主机
|
||||
*/
|
||||
@JsonView(IConfig.Views.Persistence.class)
|
||||
private String proxyHost = "";
|
||||
|
||||
/**
|
||||
* 代理端口
|
||||
*/
|
||||
@JsonView(IConfig.Views.Persistence.class)
|
||||
private Integer proxyPort = 1;
|
||||
|
||||
/**
|
||||
* 是否忽略 HTTPS 证书校验
|
||||
*/
|
||||
@JsonView(IConfig.Views.Persistence.class)
|
||||
private Boolean ignoreHttpsVerification = false;
|
||||
|
||||
/**
|
||||
* 通过代理后的 IP,不做存储,只做呈现
|
||||
*/
|
||||
private IpInfo ipInfo;
|
||||
|
||||
public void afterBeanInit() {
|
||||
//refreshIpThroughProxy();
|
||||
}
|
||||
|
||||
public synchronized IpInfo refreshIpThroughProxy() {
|
||||
ipInfo = GeoIPUtil.getIpInfoThroughProxy(this);
|
||||
return ipInfo;
|
||||
}
|
||||
|
||||
|
||||
public ProxyConfig() {}
|
||||
|
||||
/**
|
||||
* 根据配置获取代理
|
||||
* @return
|
||||
*/
|
||||
public Proxy getProxy() {
|
||||
if (getProxyType() != null && getProxyType() != Proxy.Type.DIRECT) {
|
||||
return new Proxy(getProxyType(),
|
||||
new InetSocketAddress(getProxyHost(), getProxyPort()));
|
||||
}
|
||||
return Proxy.NO_PROXY;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package quant.rich.emoney.entity.postgre;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 益盟个股策略信息,包含策略类型和日期
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain=true)
|
||||
public class StockStrategy {
|
||||
|
||||
private String tsCode;
|
||||
|
||||
public StockStrategy setTsCodeFromGoodsId(Integer goodsId) {
|
||||
// 自动将益盟 goodsId 转换成 tsCode
|
||||
// 1301325 -> 301325.SZ
|
||||
// 600325 -> 600325.SH
|
||||
// 1920009 -> 920009.BJ
|
||||
String goodsIdStr = goodsId.toString();
|
||||
RuntimeException e = new RuntimeException("无法将 goodsId " + goodsIdStr + " 转换为 tsCode");
|
||||
if (goodsIdStr.length() == 6) {
|
||||
// SH
|
||||
return setTsCode(goodsIdStr + ".SH");
|
||||
}
|
||||
else if (goodsIdStr.length() == 7) {
|
||||
if (goodsIdStr.charAt(0) != '1') {
|
||||
throw e;
|
||||
}
|
||||
if (goodsIdStr.charAt(1) == '9') {
|
||||
// BJ
|
||||
return setTsCode(goodsIdStr.substring(1) + ".BJ");
|
||||
}
|
||||
// SZ
|
||||
return setTsCode(goodsIdStr.substring(1) + ".SZ");
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -26,6 +26,8 @@ public class ProxySetting {
|
||||
@TableId(value="id", type=IdType.AUTO)
|
||||
private Integer id;
|
||||
|
||||
private Boolean isDefault;
|
||||
|
||||
@Nonnull
|
||||
private String proxyName;
|
||||
|
||||
|
||||
@@ -7,12 +7,14 @@ 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.baomidou.mybatisplus.extension.activerecord.Model;
|
||||
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.EqualsAndHashCode;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -20,21 +22,36 @@ 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;
|
||||
import quant.rich.emoney.validator.RequestInfoValid;
|
||||
|
||||
/**
|
||||
* 用于配置请求时的请求行为,一般而言,请求头与安卓系统的信息有关(build.prop)
|
||||
* 虽然部分请求对应服务器可能不进行审核,但合理的请求头能尽可能模仿真机行为,避免风险
|
||||
* @see DeviceInfoConfig
|
||||
* @see AndroidSdkLevelConfig
|
||||
* @see ChromeVersionsConfig
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper=false)
|
||||
@Accessors(chain = true)
|
||||
@Slf4j
|
||||
@RequestInfoValid
|
||||
@TableName(value = "request_info")
|
||||
public class RequestInfo {
|
||||
public class RequestInfo extends Model<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);
|
||||
private static final long serialVersionUID = -3113053377999289627L;
|
||||
|
||||
private volatile static AndroidSdkLevelConfig androidSdkLevelConfig = SpringContextHolder.getBean(AndroidSdkLevelConfig.class);
|
||||
private volatile static DeviceInfoConfig deviceInfoConfig = SpringContextHolder.getBean(DeviceInfoConfig.class);
|
||||
private volatile static ChromeVersionsConfig chromeVersionsConfig = SpringContextHolder.getBean(ChromeVersionsConfig.class);
|
||||
|
||||
/**
|
||||
* 使用随机设备信息(DeviceInfo)初始化对象<p>
|
||||
* @see DeviceInfo
|
||||
*/
|
||||
public RequestInfo() {
|
||||
DeviceInfo deviceInfo = deviceInfoConfig.getRandomDeviceInfo();
|
||||
setRelativeFieldsFromDeviceInfo(deviceInfo);
|
||||
@@ -43,15 +60,15 @@ public class RequestInfo {
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* 该请求是否设为默认请求
|
||||
*/
|
||||
private Boolean isDefault = false;
|
||||
|
||||
/**
|
||||
* 该请求信息配置的名称,助记用
|
||||
*/
|
||||
private String name = "";
|
||||
|
||||
/**
|
||||
* 是否匿名登录
|
||||
*/
|
||||
private Boolean isAnonymous = true;
|
||||
|
||||
/**
|
||||
* 非匿名登录时的用户名
|
||||
@@ -88,8 +105,9 @@ public class RequestInfo {
|
||||
* <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>
|
||||
* <b>来源:</b><code><b>DeviceInfo</b></code>
|
||||
* @see DeviceInfoConfig
|
||||
* @see DeviceInfo
|
||||
*/
|
||||
@Setter(AccessLevel.PRIVATE)
|
||||
@TableField(exist=false)
|
||||
@@ -100,8 +118,9 @@ public class RequestInfo {
|
||||
* <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>
|
||||
* <b>来源:</b><code><b>DeviceInfo</b>, 经由 <code><b>AndroidSdkLevelConfig</b></code> 转换,由本例代管</code>
|
||||
* @see DeviceInfoConfig
|
||||
* @see DeviceInfo
|
||||
* @see AndroidSdkLevelConfig
|
||||
*/
|
||||
@Setter(AccessLevel.PRIVATE)
|
||||
@@ -112,8 +131,9 @@ public class RequestInfo {
|
||||
* <b>用于:</b><ul>
|
||||
* <li>益盟登录接口 <code><i>softwareType</i> = <b>softwareType</b></code></li>
|
||||
* </ul>
|
||||
* <b>来源:</b><code><b>DeviceInfoConfig</b>,由本例代管</code>
|
||||
* <b>来源:</b><code><b>DeviceInfo</b>
|
||||
* @see DeviceInfoConfig
|
||||
* @see DeviceInfo
|
||||
*/
|
||||
private String softwareType;
|
||||
|
||||
@@ -131,8 +151,9 @@ public class RequestInfo {
|
||||
* <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>, 由本例代为管理
|
||||
* <b>来源:</b><code><b>DeviceInfo</b>
|
||||
* @see DeviceInfoConfig
|
||||
* @see DeviceInfo
|
||||
*/
|
||||
private String deviceName;
|
||||
|
||||
@@ -177,7 +198,7 @@ public class RequestInfo {
|
||||
* </ul>
|
||||
* 由程序版本决定<br>
|
||||
* <b>来源:</b>本例管理
|
||||
* @see EmoneyRequestConfig.androidSdkLevel
|
||||
* @see #androidSdkLevel
|
||||
*/
|
||||
private String emoneyVersion = "5.8.1";
|
||||
|
||||
@@ -429,5 +450,9 @@ public class RequestInfo {
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
public boolean isAnonymous() {
|
||||
return !StringUtils.isAnyBlank(getUsername(), getPassword());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ public class StrategyAndPool implements Comparable<StrategyAndPool> {
|
||||
private String strategyName;
|
||||
private Integer strategyId;
|
||||
private String poolName;
|
||||
private String type;
|
||||
@TableId
|
||||
private Integer poolId;
|
||||
|
||||
@@ -28,7 +29,8 @@ public class StrategyAndPool implements Comparable<StrategyAndPool> {
|
||||
|
||||
}
|
||||
|
||||
public StrategyAndPool(String strategyName, Integer strategyId, String poolName, Integer poolId) {
|
||||
public StrategyAndPool(String type, String strategyName, Integer strategyId, String poolName, Integer poolId) {
|
||||
this.type = type;
|
||||
this.strategyName = strategyName;
|
||||
this.strategyId = strategyId;
|
||||
this.poolName = poolName;
|
||||
@@ -38,18 +40,20 @@ public class StrategyAndPool implements Comparable<StrategyAndPool> {
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null) return false;
|
||||
if (!(o instanceof StrategyAndPool)) return false;
|
||||
StrategyAndPool strategyAndPool = (StrategyAndPool) o;
|
||||
return
|
||||
strategyName == strategyAndPool.strategyName &&
|
||||
strategyId == strategyAndPool.strategyId &&
|
||||
poolName == strategyAndPool.poolName &&
|
||||
poolId == strategyAndPool.poolId;
|
||||
Objects.equals(strategyName, strategyAndPool.strategyName) &&
|
||||
Objects.equals(strategyId, strategyAndPool.strategyId) &&
|
||||
Objects.equals(poolName, strategyAndPool.poolName) &&
|
||||
Objects.equals(poolId, strategyAndPool.poolId) &&
|
||||
Objects.equals(type, strategyAndPool.type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(strategyName, strategyId, poolName, poolId);
|
||||
return Objects.hash(strategyName, strategyId, poolName, poolId, type);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user