新增(移动)一些应该放在“管理”形成列表管理,而非放在“设置”形成单一配置的内容
This commit is contained in:
@@ -13,7 +13,8 @@ import org.springframework.scheduling.annotation.EnableScheduling;
|
|||||||
public class EmoneyAutoApplication {
|
public class EmoneyAutoApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(EmoneyAutoApplication.class, args);
|
SpringApplication app = new SpringApplication(EmoneyAutoApplication.class);
|
||||||
|
app.run(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ public class SecurityConfig {
|
|||||||
.headers(headers -> headers.cacheControl(cache -> cache.disable()))
|
.headers(headers -> headers.cacheControl(cache -> cache.disable()))
|
||||||
.csrf(csrf -> csrf.disable())
|
.csrf(csrf -> csrf.disable())
|
||||||
.authorizeHttpRequests(auth -> auth
|
.authorizeHttpRequests(auth -> auth
|
||||||
|
.requestMatchers("/favicon.ico").permitAll()
|
||||||
.requestMatchers("/admin/*/login").permitAll()
|
.requestMatchers("/admin/*/login").permitAll()
|
||||||
.requestMatchers("/admin/*/static/**").permitAll()
|
.requestMatchers("/admin/*/static/**").permitAll()
|
||||||
.requestMatchers("/public/**").permitAll()
|
.requestMatchers("/public/**").permitAll()
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
package quant.rich.emoney.config;
|
package quant.rich.emoney.config;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
import org.apache.ibatis.session.SqlSessionFactory;
|
import org.apache.ibatis.session.SqlSessionFactory;
|
||||||
@@ -8,6 +14,7 @@ import org.mybatis.spring.annotation.MapperScan;
|
|||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.DbType;
|
import com.baomidou.mybatisplus.annotation.DbType;
|
||||||
@@ -15,16 +22,87 @@ import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
|||||||
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||||
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
|
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
|
||||||
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import quant.rich.emoney.EmoneyAutoApplication;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
@Configuration
|
@Configuration
|
||||||
@MapperScan(basePackages = "quant.rich.emoney.mapper.sqlite", sqlSessionTemplateRef = "sqliteSqlSessionTemplate")
|
@MapperScan(basePackages = "quant.rich.emoney.mapper.sqlite", sqlSessionTemplateRef = "sqliteSqlSessionTemplate")
|
||||||
public class SqliteMybatisConfig {
|
public class SqliteMybatisConfig {
|
||||||
|
|
||||||
|
private static final String RESOURCE_PATH = "database.db";
|
||||||
|
|
||||||
public static final String SQLITE_TRANSACTION_MANAGER = "sqliteTransactionManager";
|
public static final String SQLITE_TRANSACTION_MANAGER = "sqliteTransactionManager";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置数据库连接
|
||||||
|
* <ul>
|
||||||
|
* <li>如果非打包状态,则直接选取当前项目内数据库位置</li>
|
||||||
|
* <li>如果打包状态,以 JDBC 链接位置为主,如果位置不存在则覆盖</li>
|
||||||
|
* </ul>
|
||||||
|
* @param dataSource
|
||||||
|
*/
|
||||||
|
public void initSQLiteLocation(DataSource dataSource) {
|
||||||
|
// 指定 sqlite 路径
|
||||||
|
if (dataSource instanceof HikariDataSource hikariDataSource) {
|
||||||
|
|
||||||
|
String filePath = hikariDataSource.getJdbcUrl();
|
||||||
|
if (filePath == null || !filePath.startsWith("jdbc:sqlite:")) {
|
||||||
|
log.warn("无法在 SQLite HikariDataSource 中找到合法 SQLite JDBC url, 数据库可能会加载失败。获取到的 jdbc-url: {}", filePath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
filePath = filePath.substring("jdbc:sqlite:".length()).trim();
|
||||||
|
|
||||||
|
ClassPathResource original = new ClassPathResource(RESOURCE_PATH);
|
||||||
|
if (!original.exists()) {
|
||||||
|
log.warn("未找到 SQLite 资源: {}", RESOURCE_PATH);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String protocol = EmoneyAutoApplication.class.getProtectionDomain().getCodeSource().getLocation().getProtocol();
|
||||||
|
boolean isJar = "jar".equals(protocol);
|
||||||
|
|
||||||
|
if (isJar) {
|
||||||
|
// 复制到外部 yml 指定路径,已存在则不复制
|
||||||
|
File dest = new File(filePath), parentDir = dest.getParentFile();
|
||||||
|
String destAbsolutePath = dest.getAbsolutePath();
|
||||||
|
if (!parentDir.exists() && !parentDir.mkdirs()) {
|
||||||
|
log.warn("无法创建放置 SQLite 文件的目录: {}", parentDir.getAbsolutePath());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dest.exists()) {
|
||||||
|
// 已存在
|
||||||
|
log.warn("目标资源 {} 已存在,忽略", destAbsolutePath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (InputStream in = getClass().getClassLoader().getResourceAsStream(RESOURCE_PATH)) {
|
||||||
|
if (in == null) {
|
||||||
|
log.warn("无法读取 SQLite 资源: {}", RESOURCE_PATH);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Files.copy(in, dest.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
log.info("SQLite 数据库文件已复制至:{}", destAbsolutePath);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("复制 SQLite 数据库文件失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 使用当前绝对路径
|
||||||
|
Path path = Path.of("src/main/resources", RESOURCE_PATH);
|
||||||
|
hikariDataSource.setJdbcUrl("jdbc:sqlite:" + path.toAbsolutePath().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Bean("sqliteSqlSessionFactory")
|
@Bean("sqliteSqlSessionFactory")
|
||||||
public SqlSessionFactory sqliteSqlSessionFactory(
|
public SqlSessionFactory sqliteSqlSessionFactory(
|
||||||
@Qualifier("sqliteDataSource") DataSource dataSource) throws Exception {
|
@Qualifier("sqliteDataSource") DataSource dataSource) throws Exception {
|
||||||
|
|
||||||
|
initSQLiteLocation(dataSource);
|
||||||
|
|
||||||
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
|
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
|
||||||
factory.setDataSource(dataSource);
|
factory.setDataSource(dataSource);
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ public class IndexControllerV1 extends BaseController {
|
|||||||
String newPassword,
|
String newPassword,
|
||||||
String email) {
|
String email) {
|
||||||
|
|
||||||
if (passwordIsNotEmpty(newPassword)) {
|
if (EncryptUtils.passwordIsNotEmpty(newPassword)) {
|
||||||
if (!platformConfig.getPassword().equals(password)) {
|
if (!platformConfig.getPassword().equals(password)) {
|
||||||
throw RException.badRequest("密码错误");
|
throw RException.badRequest("密码错误");
|
||||||
}
|
}
|
||||||
@@ -75,10 +75,4 @@ public class IndexControllerV1 extends BaseController {
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static final String EMPTY_PASSWORD = EncryptUtils.sha3("", 224);
|
|
||||||
|
|
||||||
static boolean passwordIsNotEmpty(String password) {
|
|
||||||
return StringUtils.isNotEmpty(password) && !password.equalsIgnoreCase(EMPTY_PASSWORD);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public class LoginControllerV1 extends BaseController {
|
|||||||
if (Objects.isNull(sessionCaptcha) || !captcha.equalsIgnoreCase(sessionCaptcha.toString())) {
|
if (Objects.isNull(sessionCaptcha) || !captcha.equalsIgnoreCase(sessionCaptcha.toString())) {
|
||||||
throw new LoginException("验证码错误");
|
throw new LoginException("验证码错误");
|
||||||
}
|
}
|
||||||
if (StringUtils.isAnyBlank(username) || !passwordIsNotEmpty(password)) {
|
if (StringUtils.isAnyBlank(username) || !EncryptUtils.passwordIsNotEmpty(password)) {
|
||||||
throw new LoginException("用户名和密码不能为空");
|
throw new LoginException("用户名和密码不能为空");
|
||||||
}
|
}
|
||||||
if (!username.equals(platformConfig.getUsername())
|
if (!username.equals(platformConfig.getUsername())
|
||||||
@@ -81,7 +81,7 @@ public class LoginControllerV1 extends BaseController {
|
|||||||
}
|
}
|
||||||
// 初始化流程
|
// 初始化流程
|
||||||
|
|
||||||
if (StringUtils.isAnyBlank(username) || !passwordIsNotEmpty(password)) {
|
if (StringUtils.isAnyBlank(username) || !EncryptUtils.passwordIsNotEmpty(password)) {
|
||||||
throw new LoginException("用户名和密码不能为空");
|
throw new LoginException("用户名和密码不能为空");
|
||||||
}
|
}
|
||||||
platformConfig.setUsername(username).setPassword(password).setIsInited(true);
|
platformConfig.setUsername(username).setPassword(password).setIsInited(true);
|
||||||
@@ -99,10 +99,4 @@ public class LoginControllerV1 extends BaseController {
|
|||||||
return "redirect:/admin/v1/login";
|
return "redirect:/admin/v1/login";
|
||||||
}
|
}
|
||||||
|
|
||||||
static final String EMPTY_PASSWORD = EncryptUtils.sha3("", 224);
|
|
||||||
|
|
||||||
static boolean passwordIsNotEmpty(String password) {
|
|
||||||
return StringUtils.isNotEmpty(password) && !password.equalsIgnoreCase(EMPTY_PASSWORD);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package quant.rich.emoney.controller.api;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.google.protobuf.nano.MessageNano;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.reflections.Reflections;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import nano.BaseResponse.Base_Response;
|
||||||
|
import quant.rich.emoney.entity.sqlite.ProtocolMatch;
|
||||||
|
import quant.rich.emoney.exception.RException;
|
||||||
|
import quant.rich.emoney.interfaces.IQueryableEnum;
|
||||||
|
import quant.rich.emoney.pojo.dto.EmoneyConvertResult;
|
||||||
|
import quant.rich.emoney.pojo.dto.EmoneyProtobufBody;
|
||||||
|
import quant.rich.emoney.service.sqlite.ProtocolMatchService;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/common")
|
||||||
|
@Slf4j
|
||||||
|
public class CommonAbilityControllerV1 {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
Reflections reflections;
|
||||||
|
|
||||||
|
@GetMapping("/getIQueryableEnum")
|
||||||
|
public Map<String, String> getIQueryableEnum(String enumName) {
|
||||||
|
Set<Class<? extends IQueryableEnum>> enums = reflections.getSubTypesOf(IQueryableEnum.class);
|
||||||
|
Map<String, String> map = new HashMap<>();
|
||||||
|
for (Class<? extends IQueryableEnum> clazz : enums) {
|
||||||
|
if (clazz.getSimpleName().equals(enumName) && clazz.isEnum()) {
|
||||||
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
|
Class<? extends Enum> enumClass = (Class<? extends Enum<?>>) clazz;
|
||||||
|
for (Enum<?> e : enumClass.getEnumConstants()) {
|
||||||
|
if (e instanceof IQueryableEnum iqe) {
|
||||||
|
map.put(e.name(), iqe.getNote());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,9 +3,12 @@ package quant.rich.emoney.controller.common;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import jakarta.servlet.http.HttpSession;
|
import jakarta.servlet.http.HttpSession;
|
||||||
|
import quant.rich.emoney.pojo.dto.R;
|
||||||
import quant.rich.emoney.service.AuthService;
|
import quant.rich.emoney.service.AuthService;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
@@ -26,5 +29,4 @@ public abstract class BaseController {
|
|||||||
protected Boolean isLogin() {
|
protected Boolean isLogin() {
|
||||||
return authService.isLogin();
|
return authService.isLogin();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package quant.rich.emoney.controller.common;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import quant.rich.emoney.exception.RException;
|
||||||
|
import quant.rich.emoney.pojo.dto.R;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public abstract class UpdateBoolController<T, M extends BaseMapper<T>, S extends ServiceImpl<M, T>> extends BaseController {
|
||||||
|
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|
||||||
|
protected
|
||||||
|
R<?> updateBool(S service, Class<T> clazz, SFunction<T, ?> idName, Object idValue, String field, Boolean value) {
|
||||||
|
TableInfo tableInfo = TableInfoHelper.getTableInfo(clazz);
|
||||||
|
Object converted = mapper.convertValue(idValue, tableInfo.getKeyType());
|
||||||
|
try {
|
||||||
|
Field declaredField = clazz.getDeclaredField(field);
|
||||||
|
Optional<TableFieldInfo> fieldInfo = tableInfo.getFieldList().stream()
|
||||||
|
.filter(f -> f.getProperty().equals(field))
|
||||||
|
.findFirst();
|
||||||
|
if (declaredField.getType().equals(Boolean.class)) {
|
||||||
|
return R.judge(service.update(
|
||||||
|
new UpdateWrapper<T>()
|
||||||
|
.set(fieldInfo.get().getColumn(), value)
|
||||||
|
.lambda().eq(idName, converted)
|
||||||
|
), "更新失败,请查看日志");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("update bool failed", e);
|
||||||
|
}
|
||||||
|
throw RException.badRequest().setLogRequest(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/updateBool")
|
||||||
|
@ResponseBody
|
||||||
|
abstract protected R<?> updateBool(String id, String field, Boolean value);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
package quant.rich.emoney.controller.manage;
|
package quant.rich.emoney.controller.manage;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.io.Serializable;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
@@ -16,17 +14,15 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
|
||||||
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
|
|
||||||
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
|
||||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
|
||||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import quant.rich.emoney.controller.common.BaseController;
|
import quant.rich.emoney.controller.common.UpdateBoolController;
|
||||||
import quant.rich.emoney.entity.sqlite.Plan;
|
import quant.rich.emoney.entity.sqlite.Plan;
|
||||||
import quant.rich.emoney.exception.RException;
|
import quant.rich.emoney.exception.RException;
|
||||||
|
import quant.rich.emoney.interfaces.IQueryableEnum;
|
||||||
|
import quant.rich.emoney.mapper.sqlite.PlanMapper;
|
||||||
import quant.rich.emoney.pojo.dto.LayPageReq;
|
import quant.rich.emoney.pojo.dto.LayPageReq;
|
||||||
import quant.rich.emoney.pojo.dto.LayPageResp;
|
import quant.rich.emoney.pojo.dto.LayPageResp;
|
||||||
import quant.rich.emoney.pojo.dto.R;
|
import quant.rich.emoney.pojo.dto.R;
|
||||||
@@ -35,7 +31,7 @@ import quant.rich.emoney.service.sqlite.PlanService;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
@Controller
|
@Controller
|
||||||
@RequestMapping("/admin/v1/manage/plan")
|
@RequestMapping("/admin/v1/manage/plan")
|
||||||
public class PlanControllerV1 extends BaseController {
|
public class PlanControllerV1 extends UpdateBoolController<Plan, PlanMapper, PlanService> {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
PlanService planService;
|
PlanService planService;
|
||||||
@@ -79,24 +75,8 @@ public class PlanControllerV1 extends BaseController {
|
|||||||
|
|
||||||
@PostMapping("/updateBool")
|
@PostMapping("/updateBool")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public R<?> updateBool(String planId, String field, Boolean value) {
|
public R<?> updateBool(String id, String field, Boolean value) {
|
||||||
TableInfo tableInfo = TableInfoHelper.getTableInfo(Plan.class);
|
return updateBool(planService, Plan.class, Plan::getPlanId, id, field, value);
|
||||||
try {
|
|
||||||
Field declaredField = Plan.class.getDeclaredField(field);
|
|
||||||
|
|
||||||
Optional<TableFieldInfo> fieldInfo = tableInfo.getFieldList().stream()
|
|
||||||
.filter(f -> f.getProperty().equals(field))
|
|
||||||
.findFirst();
|
|
||||||
if (declaredField.getType().equals(Boolean.class)) {
|
|
||||||
planService.update(
|
|
||||||
new UpdateWrapper<Plan>()
|
|
||||||
.eq("plan_id", planId)
|
|
||||||
.set(fieldInfo.get().getColumn(), value));
|
|
||||||
return R.ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e) {}
|
|
||||||
throw RException.badRequest();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/save")
|
@PostMapping("/save")
|
||||||
@@ -121,14 +101,17 @@ public class PlanControllerV1 extends BaseController {
|
|||||||
@ResponseBody
|
@ResponseBody
|
||||||
public R<?> batchOp(
|
public R<?> batchOp(
|
||||||
@RequestParam(value="ids[]", required=true)
|
@RequestParam(value="ids[]", required=true)
|
||||||
String[] ids, String op) {
|
String[] ids, PlanBatchOp op) {
|
||||||
if (Objects.isNull(ids) || ids.length == 0) {
|
if (Objects.isNull(ids) || ids.length == 0) {
|
||||||
throw RException.badRequest("提供的计划 ID 不能为空");
|
throw RException.badRequest("提供的计划 ID 不能为空");
|
||||||
}
|
}
|
||||||
List<String> idArray = Arrays.asList(ids);
|
List<String> idArray = Arrays.asList(ids);
|
||||||
|
|
||||||
if (StringUtils.isBlank(op)) {
|
if (op == null) {
|
||||||
// op 为空是删除
|
// op 为空是删除
|
||||||
|
throw RException.badRequest("操作类型不能为空");
|
||||||
|
}
|
||||||
|
else if (PlanBatchOp.DELETE == op) {
|
||||||
return R.judge(
|
return R.judge(
|
||||||
planService.removeBatchByIds(idArray));
|
planService.removeBatchByIds(idArray));
|
||||||
}
|
}
|
||||||
@@ -136,23 +119,42 @@ public class PlanControllerV1 extends BaseController {
|
|||||||
LambdaUpdateWrapper<Plan> uw = new LambdaUpdateWrapper<>();
|
LambdaUpdateWrapper<Plan> uw = new LambdaUpdateWrapper<>();
|
||||||
uw.in(Plan::getPlanId, idArray);
|
uw.in(Plan::getPlanId, idArray);
|
||||||
|
|
||||||
if ("enable".equals(op)) {
|
switch (op) {
|
||||||
uw.set(Plan::getEnabled, true);
|
case ENABLE:
|
||||||
}
|
uw.set(Plan::getEnabled, true);
|
||||||
else if ("disable".equals(op)) {
|
break;
|
||||||
|
case DISABLE:
|
||||||
uw.set(Plan::getEnabled, false);
|
uw.set(Plan::getEnabled, false);
|
||||||
}
|
break;
|
||||||
else if ("enableOpenDayCheck".equals(op)) {
|
case ENABLE_OPEN_DAY_CHECK:
|
||||||
uw.set(Plan::getOpenDayCheck, true);
|
uw.set(Plan::getOpenDayCheck, true);
|
||||||
}
|
break;
|
||||||
else if ("disableOpenDayCheck".equals(op)) {
|
case DISABLE_OPEN_DAY_CHECK:
|
||||||
uw.set(Plan::getOpenDayCheck, false);
|
uw.set(Plan::getOpenDayCheck, false);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw RException.badRequest("未知操作");
|
||||||
}
|
}
|
||||||
else {
|
return R.judge(() -> planService.update(uw));
|
||||||
throw RException.badRequest("未识别的操作");
|
|
||||||
}
|
|
||||||
|
|
||||||
return R.judge(planService.update(uw));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static enum PlanBatchOp implements IQueryableEnum {
|
||||||
|
DELETE("删除"),
|
||||||
|
ENABLE("启用"),
|
||||||
|
DISABLE("停用"),
|
||||||
|
ENABLE_OPEN_DAY_CHECK("开启交易日校验"),
|
||||||
|
DISABLE_OPEN_DAY_CHECK("关闭交易日校验");
|
||||||
|
|
||||||
|
private String note;
|
||||||
|
|
||||||
|
private PlanBatchOp(String note) {
|
||||||
|
this.note = note;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNote() {
|
||||||
|
return note;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
package quant.rich.emoney.controller.manage;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import quant.rich.emoney.controller.common.BaseController;
|
||||||
|
import quant.rich.emoney.entity.sqlite.ProxySetting;
|
||||||
|
import quant.rich.emoney.exception.RException;
|
||||||
|
import quant.rich.emoney.pojo.dto.LayPageReq;
|
||||||
|
import quant.rich.emoney.pojo.dto.LayPageResp;
|
||||||
|
import quant.rich.emoney.pojo.dto.R;
|
||||||
|
import quant.rich.emoney.service.sqlite.ProxySettingService;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Controller
|
||||||
|
@RequestMapping("/admin/v1/manage/proxySetting")
|
||||||
|
public class ProxySettingControllerV1 extends BaseController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ProxySettingService proxySettingService;
|
||||||
|
|
||||||
|
@GetMapping({"", "/", "/index"})
|
||||||
|
public String index() {
|
||||||
|
return "/admin/v1/manage/proxySetting/index";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/list")
|
||||||
|
@ResponseBody
|
||||||
|
public LayPageResp<?> list(LayPageReq<ProxySetting> pageReq) {
|
||||||
|
Page<ProxySetting> planPage = proxySettingService.page(pageReq);
|
||||||
|
return new LayPageResp<>(planPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/getOne")
|
||||||
|
@ResponseBody
|
||||||
|
public R<?> getOne(String id) {
|
||||||
|
|
||||||
|
// 如果 planId 是空,说明可能希望新建一个 ProxySetting,需要返回默认实例化对象
|
||||||
|
if (id == null) {
|
||||||
|
return R.ok(new ProxySetting());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 否则从数据库取
|
||||||
|
ProxySetting proxy = proxySettingService.getById(id);
|
||||||
|
return R.judge(proxy != null, proxy, "无法找到对应 ID 的 ProxySetting");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/updateBool")
|
||||||
|
@ResponseBody
|
||||||
|
public R<?> updateBool(String id, String field, Boolean value) {
|
||||||
|
TableInfo tableInfo = TableInfoHelper.getTableInfo(ProxySetting.class);
|
||||||
|
try {
|
||||||
|
Field declaredField = ProxySetting.class.getDeclaredField(field);
|
||||||
|
|
||||||
|
Optional<TableFieldInfo> fieldInfo = tableInfo.getFieldList().stream()
|
||||||
|
.filter(f -> f.getProperty().equals(field))
|
||||||
|
.findFirst();
|
||||||
|
if (declaredField.getType().equals(Boolean.class)) {
|
||||||
|
proxySettingService.update(
|
||||||
|
new UpdateWrapper<ProxySetting>()
|
||||||
|
.eq("id", id)
|
||||||
|
.set(fieldInfo.get().getColumn(), value));
|
||||||
|
return R.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {}
|
||||||
|
throw RException.badRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/save")
|
||||||
|
@ResponseBody
|
||||||
|
public R<?> save(@RequestBody ProxySetting proxySetting) {
|
||||||
|
if (!Objects.isNull(proxySetting.getId())) {
|
||||||
|
proxySettingService.updateById(proxySetting);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
proxySettingService.save(proxySetting.setId(null));
|
||||||
|
}
|
||||||
|
return R.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/delete")
|
||||||
|
@ResponseBody
|
||||||
|
public R<?> delete(String id) {
|
||||||
|
return R.judge(proxySettingService.removeById(id), "删除失败,是否已删除?");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/batchOp")
|
||||||
|
@ResponseBody
|
||||||
|
public R<?> batchOp(
|
||||||
|
@RequestParam(value="ids[]", required=true)
|
||||||
|
String[] ids, ProxySettingBatchOp op) {
|
||||||
|
if (Objects.isNull(ids) || ids.length == 0) {
|
||||||
|
throw RException.badRequest("提供的计划 ID 不能为空");
|
||||||
|
}
|
||||||
|
List<String> idArray = Arrays.asList(ids);
|
||||||
|
|
||||||
|
if (op == null) {
|
||||||
|
// op 为空是删除
|
||||||
|
throw RException.badRequest("操作类型不能为空");
|
||||||
|
}
|
||||||
|
else if (ProxySettingBatchOp.DELETE == op) {
|
||||||
|
return R.judge(
|
||||||
|
proxySettingService.removeBatchByIds(idArray));
|
||||||
|
}
|
||||||
|
|
||||||
|
LambdaUpdateWrapper<ProxySetting> uw = new LambdaUpdateWrapper<>();
|
||||||
|
uw.in(ProxySetting::getId, idArray);
|
||||||
|
|
||||||
|
switch (op) {
|
||||||
|
case CHECK:
|
||||||
|
// TODO: 检查连通性
|
||||||
|
break;
|
||||||
|
case DISABLE_HTTPS_VERIFY:
|
||||||
|
uw.set(ProxySetting::getIgnoreHttpsVerification, false);
|
||||||
|
break;
|
||||||
|
case ENABLE_HTTP_VERIFY:
|
||||||
|
uw.set(ProxySetting::getIgnoreHttpsVerification, true);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw RException.badRequest("未知操作");
|
||||||
|
}
|
||||||
|
return R.judge(() -> proxySettingService.update(uw));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static enum ProxySettingBatchOp {
|
||||||
|
DELETE,
|
||||||
|
CHECK,
|
||||||
|
DISABLE_HTTPS_VERIFY,
|
||||||
|
ENABLE_HTTP_VERIFY
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package quant.rich.emoney.controller.manage;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import quant.rich.emoney.controller.common.BaseController;
|
||||||
|
import quant.rich.emoney.controller.common.UpdateBoolController;
|
||||||
|
import quant.rich.emoney.entity.sqlite.RequestInfo;
|
||||||
|
import quant.rich.emoney.exception.RException;
|
||||||
|
import quant.rich.emoney.mapper.sqlite.RequestInfoMapper;
|
||||||
|
import quant.rich.emoney.pojo.dto.LayPageReq;
|
||||||
|
import quant.rich.emoney.pojo.dto.LayPageResp;
|
||||||
|
import quant.rich.emoney.pojo.dto.R;
|
||||||
|
import quant.rich.emoney.service.sqlite.RequestInfoService;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Controller
|
||||||
|
@RequestMapping("/admin/v1/manage/requestInfo")
|
||||||
|
public class RequestInfoControllerV1 extends UpdateBoolController<RequestInfo, RequestInfoMapper, RequestInfoService> {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
RequestInfoService requestInfoService;
|
||||||
|
|
||||||
|
@GetMapping({"", "/", "/index"})
|
||||||
|
public String index() {
|
||||||
|
return "/admin/v1/manage/requestInfo/index";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/list")
|
||||||
|
@ResponseBody
|
||||||
|
public LayPageResp<?> list(LayPageReq<RequestInfo> pageReq) {
|
||||||
|
Page<RequestInfo> planPage = requestInfoService.page(pageReq);
|
||||||
|
return new LayPageResp<>(planPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/getOne")
|
||||||
|
@ResponseBody
|
||||||
|
public R<?> getOne(Integer id) {
|
||||||
|
|
||||||
|
// 如果 planId 是空,说明可能希望新建一个 Plan,需要返回默认实例化对象
|
||||||
|
if (id == null) {
|
||||||
|
return R.ok(new RequestInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 否则从数据库取
|
||||||
|
RequestInfo plan = requestInfoService.getById(id);
|
||||||
|
return R.judge(plan != null, plan, "无法找到对应 ID 的 Plan");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/updateBool")
|
||||||
|
@ResponseBody
|
||||||
|
@Override
|
||||||
|
protected R<?> updateBool(String id, String field, Boolean value) {
|
||||||
|
return updateBool(requestInfoService, RequestInfo.class, RequestInfo::getId, id, field, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/save")
|
||||||
|
@ResponseBody
|
||||||
|
public R<?> save(@RequestBody @NonNull RequestInfo plan) {
|
||||||
|
return R.judge(() -> requestInfoService.saveOrUpdate(plan));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/delete")
|
||||||
|
@ResponseBody
|
||||||
|
public R<?> delete(String id) {
|
||||||
|
return R.judge(requestInfoService.removeById(id), "删除失败,是否已删除?");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/batchOp")
|
||||||
|
@ResponseBody
|
||||||
|
public R<?> batchOp(
|
||||||
|
@RequestParam(value="ids[]", required=true)
|
||||||
|
String[] ids, String op) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
433
src/main/java/quant/rich/emoney/entity/sqlite/RequestInfo.java
Normal file
433
src/main/java/quant/rich/emoney/entity/sqlite/RequestInfo.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -97,7 +97,15 @@ public class EnumOptionsInterceptor implements HandlerInterceptor {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将其注解到类型为 enum 的字段上,可以将 enum 注入到 Model 中,供 thymeleaf 使用
|
||||||
|
* <p>例:某 entity 的某字段为:</p>
|
||||||
|
* <code><b>private</b> Proxy.Type proxyType;</code>
|
||||||
|
* <p>则在其上注解 @EnumOptions("ProxyTypeEnum"), 即可在 thymeleaf 中直接使用:</p>
|
||||||
|
* ProxyTypeEnum.DIRECT
|
||||||
|
* <p>如果只是注解 @EnumOptions,未指定 value,则在 thymeleaf 为字段名 + Options,上例中便为:</p>
|
||||||
|
* ProxyTypeOptions.DIRECT
|
||||||
|
*/
|
||||||
@Target(ElementType.FIELD)
|
@Target(ElementType.FIELD)
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Documented
|
@Documented
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ public @interface ConfigInfo {
|
|||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* 为 true 时,最终会调用其无参构造方法,若需要填充默认值的,请在无参构造器中填充。
|
* 为 true 时,最终会调用其无参构造方法,若需要填充默认值的,请在无参构造器中填充。
|
||||||
* 当为 true 且无法载入配置文件而触发初始化时,若存在 ./conf/system/{field}.fallback.json 文件时,从 fallback
|
* 当为 true 且无法载入配置文件而触发初始化时,若存在 /conf/system/{field}.fallback.json 文件时,从 fallback
|
||||||
* 文件中初始化。fallback 仅参与初始化,不参与持久化
|
* 文件中初始化。fallback 仅参与初始化,不参与持久化
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package quant.rich.emoney.interfaces;
|
||||||
|
|
||||||
|
public interface IQueryableEnum {
|
||||||
|
|
||||||
|
public default String getName() {
|
||||||
|
return name();
|
||||||
|
}
|
||||||
|
String name();
|
||||||
|
public String getNote();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package quant.rich.emoney.mapper.sqlite;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import com.baomidou.dynamic.datasource.annotation.DS;
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
|
||||||
|
import quant.rich.emoney.entity.sqlite.ProxySetting;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Mapper
|
||||||
|
@DS("sqlite")
|
||||||
|
public interface ProxySettingMapper extends BaseMapper<ProxySetting> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package quant.rich.emoney.mapper.sqlite;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import com.baomidou.dynamic.datasource.annotation.DS;
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
|
||||||
|
import quant.rich.emoney.entity.sqlite.RequestInfo;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Mapper
|
||||||
|
@DS("sqlite")
|
||||||
|
public interface RequestInfoMapper extends BaseMapper<RequestInfo> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -7,6 +7,18 @@ import lombok.experimental.Accessors;
|
|||||||
|
|
||||||
public interface IndexDetail {
|
public interface IndexDetail {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置原始文本内容
|
||||||
|
* @param original
|
||||||
|
*/
|
||||||
|
public void setOriginal(String original);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取原始 json 文本内容
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getOriginal();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 indexName
|
* 获取 indexName
|
||||||
* @return
|
* @return
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import lombok.experimental.Accessors;
|
|||||||
import quant.rich.emoney.util.HtmlSanitizer;
|
import quant.rich.emoney.util.HtmlSanitizer;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@Accessors(chain=true)
|
|
||||||
public class NonParamsIndexDetail implements IndexDetail {
|
public class NonParamsIndexDetail implements IndexDetail {
|
||||||
|
|
||||||
@JsonView(IndexDetail.class)
|
@JsonView(IndexDetail.class)
|
||||||
@@ -24,6 +23,8 @@ public class NonParamsIndexDetail implements IndexDetail {
|
|||||||
private String nameCode;
|
private String nameCode;
|
||||||
@JsonView(IndexDetail.class)
|
@JsonView(IndexDetail.class)
|
||||||
private List<NonParamsIndexDetailData> data = new ArrayList<>();
|
private List<NonParamsIndexDetailData> data = new ArrayList<>();
|
||||||
|
@JsonView(IndexDetail.class)
|
||||||
|
private String original;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@Accessors(chain=true)
|
@Accessors(chain=true)
|
||||||
|
|||||||
@@ -6,11 +6,9 @@ import java.util.List;
|
|||||||
import com.fasterxml.jackson.annotation.JsonView;
|
import com.fasterxml.jackson.annotation.JsonView;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.experimental.Accessors;
|
|
||||||
import quant.rich.emoney.util.HtmlSanitizer;
|
import quant.rich.emoney.util.HtmlSanitizer;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@Accessors(chain=true)
|
|
||||||
public class ParamsIndexDetail implements IndexDetail {
|
public class ParamsIndexDetail implements IndexDetail {
|
||||||
|
|
||||||
@JsonView(IndexDetail.class)
|
@JsonView(IndexDetail.class)
|
||||||
@@ -21,15 +19,19 @@ public class ParamsIndexDetail implements IndexDetail {
|
|||||||
private String code;
|
private String code;
|
||||||
@JsonView(IndexDetail.class)
|
@JsonView(IndexDetail.class)
|
||||||
private List<String> descriptions = new ArrayList<>();
|
private List<String> descriptions = new ArrayList<>();
|
||||||
|
@JsonView(IndexDetail.class)
|
||||||
|
private String original;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getIndexName() {
|
public String getIndexName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getIndexCode() {
|
public String getIndexCode() {
|
||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Detail> getDetails() {
|
public List<Detail> getDetails() {
|
||||||
List<Detail> list = new ArrayList<>();
|
List<Detail> list = new ArrayList<>();
|
||||||
@@ -38,6 +40,7 @@ public class ParamsIndexDetail implements IndexDetail {
|
|||||||
});
|
});
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sanitize() {
|
public void sanitize() {
|
||||||
List<String> descriptions = new ArrayList<>();
|
List<String> descriptions = new ArrayList<>();
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package quant.rich.emoney.service;
|
package quant.rich.emoney.service;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import quant.rich.emoney.EmoneyAutoApplication;
|
||||||
import quant.rich.emoney.entity.config.SmartViewWriter;
|
import quant.rich.emoney.entity.config.SmartViewWriter;
|
||||||
import quant.rich.emoney.interfaces.ConfigInfo;
|
import quant.rich.emoney.interfaces.ConfigInfo;
|
||||||
import quant.rich.emoney.interfaces.IConfig;
|
import quant.rich.emoney.interfaces.IConfig;
|
||||||
|
import quant.rich.emoney.util.SmartResourceResolver;
|
||||||
import quant.rich.emoney.util.SpringContextHolder;
|
import quant.rich.emoney.util.SpringContextHolder;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
@@ -11,10 +13,13 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
import com.google.common.collect.BiMap;
|
import com.google.common.collect.BiMap;
|
||||||
import com.google.common.collect.HashBiMap;
|
import com.google.common.collect.HashBiMap;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.util.IOUtils;
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@@ -30,6 +35,7 @@ import org.reflections.scanners.Scanners;
|
|||||||
import org.reflections.util.ConfigurationBuilder;
|
import org.reflections.util.ConfigurationBuilder;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.jmx.export.annotation.ManagedAttribute;
|
import org.springframework.jmx.export.annotation.ManagedAttribute;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -47,6 +53,7 @@ public class ConfigService implements InitializingBean {
|
|||||||
@Autowired
|
@Autowired
|
||||||
Reflections reflections;
|
Reflections reflections;
|
||||||
|
|
||||||
|
static final boolean isJar = "jar".equals(EmoneyAutoApplication.class.getProtectionDomain().getCodeSource().getLocation().getProtocol());
|
||||||
static final ObjectMapper mapper = new ObjectMapper();
|
static final ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@@ -242,11 +249,12 @@ public class ConfigService implements InitializingBean {
|
|||||||
if (info.save()) {
|
if (info.save()) {
|
||||||
try {
|
try {
|
||||||
String filePath = getConfigFilePath(field, false);
|
String filePath = getConfigFilePath(field, false);
|
||||||
Path dirPath = Paths.get(filePath).getParent();
|
SmartResourceResolver.saveText(filePath, configJoString);
|
||||||
if (Files.notExists(dirPath)) {
|
//Path dirPath = Paths.get(filePath).getParent();
|
||||||
Files.createDirectories(dirPath);
|
//if (Files.notExists(dirPath)) {
|
||||||
}
|
// Files.createDirectories(dirPath);
|
||||||
Files.writeString(Path.of(filePath), configJoString);
|
//}
|
||||||
|
//Files.writeString(Path.of(filePath), configJoString);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error("Cannot write config to local file {}.json, error: {}", field, e.getMessage());
|
log.error("Cannot write config to local file {}.json, error: {}", field, e.getMessage());
|
||||||
return false;
|
return false;
|
||||||
@@ -258,12 +266,21 @@ public class ConfigService implements InitializingBean {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从指定路径获取配置文件并转换为实例对象
|
||||||
|
* @param <Config>
|
||||||
|
* @param path
|
||||||
|
* @param configClass
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
private <Config extends IConfig<Config>> Config getFromFile(String path, Class<Config> configClass) {
|
private <Config extends IConfig<Config>> Config getFromFile(String path, Class<Config> configClass) {
|
||||||
String configString;
|
String configString;
|
||||||
Config config = null;
|
Config config = null;
|
||||||
try {
|
|
||||||
configString = Files.readString(Path.of(path), Charset.defaultCharset());
|
try {
|
||||||
} catch (IOException e) {
|
// 此处只是读取文件,并不关心该文件是否可写
|
||||||
|
configString = IOUtils.toString(SmartResourceResolver.loadResource(path), Charset.defaultCharset());
|
||||||
|
} catch (UncheckedIOException e) {
|
||||||
String field = fieldClassCache.inverse().get(configClass);
|
String field = fieldClassCache.inverse().get(configClass);
|
||||||
log.warn("Cannot read config {}.json: {}", field, e.getMessage());
|
log.warn("Cannot read config {}.json: {}", field, e.getMessage());
|
||||||
return config;
|
return config;
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package quant.rich.emoney.service;
|
package quant.rich.emoney.service;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
@@ -30,6 +32,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.util.IOUtils;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import okhttp3.MediaType;
|
import okhttp3.MediaType;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
@@ -44,7 +47,7 @@ import quant.rich.emoney.pojo.dto.IndexDetail;
|
|||||||
import quant.rich.emoney.pojo.dto.NonParamsIndexDetail;
|
import quant.rich.emoney.pojo.dto.NonParamsIndexDetail;
|
||||||
import quant.rich.emoney.pojo.dto.NonParamsIndexDetail.NonParamsIndexDetailData;
|
import quant.rich.emoney.pojo.dto.NonParamsIndexDetail.NonParamsIndexDetailData;
|
||||||
import quant.rich.emoney.util.EncryptUtils;
|
import quant.rich.emoney.util.EncryptUtils;
|
||||||
import quant.rich.emoney.util.SpringContextHolder;
|
import quant.rich.emoney.util.SmartResourceResolver;
|
||||||
import quant.rich.emoney.pojo.dto.ParamsIndexDetail;
|
import quant.rich.emoney.pojo.dto.ParamsIndexDetail;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -76,13 +79,17 @@ public class IndexDetailService {
|
|||||||
*/
|
*/
|
||||||
@CacheEvict(cacheNames="@indexDetailService.getIndexDetail(Serializable)", key="#indexCode.toString()")
|
@CacheEvict(cacheNames="@indexDetailService.getIndexDetail(Serializable)", key="#indexCode.toString()")
|
||||||
public IndexDetail forceRefreshAndGetIndexDetail(Serializable indexCode) {
|
public IndexDetail forceRefreshAndGetIndexDetail(Serializable indexCode) {
|
||||||
Path path = getIndexDetailPath(indexCode);
|
|
||||||
try {
|
// 刷新的本质就是从网络获取,因为此处已经清理了缓存,所以直接从网络获取后再
|
||||||
Files.deleteIfExists(path);
|
// 走一次 getIndexDetail,获取到的就是从网络保存到了本地的,此时缓存也更新了
|
||||||
} catch (IOException e) {
|
|
||||||
String msg = MessageFormat.format("本地 IndexDetail 文件删除失败,path: {0}, msg: {1}", path.toString(), e.getLocalizedMessage());
|
if (!hasParams(indexCode)) {
|
||||||
throw new RuntimeException(msg, e);
|
getNonParamsIndexDetailOnline(indexCode);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
getParamsIndexDetailOnline(indexCode);
|
||||||
|
}
|
||||||
|
|
||||||
return getIndexDetail(indexCode);
|
return getIndexDetail(indexCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,11 +112,11 @@ public class IndexDetailService {
|
|||||||
|
|
||||||
private ParamsIndexDetail getParamsIndexDetail(Serializable indexCode) {
|
private ParamsIndexDetail getParamsIndexDetail(Serializable indexCode) {
|
||||||
// 先判断本地有没有
|
// 先判断本地有没有
|
||||||
Path localFilePath = getIndexDetailPath(indexCode);
|
InputStream stream = getIndexDetailStream(indexCode);
|
||||||
if (Files.exists(localFilePath)) {
|
if (stream != null) {
|
||||||
ParamsIndexDetail detail = null;
|
ParamsIndexDetail detail = null;
|
||||||
try {
|
try {
|
||||||
String str = Files.readString(localFilePath);
|
String str = IOUtils.toString(stream, StandardCharsets.UTF_8);
|
||||||
detail = mapper.readValue(str, ParamsIndexDetail.class);
|
detail = mapper.readValue(str, ParamsIndexDetail.class);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.warn("无法获取本地无参数指标说明,将尝试重新从网络获取 indexCode: {}", indexCode, e);
|
log.warn("无法获取本地无参数指标说明,将尝试重新从网络获取 indexCode: {}", indexCode, e);
|
||||||
@@ -122,6 +129,11 @@ public class IndexDetailService {
|
|||||||
return getParamsIndexDetailOnline(indexCode);
|
return getParamsIndexDetailOnline(indexCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从网络获取有参指标详情
|
||||||
|
* @param indexCode
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
private ParamsIndexDetail getParamsIndexDetailOnline(Serializable indexCode) {
|
private ParamsIndexDetail getParamsIndexDetailOnline(Serializable indexCode) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -147,13 +159,16 @@ public class IndexDetailService {
|
|||||||
if (code == 0) {
|
if (code == 0) {
|
||||||
ParamsIndexDetail detail = mapper.treeToValue(result.get("detail"), ParamsIndexDetail.class);
|
ParamsIndexDetail detail = mapper.treeToValue(result.get("detail"), ParamsIndexDetail.class);
|
||||||
if (detail == null) {
|
if (detail == null) {
|
||||||
// 网络访问成功但为 null, 新建一空 detail
|
/** 网络访问成功但为 null, 新建一空 detail **/
|
||||||
detail = new ParamsIndexDetail();
|
detail = new ParamsIndexDetail();
|
||||||
detail.setCode(indexCode.toString());
|
detail.setCode(indexCode.toString());
|
||||||
detail.getDescriptions().add("该指标说明接口返回为空");
|
detail.getDescriptions().add("该指标说明接口返回为空");
|
||||||
}
|
}
|
||||||
// 清洗内容:凡是文本类型的内容的,都要清洗一遍,判断是否有脚本、
|
else {
|
||||||
// 视频和图片等,有的要清除并记录,以免有泄露网址、客户端 IP 风险
|
detail.setOriginal(result.get("detail").toString());
|
||||||
|
}
|
||||||
|
/** 清洗内容:凡是文本类型的内容的,都要清洗一遍,判断是否有脚本、
|
||||||
|
视频和图片等,有的要清除并记录,以免有泄露网址、客户端 IP 风险*/
|
||||||
detail.sanitize();
|
detail.sanitize();
|
||||||
saveIndexDetail(detail);
|
saveIndexDetail(detail);
|
||||||
return detail;
|
return detail;
|
||||||
@@ -180,11 +195,11 @@ public class IndexDetailService {
|
|||||||
*/
|
*/
|
||||||
private NonParamsIndexDetail getNonParamsIndexDetail(Serializable indexCode) {
|
private NonParamsIndexDetail getNonParamsIndexDetail(Serializable indexCode) {
|
||||||
// 先判断本地有没有
|
// 先判断本地有没有
|
||||||
Path localFilePath = getIndexDetailPath(indexCode);
|
InputStream stream = getIndexDetailStream(indexCode);
|
||||||
if (Files.exists(localFilePath)) {
|
if (stream != null) {
|
||||||
NonParamsIndexDetail detail = null;
|
NonParamsIndexDetail detail = null;
|
||||||
try {
|
try {
|
||||||
String str = Files.readString(localFilePath);
|
String str = IOUtils.toString(stream, StandardCharsets.UTF_8);
|
||||||
detail = mapper.readValue(str, NonParamsIndexDetail.class);
|
detail = mapper.readValue(str, NonParamsIndexDetail.class);
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
@@ -270,6 +285,9 @@ public class IndexDetailService {
|
|||||||
.header("Referer", url)
|
.header("Referer", url)
|
||||||
.header("Accept-Language", "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7");
|
.header("Accept-Language", "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7");
|
||||||
List<NonParamsIndexDetail> valids = new ArrayList<>();
|
List<NonParamsIndexDetail> valids = new ArrayList<>();
|
||||||
|
|
||||||
|
// 循环获取脚本,一旦获取的脚本内正则匹配到包含无参
|
||||||
|
// 指标的文本,立即转换为 json、转换指标并结束循环
|
||||||
scriptLoop:
|
scriptLoop:
|
||||||
for (String scriptUrl : scripts) {
|
for (String scriptUrl : scripts) {
|
||||||
Request scriptRequest = scriptBuilder.url(scriptUrl).build();
|
Request scriptRequest = scriptBuilder.url(scriptUrl).build();
|
||||||
@@ -298,6 +316,7 @@ public class IndexDetailService {
|
|||||||
obj.has("nameCode") && obj.get("nameCode").isTextual() &&
|
obj.has("nameCode") && obj.get("nameCode").isTextual() &&
|
||||||
obj.has("data") && obj.get("data").isArray()) {
|
obj.has("data") && obj.get("data").isArray()) {
|
||||||
NonParamsIndexDetail detail = mapper.treeToValue(obj, NonParamsIndexDetail.class);
|
NonParamsIndexDetail detail = mapper.treeToValue(obj, NonParamsIndexDetail.class);
|
||||||
|
detail.setOriginal(obj.toString());
|
||||||
valids.add(detail);
|
valids.add(detail);
|
||||||
foundAny = true;
|
foundAny = true;
|
||||||
}
|
}
|
||||||
@@ -330,7 +349,7 @@ public class IndexDetailService {
|
|||||||
if (!numericPattern.matcher(detail.getNameCode()).matches()) {
|
if (!numericPattern.matcher(detail.getNameCode()).matches()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Path path = getIndexDetailPath(detail);
|
String path = getIndexDetailPath(detail);
|
||||||
// 判断是否是需求的 detail
|
// 判断是否是需求的 detail
|
||||||
if (indexCode.toString().equals(detail.getIndexCode())) {
|
if (indexCode.toString().equals(detail.getIndexCode())) {
|
||||||
loadImages(detail);
|
loadImages(detail);
|
||||||
@@ -340,10 +359,24 @@ public class IndexDetailService {
|
|||||||
// 视频和图片等,有的要清除并记录,以免有泄露网址、客户端 IP 风险
|
// 视频和图片等,有的要清除并记录,以免有泄露网址、客户端 IP 风险
|
||||||
detail.sanitize();
|
detail.sanitize();
|
||||||
|
|
||||||
if (!Files.exists(path)) {
|
InputStream inputStream = SmartResourceResolver.loadResource(path);
|
||||||
|
if (inputStream == null) {
|
||||||
// 不存在则保存
|
// 不存在则保存
|
||||||
saveIndexDetail(detail);
|
saveIndexDetail(detail);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// 判断 original 是否一致,不一致则更新
|
||||||
|
NonParamsIndexDetail existed;
|
||||||
|
try {
|
||||||
|
existed = mapper.readValue(inputStream, NonParamsIndexDetail.class);
|
||||||
|
if (!existed.getOriginal().equals(detail.getOriginal())) {
|
||||||
|
saveIndexDetail(detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
log.debug("读取本地存在的 NonParamsIndexDetail 文件成功,但转换失败。格式错误?路径:{}", path, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetDetail == null) {
|
if (targetDetail == null) {
|
||||||
@@ -354,9 +387,9 @@ public class IndexDetailService {
|
|||||||
List<String> items = List.of("该指标说明接口返回为空");
|
List<String> items = List.of("该指标说明接口返回为空");
|
||||||
data.setItems(items);
|
data.setItems(items);
|
||||||
targetDetail.getData().add(data);
|
targetDetail.getData().add(data);
|
||||||
Path path = getIndexDetailPath(targetDetail);
|
String path = getIndexDetailPath(targetDetail);
|
||||||
|
|
||||||
if (!Files.exists(path)) {
|
if (SmartResourceResolver.loadResource(path) == null) {
|
||||||
// 不存在则保存
|
// 不存在则保存
|
||||||
saveIndexDetail(targetDetail);
|
saveIndexDetail(targetDetail);
|
||||||
}
|
}
|
||||||
@@ -431,16 +464,16 @@ public class IndexDetailService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存指标详情到本地文件
|
* 保存指标详情到本地文件,无论其原本是否存在
|
||||||
* @param <Description>
|
* @param <Description>
|
||||||
* @param des
|
* @param des
|
||||||
*/
|
*/
|
||||||
private <Description extends IndexDetail> void saveIndexDetail(Description des) {
|
private <Description extends IndexDetail> void saveIndexDetail(Description des) {
|
||||||
SmartViewWriter writer = new SmartViewWriter();
|
SmartViewWriter writer = new SmartViewWriter();
|
||||||
String joString = writer.writeWithSmartView(des, IndexDetail.class);
|
String joString = writer.writeWithSmartView(des, IndexDetail.class);
|
||||||
Path path = getIndexDetailPath(des);
|
String path = getIndexDetailPath(des);
|
||||||
try {
|
try {
|
||||||
Files.writeString(path, joString);
|
SmartResourceResolver.saveText(path, joString);
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
log.error("写入指标详情到 {} 失败", path.toString(), e);
|
log.error("写入指标详情到 {} 失败", path.toString(), e);
|
||||||
@@ -453,12 +486,12 @@ public class IndexDetailService {
|
|||||||
* @param description
|
* @param description
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private <Description extends IndexDetail> Path getIndexDetailPath(Description description) {
|
private <Description extends IndexDetail> String getIndexDetailPath(Description description) {
|
||||||
Path path = Path.of(new StringBuilder(filePath)
|
Path path = Path.of(new StringBuilder(filePath)
|
||||||
.append((description instanceof NonParamsIndexDetail) ? "nonParams/": "params/")
|
.append((description instanceof NonParamsIndexDetail) ? "nonParams/": "params/")
|
||||||
.append(description.getIndexCode())
|
.append(description.getIndexCode())
|
||||||
.append(".json").toString());
|
.append(".json").toString());
|
||||||
return path;
|
return path.normalize().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -466,13 +499,14 @@ public class IndexDetailService {
|
|||||||
* @param indexCode
|
* @param indexCode
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private Path getIndexDetailPath(Serializable indexCode) {
|
private InputStream getIndexDetailStream(Serializable indexCode) {
|
||||||
boolean hasParams = hasParams(indexCode);
|
boolean hasParams = hasParams(indexCode);
|
||||||
Path path = Path.of(new StringBuilder(filePath)
|
String path = new StringBuilder(filePath)
|
||||||
.append(!hasParams ? "nonParams/": "params/")
|
.append(!hasParams ? "nonParams/": "params/")
|
||||||
.append(indexCode)
|
.append(indexCode)
|
||||||
.append(".json").toString());
|
.append(".json").toString();
|
||||||
return path;
|
InputStream inputStream = SmartResourceResolver.loadResource(path);
|
||||||
|
return inputStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package quant.rich.emoney.service.sqlite;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import com.baomidou.dynamic.datasource.annotation.DS;
|
||||||
|
import quant.rich.emoney.entity.sqlite.ProxySetting;
|
||||||
|
import quant.rich.emoney.mapper.sqlite.ProxySettingMapper;
|
||||||
|
|
||||||
|
@DS("sqlite")
|
||||||
|
@Service
|
||||||
|
public class ProxySettingService extends SqliteServiceImpl<ProxySettingMapper, ProxySetting> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package quant.rich.emoney.service.sqlite;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import com.baomidou.dynamic.datasource.annotation.DS;
|
||||||
|
|
||||||
|
import quant.rich.emoney.entity.config.DeviceInfoConfig.DeviceInfo;
|
||||||
|
import quant.rich.emoney.entity.sqlite.RequestInfo;
|
||||||
|
import quant.rich.emoney.mapper.sqlite.RequestInfoMapper;
|
||||||
|
|
||||||
|
@DS("sqlite")
|
||||||
|
@Service
|
||||||
|
public class RequestInfoService extends SqliteServiceImpl<RequestInfoMapper, RequestInfo> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import java.lang.StackWalker;
|
|||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
@@ -38,6 +39,44 @@ public class CallerLockUtil {
|
|||||||
}).get();
|
}).get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ✅ 方式三:尝试获取锁并运行,支持超时,失败后不阻塞
|
||||||
|
* @return true 表示成功执行,false 表示未获得锁
|
||||||
|
*/
|
||||||
|
public static boolean tryRunWithCallerLock(Runnable task, long timeoutMs, Object... extraKeys) {
|
||||||
|
ReentrantLock lock = acquireLock(extraKeys);
|
||||||
|
boolean locked = false;
|
||||||
|
try {
|
||||||
|
locked = lock.tryLock(timeoutMs, TimeUnit.MILLISECONDS);
|
||||||
|
if (locked) {
|
||||||
|
task.run();
|
||||||
|
}
|
||||||
|
return locked;
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
if (locked) lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ✅ 非阻塞获取锁,超时失败返回 null 或抛异常
|
||||||
|
*/
|
||||||
|
public static <T> Optional<T> tryCallWithCallerLock(Callable<T> task, long timeoutMs, Object... extraKeys) {
|
||||||
|
ReentrantLock lock = acquireLock(extraKeys);
|
||||||
|
boolean locked = false;
|
||||||
|
try {
|
||||||
|
locked = lock.tryLock(timeoutMs, TimeUnit.MILLISECONDS);
|
||||||
|
if (!locked) return Optional.empty();
|
||||||
|
return Optional.of(task.call());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} finally {
|
||||||
|
if (locked) lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造调用者方法 + 附加参数为 key
|
* 构造调用者方法 + 附加参数为 key
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -38,6 +38,16 @@ public class EncryptUtils {
|
|||||||
private static final String EM_SIGN_MESS_2 = "994fec3c512f2f7756fd5e4403147f01";
|
private static final String EM_SIGN_MESS_2 = "994fec3c512f2f7756fd5e4403147f01";
|
||||||
private static final String SLASH = "/";
|
private static final String SLASH = "/";
|
||||||
private static final String COLON = ":";
|
private static final String COLON = ":";
|
||||||
|
private static final String EMPTY_SHA3_224 = sha3("", 224);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断密码是否空字符串,或经过 SHA_224 加密过的空字符串
|
||||||
|
* @param password
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean passwordIsNotEmpty(String password) {
|
||||||
|
return StringUtils.isNotEmpty(password) && !password.equalsIgnoreCase(EMPTY_SHA3_224);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加密用于 Emoney 登录的密码
|
* 加密用于 Emoney 登录的密码
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ import quant.rich.emoney.client.OkHttpClientProvider;
|
|||||||
import quant.rich.emoney.entity.config.ProxyConfig;
|
import quant.rich.emoney.entity.config.ProxyConfig;
|
||||||
import quant.rich.emoney.pojo.dto.IpInfo;
|
import quant.rich.emoney.pojo.dto.IpInfo;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class GeoIPUtil {
|
public class GeoIPUtil {
|
||||||
@@ -28,16 +28,16 @@ public class GeoIPUtil {
|
|||||||
|
|
||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
cityReader = new DatabaseReader.Builder(new File("./conf/extra/GeoLite2-City.mmdb")).build();
|
ClassPathResource geoLite2CityResource = new ClassPathResource("/conf/extra/GeoLite2-City.mmdb");
|
||||||
|
cityReader = new DatabaseReader.Builder(geoLite2CityResource.getInputStream()).build();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException("IP 地址库初始化失败", e);
|
throw new RuntimeException("IP 地址库初始化失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Async
|
||||||
public static IpInfo getIpInfoThroughProxy(ProxyConfig proxyConfig) {
|
public static IpInfo getIpInfoThroughProxy(ProxyConfig proxyConfig) {
|
||||||
ReentrantLock lock = CallerLockUtil.acquireLock();
|
return CallerLockUtil.tryCallWithCallerLock(() -> {
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
Proxy proxy = proxyConfig.getProxy();
|
Proxy proxy = proxyConfig.getProxy();
|
||||||
boolean ignoreHttpsVerification = proxyConfig.getIgnoreHttpsVerification();
|
boolean ignoreHttpsVerification = proxyConfig.getIgnoreHttpsVerification();
|
||||||
// OkHttp 客户端配置
|
// OkHttp 客户端配置
|
||||||
@@ -82,10 +82,7 @@ public class GeoIPUtil {
|
|||||||
log.warn("Proxy ipv6 error {}", e.getMessage());
|
log.warn("Proxy ipv6 error {}", e.getMessage());
|
||||||
}
|
}
|
||||||
return queryIpInfoGeoLite(ipInfo);
|
return queryIpInfoGeoLite(ipInfo);
|
||||||
}
|
}, 100, proxyConfig).orElse(IpInfo.EMPTY);
|
||||||
finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
111
src/main/java/quant/rich/emoney/util/SmartResourceResolver.java
Normal file
111
src/main/java/quant/rich/emoney/util/SmartResourceResolver.java
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
package quant.rich.emoney.util;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.*;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class SmartResourceResolver {
|
||||||
|
|
||||||
|
private static RunningFrom runningFrom;
|
||||||
|
|
||||||
|
static {
|
||||||
|
if (isRunningFromJar()) {
|
||||||
|
runningFrom = RunningFrom.JAR;
|
||||||
|
}
|
||||||
|
else if (isRunningFromWar()) {
|
||||||
|
runningFrom = RunningFrom.WAR;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
runningFrom = RunningFrom.IDE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
获取资源
|
||||||
|
<p>
|
||||||
|
<ul>
|
||||||
|
<li>JAR
|
||||||
|
<ul>
|
||||||
|
<li>优先以 jar 文件所在目录为基准,寻找相对路径外部文件</li>
|
||||||
|
<li>当外部文件不存在时,读取 classpath,即 jar 内部资源文件</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>WAR 只获取 classpath 文件,即 /WEB-INF/classes/ 下文件</li>
|
||||||
|
<li>IDE 只获取源文件,即 src/main/resources/ 下文件</li>
|
||||||
|
</ul></p>
|
||||||
|
* @param relativePath 相对路径
|
||||||
|
* @param writable 是否一定可写
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static InputStream loadResource(String relativePath) {
|
||||||
|
try {
|
||||||
|
Path externalPath = resolveExternalPath(relativePath);
|
||||||
|
|
||||||
|
if (externalPath != null && Files.exists(externalPath)) {
|
||||||
|
log.debug("从外部文件系统加载资源: {}", externalPath);
|
||||||
|
return Files.newInputStream(externalPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 否则回退到 classpath(JAR、WAR、IDE)
|
||||||
|
InputStream in = SmartResourceResolver.class.getClassLoader().getResourceAsStream(relativePath);
|
||||||
|
if (in != null) {
|
||||||
|
log.debug("从 classpath 内部加载资源: {}", relativePath);
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new FileNotFoundException("无法找到资源: " + relativePath);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("读取资源失败: " + relativePath, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void saveText(String relativePath, String content) throws IOException {
|
||||||
|
Path outputPath = resolveExternalPath(relativePath);
|
||||||
|
Files.createDirectories(outputPath.getParent()); // 确保目录存在
|
||||||
|
Files.writeString(outputPath, content, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||||
|
log.debug("写入外部资源文件成功: {}", outputPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Path resolveExternalPath(String relativePath) {
|
||||||
|
try {
|
||||||
|
Path basePath;
|
||||||
|
if (runningFrom == RunningFrom.JAR) {
|
||||||
|
basePath = Paths.get(SmartResourceResolver.class.getProtectionDomain()
|
||||||
|
.getCodeSource().getLocation().toURI()).getParent();
|
||||||
|
return basePath.resolve(relativePath).normalize();
|
||||||
|
} else if (runningFrom == RunningFrom.WAR) {
|
||||||
|
basePath = Paths.get(SmartResourceResolver.class.getProtectionDomain()
|
||||||
|
.getCodeSource().getLocation().toURI()); // e.g., WEB-INF/classes/
|
||||||
|
return basePath.resolve(relativePath).normalize();
|
||||||
|
} else {
|
||||||
|
// IDE 环境:返回 src/main/resources 下真实文件
|
||||||
|
return Paths.get("src/main/resources", relativePath).normalize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isRunningFromJar() {
|
||||||
|
String path = SmartResourceResolver.class.getResource(
|
||||||
|
SmartResourceResolver.class.getSimpleName() + ".class").toString();
|
||||||
|
return path.startsWith("jar:");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isRunningFromWar() {
|
||||||
|
String path = SmartResourceResolver.class.getResource(
|
||||||
|
SmartResourceResolver.class.getSimpleName() + ".class").toString();
|
||||||
|
return path.contains("/WEB-INF/classes/");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static enum RunningFrom {
|
||||||
|
JAR,
|
||||||
|
WAR,
|
||||||
|
IDE
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package quant.rich.emoney.validator;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
import jakarta.validation.Constraint;
|
||||||
|
import jakarta.validation.Payload;
|
||||||
|
|
||||||
|
@Documented
|
||||||
|
@Constraint(validatedBy = ProxySettingValidator.class)
|
||||||
|
@Target({ ElementType.TYPE })
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface ProxySettingValid {
|
||||||
|
String message() default "非法的 ProxySetting";
|
||||||
|
Class<?>[] groups() default {};
|
||||||
|
Class<? extends Payload>[] payload() default {};
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package quant.rich.emoney.validator;
|
||||||
|
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.util.Objects;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import jakarta.validation.ConstraintValidator;
|
||||||
|
import jakarta.validation.ConstraintValidatorContext;
|
||||||
|
import quant.rich.emoney.entity.sqlite.ProxySetting;
|
||||||
|
|
||||||
|
public class ProxySettingValidator implements IValidator, ConstraintValidator<ProxySettingValid, ProxySetting> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid(ProxySetting value, ConstraintValidatorContext context) {
|
||||||
|
|
||||||
|
if (value == null) return true;
|
||||||
|
if (!(value instanceof ProxySetting proxySetting)) return true;
|
||||||
|
|
||||||
|
if (proxySetting.getProxyType() != null && proxySetting.getProxyType() != Proxy.Type.DIRECT) {
|
||||||
|
if (StringUtils.isBlank(proxySetting.getProxyHost())) {
|
||||||
|
return invalid(context, "设置代理为 HTTP 或 SOCKS 时,代理地址不允许为空");
|
||||||
|
}
|
||||||
|
if (Objects.isNull(proxySetting.getProxyPort()) || proxySetting.getProxyPort() <= 0 || proxySetting.getProxyPort() > 65535) {
|
||||||
|
return invalid(context, "端口不合法");
|
||||||
|
}
|
||||||
|
// 不做连通性校验:有的代理可能先添加后生效
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -19,7 +19,10 @@ spring:
|
|||||||
devtools:
|
devtools:
|
||||||
restart:
|
restart:
|
||||||
enabled: true
|
enabled: true
|
||||||
additional-exclude: '**/*.html'
|
additional-exclude:
|
||||||
|
- '**/*.html'
|
||||||
|
- '**/*.js'
|
||||||
|
- '**/*.css'
|
||||||
additional-paths: lib/
|
additional-paths: lib/
|
||||||
jackson:
|
jackson:
|
||||||
date-format: yyyy-MM-dd HH:mm:ss
|
date-format: yyyy-MM-dd HH:mm:ss
|
||||||
|
|||||||
Binary file not shown.
@@ -382,6 +382,7 @@ blockquote.layui-elem-quote {
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
box-shadow: 1px 1px 10px rgba(0, 0, 0, .1);
|
box-shadow: 1px 1px 10px rgba(0, 0, 0, .1);
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
overflow: auto
|
||||||
}
|
}
|
||||||
.layui-layer-indexDetail>.layui-layer-content>*:not(:last-child) {
|
.layui-layer-indexDetail>.layui-layer-content>*:not(:last-child) {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
@@ -394,7 +395,7 @@ blockquote.layui-elem-quote {
|
|||||||
display: block
|
display: block
|
||||||
}
|
}
|
||||||
.layui-layer-adminRight>.layui-layer-content {
|
.layui-layer-adminRight>.layui-layer-content {
|
||||||
overflow: visible !important;
|
/* overflow: visible !important; */
|
||||||
}
|
}
|
||||||
.layui-anim-rl {
|
.layui-anim-rl {
|
||||||
-webkit-animation-name: layui-rl;
|
-webkit-animation-name: layui-rl;
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
function InitDog() {
|
function InitDog() {
|
||||||
const dog = {};
|
const dog = {};
|
||||||
dog.error = ({msg = '服务器错误', time = 2000, onClose}) => {
|
dog.error = ({msg = '服务器错误', defaultMsg = '服务器错误', time = 2000, onClose}) => {
|
||||||
|
if (typeof msg === 'object') {
|
||||||
|
if (msg.responseJSON) {
|
||||||
|
const r = msg.responseJSON;
|
||||||
|
msg = r && r.data || defaultMsg
|
||||||
|
}
|
||||||
|
}
|
||||||
return layui.layer.msg(
|
return layui.layer.msg(
|
||||||
'<i class="fa-solid fa-circle-exclamation"></i>' + msg, {
|
'<i class="fa-solid fa-circle-exclamation"></i>' + msg, {
|
||||||
offset: '15px',
|
offset: '15px',
|
||||||
@@ -10,7 +16,7 @@ function InitDog() {
|
|||||||
end: onClose
|
end: onClose
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
dog.success = ({msg = '操作成功', time = 2000, onClose}) => {
|
dog.success = ({msg = '操作成功', defaultMsg = '操作成功', time = 2000, onClose}) => {
|
||||||
return layui.layer.msg(
|
return layui.layer.msg(
|
||||||
'<i class="fa-solid fa-circle-check"></i>' + msg, {
|
'<i class="fa-solid fa-circle-check"></i>' + msg, {
|
||||||
offset: '15px',
|
offset: '15px',
|
||||||
@@ -19,8 +25,17 @@ function InitDog() {
|
|||||||
skin: 'dog success',
|
skin: 'dog success',
|
||||||
end: onClose
|
end: onClose
|
||||||
})
|
})
|
||||||
|
};
|
||||||
|
dog.reloadTable = (tableFilter) => {
|
||||||
|
if (!tableFilter) {
|
||||||
|
tableFilter = document.querySelector('table[lay-filter]').getAttribute('lay-filter');
|
||||||
|
}
|
||||||
|
layui.table.reload(tableFilter, {
|
||||||
|
page: {
|
||||||
|
curr: $('.layui-laypage-em').next().html()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return dog;
|
return dog;
|
||||||
}
|
}
|
||||||
const Dog = window.Dog = InitDog();
|
const Dog = window.Dog = InitDog();
|
||||||
@@ -1,14 +1,6 @@
|
|||||||
if (!window.Helper) { window.Helper = {} }
|
if (!window.Helper) { window.Helper = {} }
|
||||||
window.Helper = {
|
window.Helper = {
|
||||||
emoneyPeriodToName: function(x) {
|
emoneyPeriodToName: x => x < 10000 && `${x} 分钟线` || [null, '日线', '周线', '月线', '季线', '半年线', '年线'][x/10000],
|
||||||
if (x < 10000) return `${x} 分钟`;
|
|
||||||
if (x == 10000) return '日线';
|
|
||||||
if (x == 20000) return '周线';
|
|
||||||
if (x == 30000) return '月线';
|
|
||||||
if (x == 40000) return '季线';
|
|
||||||
if (x == 50000) return '半年线';
|
|
||||||
if (x == 60000) return '年线';
|
|
||||||
},
|
|
||||||
allEmoneyPeriods: [1, 5, 15, 30, 60, 120, 10000, 20000, 30000, 40000, 50000, 60000],
|
allEmoneyPeriods: [1, 5, 15, 30, 60, 120, 10000, 20000, 30000, 40000, 50000, 60000],
|
||||||
showIndexDetailLayer: async function(obj, forceRefresh) {
|
showIndexDetailLayer: async function(obj, forceRefresh) {
|
||||||
// obj: {indexCode: _, indexName: _}
|
// obj: {indexCode: _, indexName: _}
|
||||||
@@ -50,14 +42,12 @@ window.Helper = {
|
|||||||
skin: 'layui-layer-indexDetail',
|
skin: 'layui-layer-indexDetail',
|
||||||
area: ['520px', '320px'],
|
area: ['520px', '320px'],
|
||||||
btn: ['刷新', '确定'],
|
btn: ['刷新', '确定'],
|
||||||
btn1: function(index, layero, that) {
|
btn1: function(index, _, _) {
|
||||||
layer.close(index);
|
layer.close(index);
|
||||||
Helper.showIndexDetailLayer(obj, !0);
|
Helper.showIndexDetailLayer(obj, !0);
|
||||||
},
|
},
|
||||||
success: function(layero, index) {
|
success: function(layero, _) {
|
||||||
var btns = layero.find('.layui-layer-btn>*');
|
Helper.setLayerMainBtn(layero, -1);
|
||||||
btns[0].setAttribute('class', 'layui-layer-btn1');
|
|
||||||
btns[1].setAttribute('class', 'layui-layer-btn0');
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -70,6 +60,211 @@ window.Helper = {
|
|||||||
const escaped = chars.split('').map(c => c.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('');
|
const escaped = chars.split('').map(c => c.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('');
|
||||||
const pattern = new RegExp(`^[${escaped}]+|[${escaped}]+$`, 'g');
|
const pattern = new RegExp(`^[${escaped}]+|[${escaped}]+$`, 'g');
|
||||||
return str.replace(pattern, '');
|
return str.replace(pattern, '');
|
||||||
}
|
},
|
||||||
|
setLayerMainBtn: function (layero, index) {
|
||||||
|
var btns = layero.find('.layui-layer-btn>*'), j = 1;
|
||||||
|
if (index < 0) index = btns.length + index;
|
||||||
|
for (let i = 0; i < btns.length; i++) {
|
||||||
|
const btn = btns[i];
|
||||||
|
const clazz = btn.getAttribute('class');
|
||||||
|
const classes = clazz.split(' ');
|
||||||
|
let filtered = classes.filter(str => !/^layui\-layer\-btn/gi.test(str));
|
||||||
|
filtered.push('layui-layer-btn' + (index == i ? '0' : j++));
|
||||||
|
btn.setAttribute('class', filtered.join(' '));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openR: function (option) {
|
||||||
|
const defaultOption = {
|
||||||
|
type: 1, area: '500px',
|
||||||
|
skin: 'layui-anim layui-anim-rl layui-layer-adminRight',
|
||||||
|
anim: -1, shadeClose: !0, closeBtn: !0, move: !1, offset: 'r'
|
||||||
|
};
|
||||||
|
option = $.extend(defaultOption, option);
|
||||||
|
return layui.layer.open(option)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 按照通用配置来渲染表格
|
||||||
|
* option: 和 table.render 选项基本一致, 但需要额外提供:
|
||||||
|
* idName: 该表格行对象 id 的名称
|
||||||
|
* baseUrl: 当前基本 URL, 将在此基础上拼接 list/updateBool/batchOp 等内容
|
||||||
|
* batchOpEnum: 批量操作枚举名,若不提供则不渲染批量操作控件和回调逻辑,若为 true 则仅渲染批量删除逻辑
|
||||||
|
* 除此以外,cols 内列如果需要开关选项的, 在相应 col 内添加 switchTemplet: true
|
||||||
|
*/
|
||||||
|
renderTable: (option) => {
|
||||||
|
const defaultOption = {
|
||||||
|
page: !0, skin: 'line'
|
||||||
|
};
|
||||||
|
option = $.extend(defaultOption, option);
|
||||||
|
if (!option.idName) throw new Error('idName 不允许为空');
|
||||||
|
if (!option.baseUrl) throw new Error('baseUrl 不允许为空');
|
||||||
|
if (!option.baseUrl.endsWith('/')) option.baseUrl += '/';
|
||||||
|
option.url = option.baseUrl + 'list';
|
||||||
|
let tableSwitchTemplet = function () {
|
||||||
|
// 以 elem 选择器 + '.' + switchFilter 作为 filter
|
||||||
|
const filter = `${option.elem}.switchFilter`;
|
||||||
|
layui.form.on(`switch(${filter})`, function (obj) {
|
||||||
|
console.log(obj, obj.elem.checked);
|
||||||
|
const data = {
|
||||||
|
field: obj.elem.dataset.field,
|
||||||
|
value: obj.elem.checked,
|
||||||
|
id: obj.elem.dataset.id
|
||||||
|
};
|
||||||
|
$.ajax({
|
||||||
|
url: option.baseUrl + 'updateBool', method: 'POST',
|
||||||
|
data:data,
|
||||||
|
success: () => Dog.success({time: 1000}),
|
||||||
|
error: function (res) {
|
||||||
|
Dog.error({msg: res})
|
||||||
|
// 恢复 enabled 状态
|
||||||
|
obj.elem.checked = !obj.elem.checked;
|
||||||
|
layui.form.render('checkbox')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return d => {
|
||||||
|
var fieldName = d.LAY_COL.field;
|
||||||
|
return `<input type="checkbox" lay-skin="switch" lay-text="|"
|
||||||
|
data-field="${fieldName}" data-id="${d[option.idName]}"
|
||||||
|
${d[fieldName] ? 'checked' : ''} lay-filter="${filter}">`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let cols = option.cols[0];
|
||||||
|
cols.forEach(col => {
|
||||||
|
if (col.switchTemplet) {
|
||||||
|
col.templet = tableSwitchTemplet()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
layui.table.render(option)
|
||||||
|
},
|
||||||
|
onSubmitForm: (submitButtonFilter, func) => {
|
||||||
|
layui.form.on(`submit(${submitButtonFilter})`, _ => {
|
||||||
|
const buttonEl = $(`[lay-filter="${submitButtonFilter}"]`);
|
||||||
|
if (!buttonEl.length) {
|
||||||
|
Dog.error({msg: `找不到对应 filter 为 ${submitButtonFilter} 的 button`});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const form = $(buttonEl.parents('.layui-form')[0] || buttonEl.parents('form')[0]);
|
||||||
|
if (!form.length) {
|
||||||
|
Dog.error({msg: `找不到对应 filter 为 ${submitButtonFilter} 的 button 对应的表单`});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 获取 form 内所有表单
|
||||||
|
const els = form.find('input[name], select[name], textarea[name], button[name]');
|
||||||
|
let obj = {form: form[0], field: {}};
|
||||||
|
$.each(els, (i, el) => {
|
||||||
|
const name = el.name;
|
||||||
|
if (!name) return true
|
||||||
|
switch (el.type) {
|
||||||
|
case 'checkbox':
|
||||||
|
if (el.getAttribute('lay-skin')) {
|
||||||
|
// 带 skin 当做二值简单表单
|
||||||
|
obj.field[name] = el.value;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!obj.field.hasOwnProperty(name)) {
|
||||||
|
obj.field[name] = [];
|
||||||
|
}
|
||||||
|
if (el.checked) {
|
||||||
|
obj.field[name].push(el.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'radio':
|
||||||
|
if (el.checked) {
|
||||||
|
obj.field[name] = el.value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'select-multiple':
|
||||||
|
obj.field[name] = Array.from(el.selectedOptions).map(opt => opt.value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
obj.field[name] = el.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (func && typeof func === 'function') return func(obj);
|
||||||
|
else if (func && typeof func === 'string') {
|
||||||
|
// 按照默认来 post
|
||||||
|
$.ajax({
|
||||||
|
url: func,
|
||||||
|
method: 'POST',
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify(obj.field),
|
||||||
|
success: function (r) {
|
||||||
|
Dog.success({onClose: () => {
|
||||||
|
if (window.editLayer) layui.layer.close(window.editLayer);
|
||||||
|
Dog.reloadTable()}})
|
||||||
|
},
|
||||||
|
error: res => Dog.error({msg: res}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
fillEditForm: (r, layero, layerIndex, extraSwitchFuncs) => {
|
||||||
|
const el = $(layero);
|
||||||
|
let switchFuncs = [];
|
||||||
|
for (let key in r.data) {
|
||||||
|
let val = r.data[key];
|
||||||
|
const fieldEl = el[0].querySelector(`[name="${key}"]`);
|
||||||
|
if (!fieldEl) continue;
|
||||||
|
const type = fieldEl.type;
|
||||||
|
switch (type) {
|
||||||
|
case 'checkbox':
|
||||||
|
const checked = fieldEl.value = fieldEl.chceked = val == 'true' || val == true;
|
||||||
|
const laySkin = fieldEl.getAttribute('lay-skin');
|
||||||
|
if (laySkin) {
|
||||||
|
switchFuncs[key] = function (obj) {
|
||||||
|
obj.elem.value = obj.elem.checked;
|
||||||
|
layui.form.render();
|
||||||
|
}
|
||||||
|
layui.form.on(`switch(${key})`, function (obj) {
|
||||||
|
switchFuncs[obj.elem.name](obj);
|
||||||
|
})
|
||||||
|
layui.event.call(this, 'form', `switch(${key})`, {
|
||||||
|
elem: fieldEl,
|
||||||
|
value: checked
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'radio':
|
||||||
|
fieldEl.value = fieldEl.chceked = val == 'true' || val == true;
|
||||||
|
break;
|
||||||
|
case 'select-one':
|
||||||
|
const options = fieldEl.querySelectorAll('option');
|
||||||
|
options.forEach(option => {
|
||||||
|
if (option.value == val) {
|
||||||
|
option.selected = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fieldEl.value = val
|
||||||
|
}
|
||||||
|
if (type === 'text' || type === 'password' || type === 'hidden') {
|
||||||
|
fieldEl.value = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (extraSwitchFuncs) {
|
||||||
|
switchFuncs = $.extend(switchFuncs, extraSwitchFuncs)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tableSwitchTemplet: idName => {
|
||||||
|
layui.form.on('switch(switchFilter)', function (obj) {
|
||||||
|
console.log(obj, obj.elem.checked);
|
||||||
|
$.ajax({
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return d => {
|
||||||
|
var fieldName = d.LAY_COL.field;
|
||||||
|
return `<input type="checkbox" lay-skin="switch" lay-text="|"
|
||||||
|
data-field="${fieldName}" data-id="${d[idName]}"
|
||||||
|
${d[fieldName] ? 'checked' : ''} lay-filter="switchFilter">`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
randomHexString: n => [...Array(n)].map(() => 'abcdef0123456789'[~~(Math.random() * 16)]).join('')
|
||||||
}
|
}
|
||||||
BIN
src/main/resources/static/img/emograb_logo.webp
Normal file
BIN
src/main/resources/static/img/emograb_logo.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
@@ -2,80 +2,131 @@
|
|||||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
<head th:fragment="head">
|
<head th:fragment="head">
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>
|
<title>[[${(title!=null?title:'后台管理')}]]</title>
|
||||||
[[${(title!=null?title:'后台管理')}]]
|
<link rel="shortcut icon" th:href="@{/favicon.ico}" />
|
||||||
</title>
|
<meta name="renderer" content="webkit" />
|
||||||
<link rel="shortcut icon" th:href="@{/favicon.ico}" />
|
<meta name="base" th:content="@{/}" />
|
||||||
<meta name="renderer" content="webkit" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||||
<meta name="base" th:content="@{/}" />
|
<meta name="viewport"
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0" />
|
||||||
<meta name="viewport"
|
<link rel="stylesheet" th:href="@{/admin/v1/static/layui/css/layui.css}"
|
||||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0" />
|
media="all" />
|
||||||
<link rel="stylesheet" th:href="@{/admin/v1/static/layui/css/layui.css}" media="all" />
|
<link rel="stylesheet"
|
||||||
<link rel="stylesheet" th:href="@{/admin/v1/static/layuiadmin/style/login.css}" media="all" />
|
th:href="@{/admin/v1/static/layuiadmin/style/login.css}" media="all" />
|
||||||
<link rel="stylesheet" th:href="@{/admin/v1/static/css/admin.css}" media="all" />
|
<link rel="stylesheet" th:href="@{/admin/v1/static/css/admin.css}"
|
||||||
<link rel="stylesheet" th:href="@{/public/plugins/fa5/css/all.min.css}" media="all" />
|
media="all" />
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
|
<link rel="stylesheet" th:href="@{/public/plugins/fa6/css/all.min.css}"
|
||||||
<style>
|
media="all" />
|
||||||
.nav-last>i.layui-icon {
|
<style>
|
||||||
top: 30px;
|
.nav-last>i.layui-icon {
|
||||||
line-height: 0;
|
top: 30px;
|
||||||
}
|
line-height: 0;
|
||||||
</style>
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="main-nav" th:fragment="nav">
|
<div class="main-nav" th:fragment="nav">
|
||||||
<ul class="layui-nav" style="display:table;width:100%">
|
<ul class="layui-nav" style="display: table; width: 100%">
|
||||||
<li class="layui-nav-item">
|
<li class="layui-nav-item"><a href="#"> <i
|
||||||
<a href="#">控制台</a>
|
class="fa-fw fa-solid fa-chalkboard "></i> 控制台
|
||||||
|
</a>
|
||||||
<dl class="layui-nav-child">
|
<dl class="layui-nav-child">
|
||||||
<dd><a th:href="@{/admin/v1/index}">概要</a></dd>
|
<dd>
|
||||||
<dd><a href="#">个人设置</a></dd>
|
<a th:href="@{/admin/v1/index}">概要</a>
|
||||||
<dd><a href="#">插件</a></dd>
|
</dd>
|
||||||
<dd><a href="#">外观</a></dd>
|
<dd>
|
||||||
<dd><a href="#">备份</a></dd>
|
<a href="#">个人设置</a>
|
||||||
</dl>
|
</dd>
|
||||||
</li>
|
<dd>
|
||||||
<li class="layui-nav-item">
|
<a href="#">插件</a>
|
||||||
<a href="javascript:;">管理</a>
|
</dd>
|
||||||
|
<dd>
|
||||||
|
<a href="#">外观</a>
|
||||||
|
</dd>
|
||||||
|
<dd>
|
||||||
|
<a href="#">备份</a>
|
||||||
|
</dd>
|
||||||
|
</dl></li>
|
||||||
|
<li class="layui-nav-item"><a href="javascript:;"> <i
|
||||||
|
class="fa-fw fa-solid fa-screwdriver-wrench"></i> 管理
|
||||||
|
</a>
|
||||||
<dl class="layui-nav-child">
|
<dl class="layui-nav-child">
|
||||||
<dd><a th:href="@{/admin/v1/manage/plan}">计划任务</a></dd>
|
<dd>
|
||||||
<dd><a th:href="@{/admin/v1/manage/indexInfo}">指标配置</a></dd>
|
<a th:href="@{/admin/v1/manage/plan}"> <i
|
||||||
<dd><a th:href="@{/admin/v1/manage/protocolMatch}">Protocol 配置</a></dd>
|
class="fa-fw fa-regular fa-calendar"></i> 计划任务
|
||||||
</dl>
|
</a>
|
||||||
</li>
|
</dd>
|
||||||
<li class="layui-nav-item">
|
<dd>
|
||||||
<a href="javascript:;">设置</a>
|
<a th:href="@{/admin/v1/manage/requestInfo}"> <i
|
||||||
|
class="fas fa-fw fa-envelope-open-text"></i> 请求配置
|
||||||
|
</a>
|
||||||
|
</dd>
|
||||||
|
<dd>
|
||||||
|
<a th:href="@{/admin/v1/manage/indexInfo}"> <i
|
||||||
|
class="fa-fw fa-solid fa-chart-line "></i> 指标配置
|
||||||
|
</a>
|
||||||
|
</dd>
|
||||||
|
<dd>
|
||||||
|
<a th:href="@{/admin/v1/manage/proxySetting}"> <i
|
||||||
|
class="fa-fw fa-solid fa-network-wired"></i> 代理配置
|
||||||
|
</a>
|
||||||
|
</dd>
|
||||||
|
<dd>
|
||||||
|
<a th:href="@{/admin/v1/manage/protocolMatch}"> <i
|
||||||
|
class="fa-fw fa-regular fa-handshake "></i> Protocol 配置
|
||||||
|
</a>
|
||||||
|
</dd>
|
||||||
|
</dl></li>
|
||||||
|
<li class="layui-nav-item"><a href="javascript:;"> <i
|
||||||
|
class="fa-fw fa-solid fa-gears"></i> 设置
|
||||||
|
</a>
|
||||||
<dl class="layui-nav-child">
|
<dl class="layui-nav-child">
|
||||||
<dd><a th:href="@{/admin/v1/config/emoneyRequest}">请求头设置</a></dd>
|
<dd>
|
||||||
<dd><a th:href="@{/admin/v1/config/proxy}">代理设置</a></dd>
|
<a th:href="@{/admin/v1/config/emoneyRequest}"> <i
|
||||||
</dl>
|
class="fa-fw fa-solid fa-heading"></i> 请求头设置
|
||||||
</li>
|
</a>
|
||||||
<li class="layui-nav-item" style="float:right;margin-right: 1px;" lay-unselect="">
|
</dd>
|
||||||
<a href="javascript:;" class="nav-last nav-user-info" >
|
<dd>
|
||||||
<img id="adminUserGravatar" th:src="@{/img/dog-avatar.webp}" loading="lazy" referrerpolicy="same-origin" class="layui-nav-img" />
|
<a th:href="@{/admin/v1/config/proxy}"> <i
|
||||||
<span id="adminUserNickname">[[${@platformConfig.username}]]</span>
|
class="fa-fw fa-solid fa-network-wired"></i> 代理设置
|
||||||
</a>
|
</a>
|
||||||
|
</dd>
|
||||||
|
</dl></li>
|
||||||
|
<li class="layui-nav-item"
|
||||||
|
style="float: right; margin-right: 1px;" lay-unselect=""><a
|
||||||
|
href="javascript:;" class="nav-last nav-user-info"> <img
|
||||||
|
id="adminUserGravatar" th:src="@{/img/dog-avatar.webp}"
|
||||||
|
loading="lazy" referrerpolicy="same-origin"
|
||||||
|
class="layui-nav-img" /> <span id="adminUserNickname">[[${@platformConfig.username}]]</span>
|
||||||
|
</a>
|
||||||
<dl class="layui-nav-child">
|
<dl class="layui-nav-child">
|
||||||
<dd><a href="#" class="change-user-info">修改信息</a></dd>
|
<dd>
|
||||||
<dd><a th:href="@{/admin/v1/logout}">退出登录</a></dd>
|
<a href="#" class="change-user-info">修改信息</a>
|
||||||
</dl>
|
</dd>
|
||||||
</li>
|
<dd>
|
||||||
<li class="layui-nav-item ipInfo" style="float:right;margin-right: 1px" lay-unselect="">
|
<a th:href="@{/admin/v1/logout}">退出登录</a>
|
||||||
<a id="ipThroughProxy" href="javascript:manualRefreshIp()" title="立即刷新">
|
</dd>
|
||||||
IP 属地:
|
</dl></li>
|
||||||
<span th:if="${@proxyConfig.ipInfo == null}" class="layui-badge layui-bg-cyan">加载中...</span>
|
<li class="layui-nav-item ipInfo"
|
||||||
<span th:if="${@proxyConfig.ipInfo != null}" class="layui-badge layui-bg-cyan">[[${@proxyConfig.ipInfo.geoString}]]</span>
|
style="float: right; margin-right: 1px" lay-unselect=""><a
|
||||||
</a>
|
id="ipThroughProxy" href="javascript:manualRefreshIp()"
|
||||||
<th:block th:if="${@proxyConfig.ipInfo != null}">
|
title="立即刷新"> IP 属地: <span
|
||||||
|
th:if="${@proxyConfig.ipInfo == null}"
|
||||||
|
class="layui-badge layui-bg-cyan">加载中...</span> <span
|
||||||
|
th:if="${@proxyConfig.ipInfo != null}"
|
||||||
|
class="layui-badge layui-bg-cyan">[[${@proxyConfig.ipInfo.geoString}]]</span>
|
||||||
|
</a> <th:block th:if="${@proxyConfig.ipInfo != null}">
|
||||||
<dl class="layui-nav-child">
|
<dl class="layui-nav-child">
|
||||||
<dd class="ip"><a title="点击复制">[[${@proxyConfig.ipInfo.ip}]]</a></dd>
|
<dd class="ip">
|
||||||
<dd class="ipv6" th:if="${@proxyConfig.ipInfo.ipv6 != null}"><a title="点击复制">[[${@proxyConfig.ipInfo.ipv6}]]</a></dd>
|
<a title="点击复制">[[${@proxyConfig.ipInfo.ip}]]</a>
|
||||||
|
</dd>
|
||||||
|
<dd class="ipv6" th:if="${@proxyConfig.ipInfo.ipv6 != null}">
|
||||||
|
<a title="点击复制">[[${@proxyConfig.ipInfo.ipv6}]]</a>
|
||||||
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</th:block>
|
</th:block></li>
|
||||||
</li>
|
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
<script type="text/html" id="editUser">
|
<script type="text/html" id="editUser">
|
||||||
@@ -116,9 +167,11 @@
|
|||||||
<th:block th:fragment="head-script">
|
<th:block th:fragment="head-script">
|
||||||
<script type="text/javascript" src="/admin/v1/static/js/helper.js"></script>
|
<script type="text/javascript" src="/admin/v1/static/js/helper.js"></script>
|
||||||
<script th:src="@{/admin/v1/static/layui/layui.js}"></script>
|
<script th:src="@{/admin/v1/static/layui/layui.js}"></script>
|
||||||
<script th:src="@{/public/plugins/jquery/1.12.4/jquery-1.12.4.min.js}"></script>
|
<script
|
||||||
|
th:src="@{/public/plugins/jquery/1.12.4/jquery-1.12.4.min.js}"></script>
|
||||||
<script th:src="@{/public/plugins/js-sha3/sha3.min.js}"></script>
|
<script th:src="@{/public/plugins/js-sha3/sha3.min.js}"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
<script th:src="@{/public/plugins/alpinejs@3.14.9/dist/cdn.min.js}"
|
||||||
|
defer></script>
|
||||||
<script th:src="@{/admin/v1/static/giggity/toast.js}"></script>
|
<script th:src="@{/admin/v1/static/giggity/toast.js}"></script>
|
||||||
<script th:src="@{/admin/v1/static/js/dog.js}"></script>
|
<script th:src="@{/admin/v1/static/js/dog.js}"></script>
|
||||||
|
|
||||||
@@ -139,7 +192,7 @@
|
|||||||
try {
|
try {
|
||||||
let geoEl =
|
let geoEl =
|
||||||
document.querySelector('#ipThroughProxy>span');
|
document.querySelector('#ipThroughProxy>span');
|
||||||
geoEl.textContent = '加载中...';
|
//geoEl.textContent = '加载中...';
|
||||||
let res = await (await fetch('/admin/v1/config/proxy/refreshIpThroughProxy')).json();
|
let res = await (await fetch('/admin/v1/config/proxy/refreshIpThroughProxy')).json();
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
geoEl.textContent = res.data.geoString || '获取失败';
|
geoEl.textContent = res.data.geoString || '获取失败';
|
||||||
@@ -269,10 +322,9 @@
|
|||||||
</script>
|
</script>
|
||||||
</th:block>
|
</th:block>
|
||||||
<div th:fragment="feet" class="layui-trans layadmin-user-login-footer">
|
<div th:fragment="feet" class="layui-trans layadmin-user-login-footer">
|
||||||
Driven by Latte<br />
|
Driven by Latte<br /> ©2025-[[${#dates.format(new
|
||||||
©2025-[[${#dates.format(new java.util.Date().getTime(),'yyyy')}]]
|
java.util.Date().getTime(),'yyyy')}]] <a href="#">Latte</a> All
|
||||||
<a href="#">Latte</a>
|
Rights Reserved.
|
||||||
All Rights Reserved.
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<!-- Alpine.js -->
|
<!-- Alpine.js -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
<script th:src="@{/public/plugins/alpinejs@3.14.9/dist/cdn.min.js}" defer></script>
|
||||||
<!-- SHA3 224 -->
|
<!-- SHA3 224 -->
|
||||||
<script th:src="@{/public/plugins/js-sha3/sha3.min.js}"></script>
|
<script th:src="@{/public/plugins/js-sha3/sha3.min.js}"></script>
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
<style>
|
<style>
|
||||||
.logo {
|
.logo {
|
||||||
margin-top: -50px!important;
|
margin-top: -50px!important;
|
||||||
max-width: 50%;
|
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -23,7 +22,7 @@
|
|||||||
|
|
||||||
<div class="flex items-center justify-center flex-1">
|
<div class="flex items-center justify-center flex-1">
|
||||||
<div x-data="loginForm()">
|
<div x-data="loginForm()">
|
||||||
<img class="logo" th:src="@{/img/emograb_logo.png}"/>
|
<img class="logo" th:src="@{/img/emograb_logo.webp}"/>
|
||||||
<h2 class="text-2xl font-bold text-center">EmoGrab</h2>
|
<h2 class="text-2xl font-bold text-center">EmoGrab</h2>
|
||||||
<p class="text-center text-gray-500 text-sm pb-4">
|
<p class="text-center text-gray-500 text-sm pb-4">
|
||||||
EA Data Crawling Platform
|
EA Data Crawling Platform
|
||||||
@@ -69,7 +68,7 @@
|
|||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<!-- Alpine.js -->
|
<!-- Alpine.js -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
<script th:src="@{/public/plugins/alpinejs@3.14.9/dist/cdn.min.js}" defer></script>
|
||||||
<!-- SHA3 224 -->
|
<!-- SHA3 224 -->
|
||||||
<script th:src="@{/public/plugins/js-sha3/sha3.min.js}"></script>
|
<script th:src="@{/public/plugins/js-sha3/sha3.min.js}"></script>
|
||||||
<script th:src="@{/admin/v1/static/giggity/toast.js}"></script>
|
<script th:src="@{/admin/v1/static/giggity/toast.js}"></script>
|
||||||
|
|||||||
@@ -270,9 +270,7 @@
|
|||||||
openEditForm(json)
|
openEditForm(json)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
window.toastModule.errorLayer({
|
Dog.error()
|
||||||
msg: json.data || '服务器错误'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
layui.table.on('tool(plans)', async function (obj) {
|
layui.table.on('tool(plans)', async function (obj) {
|
||||||
@@ -282,24 +280,16 @@
|
|||||||
else if (obj.event == 'del') {
|
else if (obj.event == 'del') {
|
||||||
layui.layer.confirm('确定删除该计划任务吗?', function (index) {
|
layui.layer.confirm('确定删除该计划任务吗?', function (index) {
|
||||||
layui.layer.close(index);
|
layui.layer.close(index);
|
||||||
|
const load = layui.layer.load(2);
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/admin/v1/manage/plan/delete',
|
url: '/admin/v1/manage/plan/delete',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: {planId: obj.data.planId},
|
data: {planId: obj.data.planId},
|
||||||
success: function (data) {
|
success: () => Dog.success({
|
||||||
layui.table.reload('plans', {
|
msg: '删除成功', onClose: () => Dog.reloadTable('plans')
|
||||||
page: {
|
}),
|
||||||
curr: $(".layui-laypage-em").next().html() //当前页码值
|
error: res => Dog.error({msg: res}),
|
||||||
}
|
complete: () => layui.layer.close(load)
|
||||||
});
|
|
||||||
layer.msg('删除成功', {offset: '15px', icon: 1, time: 1000})
|
|
||||||
},
|
|
||||||
error: function (res) {
|
|
||||||
var r = res.responseJSON;
|
|
||||||
layer.msg(r && r.data || '服务器错误',
|
|
||||||
{offset: '15px', icon: 2, time: 2000});
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||||
<head th:insert="~{admin/v1/include::head}"
|
<head th:insert="~{admin/v1/include::head}"
|
||||||
th:with="title=${'计划任务管理'}">
|
th:with="title=${'计划任务'}">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@@ -10,7 +10,8 @@
|
|||||||
<div class="manage-body">
|
<div class="manage-body">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="manage-title">
|
<h1 class="manage-title">
|
||||||
<b>计划任务列表</b><a href="javascript:openNewForm()" class="operate">新增</a>
|
<i class="fa-fw fa-regular fa-calendar"></i>
|
||||||
|
<b>计划任务</b><a href="javascript:openNewForm()" class="operate">新增</a>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -39,25 +40,19 @@
|
|||||||
})
|
})
|
||||||
.use(['table', 'form', 'dropdown', 'layer', 'xmSelect', 'cron'], function(){
|
.use(['table', 'form', 'dropdown', 'layer', 'xmSelect', 'cron'], function(){
|
||||||
var dropdown = layui.dropdown, table = layui.table, form = layui.form;
|
var dropdown = layui.dropdown, table = layui.table, form = layui.form;
|
||||||
function switchTemplet(d) {
|
Helper.renderTable({
|
||||||
var fieldName = d.LAY_COL.field;
|
|
||||||
return `<input type="checkbox" lay-skin="switch" lay-text="|"
|
|
||||||
data-field="${fieldName}" data-id="${d.planId}"
|
|
||||||
${d[fieldName] ? 'checked' : ''} lay-filter="switchFilter">`;
|
|
||||||
}
|
|
||||||
table.render({
|
|
||||||
elem: '#plans',
|
elem: '#plans',
|
||||||
url:'/admin/v1/manage/plan/list',
|
idName: 'planId',
|
||||||
page:true, skin:'line',
|
baseUrl:'/admin/v1/manage/plan',
|
||||||
cols: [ [
|
cols: [ [
|
||||||
{type:'checkbox'},
|
{type:'checkbox'},
|
||||||
{field:'enabled', title: '启用', width: 95, templet: switchTemplet},
|
{field:'enabled', title: '启用', width: 95, switchTemplet: true},
|
||||||
{field:'openDayCheck', title: '交易日校验', width: 95, templet: switchTemplet},
|
{field:'openDayCheck', title: '交易日校验', width: 95, switchTemplet: true},
|
||||||
{field:'planId', hide: true, width: 60, title: 'ID'},
|
{field:'planId', hide: true, width: 60, title: 'ID'},
|
||||||
{field:'planName', title: '计划名称'},
|
{field:'planName', title: '计划名称'},
|
||||||
{field:'cronExpression', title: '计划表达式'},
|
{field:'cronExpression', title: '计划表达式'},
|
||||||
{field:'indexCode', title: '指标代码'},
|
{field:'indexCode', title: '指标代码'},
|
||||||
{field:'params', title: '请求参数',templet: function(d) {
|
{field:'params', title: '请求参数', templet: function(d) {
|
||||||
if (typeof d.params === 'object' && d.params !== null) {
|
if (typeof d.params === 'object' && d.params !== null) {
|
||||||
return Object.entries(d.params).map(([key, value]) => `${key}=${value}`).join(', ');
|
return Object.entries(d.params).map(([key, value]) => `${key}=${value}`).join(', ');
|
||||||
}
|
}
|
||||||
@@ -71,49 +66,15 @@
|
|||||||
}},
|
}},
|
||||||
{field:'operation', title: '操作', toolbar: '#operationTpl'}
|
{field:'operation', title: '操作', toolbar: '#operationTpl'}
|
||||||
]]
|
]]
|
||||||
});
|
})
|
||||||
form.on('switch(switchFilter)', function(obj) {
|
|
||||||
console.log(obj);
|
|
||||||
console.log(obj.elem.checked);
|
|
||||||
$.ajax({
|
|
||||||
url: '/admin/v1/manage/plan/updateBool',
|
|
||||||
method: 'POST',
|
|
||||||
data: {
|
|
||||||
planId: obj.elem.dataset.id,
|
|
||||||
field: obj.elem.dataset.field,
|
|
||||||
value: obj.elem.checked
|
|
||||||
},
|
|
||||||
success: function () {
|
|
||||||
layer.msg('操作成功', {
|
|
||||||
offset: '15px',
|
|
||||||
icon: 1,
|
|
||||||
time: 1000
|
|
||||||
},
|
|
||||||
function() {}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
error: function (res) {
|
|
||||||
var r = res.responseJSON;
|
|
||||||
layer.msg(r && r.data || '服务器错误', {
|
|
||||||
offset: '15px',
|
|
||||||
icon: 2,
|
|
||||||
time: 1000
|
|
||||||
});
|
|
||||||
// 恢复 enabled 状态
|
|
||||||
obj.elem.checked = !obj.elem.checked;
|
|
||||||
layui.form.render('checkbox')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
dropdown.render({
|
dropdown.render({
|
||||||
elem: '.operdown',
|
elem: '.operdown',
|
||||||
data: [
|
data: [
|
||||||
{title: '删除'},
|
{title: '删除', op: 'DELETE'},
|
||||||
{title: '启用', op: 'enable'},
|
{title: '启用', op: 'ENABLE'},
|
||||||
{title: '停用', op: 'disable'},
|
{title: '停用', op: 'DISABLE'},
|
||||||
{title: '开启交易日校验', op: 'enableOpenDayCheck'},
|
{title: '开启交易日校验', op: 'ENABLE_OPEN_DAY_CHECK'},
|
||||||
{title: '关闭交易日校验', op: 'disableOpenDayCheck'}],
|
{title: '关闭交易日校验', op: 'DISABLE_OPEN_DAY_CHECK'}],
|
||||||
click: function (data, othis){
|
click: function (data, othis){
|
||||||
var checked = layui.table.checkStatus('plans'), planIds = [];
|
var checked = layui.table.checkStatus('plans'), planIds = [];
|
||||||
if (!checked.data.length) {
|
if (!checked.data.length) {
|
||||||
@@ -124,7 +85,7 @@
|
|||||||
planIds.push(plan.planId);
|
planIds.push(plan.planId);
|
||||||
});
|
});
|
||||||
data = $.extend(data, {ids: planIds});
|
data = $.extend(data, {ids: planIds});
|
||||||
var op = function() {
|
var op = async function() {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/admin/v1/manage/plan/batchOp',
|
url: '/admin/v1/manage/plan/batchOp',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -155,7 +116,7 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
data.op ? op() : layer.confirm('确认批量删除吗?该操作不可恢复', function(){
|
data.op != 'DELETE' ? op() : layer.confirm('确认批量删除吗?该操作不可恢复', function(){
|
||||||
op();
|
op();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,10 +72,7 @@ function openEditForm(r) {
|
|||||||
offset: 'r',
|
offset: 'r',
|
||||||
content: $('#addProtocolMatch').html(),
|
content: $('#addProtocolMatch').html(),
|
||||||
success: function(layero, layerIndex) {
|
success: function(layero, layerIndex) {
|
||||||
var el = $(layero);
|
Helper.fillEditForm(r, layero, layerIndex);
|
||||||
['protocolId', 'className'].forEach(x => {
|
|
||||||
el.find(`[name="${x}"]`).val(r.data[x])
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<div th:fragment="proxyExtra">
|
||||||
|
<script id="addProxySetting" type="text/html">
|
||||||
|
<style>.layui-form-select dl{max-height: 160px}</style>
|
||||||
|
<div class="layui-form" style="margin:10px 15px" id="editPlanForm" lay-filter="editPlanForm">
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<input type="hidden" name="id"/>
|
||||||
|
<label class="layui-form-label">代理名称<span>*</span></label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="text" lay-verify="required" name="proxyName" placeholder="" autocomplete="off" class="layui-input"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">代理类型<span>*</span></label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<select name="proxyType" lay-filter="proxyTypeFilter">
|
||||||
|
<option value="">选择代理类型</option>
|
||||||
|
<option value="DIRECT">直连</option>
|
||||||
|
<option value="HTTP">Http(s)</option>
|
||||||
|
<option value="SOCKS">Socks</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">代理主机<span>*</span></label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="text" lay-verify="required" name="proxyHost" placeholder="" autocomplete="off" class="layui-input"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">代理端口<span>*</span></label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="text" lay-verify="required" name="proxyPort" placeholder="" autocomplete="off" class="layui-input"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">忽略 HTTPS 校验<span>*</span></label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="checkbox"
|
||||||
|
name="ignoreHttpsVerification" lay-skin="switch" lay-filter="ignoreHttpsVerification" lay-text="ON|OFF">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display:none" class="layui-form-item">
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<button class="layui-btn" lay-submit="*" lay-filter="submitProxySetting">提交</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
Helper.onSubmitForm('submitProxySetting', '/admin/v1/manage/proxySetting/save');
|
||||||
|
function openEditForm(r) {
|
||||||
|
if (r && r.ok) {
|
||||||
|
window.editLayer = Helper.openR({
|
||||||
|
title: `${r.data.id ? '编辑' : '新增'}代理设置`,
|
||||||
|
btn: ['提交', '关闭'],
|
||||||
|
yes: function (index, layero) {
|
||||||
|
layero.find('[lay-filter="submitProxySetting"]').click()
|
||||||
|
},
|
||||||
|
content: $('#addProxySetting').html(),
|
||||||
|
success: async function (layero, layerIndex) {
|
||||||
|
Helper.fillEditForm(r, layero, layerIndex);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else Dog.error({
|
||||||
|
msg: r && r.data || '服务器错误'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async function openNewForm(id) {
|
||||||
|
const json = await (await fetch((() => {
|
||||||
|
const url = '/admin/v1/manage/proxySetting/getOne';
|
||||||
|
if (id) return url + '?id=' + id;
|
||||||
|
return url
|
||||||
|
})())).json();
|
||||||
|
if (json.ok) {
|
||||||
|
openEditForm(json)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Dog.error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layui.table.on('tool(proxySettings)', async function (obj) {
|
||||||
|
if (obj.event == 'edit') {
|
||||||
|
openNewForm(obj.data.id)
|
||||||
|
}
|
||||||
|
else if (obj.event == 'del') {
|
||||||
|
layui.layer.confirm('确定删除该代理配置吗?', function (index) {
|
||||||
|
layui.layer.close(index);
|
||||||
|
const load = layui.layer.load(2);
|
||||||
|
$.ajax({
|
||||||
|
url: '/admin/v1/manage/proxySetting/delete',
|
||||||
|
method: 'POST',
|
||||||
|
data: {id: obj.data.id},
|
||||||
|
success: () => Dog.success({
|
||||||
|
msg: '删除成功', onClose: () => Dog.reloadTable('proxySettings')
|
||||||
|
}),
|
||||||
|
error: res => Dog.error({msg: res}),
|
||||||
|
complete: () => layui.layer.close(load)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head th:insert="~{admin/v1/include::head}"
|
||||||
|
th:with="title=${'代理设置'}">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<ul th:replace="~{admin/v1/include::nav}"></ul>
|
||||||
|
|
||||||
|
<div class="manage-body">
|
||||||
|
<div>
|
||||||
|
<h1 class="manage-title">
|
||||||
|
<i class="fa-fw fa-regular fa-calendar"></i>
|
||||||
|
<b>代理设置</b><a href="javascript:openNewForm()" class="operate">新增</a>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<button class="layui-btn layui-btn-sm operdown">
|
||||||
|
<span>选中项<i
|
||||||
|
class="layui-icon layui-icon-sm layui-icon-triangle-d"></i></span>
|
||||||
|
</button>
|
||||||
|
<div>
|
||||||
|
<table class="layui-table" id="proxySettings" lay-filter="proxySettings">
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div th:replace="~{admin/v1/include::feet}"></div>
|
||||||
|
<script type="text/html" id="operationTpl">
|
||||||
|
<div class="layui-btn-group">
|
||||||
|
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
|
||||||
|
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
<th:block th:replace="~{admin/v1/include::head-script}"></th:block>
|
||||||
|
<script th:inline="javascript">
|
||||||
|
layui
|
||||||
|
.extend({
|
||||||
|
xmSelect: '/admin/v1/static/layuiadmin/lib/xm-select',
|
||||||
|
cron: '/admin/v1/static/layuiadmin/lib/cron'
|
||||||
|
})
|
||||||
|
.use(['table', 'form', 'dropdown', 'layer', 'xmSelect', 'cron'], function(){
|
||||||
|
var dropdown = layui.dropdown, table = layui.table, form = layui.form;
|
||||||
|
table.render({
|
||||||
|
elem: '#proxySettings',
|
||||||
|
url:'/admin/v1/manage/proxySetting/list',
|
||||||
|
page:true, skin:'line',
|
||||||
|
cols: [ [
|
||||||
|
{type:'checkbox'},
|
||||||
|
{field:'id', hide: true, width: 60, title: 'ID'},
|
||||||
|
{field:'proxyName', title: '名称'},
|
||||||
|
{field:'proxyType', title: '类型'},
|
||||||
|
{field:'proxyHost', title: '主机'},
|
||||||
|
{field:'proxyPort', title: '端口'},
|
||||||
|
{field:'ignoreHttpsVerification', title: '忽略 HTTPS 校验', width: 95, templet: Helper.tableSwitchTemplet('id')},
|
||||||
|
{field:'operation', title: '操作', toolbar: '#operationTpl'}
|
||||||
|
]]
|
||||||
|
});
|
||||||
|
form.on('switch(switchFilter)', function(obj) {
|
||||||
|
console.log(obj);
|
||||||
|
console.log(obj.elem.checked);
|
||||||
|
$.ajax({
|
||||||
|
url: '/admin/v1/manage/proxySetting/updateBool',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
id: obj.elem.dataset.id,
|
||||||
|
field: obj.elem.dataset.field,
|
||||||
|
value: obj.elem.checked
|
||||||
|
},
|
||||||
|
success: () => Dog.success({time: 1000}),
|
||||||
|
error: function (res) {
|
||||||
|
Dog.error({msg: res})
|
||||||
|
// 恢复 enabled 状态
|
||||||
|
obj.elem.checked = !obj.elem.checked;
|
||||||
|
layui.form.render('checkbox')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
dropdown.render({
|
||||||
|
elem: '.operdown',
|
||||||
|
data: [
|
||||||
|
{title: '删除', op: 'DELETE'},
|
||||||
|
{title: '启用', op: 'ENABLE'},
|
||||||
|
{title: '停用', op: 'DISABLE'},
|
||||||
|
{title: '开启交易日校验', op: 'ENABLE_OPEN_DAY_CHECK'},
|
||||||
|
{title: '关闭交易日校验', op: 'DISABLE_OPEN_DAY_CHECK'}],
|
||||||
|
click: function (data, othis){
|
||||||
|
var checked = layui.table.checkStatus('proxySettings'), planIds = [];
|
||||||
|
if (!checked.data.length) {
|
||||||
|
layui.layer.msg('未选中任何项', {time: 1000});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$.each(checked.data, function (i, plan){
|
||||||
|
planIds.push(plan.planId);
|
||||||
|
});
|
||||||
|
data = $.extend(data, {ids: planIds});
|
||||||
|
var op = async function() {
|
||||||
|
$.ajax({
|
||||||
|
url: '/admin/v1/manage/plan/batchOp',
|
||||||
|
method: 'POST',
|
||||||
|
data: data,
|
||||||
|
success: function () {
|
||||||
|
layer.msg('批量操作成功', {
|
||||||
|
offset: '15px',
|
||||||
|
icon: 1,
|
||||||
|
time: 1000
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
layui.table.reload('proxySettings', {
|
||||||
|
page: {
|
||||||
|
curr: $(".layui-laypage-em").next().html() //当前页码值
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
error: function (res) {
|
||||||
|
var r = res.responseJSON;
|
||||||
|
layer.msg(r&&r.data||'服务器错误', {
|
||||||
|
offset: '15px',
|
||||||
|
icon: 2,
|
||||||
|
time: 1000
|
||||||
|
});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
data.op != 'DELETE' ? op() : layer.confirm('确认批量删除吗?该操作不可恢复', function(){
|
||||||
|
op();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<th:block th:replace="~{admin/v1/manage/proxySetting/include::proxyExtra}"></th:block>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<div th:fragment="requestInfoExtra">
|
||||||
|
<script id="addRequestInfo" type="text/html">
|
||||||
|
<style>.layui-form-select dl{max-height: 160px}</style>
|
||||||
|
<div class="layui-form" style="margin:10px 15px" id="editRequestInfoForm" lay-filter="editRequestInfoForm">
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<input type="hidden" name="id"/>
|
||||||
|
<label class="layui-form-label">计划名称<span>*</span></label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="text" lay-verify="required" name="name" placeholder="" autocomplete="off" class="layui-input"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">匿名<span>*</span></label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="checkbox" name="isAnonymous" lay-skin="switch" lay-filter="isAnonymous" checked lay-text="ON|OFF">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item non-anonymous">
|
||||||
|
<label class="layui-form-label">用户名</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="text" name="username" placeholder="" class="layui-input"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item non-anonymous">
|
||||||
|
<label class="layui-form-label">密码</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="password" name="password" placeholder="" autocomplete="new-password" class="layui-input"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">鉴权信息</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="text" readonly name="authorization" placeholder="" autocomplete="off" class="layui-input"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">UID</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="text" readonly name="uid" placeholder="" autocomplete="off" class="layui-input"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">Android ID<span>*</span></label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="text" lay-verify="required" name="androidId" placeholder="" autocomplete="off" class="layui-input"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">设备名称<span>*</span></label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="text" lay-verify="required" name="deviceName" placeholder="" autocomplete="off" class="layui-input"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">指纹<span>*</span></label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="text" lay-verify="required" name="fingerprint" placeholder="" autocomplete="off" class="layui-input"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">Software Type<span>*</span></label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="text" lay-verify="required" name="softwareType" placeholder="" autocomplete="off" class="layui-input"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">Chrome 版本号<span>*</span></label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="text" lay-verify="required" name="chromeVersion" placeholder="" autocomplete="off" class="layui-input"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">益盟版本号<span>*</span></label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="text" lay-verify="required" name="emoneyVersion" placeholder="" autocomplete="off" class="layui-input"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">OkHttp UA<span>*</span></label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="text" lay-verify="required" name="okHttpUserAgent" placeholder="" autocomplete="off" class="layui-input"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">Emapp-ViewMode<span>*</span></label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="text" lay-verify="required" name="emappViewMode" placeholder="" autocomplete="off" class="layui-input"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display:none" class="layui-form-item">
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<button class="layui-btn" lay-submit="*" lay-filter="submitRequestInfo">提交</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
Helper.onSubmitForm('submitRequestInfo', '/admin/v1/manage/requestInfo/save');
|
||||||
|
function refreshAndroidId() {
|
||||||
|
let androidIdEl = document.querySelector('[name="androidId"]');
|
||||||
|
androidIdEl.value = Helper.randomHexString(16);
|
||||||
|
}
|
||||||
|
async function refreshDeviceInfo() {
|
||||||
|
let deviceNameEl = document.querySelector('[name="deviceName"]');
|
||||||
|
let fingerprintEl = document.querySelector('[name="fingerprint"]');
|
||||||
|
let softwareTypeEl = document.querySelector('[name="softwareType"]');
|
||||||
|
let json = await (await fetch('/admin/v1/config/emoneyRequest/getRandomDeviceInfo')).json();
|
||||||
|
|
||||||
|
if (!json.ok) {
|
||||||
|
Dog.error({msg: json.message || '获取随机设备信息失败'});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
deviceNameEl.value = json.data.model;
|
||||||
|
fingerprintEl.value = json.data.fingerprint;
|
||||||
|
softwareTypeEl.value = json.data.deviceType;
|
||||||
|
refreshAndroidId()
|
||||||
|
}
|
||||||
|
async function refreshChromeVersion() {
|
||||||
|
let chromeVersionEl = document.querySelector('[name="chromeVersion"]');
|
||||||
|
let json = await (await fetch('/admin/v1/config/emoneyRequest/getRandomChromeVersion')).json();
|
||||||
|
|
||||||
|
if (!json.ok) {
|
||||||
|
Dog.error({msg: json.message || '获取随机 Chrome Version 失败'});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
chromeVersionEl.value = json.data;
|
||||||
|
}
|
||||||
|
function openEditForm(r, done) {
|
||||||
|
if (done && typeof done === 'function') done(r);
|
||||||
|
if (r && r.ok) {
|
||||||
|
window.editLayer = Helper.openR({
|
||||||
|
title: `${r.data.id ? '编辑' : '新增'}请求信息`,
|
||||||
|
btn: ['随机设备信息', '随机 Chrome 版本', r.data.id ? '编辑' : '新增'],
|
||||||
|
btn1: () => refreshDeviceInfo() && !1,
|
||||||
|
btn2: () => refreshChromeVersion() && !1,
|
||||||
|
btn3: (_, o) => o.find('[lay-filter="submitRequestInfo"]').click() && !1,
|
||||||
|
content: $('#addRequestInfo').html(),
|
||||||
|
success: async function (layero, layerIndex) {
|
||||||
|
Helper.setLayerMainBtn(layero, -1);
|
||||||
|
var el = $(layero), extraSwitchFuncs = [];
|
||||||
|
// 覆写 isAnonymous
|
||||||
|
extraSwitchFuncs.isAnonymous = function (obj) {
|
||||||
|
const checked = obj.elem.value = obj.elem.checked;
|
||||||
|
const nonAnonymouses = document.querySelectorAll('.non-anonymous');
|
||||||
|
nonAnonymouses.forEach(non => {
|
||||||
|
if (!checked) {
|
||||||
|
// 非匿名,那 username/password 等就需要选必选
|
||||||
|
if (!non.querySelector('label>span')) {
|
||||||
|
$(non).children('label').append('<span>*</span>');
|
||||||
|
}
|
||||||
|
const input = $(non).find('input:first');
|
||||||
|
input.attr('lay-verify', 'required');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$(non).children('label').children('span').remove();
|
||||||
|
$(non).find('lay-verify').removeAttr('lay-verify');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Helper.fillEditForm(r, layero, layerIndex, extraSwitchFuncs);
|
||||||
|
layui.event.call(this, 'form', 'switch(isAnonymous)', {
|
||||||
|
elem: el[0].querySelector('[name="isAnonymous"]'),
|
||||||
|
value: r.data.isAnonymous == 'true' || r.data.isAnonymous == true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else Dog.error({
|
||||||
|
msg: r && r.data || '服务器错误'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async function openNewRequestInfoForm(id, done) {
|
||||||
|
const json = await (await fetch((() => {
|
||||||
|
const url = '/admin/v1/manage/requestInfo/getOne';
|
||||||
|
if (id) return url + '?id=' + id;
|
||||||
|
return url
|
||||||
|
})())).json();
|
||||||
|
if (json.ok) {
|
||||||
|
openEditForm(json, done)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
window.toastModule.errorLayer({
|
||||||
|
msg: json.data || '服务器错误'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layui.table.on('tool(requestInfos)', async function (obj) {
|
||||||
|
if (obj.event == 'edit') {
|
||||||
|
openNewRequestInfoForm(obj.data.id)
|
||||||
|
}
|
||||||
|
else if (obj.event == 'copy') {
|
||||||
|
openNewRequestInfoForm(obj.data.id, r => {
|
||||||
|
r.data.id = null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else if (obj.event == 'del') {
|
||||||
|
layui.layer.confirm('确定删除该请求配置吗?', function (index) {
|
||||||
|
layui.layer.close(index);
|
||||||
|
const load = layui.layer.load(2);
|
||||||
|
$.ajax({
|
||||||
|
url: '/admin/v1/manage/requestInfo/delete',
|
||||||
|
method: 'POST',
|
||||||
|
data: {id: obj.data.id},
|
||||||
|
success: () => Dog.success({
|
||||||
|
msg: '删除成功', onClose: () => Dog.reloadTable('requestInfos')
|
||||||
|
}),
|
||||||
|
error: res => Dog.error({msg: res}),
|
||||||
|
complete: () => layui.layer.close(load)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head th:insert="~{admin/v1/include::head}" th:with="title=${'计划任务管理'}">
|
||||||
|
</head>
|
||||||
|
<style>
|
||||||
|
.layui-form-item:after {
|
||||||
|
clear: unset;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<body>
|
||||||
|
<ul th:replace="~{admin/v1/include::nav}"></ul>
|
||||||
|
|
||||||
|
<div class="manage-body">
|
||||||
|
<div>
|
||||||
|
<h1 class="manage-title">
|
||||||
|
<b>请求信息管理</b><a href="javascript:openNewRequestInfoForm()"
|
||||||
|
class="operate">新增</a>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<button class="layui-btn layui-btn-sm operdown">
|
||||||
|
<span>选中项<i
|
||||||
|
class="layui-icon layui-icon-sm layui-icon-triangle-d"></i></span>
|
||||||
|
</button>
|
||||||
|
<div>
|
||||||
|
<table class="layui-table" id="requestInfos"
|
||||||
|
lay-filter="requestInfos">
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div th:replace="~{admin/v1/include::feet}"></div>
|
||||||
|
<script type="text/html" id="operationTpl">
|
||||||
|
<div class="layui-btn-group">
|
||||||
|
<a class="layui-btn layui-btn-xs" lay-event="copy">复制</a>
|
||||||
|
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="edit">编辑</a>
|
||||||
|
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
<th:block th:replace="~{admin/v1/include::head-script}"></th:block>
|
||||||
|
<script th:inline="javascript">
|
||||||
|
layui
|
||||||
|
.use(['table', 'form', 'dropdown', 'layer'], function(){
|
||||||
|
var dropdown = layui.dropdown, table = layui.table, form = layui.form;
|
||||||
|
Helper.renderTable({
|
||||||
|
elem: '#requestInfos',
|
||||||
|
baseUrl: '/admin/v1/manage/requestInfo',
|
||||||
|
idName: 'id',
|
||||||
|
cols: [ [
|
||||||
|
{type:'checkbox'},
|
||||||
|
{field:'id', width: 60, title: 'ID'},
|
||||||
|
{field:'name', title: '名称'},
|
||||||
|
{field:'isAnonymous', title: '匿名', width: 95, switchTemplet: true},
|
||||||
|
{field:'username', title: '用户名', width: 95},
|
||||||
|
{field:'uid', title: 'UID'},
|
||||||
|
{field:'androidVersion', title: '安卓版本'},
|
||||||
|
{field:'emoneyVersion', title: '益盟版本'},
|
||||||
|
{field:'operation', title: '操作', toolbar: '#operationTpl'}
|
||||||
|
]]
|
||||||
|
})
|
||||||
|
dropdown.render({
|
||||||
|
elem: '.operdown',
|
||||||
|
data: [
|
||||||
|
{title: '删除', op: 'DELETE'},
|
||||||
|
{title: '启用', op: 'enable'},
|
||||||
|
{title: '停用', op: 'disable'},
|
||||||
|
{title: '开启交易日校验', op: 'enableOpenDayCheck'},
|
||||||
|
{title: '关闭交易日校验', op: 'disableOpenDayCheck'}],
|
||||||
|
click: function (data, othis){
|
||||||
|
var checked = layui.table.checkStatus('requestInfos'), requestInfoIds = [];
|
||||||
|
if (!checked.data.length) {
|
||||||
|
Dog.error({msg: '未选中任何项', time: 1000});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$.each(checked.data, function (i, requestInfo){
|
||||||
|
requestInfoIds.push(requestInfo.id);
|
||||||
|
});
|
||||||
|
data = $.extend(data, {ids: requestInfoIds});
|
||||||
|
var op = function() {
|
||||||
|
$.ajax({
|
||||||
|
url: '/admin/v1/manage/requestInfo/batchOp',
|
||||||
|
method: 'POST',
|
||||||
|
data: data,
|
||||||
|
success: () => Dog.success({msg: '批量操作成功', onClose: () => Dog.reloadTable('plans')}),
|
||||||
|
error: res => Dog.error({msg: res, time: 1000})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
data.op !== 'DELETE' ? op() : layer.confirm('确认批量删除吗?该操作不可恢复', function(){
|
||||||
|
op();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<th:block
|
||||||
|
th:replace="~{/admin/v1/manage/requestInfo/include::requestInfoExtra}"></th:block>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import java.io.IOException;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
|
||||||
|
|
||||||
import quant.rich.emoney.util.VersionComparator;
|
|
||||||
|
|
||||||
public class WeibaPlayground {
|
|
||||||
|
|
||||||
private static final short[] XCUtil_short = new short[]{2176, 1289, 2869, 2868, 2854, 2893, 2904, 2219, 2231, 2231, 2227, 2297, 2284, 2284, 2228, 2214, 2218, 2209, 2210, 2285, 2215, 2230, 2220, 2216, 2210, 2218, 2234, 2210, 2285, 2208, 2220, 2222, 2284, 2221, 2214, 2228, 2227, 2219, 2220, 2221, 2214, 2285, 2227, 2219, 2227, 2300, 2218, 2215, 2302, 1330, 1378, 1393, 1382, 1321, 1971, 2022, 2044, 2034, 2043, 1960};
|
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
|
||||||
System.out.println(
|
|
||||||
((Object)"ۤۢۢ").hashCode()
|
|
||||||
|
|
||||||
);
|
|
||||||
System.out.println(decodeShort(XCUtil_short, 1, 1, 1397));
|
|
||||||
// (1745665457, MTc0NTY2NTQ1N3xkZjJkOThjYw==)
|
|
||||||
// FM%5C%29GMR%2BGMJ*G%2CqdScCdHMacRp%3D%3D
|
|
||||||
Path path = Path.of("E:\\eclipse-workspace\\emoney-auto\\conf\\system\\emoneyRequest.androidChromeVersions.json");
|
|
||||||
String str = Files.readString(path);
|
|
||||||
|
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
|
||||||
ArrayNode node = (ArrayNode) mapper.readTree(str);
|
|
||||||
|
|
||||||
List<String> ver = mapper.convertValue(node, new TypeReference<List<String>>() {});
|
|
||||||
ver.sort(VersionComparator.INSTANCE.reversed());
|
|
||||||
Set<String> verSet = new LinkedHashSet<>(ver);
|
|
||||||
ver = List.copyOf(verSet);
|
|
||||||
|
|
||||||
ArrayNode ja = mapper.valueToTree(verSet);
|
|
||||||
Files.writeString(path, ja.toPrettyString());
|
|
||||||
//System.out.println(ccc("1745665457", "MTc0NTY2NTQ1N3xkZjJkOThjYw=="));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String decodeShort(short[] sArr, int i, int i2, int i3) {
|
|
||||||
char[] cArr = new char[i2];
|
|
||||||
for (int i4 = 0; i4 < i2; i4++) {
|
|
||||||
cArr[i4] = (char) (sArr[i + i4] ^ i3);
|
|
||||||
}
|
|
||||||
return new String(cArr);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String ccc(String var0, String var1) throws UnsupportedEncodingException {
|
|
||||||
String decoded = new String(Base64.getDecoder().decode(var1)).trim();
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
// 获取时间戳最后一位作为减法因子(如 1745660488 → 8)
|
|
||||||
int offset = var0.charAt(9) - '0'; // var0 是时间戳
|
|
||||||
|
|
||||||
for (char ch : decoded.toCharArray()) {
|
|
||||||
if (ch == '=') continue;
|
|
||||||
sb.append((char)(ch - offset));
|
|
||||||
}
|
|
||||||
|
|
||||||
return URLEncoder.encode(sb.toString(), "UTF-8");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -233,7 +233,7 @@ public class EmoneyIndexScraper {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
Files.writeString(
|
Files.writeString(
|
||||||
Path.of("./conf/extra/nonParamsIndexDetail." + detail.getNameCode() + ".json"),
|
Path.of("./resources/conf/extra/nonParamsIndexDetail." + detail.getNameCode() + ".json"),
|
||||||
MAPPER.valueToTree(detail).toPrettyString());
|
MAPPER.valueToTree(detail).toPrettyString());
|
||||||
} catch (IllegalArgumentException | IOException e) {
|
} catch (IllegalArgumentException | IOException e) {
|
||||||
// TODO Auto-generated catch block
|
// TODO Auto-generated catch block
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import quant.rich.emoney.EmoneyAutoApplication;
|
|||||||
import quant.rich.emoney.client.EmoneyClient;
|
import quant.rich.emoney.client.EmoneyClient;
|
||||||
import quant.rich.emoney.client.WebviewClient;
|
import quant.rich.emoney.client.WebviewClient;
|
||||||
import quant.rich.emoney.client.WebviewClient.WebviewResponseWrapper;
|
import quant.rich.emoney.client.WebviewClient.WebviewResponseWrapper;
|
||||||
|
import quant.rich.emoney.util.SmartResourceResolver;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
@@ -21,6 +23,8 @@ import org.springframework.boot.test.context.SpringBootTest;
|
|||||||
import org.springframework.test.context.ContextConfiguration;
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.util.IOUtils;
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
@ContextConfiguration(classes = EmoneyAutoApplication.class)
|
@ContextConfiguration(classes = EmoneyAutoApplication.class)
|
||||||
@RunWith(SpringJUnit4ClassRunner.class)
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
@@ -32,7 +36,7 @@ public class RelativeEmoneyScraper {
|
|||||||
@Test
|
@Test
|
||||||
void test() throws Exception {
|
void test() throws Exception {
|
||||||
|
|
||||||
String js = Files.readString(Path.of("./conf/extra/indexJs.js"));
|
String js = IOUtils.toString(SmartResourceResolver.loadResource("./conf/extra/indexJs.js"), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
String jsArrayText;
|
String jsArrayText;
|
||||||
Matcher m = nonParamsIndexDetailPattern.matcher(js);
|
Matcher m = nonParamsIndexDetailPattern.matcher(js);
|
||||||
|
|||||||
38
src/test/java/quant/rich/SmartResourceLoaderTest.java
Normal file
38
src/test/java/quant/rich/SmartResourceLoaderTest.java
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package quant.rich;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import quant.rich.emoney.EmoneyAutoApplication;
|
||||||
|
import quant.rich.emoney.client.EmoneyClient;
|
||||||
|
import quant.rich.emoney.client.WebviewClient;
|
||||||
|
import quant.rich.emoney.client.WebviewClient.WebviewResponseWrapper;
|
||||||
|
import quant.rich.emoney.util.SmartResourceResolver;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import javax.script.ScriptEngine;
|
||||||
|
import javax.script.ScriptEngineManager;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
@ContextConfiguration(classes = EmoneyAutoApplication.class)
|
||||||
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
|
@Slf4j
|
||||||
|
public class SmartResourceLoaderTest {
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void test() throws Exception {
|
||||||
|
|
||||||
|
SmartResourceResolver.loadResource("/conf/system/proxy.json");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
package quant.rich;
|
package quant.rich;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -11,17 +10,32 @@ import java.util.regex.Matcher;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import org.graalvm.polyglot.*;
|
import org.graalvm.polyglot.*;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.util.IOUtils;
|
||||||
|
import quant.rich.emoney.entity.config.SmartViewWriter;
|
||||||
|
import quant.rich.emoney.pojo.dto.IndexDetail;
|
||||||
|
import quant.rich.emoney.pojo.dto.NonParamsIndexDetail;
|
||||||
|
import quant.rich.emoney.util.SmartResourceResolver;
|
||||||
|
|
||||||
|
|
||||||
public class TestIndexJsMatch {
|
public class TestIndexJsMatch {
|
||||||
|
|
||||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||||
|
private static final SmartViewWriter writer = new SmartViewWriter();
|
||||||
|
|
||||||
|
|
||||||
|
static {
|
||||||
|
MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException {
|
||||||
String script = Files.readString(Path.of("./conf/extra/indexJs.js"), StandardCharsets.UTF_8);
|
//String script = Files.readString(Path.of("./conf/extra/indexJs.js"), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
String script = IOUtils.toString(SmartResourceResolver.loadResource("./conf/extra/indexJs.js"), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
/** 一般来说如果要匹配,只会匹配出一个,但是为了保险起见还是做一个 List 来存储 */
|
/** 一般来说如果要匹配,只会匹配出一个,但是为了保险起见还是做一个 List 来存储 */
|
||||||
|
|
||||||
@@ -30,6 +44,16 @@ public class TestIndexJsMatch {
|
|||||||
System.out.println("✔ 匹配数组,共 " + array.size() + " 个对象");
|
System.out.println("✔ 匹配数组,共 " + array.size() + " 个对象");
|
||||||
for (JsonNode obj : array) {
|
for (JsonNode obj : array) {
|
||||||
System.out.println(obj.toPrettyString());
|
System.out.println(obj.toPrettyString());
|
||||||
|
if (!obj.has("nameCode")) continue;
|
||||||
|
String nameCode = obj.get("nameCode").asText();
|
||||||
|
// 载入已有本地文件填充 original 字段
|
||||||
|
String path = "./conf/extra/indexDetail/nonParams/" + nameCode + ".json";
|
||||||
|
InputStream stream = SmartResourceResolver.loadResource(path);
|
||||||
|
NonParamsIndexDetail detail = MAPPER.readValue(stream, NonParamsIndexDetail.class);
|
||||||
|
detail.setOriginal(obj.toString());
|
||||||
|
|
||||||
|
String joString = MAPPER.writerWithView(IndexDetail.class).writeValueAsString(detail);
|
||||||
|
SmartResourceResolver.saveText(path, joString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user