feat: 添加AOP支持和更新链接控制器逻辑
主要修改: 1. 在pom.xml中新增spring-boot-starter-aop依赖,支持面向切面编程。 2. 在LinkController中移除DeviceCodeMappingService的依赖,更新二维码获取逻辑,使用linkStatusService获取设备ID。 3. 在SelectRegionResponse中新增mecmachineId字段,便于调试和维护。 4. 在SecurityConfig中允许二维码HEAD请求公开访问。 技术细节: - 通过引入AOP支持,提升了代码的可维护性和扩展性,同时优化了链接控制器的逻辑,确保设备ID的获取更加灵活。
This commit is contained in:
@@ -113,6 +113,11 @@ public class SystemConfigService {
|
||||
return getConfigValue("device.idle_status", "空闲");
|
||||
}
|
||||
|
||||
// 获取首次选区后链接过期时间(秒)
|
||||
public Integer getFirstRegionExpireSeconds() {
|
||||
return getConfigValueAsInt("link.first_region_expire", 600);
|
||||
}
|
||||
|
||||
// 批量更新配置
|
||||
public boolean updateConfigs(List<SystemConfig> configs) {
|
||||
if (configs == null || configs.isEmpty()) {
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
package com.gameplatform.server.service.device;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* 设备代码映射服务
|
||||
* 用于隐藏真实设备编号,避免被恶意用户直接访问
|
||||
*/
|
||||
@Service
|
||||
public class DeviceCodeMappingService {
|
||||
private static final Logger log = LoggerFactory.getLogger(DeviceCodeMappingService.class);
|
||||
|
||||
// 代理code -> 真实设备编号的映射
|
||||
private final Map<String, String> codeToDeviceMap = new ConcurrentHashMap<>();
|
||||
// 真实设备编号 -> 代理code的映射
|
||||
private final Map<String, String> deviceToCodeMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 为设备创建代理code
|
||||
* @param deviceId 真实设备编号 (如 f1, ss9)
|
||||
* @return 代理code
|
||||
*/
|
||||
public String createProxyCode(String deviceId) {
|
||||
// 如果设备已经有代理code,则返回现有的
|
||||
if (deviceToCodeMap.containsKey(deviceId)) {
|
||||
String existingCode = deviceToCodeMap.get(deviceId);
|
||||
log.debug("设备 {} 已有代理code: {}", deviceId, existingCode);
|
||||
return existingCode;
|
||||
}
|
||||
|
||||
// 生成新的代理code
|
||||
String proxyCode = generateUniqueCode();
|
||||
|
||||
// 保存映射关系
|
||||
codeToDeviceMap.put(proxyCode, deviceId);
|
||||
deviceToCodeMap.put(deviceId, proxyCode);
|
||||
|
||||
log.info("为设备 {} 创建代理code: {}", deviceId, proxyCode);
|
||||
return proxyCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据代理code获取真实设备编号
|
||||
* @param proxyCode 代理code
|
||||
* @return 真实设备编号,如果不存在则返回null
|
||||
*/
|
||||
public String getDeviceId(String proxyCode) {
|
||||
String deviceId = codeToDeviceMap.get(proxyCode);
|
||||
if (deviceId != null) {
|
||||
log.debug("代理code {} 对应设备: {}", proxyCode, deviceId);
|
||||
} else {
|
||||
log.warn("无效的代理code: {}", proxyCode);
|
||||
}
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据设备编号获取代理code
|
||||
* @param deviceId 真实设备编号
|
||||
* @return 代理code,如果不存在则返回null
|
||||
*/
|
||||
public String getProxyCode(String deviceId) {
|
||||
String proxyCode = deviceToCodeMap.get(deviceId);
|
||||
if (proxyCode != null) {
|
||||
log.debug("设备 {} 对应代理code: {}", deviceId, proxyCode);
|
||||
}
|
||||
return proxyCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除设备的代理映射
|
||||
* @param deviceId 真实设备编号
|
||||
*/
|
||||
public void removeMapping(String deviceId) {
|
||||
String proxyCode = deviceToCodeMap.remove(deviceId);
|
||||
if (proxyCode != null) {
|
||||
codeToDeviceMap.remove(proxyCode);
|
||||
log.info("移除设备 {} 的代理映射: {}", deviceId, proxyCode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查代理code是否有效
|
||||
* @param proxyCode 代理code
|
||||
* @return true if valid
|
||||
*/
|
||||
public boolean isValidProxyCode(String proxyCode) {
|
||||
return codeToDeviceMap.containsKey(proxyCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前映射数量
|
||||
* @return 映射数量
|
||||
*/
|
||||
public int getMappingCount() {
|
||||
return codeToDeviceMap.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有映射
|
||||
*/
|
||||
public void clearAllMappings() {
|
||||
int count = codeToDeviceMap.size();
|
||||
codeToDeviceMap.clear();
|
||||
deviceToCodeMap.clear();
|
||||
log.info("清空所有设备代理映射, 共 {} 个", count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成唯一的代理code
|
||||
* 格式: CODE + 8位随机数字字母
|
||||
*/
|
||||
private String generateUniqueCode() {
|
||||
String code;
|
||||
do {
|
||||
code = "CODE" + generateRandomString(8);
|
||||
} while (codeToDeviceMap.containsKey(code));
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机字符串(数字+字母大写)
|
||||
*/
|
||||
private String generateRandomString(int length) {
|
||||
String chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < length; i++) {
|
||||
int index = ThreadLocalRandom.current().nextInt(chars.length());
|
||||
sb.append(chars.charAt(index));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gameplatform.server.service.external;
|
||||
|
||||
import com.gameplatform.server.annotation.RepeatCall;
|
||||
import com.gameplatform.server.model.dto.device.DeviceStatusResponse;
|
||||
import com.gameplatform.server.service.device.DeviceStatusService;
|
||||
import org.slf4j.Logger;
|
||||
@@ -59,6 +60,7 @@ public class ScriptClient {
|
||||
.retrieve()
|
||||
.bodyToMono(byte[].class)
|
||||
.timeout(Duration.ofSeconds(5))
|
||||
.retry(3) // 失败时重试3次
|
||||
.doOnError(e -> log.warn("ScriptClient.getQrPng error path={} err={}", path, e.toString()));
|
||||
}
|
||||
|
||||
@@ -75,6 +77,7 @@ public class ScriptClient {
|
||||
.retrieve()
|
||||
.bodyToMono(byte[].class)
|
||||
.timeout(Duration.ofSeconds(10))
|
||||
.retry(3) // 失败时重试3次
|
||||
.doOnSuccess(data -> log.debug("获取图片成功: path={}, 数据大小={}字节", path, data != null ? data.length : 0))
|
||||
.doOnError(e -> log.warn("获取图片失败: path={}, error={}", path, e.toString()));
|
||||
}
|
||||
@@ -86,6 +89,7 @@ public class ScriptClient {
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.timeout(Duration.ofSeconds(5))
|
||||
.retry(3) // 失败时重试3次
|
||||
.doOnError(e -> log.warn("ScriptClient.getText error path={} err={}", path, e.toString()));
|
||||
}
|
||||
|
||||
@@ -101,6 +105,7 @@ public class ScriptClient {
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.timeout(Duration.ofSeconds(10))
|
||||
.retry(3) // 失败时重试3次
|
||||
.doOnSuccess(result -> log.debug("检查空闲设备成功: {}", result))
|
||||
.doOnError(e -> log.warn("检查空闲设备失败: {}", e.toString()));
|
||||
}
|
||||
@@ -134,7 +139,16 @@ public class ScriptClient {
|
||||
* @param region 区域参数 (Q或V)
|
||||
* @return 选区结果
|
||||
*/
|
||||
@RepeatCall(times = 3, description = "选区操作")
|
||||
public Mono<String> selectRegion(String deviceId, String region) {
|
||||
if (deviceId == null || region == null) {
|
||||
log.warn("选区操作失败: 设备={}, 区域={}", deviceId, region);
|
||||
return Mono.just("选区操作失败");
|
||||
}
|
||||
if (!"Q".equals(region) && !"V".equals(region)) {
|
||||
log.warn("选区操作失败: 区域={} 必须是Q或V", region);
|
||||
return Mono.just("选区操作失败");
|
||||
}
|
||||
// 构建选区URL,使用设备编号作为参数名,区域作为参数值
|
||||
// 示例: {apiBaseUrl}/yijianwan_netfile/saveMsg?文件名=判断系统&f1=Q
|
||||
String url = String.format(apiBaseUrl + "/yijianwan_netfile/saveMsg?文件名=判断系统&%s=%s", deviceId, region);
|
||||
@@ -154,27 +168,14 @@ public class ScriptClient {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新操作
|
||||
*/
|
||||
public Mono<String> refresh(String codeNo) {
|
||||
String url = apiBaseUrl + "/yijianwan_netfile/saveMsg?文件名=判断刷新&编号=刷新";
|
||||
log.debug("刷新操作: codeNo={}, url={}", codeNo, url);
|
||||
return webClient.post()
|
||||
.uri(url)
|
||||
.accept(MediaType.TEXT_PLAIN)
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.timeout(Duration.ofSeconds(10))
|
||||
.doOnSuccess(result -> log.debug("刷新操作成功: codeNo={}, result={}", codeNo, result))
|
||||
.doOnError(e -> log.warn("刷新操作失败: codeNo={}, error={}", codeNo, e.toString()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断刷新接口 - 统一管理刷新判断逻辑
|
||||
*/
|
||||
public Mono<String> checkRefresh() {
|
||||
String url = apiBaseUrl + "/yijianwan_netfile/saveMsg?文件名=判断刷新&f4=刷新";
|
||||
@RepeatCall(times = 3, description = "判断刷新")
|
||||
public Mono<String> refresh(String machineId) {
|
||||
String url = String.format(apiBaseUrl + "/yijianwan_netfile/saveMsg?文件名=判断刷新&%s=刷新", machineId);
|
||||
log.info("调用判断刷新接口: {}", url);
|
||||
return webClient.get()
|
||||
.uri(url)
|
||||
@@ -203,6 +204,7 @@ public class ScriptClient {
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.timeout(Duration.ofSeconds(10))
|
||||
.retry(3) // 失败时重试3次
|
||||
.doOnSuccess(result -> log.debug("检查设备上号状态成功: 设备={}, 状态={}", deviceId, result))
|
||||
.doOnError(e -> log.warn("检查设备上号状态失败: 设备={}, 错误={}", deviceId, e.toString()));
|
||||
}
|
||||
@@ -275,6 +277,7 @@ public class ScriptClient {
|
||||
.retrieve()
|
||||
.bodyToMono(byte[].class)
|
||||
.timeout(Duration.ofSeconds(10))
|
||||
.retry(3) // 失败时重试3次
|
||||
.doOnSuccess(data -> {
|
||||
log.debug("获取设备二维码成功: 设备={}, 数据大小={}字节", deviceId, data.length);
|
||||
})
|
||||
@@ -286,33 +289,35 @@ public class ScriptClient {
|
||||
/**
|
||||
* 获取目标分数
|
||||
*/
|
||||
public Mono<String> getTargetScore(String codeNo) {
|
||||
String url = String.format(apiBaseUrl + "/yijianwan_netfile/readMsg?文件名=判断分数&对象名=%s", codeNo);
|
||||
log.debug("获取目标分数: codeNo={}, url={}", codeNo, url);
|
||||
public Mono<String> getTargetScore(String machineId) {
|
||||
String url = String.format(apiBaseUrl + "/yijianwan_netfile/readMsg?文件名=判断分数&对象名=%s", machineId);
|
||||
log.debug("获取目标分数: machineId={}, url={}", machineId, url);
|
||||
return webClient.get()
|
||||
.uri(url)
|
||||
.accept(MediaType.TEXT_PLAIN)
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.timeout(Duration.ofSeconds(5))
|
||||
.doOnSuccess(result -> log.debug("获取目标分数成功: codeNo={}, result={}", codeNo, result))
|
||||
.doOnError(e -> log.warn("获取目标分数失败: codeNo={}, error={}", codeNo, e.toString()));
|
||||
.retry(3) // 失败时重试3次
|
||||
.doOnSuccess(result -> log.debug("获取目标分数成功: machineId={}, result={}", machineId, result))
|
||||
.doOnError(e -> log.warn("获取目标分数失败: machineId={}, error={}", machineId, e.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置次数(生成链接时调用)
|
||||
* 设置次数(生成链接时调用)- 使用 @RepeatCall 注解自动重复执行3次
|
||||
*/
|
||||
public Mono<String> setTimes(String codeNo, int times) {
|
||||
String url = String.format(apiBaseUrl + "/yijianwan_netfile/saveMsg?文件名=总次数&编号=%d", times);
|
||||
log.debug("设置次数: codeNo={}, times={}, url={}", codeNo, times, url);
|
||||
@RepeatCall(times = 3, description = "设置次数")
|
||||
public Mono<String> setTimes(String machineId, int times) {
|
||||
String url = String.format(apiBaseUrl + "/yijianwan_netfile/saveMsg?文件名=总次数&%s=%d", machineId, times);
|
||||
log.debug("设置次数: machineId={}, times={}, url={}", machineId, times, url);
|
||||
return webClient.post()
|
||||
.uri(url)
|
||||
.accept(MediaType.TEXT_PLAIN)
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.timeout(Duration.ofSeconds(10))
|
||||
.doOnSuccess(result -> log.debug("设置次数成功: codeNo={}, times={}, result={}", codeNo, times, result))
|
||||
.doOnError(e -> log.warn("设置次数失败: codeNo={}, times={}, error={}", codeNo, times, e.toString()));
|
||||
.doOnSuccess(result -> log.debug("设置次数成功: machineId={}, times={}, result={}", machineId, times, result))
|
||||
.doOnError(e -> log.warn("设置次数失败: machineId={}, times={}, error={}", machineId, times, e.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -320,8 +325,10 @@ public class ScriptClient {
|
||||
* @param times 总次数
|
||||
* @return 保存结果
|
||||
*/
|
||||
public Mono<String> saveTotalTimes(int times) {
|
||||
String url = String.format(apiBaseUrl + "/yijianwan_netfile/saveMsg?文件名=总次数&f4=%d", times);
|
||||
@RepeatCall(times = 3, description = "保存总次数")
|
||||
public Mono<String> saveTotalTimes(String meachainId,int times) {
|
||||
String url = String.format(apiBaseUrl + "/yijianwan_netfile/saveMsg?文件名=总次数&%s=%d", meachainId,times);
|
||||
log.info("保存刷副本的次数的url: {}", url);
|
||||
log.info("开始调用保存总次数接口: times={}, url={}", times, url);
|
||||
return webClient.get()
|
||||
.uri(url)
|
||||
@@ -334,6 +341,7 @@ public class ScriptClient {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* 获取特定设备的状态信息(用于检查是否完成游戏)
|
||||
* @param machineId 设备ID
|
||||
* @return 设备状态信息的Map,包含f0(点数)和f1(状态)等信息
|
||||
@@ -348,6 +356,7 @@ public class ScriptClient {
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.timeout(Duration.ofSeconds(10))
|
||||
.retry(3) // 失败时重试3次
|
||||
.map(jsonResponse -> {
|
||||
// 解析JSON响应,提取指定设备的状态信息
|
||||
return deviceStatusService.parseDeviceStatusForMachine(jsonResponse, machineId);
|
||||
@@ -365,8 +374,9 @@ public class ScriptClient {
|
||||
* @param machineId 真实设备编号 (如 f1, ss9)
|
||||
* @return 退单操作结果
|
||||
*/
|
||||
@RepeatCall(times = 3, description = "退单操作")
|
||||
public Mono<String> refundOrder(String machineId) {
|
||||
String url = String.format(apiBaseUrl + "/yijianwan_netfile/saveMsg?文件名=判断退单&cc2=%s", machineId);
|
||||
String url = String.format(apiBaseUrl + "/yijianwan_netfile/saveMsg?文件名=判断退单&%s=T", machineId);
|
||||
log.info("调用退单接口: 设备={}, url={}", machineId, url);
|
||||
|
||||
return webClient.get()
|
||||
|
||||
@@ -12,8 +12,8 @@ import com.gameplatform.server.model.entity.agent.LinkBatch;
|
||||
import com.gameplatform.server.model.entity.agent.LinkTask;
|
||||
|
||||
import com.gameplatform.server.service.external.ScriptClient;
|
||||
import com.gameplatform.server.service.device.DeviceCodeMappingService;
|
||||
import com.gameplatform.server.service.device.DeviceStatusCheckService;
|
||||
import com.gameplatform.server.service.admin.SystemConfigService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -37,8 +37,8 @@ public class LinkStatusService {
|
||||
private final LinkTaskMapper linkTaskMapper;
|
||||
private final LinkBatchMapper linkBatchMapper;
|
||||
private final ScriptClient scriptClient;
|
||||
private final DeviceCodeMappingService deviceCodeMappingService;
|
||||
private final DeviceStatusCheckService deviceStatusCheckService;
|
||||
private final SystemConfigService systemConfigService;
|
||||
|
||||
|
||||
// 状态描述映射
|
||||
@@ -52,14 +52,14 @@ public class LinkStatusService {
|
||||
STATUS_DESC_MAP.put("EXPIRED", "已过期");
|
||||
}
|
||||
|
||||
public LinkStatusService(LinkTaskMapper linkTaskMapper, LinkBatchMapper linkBatchMapper,
|
||||
ScriptClient scriptClient, DeviceCodeMappingService deviceCodeMappingService,
|
||||
DeviceStatusCheckService deviceStatusCheckService) {
|
||||
public LinkStatusService(LinkTaskMapper linkTaskMapper, LinkBatchMapper linkBatchMapper,
|
||||
ScriptClient scriptClient,
|
||||
DeviceStatusCheckService deviceStatusCheckService, SystemConfigService systemConfigService) {
|
||||
this.linkTaskMapper = linkTaskMapper;
|
||||
this.linkBatchMapper = linkBatchMapper;
|
||||
this.scriptClient = scriptClient;
|
||||
this.deviceCodeMappingService = deviceCodeMappingService;
|
||||
this.deviceStatusCheckService = deviceStatusCheckService;
|
||||
this.systemConfigService = systemConfigService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -515,9 +515,12 @@ public class LinkStatusService {
|
||||
private void performAutoRefresh(LinkTask linkTask) {
|
||||
try {
|
||||
log.info("开始执行刷新操作");
|
||||
|
||||
if (linkTask.getMachineId() == null) {
|
||||
log.warn("机器ID为空,无法执行刷新操作");
|
||||
return;
|
||||
}
|
||||
// 调用判断刷新接口(通过ScriptClient统一管理)
|
||||
String refreshResult = scriptClient.checkRefresh().block();
|
||||
String refreshResult = scriptClient.refresh(linkTask.getMachineId()).block();
|
||||
log.info("判断刷新接口调用完成: result={}", refreshResult);
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -588,56 +591,76 @@ public class LinkStatusService {
|
||||
throw new IllegalArgumentException("链接状态不正确,只有新建或使用中状态的链接才能选区");
|
||||
}
|
||||
|
||||
// 4. 检查链接是否过期
|
||||
if (linkTask.getExpireAt() != null && linkTask.getExpireAt().isBefore(LocalDateTime.now())) {
|
||||
log.error("链接已过期: expireAt={}", linkTask.getExpireAt());
|
||||
throw new IllegalArgumentException("链接已过期");
|
||||
}
|
||||
|
||||
// 5. 如果need_refresh=true,检查是否已等待10秒
|
||||
if (Boolean.TRUE.equals(linkTask.getNeedRefresh()) && linkTask.getRefreshTime() != null) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
long secondsSinceRefresh = ChronoUnit.SECONDS.between(linkTask.getRefreshTime(), now);
|
||||
if (secondsSinceRefresh < 10) {
|
||||
long waitTime = 10 - secondsSinceRefresh;
|
||||
log.error("刷新后需要等待,剩余等待时间: {}秒", waitTime);
|
||||
SelectRegionResponse response = new SelectRegionResponse(false, "刷新后需要等待" + waitTime + "秒才能选区");
|
||||
return response;
|
||||
// if (Boolean.TRUE.equals(linkTask.getNeedRefresh()) && linkTask.getRefreshTime() != null) {
|
||||
// LocalDateTime now = LocalDateTime.now();
|
||||
// long secondsSinceRefresh = ChronoUnit.SECONDS.between(linkTask.getRefreshTime(), now);
|
||||
// if (secondsSinceRefresh < 10) {
|
||||
// long waitTime = 10 - secondsSinceRefresh;
|
||||
// log.error("刷新后需要等待,剩余等待时间: {}秒", waitTime);
|
||||
// SelectRegionResponse response = new SelectRegionResponse(false, "刷新后需要等待" + waitTime + "秒才能选区");
|
||||
// return response;
|
||||
// }
|
||||
// }
|
||||
String selectedDeviceId;
|
||||
if(linkTask.getFirstRegionSelectAt() == null){
|
||||
log.info("开始检查空闲设备");
|
||||
DeviceStatusResponse deviceStatus = scriptClient.checkAvailableDeviceStatus().block();
|
||||
|
||||
// 检查是否有空闲设备
|
||||
if (deviceStatus.getAvailableCount() == 0) {
|
||||
log.warn("当前没有空闲设备,无法选择区域");
|
||||
throw new RuntimeException("当前没有空闲设备,请稍后再试");
|
||||
}
|
||||
}
|
||||
|
||||
log.info("空闲设备检查完成: 总设备数={}, 空闲设备数={}, 空闲设备列表={}",
|
||||
deviceStatus.getTotalDevices(), deviceStatus.getAvailableCount(), deviceStatus.getAvailableDevices());
|
||||
|
||||
// 7. 选择一个空闲设备 TODO
|
||||
selectedDeviceId = deviceStatus.getAvailableDevices().get(0); // 选择第一个空闲设备
|
||||
// String selectedDevice = "cc2";
|
||||
log.info("从空闲设备列表中选择设备: {}", selectedDeviceId);
|
||||
log.info("设备选择详情: 可用设备总数={}, 选择了第一个设备={}",
|
||||
deviceStatus.getAvailableDevices().size(), selectedDeviceId);
|
||||
}else{
|
||||
// 检查首次选区是否已过期
|
||||
LocalDateTime firstSelectTime = linkTask.getFirstRegionSelectAt();
|
||||
long expireSeconds = systemConfigService.getFirstRegionExpireSeconds();
|
||||
LocalDateTime expireTime = firstSelectTime.plusSeconds(expireSeconds);
|
||||
|
||||
if (LocalDateTime.now().isAfter(expireTime)) {
|
||||
log.warn("链接首次选区已过期: firstSelectTime={}, expireTime={}, now={}",
|
||||
firstSelectTime, expireTime, LocalDateTime.now());
|
||||
|
||||
// 将链接状态设置为过期
|
||||
linkTask.setStatus("EXPIRED");
|
||||
linkTask.setUpdatedAt(LocalDateTime.now());
|
||||
linkTaskMapper.updateById(linkTask);
|
||||
log.info("链接状态已更新为EXPIRED: linkTaskId={}", linkTask.getId());
|
||||
|
||||
throw new RuntimeException("链接已过期,请重新获取");
|
||||
}
|
||||
|
||||
selectedDeviceId = linkTask.getMachineId();
|
||||
scriptClient.refresh(selectedDeviceId).block();
|
||||
log.info("链接已选过区且未过期,继续使用之前的设备: {}", selectedDeviceId);
|
||||
}
|
||||
// 6. 检查空闲设备
|
||||
log.info("开始检查空闲设备");
|
||||
DeviceStatusResponse deviceStatus = scriptClient.checkAvailableDeviceStatus().block();
|
||||
|
||||
// 检查是否有空闲设备
|
||||
if (deviceStatus.getAvailableCount() == 0) {
|
||||
log.warn("当前没有空闲设备,无法选择区域");
|
||||
throw new RuntimeException("当前没有空闲设备,请稍后再试");
|
||||
}
|
||||
|
||||
log.info("空闲设备检查完成: 总设备数={}, 空闲设备数={}, 空闲设备列表={}",
|
||||
deviceStatus.getTotalDevices(), deviceStatus.getAvailableCount(), deviceStatus.getAvailableDevices());
|
||||
|
||||
// 7. 选择一个空闲设备 TODO
|
||||
String selectedDevice = deviceStatus.getAvailableDevices().get(0); // 选择第一个空闲设备
|
||||
// String selectedDevice = "cc2";
|
||||
log.info("从空闲设备列表中选择设备: {}", selectedDevice);
|
||||
log.info("设备选择详情: 可用设备总数={}, 选择了第一个设备={}",
|
||||
deviceStatus.getAvailableDevices().size(), selectedDevice);
|
||||
|
||||
// 7.5. 检查该设备是否有之前的LOGGED_IN状态链接任务需要完成
|
||||
try {
|
||||
log.info("检查设备 {} 是否有需要完成的链接任务", selectedDevice);
|
||||
deviceStatusCheckService.checkDeviceStatusAndUpdateTasks(selectedDevice, "选区请求");
|
||||
log.info("检查设备 {} 是否有需要完成的链接任务", selectedDeviceId);
|
||||
deviceStatusCheckService.checkDeviceStatusAndUpdateTasks(selectedDeviceId, "选区请求");
|
||||
} catch (Exception e) {
|
||||
log.warn("检查设备状态时发生异常,继续选区流程: {}", e.getMessage());
|
||||
// 不影响选区流程,只记录警告日志
|
||||
}
|
||||
|
||||
// 8. 调用保存总次数接口
|
||||
// 8. 调用保存总次数接口 TODO 应该放在登录成功之后
|
||||
try {
|
||||
scriptClient.saveTotalTimes(linkBatch.getTimes()).block();
|
||||
scriptClient.saveTotalTimes(selectedDeviceId,linkBatch.getTimes()).block();
|
||||
// saveTotalTimes方法已经包含了详细的日志记录
|
||||
} catch (Exception e) {
|
||||
log.warn("保存总次数接口调用失败: {}", e.getMessage());
|
||||
@@ -645,52 +668,48 @@ public class LinkStatusService {
|
||||
}
|
||||
|
||||
// 9. 为选中的设备创建代理code
|
||||
String proxyCode = deviceCodeMappingService.createProxyCode(selectedDevice);
|
||||
log.info("为设备 {} 创建代理code: {}", selectedDevice, proxyCode);
|
||||
// String proxyCode = deviceCodeMappingService.createProxyCode(selectedDeviceId);
|
||||
// log.info("为设备 {} 创建代理code: {}", selectedDeviceId, proxyCode);
|
||||
|
||||
// 10. 调用脚本端选区,使用选中的设备
|
||||
log.info("开始调用脚本端选区,设备={}, 区域={}", selectedDevice, region);
|
||||
String selectResult = scriptClient.selectRegion(selectedDevice, region).block();
|
||||
log.info("开始调用脚本端选区,设备={}, 区域={}", selectedDeviceId, region);
|
||||
String selectResult = scriptClient.selectRegion(selectedDeviceId, region).block();
|
||||
log.info("脚本端选区结果: {}", selectResult);
|
||||
|
||||
// 11. 等待脚本端生成二维码(这里可以添加轮询逻辑)
|
||||
log.info("等待脚本端生成二维码,等待3秒...");
|
||||
Thread.sleep(3000);
|
||||
// log.info("等待脚本端生成二维码,等待3秒...");
|
||||
// Thread.sleep(3000);
|
||||
|
||||
// 12. 更新数据库状态为USING,保存设备信息和代理code
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
linkTask.setStatus("USING");
|
||||
linkTask.setRegion(region);
|
||||
linkTask.setCodeNo(proxyCode); // 使用代理code替换原来的codeNo
|
||||
linkTask.setCodeNo(code); // 使用代理code替换原来的codeNo
|
||||
linkTask.setQrCreatedAt(now);
|
||||
linkTask.setQrExpireAt(now.plusSeconds(60)); // 60秒后过期
|
||||
linkTask.setQrExpireAt(now.plusSeconds(60)); // 60秒后过期 ToDO
|
||||
linkTask.setFirstRegionSelectAt(now); // 记录首次选区时间
|
||||
linkTask.setNeedRefresh(false);
|
||||
linkTask.setUpdatedAt(now);
|
||||
// 在machineId字段保存真实设备编号,便于调试和维护
|
||||
linkTask.setMachineId(selectedDevice);
|
||||
linkTask.setMachineId(selectedDeviceId);
|
||||
linkTaskMapper.update(linkTask);
|
||||
|
||||
log.info("链接状态更新成功: status=USING, region={}, proxyCode={}, device={}", region, proxyCode, selectedDevice);
|
||||
log.info("链接状态更新成功: status=USING, region={}, proxyCode={}, device={}", region, code, selectedDeviceId);
|
||||
|
||||
// 13. 构建响应
|
||||
SelectRegionResponse response = new SelectRegionResponse(true, "选区成功");
|
||||
response.setQrCodeUrl(scriptClient.getProxyQrCodeUrl(proxyCode));
|
||||
response.setQrCodeUrl(scriptClient.getProxyQrCodeUrl(code));
|
||||
response.setQrCreatedAt(now);
|
||||
response.setQrExpireAt(linkTask.getQrExpireAt());
|
||||
response.setStatus("USING");
|
||||
// 不返回选区字段:response.setRegion(region);
|
||||
response.setQrDelaySeconds(5); // 客户端收到响应后,等待5秒再请求二维码
|
||||
|
||||
response.setMecmachineId(selectedDeviceId); // 便于调试和维护
|
||||
log.info("=== 选区操作完成 ===");
|
||||
log.info("二维码URL: {}", response.getQrCodeUrl());
|
||||
|
||||
return response;
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.error("选区操作被中断: {}", e.getMessage());
|
||||
throw new RuntimeException("选区操作被中断", e);
|
||||
} catch (Exception e) {
|
||||
log.error("=== 选区操作失败 ===");
|
||||
log.error("错误详情: {}", e.getMessage(), e);
|
||||
@@ -705,13 +724,34 @@ public class LinkStatusService {
|
||||
log.info("=== 开始轮询上号 ===");
|
||||
log.info("code: {}", code);
|
||||
|
||||
return validatePollLoginRequest(code)
|
||||
.flatMap(result -> {
|
||||
if (result instanceof PollLoginResponse) {
|
||||
// 如果验证返回响应对象,直接返回
|
||||
return Mono.just((PollLoginResponse) result);
|
||||
}
|
||||
// 否则继续处理登录状态检查
|
||||
return checkAndHandleLoginStatus((LinkTask) result);
|
||||
})
|
||||
.doOnSuccess(response -> {
|
||||
log.info("轮询上号完成: success={}, status={}", response.isSuccess(), response.getStatus());
|
||||
})
|
||||
.doOnError(error -> {
|
||||
log.error("轮询上号失败: code={}, error={}", code, error.getMessage(), error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证轮询上号请求参数和链接状态
|
||||
*/
|
||||
private Mono<Object> validatePollLoginRequest(String code) {
|
||||
return Mono.fromCallable(() -> {
|
||||
// 1. 验证code参数
|
||||
// 验证code参数
|
||||
if (code == null || code.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("参数错误:code不能为空");
|
||||
}
|
||||
|
||||
// 2. 获取链接任务
|
||||
// 获取链接任务
|
||||
LinkTask linkTask = linkTaskMapper.findByCodeNo(code);
|
||||
if (linkTask == null) {
|
||||
throw new IllegalArgumentException("链接不存在");
|
||||
@@ -720,83 +760,128 @@ public class LinkStatusService {
|
||||
log.info("找到链接任务: id={}, status={}, codeNo={}",
|
||||
linkTask.getId(), linkTask.getStatus(), linkTask.getCodeNo());
|
||||
|
||||
// 3. 检查链接状态,只有USING状态才能轮询
|
||||
// 检查链接状态,只有USING状态才能轮询
|
||||
if (!"USING".equals(linkTask.getStatus())) {
|
||||
log.warn("链接状态不是USING,当前状态: {}", linkTask.getStatus());
|
||||
return new PollLoginResponse(false, linkTask.getStatus());
|
||||
}
|
||||
|
||||
return linkTask;
|
||||
})
|
||||
.subscribeOn(Schedulers.boundedElastic())
|
||||
.flatMap(result -> {
|
||||
if (result instanceof PollLoginResponse) {
|
||||
// 如果已经是响应对象,直接返回
|
||||
return Mono.just((PollLoginResponse) result);
|
||||
}).subscribeOn(Schedulers.boundedElastic());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务对应的设备ID
|
||||
*/
|
||||
private String getDeviceIdForTask(LinkTask linkTask) {
|
||||
if (linkTask.getMachineId() != null) {
|
||||
// 如果已保存了设备编号,直接使用
|
||||
return linkTask.getMachineId();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查登录状态并处理结果
|
||||
*/
|
||||
private Mono<PollLoginResponse> checkAndHandleLoginStatus(LinkTask linkTask) {
|
||||
// 获取真实设备编号
|
||||
String realDeviceId = getDeviceIdForTask(linkTask);
|
||||
|
||||
if (realDeviceId == null) {
|
||||
log.error("无法获取设备编号: codeNo={}, machineId={}", linkTask.getCodeNo(), linkTask.getMachineId());
|
||||
return Mono.error(new RuntimeException("设备信息异常,无法检查上号状态"));
|
||||
}
|
||||
|
||||
// 调用脚本端检查上号状态
|
||||
log.info("调用脚本端检查上号状态: 代理code={}, 真实设备={}", linkTask.getCodeNo(), realDeviceId);
|
||||
|
||||
return scriptClient.checkLoginStatus(realDeviceId)
|
||||
.map(loginResult -> processLoginResult(linkTask, realDeviceId, loginResult))
|
||||
.onErrorResume(error -> {
|
||||
log.warn("调用脚本端检查上号状态失败: codeNo={}, error={}",
|
||||
linkTask.getCodeNo(), error.getMessage());
|
||||
// 脚本端出错时,返回当前状态,不影响轮询
|
||||
return Mono.just(new PollLoginResponse(false, "USING"));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理登录检查结果
|
||||
*/
|
||||
private PollLoginResponse processLoginResult(LinkTask linkTask, String deviceId, String loginResult) {
|
||||
log.info("脚本端返回结果: {}", loginResult);
|
||||
|
||||
// 检查是否已上号
|
||||
if ("已上号".equals(loginResult) || "已登录".equals(loginResult)) {
|
||||
try {
|
||||
LinkBatch linkBatch = linkBatchMapper.findById(linkTask.getBatchId());
|
||||
scriptClient.saveTotalTimes(deviceId,linkBatch.getTimes()).block();
|
||||
// saveTotalTimes方法已经包含了详细的日志记录
|
||||
} catch (Exception e) {
|
||||
log.warn("保存总次数接口调用失败: {}", e.getMessage());
|
||||
// 不影响后续流程,只记录警告日志
|
||||
}
|
||||
|
||||
LinkTask linkTask = (LinkTask) result;
|
||||
|
||||
// 4. 获取真实设备编号 - 从代理code映射中获取
|
||||
final String realDeviceId;
|
||||
if (linkTask.getMachineId() != null) {
|
||||
// 如果已保存了设备编号,直接使用
|
||||
realDeviceId = linkTask.getMachineId();
|
||||
} else if (linkTask.getCodeNo() != null) {
|
||||
// 如果没有设备编号,尝试从代理code获取
|
||||
realDeviceId = deviceCodeMappingService.getDeviceId(linkTask.getCodeNo());
|
||||
} else {
|
||||
realDeviceId = null;
|
||||
return handleSuccessfulLogin(linkTask, deviceId);
|
||||
} else {
|
||||
// 未上号,返回当前状态
|
||||
log.debug("尚未上号,返回当前状态");
|
||||
return new PollLoginResponse(false, "USING");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理成功登录的情况
|
||||
*/
|
||||
private PollLoginResponse handleSuccessfulLogin(LinkTask linkTask, String deviceId) {
|
||||
log.info("检测到已上号,更新状态为LOGGED_IN");
|
||||
|
||||
// 更新数据库状态
|
||||
linkTask.setStatus("LOGGED_IN");
|
||||
linkTask.setUpdatedAt(LocalDateTime.now());
|
||||
linkTaskMapper.updateById(linkTask);
|
||||
|
||||
log.info("状态更新完成: codeNo={}, status=LOGGED_IN", linkTask.getCodeNo());
|
||||
|
||||
// 构建成功响应
|
||||
PollLoginResponse response = new PollLoginResponse(true, "LOGGED_IN", "SECOND",
|
||||
new PollLoginResponse.AssetsInfo(
|
||||
scriptClient.getAssetsBaseUrl(deviceId)
|
||||
));
|
||||
|
||||
log.info("=== 轮询上号成功 ===");
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据链接编号获取设备ID
|
||||
* @param code 链接编号
|
||||
* @return 设备ID,如果链接不存在或未关联设备则返回null
|
||||
*/
|
||||
public String getMechainIdByCode(String code) {
|
||||
if (code == null || code.trim().isEmpty()) {
|
||||
log.warn("获取设备ID失败: code参数为空");
|
||||
return null;
|
||||
}
|
||||
|
||||
log.debug("根据code获取设备ID: code={}", code);
|
||||
|
||||
try {
|
||||
// 查询链接任务
|
||||
LinkTask linkTask = linkTaskMapper.findByCodeNo(code.trim());
|
||||
if (linkTask == null) {
|
||||
log.warn("链接任务不存在: code={}", code);
|
||||
return null;
|
||||
}
|
||||
|
||||
String machineId = linkTask.getMachineId();
|
||||
log.debug("查询到设备ID: code={}, machineId={}", code, machineId);
|
||||
|
||||
if (realDeviceId == null) {
|
||||
log.error("无法获取设备编号: codeNo={}, machineId={}", linkTask.getCodeNo(), linkTask.getMachineId());
|
||||
throw new RuntimeException("设备信息异常,无法检查上号状态");
|
||||
}
|
||||
return machineId;
|
||||
|
||||
// 5. 调用脚本端检查上号状态,使用真实设备编号
|
||||
log.info("调用脚本端检查上号状态: 代理code={}, 真实设备={}", linkTask.getCodeNo(), realDeviceId);
|
||||
return scriptClient.checkLoginStatus(realDeviceId)
|
||||
.map(loginResult -> {
|
||||
log.info("脚本端返回结果: {}", loginResult);
|
||||
|
||||
// 6. 如果返回"已上号",更新状态为LOGGED_IN
|
||||
if ("已上号".equals(loginResult) || "已登录".equals(loginResult)) {
|
||||
log.info("检测到已上号,更新状态为LOGGED_IN");
|
||||
|
||||
// 更新数据库状态
|
||||
linkTask.setStatus("LOGGED_IN");
|
||||
linkTask.setUpdatedAt(LocalDateTime.now());
|
||||
linkTaskMapper.updateById(linkTask);
|
||||
|
||||
log.info("状态更新完成: codeNo={}, status=LOGGED_IN", linkTask.getCodeNo());
|
||||
|
||||
// 7. 返回成功响应和资源信息,使用ScriptClient统一管理资源链接
|
||||
PollLoginResponse response = new PollLoginResponse(true, "LOGGED_IN", "SECOND",
|
||||
new PollLoginResponse.AssetsInfo(
|
||||
scriptClient.getAssetsBaseUrl(realDeviceId)
|
||||
));
|
||||
|
||||
log.info("=== 轮询上号成功 ===");
|
||||
return response;
|
||||
} else {
|
||||
// 未上号,返回当前状态
|
||||
log.debug("尚未上号,返回当前状态");
|
||||
return new PollLoginResponse(false, "USING");
|
||||
}
|
||||
})
|
||||
.onErrorResume(error -> {
|
||||
log.warn("调用脚本端检查上号状态失败: codeNo={}, error={}",
|
||||
linkTask.getCodeNo(), error.getMessage());
|
||||
// 脚本端出错时,返回当前状态,不影响轮询
|
||||
return Mono.just(new PollLoginResponse(false, "USING"));
|
||||
});
|
||||
})
|
||||
.doOnSuccess(response -> {
|
||||
log.info("轮询上号完成: success={}, status={}", response.isSuccess(), response.getStatus());
|
||||
})
|
||||
.doOnError(error -> {
|
||||
log.error("轮询上号失败: code={}, error={}", code, error.getMessage(), error);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("根据code获取设备ID时发生异常: code={}, error={}", code, e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user