feat: 增强二维码和图片代理功能
主要修改: 1. 在QrProxyController中新增多个图片代理接口,包括首页、首次赏金、中途赏金和结束赏金图片的获取。 2. 更新LinkController中的链接状态查询逻辑,简化日志输出。 3. 在LinkStatusService中优化链接状态处理逻辑,增加对USING状态的过期检查。 4. 在ScriptClient中新增通用图片获取方法,支持从脚本端获取图片数据。 5. 更新SecurityConfig,允许公开访问二维码和游戏界面数据接口。 技术细节: - 新增GameInterfaceResponse DTO以支持游戏界面数据的返回格式。 - 通过脚本端接口实现图片的动态获取和链接状态的自动刷新。
This commit is contained in:
34
docs/database_migration_add_first_region_select_time.sql
Normal file
34
docs/database_migration_add_first_region_select_time.sql
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
-- 数据库迁移脚本:为link_task表添加首次选区时间字段
|
||||||
|
-- 执行时间:2025-01-XX
|
||||||
|
-- 说明:记录链接首次选区的准确时间,便于跟踪和分析
|
||||||
|
|
||||||
|
-- 为link_task表添加首次选区时间字段
|
||||||
|
ALTER TABLE `link_task`
|
||||||
|
ADD COLUMN `first_region_select_at` datetime(3) NULL DEFAULT NULL COMMENT '首次选区时间' AFTER `qr_expire_at`;
|
||||||
|
|
||||||
|
-- 添加索引以优化查询性能
|
||||||
|
ALTER TABLE `link_task`
|
||||||
|
ADD INDEX `idx_first_region_select` (`first_region_select_at` ASC);
|
||||||
|
|
||||||
|
-- 为现有USING状态的记录回填首次选区时间(使用qr_created_at作为首次选区时间)
|
||||||
|
UPDATE `link_task`
|
||||||
|
SET `first_region_select_at` = `qr_created_at`
|
||||||
|
WHERE `status` = 'USING'
|
||||||
|
AND `qr_created_at` IS NOT NULL
|
||||||
|
AND `first_region_select_at` IS NULL;
|
||||||
|
|
||||||
|
-- 验证表结构变更
|
||||||
|
SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_COMMENT
|
||||||
|
FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE()
|
||||||
|
AND TABLE_NAME = 'link_task'
|
||||||
|
AND COLUMN_NAME = 'first_region_select_at'
|
||||||
|
ORDER BY ORDINAL_POSITION;
|
||||||
|
|
||||||
|
-- 验证数据回填结果
|
||||||
|
SELECT
|
||||||
|
COUNT(*) as total_using_links,
|
||||||
|
COUNT(first_region_select_at) as links_with_first_select_time,
|
||||||
|
COUNT(qr_created_at) as links_with_qr_time
|
||||||
|
FROM `link_task`
|
||||||
|
WHERE `status` = 'USING';
|
||||||
@@ -28,6 +28,7 @@ import org.springframework.http.MediaType;
|
|||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.reactive.function.client.WebClientResponseException;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -321,8 +322,7 @@ public Mono<Boolean> deleteLink(@PathVariable("codeNo") String codeNo, Authentic
|
|||||||
|
|
||||||
return linkStatusService.getUserLinkStatus(linkId, actualCodeNo)
|
return linkStatusService.getUserLinkStatus(linkId, actualCodeNo)
|
||||||
.doOnSuccess(response -> {
|
.doOnSuccess(response -> {
|
||||||
log.info("用户端链接状态查询成功: status={}, view={}, needRefresh={}",
|
log.info("用户端链接状态查询成功: status={}", response.getStatus());
|
||||||
response.getStatus(), response.getView(), response.getNeedRefresh());
|
|
||||||
})
|
})
|
||||||
.doOnError(error -> {
|
.doOnError(error -> {
|
||||||
log.error("用户端链接状态查询失败: {}", error.getMessage(), error);
|
log.error("用户端链接状态查询失败: {}", error.getMessage(), error);
|
||||||
@@ -407,7 +407,15 @@ public Mono<Boolean> deleteLink(@PathVariable("codeNo") String codeNo, Authentic
|
|||||||
})
|
})
|
||||||
.onErrorResume(error -> {
|
.onErrorResume(error -> {
|
||||||
log.error("获取设备 {} 二维码失败: {}", deviceId, error.getMessage(), error);
|
log.error("获取设备 {} 二维码失败: {}", deviceId, error.getMessage(), error);
|
||||||
return Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build());
|
|
||||||
|
// 如果是404错误,返回404;其他错误返回500
|
||||||
|
if (error instanceof WebClientResponseException.NotFound) {
|
||||||
|
log.warn("设备 {} 的二维码文件不存在,返回404", deviceId);
|
||||||
|
return Mono.just(ResponseEntity.status(HttpStatus.NOT_FOUND).build());
|
||||||
|
} else {
|
||||||
|
log.error("获取设备 {} 二维码时发生系统错误", deviceId);
|
||||||
|
return Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
package com.gameplatform.server.controller.link;
|
package com.gameplatform.server.controller.link;
|
||||||
|
|
||||||
|
import com.gameplatform.server.mapper.agent.LinkBatchMapper;
|
||||||
|
import com.gameplatform.server.mapper.agent.LinkTaskMapper;
|
||||||
|
import com.gameplatform.server.model.dto.link.GameInterfaceResponse;
|
||||||
|
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.external.ScriptClient;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.http.CacheControl;
|
import org.springframework.http.CacheControl;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
@@ -13,16 +21,29 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/link")
|
@RequestMapping("/api/link")
|
||||||
@Tag(name = "二维码代理", description = "转发脚本端的二维码图片,避免混合内容")
|
@Tag(name = "图片代理", description = "转发脚本端的图片,避免混合内容")
|
||||||
public class QrProxyController {
|
public class QrProxyController {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(QrProxyController.class);
|
||||||
|
|
||||||
private final ScriptClient scriptClient;
|
private final ScriptClient scriptClient;
|
||||||
|
private final String appBaseUrl;
|
||||||
|
private final LinkTaskMapper linkTaskMapper;
|
||||||
|
private final LinkBatchMapper linkBatchMapper;
|
||||||
|
|
||||||
public QrProxyController(ScriptClient scriptClient) {
|
public QrProxyController(ScriptClient scriptClient,
|
||||||
|
@Value("${app.base-url}") String appBaseUrl,
|
||||||
|
LinkTaskMapper linkTaskMapper,
|
||||||
|
LinkBatchMapper linkBatchMapper) {
|
||||||
this.scriptClient = scriptClient;
|
this.scriptClient = scriptClient;
|
||||||
|
this.appBaseUrl = appBaseUrl;
|
||||||
|
this.linkTaskMapper = linkTaskMapper;
|
||||||
|
this.linkBatchMapper = linkBatchMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(value = "/{codeNo}/qr.png", produces = MediaType.IMAGE_PNG_VALUE)
|
@GetMapping(value = "/{codeNo}/qr.png", produces = MediaType.IMAGE_PNG_VALUE)
|
||||||
@@ -36,6 +57,140 @@ public class QrProxyController {
|
|||||||
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=qr.png")
|
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=qr.png")
|
||||||
.body(bytes));
|
.body(bytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "/{codeNo}/homepage.png", produces = MediaType.IMAGE_PNG_VALUE)
|
||||||
|
@Operation(summary = "首次主页图片代理")
|
||||||
|
public Mono<ResponseEntity<byte[]>> homepage(@PathVariable("codeNo") String codeNo) {
|
||||||
|
String path = "/" + codeNo + "/首次主页.png";
|
||||||
|
return scriptClient.getImagePng(path)
|
||||||
|
.map(bytes -> ResponseEntity.ok()
|
||||||
|
.contentType(MediaType.IMAGE_PNG)
|
||||||
|
.cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS).cachePublic())
|
||||||
|
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=homepage.png")
|
||||||
|
.body(bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "/{codeNo}/first-reward.png", produces = MediaType.IMAGE_PNG_VALUE)
|
||||||
|
@Operation(summary = "首次赏金图片代理")
|
||||||
|
public Mono<ResponseEntity<byte[]>> firstReward(@PathVariable("codeNo") String codeNo) {
|
||||||
|
String path = "/" + codeNo + "/首次赏金.png";
|
||||||
|
return scriptClient.getImagePng(path)
|
||||||
|
.map(bytes -> ResponseEntity.ok()
|
||||||
|
.contentType(MediaType.IMAGE_PNG)
|
||||||
|
.cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS).cachePublic())
|
||||||
|
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=first-reward.png")
|
||||||
|
.body(bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "/{codeNo}/mid-reward.png", produces = MediaType.IMAGE_PNG_VALUE)
|
||||||
|
@Operation(summary = "中途赏金图片代理")
|
||||||
|
public Mono<ResponseEntity<byte[]>> midReward(@PathVariable("codeNo") String codeNo) {
|
||||||
|
String path = "/" + codeNo + "/中途赏金.png";
|
||||||
|
return scriptClient.getImagePng(path)
|
||||||
|
.map(bytes -> ResponseEntity.ok()
|
||||||
|
.contentType(MediaType.IMAGE_PNG)
|
||||||
|
.cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS).cachePublic())
|
||||||
|
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=mid-reward.png")
|
||||||
|
.body(bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "/{codeNo}/end-reward.png", produces = MediaType.IMAGE_PNG_VALUE)
|
||||||
|
@Operation(summary = "结束赏金图片代理")
|
||||||
|
public Mono<ResponseEntity<byte[]>> endReward(@PathVariable("codeNo") String codeNo) {
|
||||||
|
String path = "/" + codeNo + "/结束赏金.png";
|
||||||
|
return scriptClient.getImagePng(path)
|
||||||
|
.map(bytes -> ResponseEntity.ok()
|
||||||
|
.contentType(MediaType.IMAGE_PNG)
|
||||||
|
.cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS).cachePublic())
|
||||||
|
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=end-reward.png")
|
||||||
|
.body(bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{codeNo}/images")
|
||||||
|
@Operation(summary = "获取所有图片代理链接")
|
||||||
|
public ResponseEntity<Map<String, String>> getImageLinks(@PathVariable("codeNo") String codeNo) {
|
||||||
|
Map<String, String> imageLinks = new HashMap<>();
|
||||||
|
|
||||||
|
// 添加二维码链接
|
||||||
|
imageLinks.put("qrCode", appBaseUrl + "/api/link/" + codeNo + "/qr.png");
|
||||||
|
|
||||||
|
// 添加其他图片链接
|
||||||
|
imageLinks.put("homepage", appBaseUrl + "/api/link/" + codeNo + "/homepage.png");
|
||||||
|
imageLinks.put("firstReward", appBaseUrl + "/api/link/" + codeNo + "/first-reward.png");
|
||||||
|
imageLinks.put("midReward", appBaseUrl + "/api/link/" + codeNo + "/mid-reward.png");
|
||||||
|
imageLinks.put("endReward", appBaseUrl + "/api/link/" + codeNo + "/end-reward.png");
|
||||||
|
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.cacheControl(CacheControl.maxAge(5, TimeUnit.MINUTES).cachePublic())
|
||||||
|
.body(imageLinks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{codeNo}/game-interface")
|
||||||
|
@Operation(summary = "获取游戏界面数据", description = "返回四张图片链接和总点数信息")
|
||||||
|
public ResponseEntity<GameInterfaceResponse> getGameInterface(@PathVariable("codeNo") String codeNo) {
|
||||||
|
log.info("获取游戏界面数据: codeNo={}", codeNo);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 查询链接任务
|
||||||
|
LinkTask linkTask = linkTaskMapper.findByCodeNo(codeNo);
|
||||||
|
if (linkTask == null) {
|
||||||
|
log.warn("链接任务不存在: codeNo={}", codeNo);
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询批次信息
|
||||||
|
LinkBatch linkBatch = linkBatchMapper.findById(linkTask.getBatchId());
|
||||||
|
if (linkBatch == null) {
|
||||||
|
log.warn("批次信息不存在: batchId={}", linkTask.getBatchId());
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建响应对象
|
||||||
|
GameInterfaceResponse response = new GameInterfaceResponse();
|
||||||
|
response.setCodeNo(codeNo);
|
||||||
|
response.setQuantity(linkBatch.getQuantity());
|
||||||
|
response.setTimes(linkBatch.getTimes());
|
||||||
|
response.setTotalPoints(linkBatch.getQuantity() * linkBatch.getTimes());
|
||||||
|
|
||||||
|
// 设置游戏区域信息
|
||||||
|
response.setRegion(linkTask.getRegion());
|
||||||
|
response.setRegionDesc(getRegionDescription(linkTask.getRegion()));
|
||||||
|
|
||||||
|
// 设置图片链接
|
||||||
|
response.setQrCodeUrl(appBaseUrl + "/api/link/" + codeNo + "/qr.png");
|
||||||
|
response.setHomepageUrl(appBaseUrl + "/api/link/" + codeNo + "/homepage.png");
|
||||||
|
response.setFirstRewardUrl(appBaseUrl + "/api/link/" + codeNo + "/first-reward.png");
|
||||||
|
response.setMidRewardUrl(appBaseUrl + "/api/link/" + codeNo + "/mid-reward.png");
|
||||||
|
response.setEndRewardUrl(appBaseUrl + "/api/link/" + codeNo + "/end-reward.png");
|
||||||
|
|
||||||
|
log.info("游戏界面数据构建完成: codeNo={}, totalPoints={}", codeNo, response.getTotalPoints());
|
||||||
|
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.cacheControl(CacheControl.maxAge(2, TimeUnit.MINUTES).cachePublic())
|
||||||
|
.body(response);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取游戏界面数据失败: codeNo={}, error={}", codeNo, e.getMessage(), e);
|
||||||
|
return ResponseEntity.internalServerError().build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取区域描述
|
||||||
|
*/
|
||||||
|
private String getRegionDescription(String region) {
|
||||||
|
if (region == null) {
|
||||||
|
return "未选择区域";
|
||||||
|
}
|
||||||
|
switch (region) {
|
||||||
|
case "Q":
|
||||||
|
return "QQ区";
|
||||||
|
case "V":
|
||||||
|
return "微信区";
|
||||||
|
default:
|
||||||
|
return "未知区域";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
package com.gameplatform.server.model.dto.link;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏界面数据响应
|
||||||
|
*/
|
||||||
|
@Schema(description = "游戏界面数据")
|
||||||
|
public class GameInterfaceResponse {
|
||||||
|
|
||||||
|
@Schema(description = "设备编号")
|
||||||
|
private String codeNo;
|
||||||
|
|
||||||
|
@Schema(description = "总点数 (quantity * times)")
|
||||||
|
private Integer totalPoints;
|
||||||
|
|
||||||
|
@Schema(description = "每次副本奖励点数")
|
||||||
|
private Integer quantity;
|
||||||
|
|
||||||
|
@Schema(description = "副本次数")
|
||||||
|
private Integer times;
|
||||||
|
|
||||||
|
@Schema(description = "游戏区域", example = "Q表示QQ区,V表示微信区")
|
||||||
|
private String region;
|
||||||
|
|
||||||
|
@Schema(description = "游戏区域描述", example = "QQ区")
|
||||||
|
private String regionDesc;
|
||||||
|
|
||||||
|
@Schema(description = "二维码图片链接")
|
||||||
|
private String qrCodeUrl;
|
||||||
|
|
||||||
|
@Schema(description = "首次主页图片链接")
|
||||||
|
private String homepageUrl;
|
||||||
|
|
||||||
|
@Schema(description = "首次赏金图片链接")
|
||||||
|
private String firstRewardUrl;
|
||||||
|
|
||||||
|
@Schema(description = "中途赏金图片链接")
|
||||||
|
private String midRewardUrl;
|
||||||
|
|
||||||
|
@Schema(description = "结束赏金图片链接")
|
||||||
|
private String endRewardUrl;
|
||||||
|
|
||||||
|
public String getCodeNo() {
|
||||||
|
return codeNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCodeNo(String codeNo) {
|
||||||
|
this.codeNo = codeNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getTotalPoints() {
|
||||||
|
return totalPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTotalPoints(Integer totalPoints) {
|
||||||
|
this.totalPoints = totalPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getQuantity() {
|
||||||
|
return quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQuantity(Integer quantity) {
|
||||||
|
this.quantity = quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getTimes() {
|
||||||
|
return times;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimes(Integer times) {
|
||||||
|
this.times = times;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRegion() {
|
||||||
|
return region;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRegion(String region) {
|
||||||
|
this.region = region;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRegionDesc() {
|
||||||
|
return regionDesc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRegionDesc(String regionDesc) {
|
||||||
|
this.regionDesc = regionDesc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getQrCodeUrl() {
|
||||||
|
return qrCodeUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQrCodeUrl(String qrCodeUrl) {
|
||||||
|
this.qrCodeUrl = qrCodeUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHomepageUrl() {
|
||||||
|
return homepageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHomepageUrl(String homepageUrl) {
|
||||||
|
this.homepageUrl = homepageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFirstRewardUrl() {
|
||||||
|
return firstRewardUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstRewardUrl(String firstRewardUrl) {
|
||||||
|
this.firstRewardUrl = firstRewardUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMidRewardUrl() {
|
||||||
|
return midRewardUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMidRewardUrl(String midRewardUrl) {
|
||||||
|
this.midRewardUrl = midRewardUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEndRewardUrl() {
|
||||||
|
return endRewardUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEndRewardUrl(String endRewardUrl) {
|
||||||
|
this.endRewardUrl = endRewardUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,91 +8,7 @@ public class UserLinkStatusResponse {
|
|||||||
@Schema(description = "链接状态", example = "NEW", allowableValues = {"NEW", "USING", "LOGGED_IN", "REFUNDED", "EXPIRED"})
|
@Schema(description = "链接状态", example = "NEW", allowableValues = {"NEW", "USING", "LOGGED_IN", "REFUNDED", "EXPIRED"})
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
@Schema(description = "是否需要刷新", example = "false")
|
// Getter and Setter
|
||||||
private Boolean needRefresh;
|
|
||||||
|
|
||||||
@Schema(description = "选择的区域", example = "Q", allowableValues = {"Q", "V"})
|
|
||||||
private String region;
|
|
||||||
|
|
||||||
@Schema(description = "二维码信息")
|
|
||||||
private QrInfo qr;
|
|
||||||
|
|
||||||
@Schema(description = "视图类型", example = "FIRST", allowableValues = {"FIRST", "SCAN", "SECOND"})
|
|
||||||
private String view;
|
|
||||||
|
|
||||||
@Schema(description = "静态资源信息")
|
|
||||||
private AssetsInfo assets;
|
|
||||||
|
|
||||||
@Schema(description = "二维码信息")
|
|
||||||
public static class QrInfo {
|
|
||||||
@Schema(description = "二维码URL", example = "http://36.138.184.60:12345/{编号}/二维码.png")
|
|
||||||
private String url;
|
|
||||||
|
|
||||||
@Schema(description = "创建时间戳", example = "1730000000000")
|
|
||||||
private Long createdAt;
|
|
||||||
|
|
||||||
@Schema(description = "过期时间戳", example = "1730000060000")
|
|
||||||
private Long expireAt;
|
|
||||||
|
|
||||||
public String getUrl() { return url; }
|
|
||||||
public void setUrl(String url) { this.url = url; }
|
|
||||||
|
|
||||||
public Long getCreatedAt() { return createdAt; }
|
|
||||||
public void setCreatedAt(Long createdAt) { this.createdAt = createdAt; }
|
|
||||||
|
|
||||||
public Long getExpireAt() { return expireAt; }
|
|
||||||
public void setExpireAt(Long expireAt) { this.expireAt = expireAt; }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Schema(description = "静态资源信息")
|
|
||||||
public static class AssetsInfo {
|
|
||||||
@Schema(description = "基础URL", example = "http://36.138.184.60:12345/{编号}/")
|
|
||||||
private String base;
|
|
||||||
|
|
||||||
@Schema(description = "首次主页图片", example = "首次主页.png")
|
|
||||||
private String firstHome;
|
|
||||||
|
|
||||||
@Schema(description = "首次赏金图片", example = "首次赏金.png")
|
|
||||||
private String firstBonus;
|
|
||||||
|
|
||||||
@Schema(description = "中途赏金图片", example = "中途赏金.png")
|
|
||||||
private String midBonus;
|
|
||||||
|
|
||||||
@Schema(description = "结束赏金图片", example = "结束赏金.png")
|
|
||||||
private String endBonus;
|
|
||||||
|
|
||||||
public String getBase() { return base; }
|
|
||||||
public void setBase(String base) { this.base = base; }
|
|
||||||
|
|
||||||
public String getFirstHome() { return firstHome; }
|
|
||||||
public void setFirstHome(String firstHome) { this.firstHome = firstHome; }
|
|
||||||
|
|
||||||
public String getFirstBonus() { return firstBonus; }
|
|
||||||
public void setFirstBonus(String firstBonus) { this.firstBonus = firstBonus; }
|
|
||||||
|
|
||||||
public String getMidBonus() { return midBonus; }
|
|
||||||
public void setMidBonus(String midBonus) { this.midBonus = midBonus; }
|
|
||||||
|
|
||||||
public String getEndBonus() { return endBonus; }
|
|
||||||
public void setEndBonus(String endBonus) { this.endBonus = endBonus; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getters and Setters
|
|
||||||
public String getStatus() { return status; }
|
public String getStatus() { return status; }
|
||||||
public void setStatus(String status) { this.status = status; }
|
public void setStatus(String status) { this.status = status; }
|
||||||
|
|
||||||
public Boolean getNeedRefresh() { return needRefresh; }
|
|
||||||
public void setNeedRefresh(Boolean needRefresh) { this.needRefresh = needRefresh; }
|
|
||||||
|
|
||||||
public String getRegion() { return region; }
|
|
||||||
public void setRegion(String region) { this.region = region; }
|
|
||||||
|
|
||||||
public QrInfo getQr() { return qr; }
|
|
||||||
public void setQr(QrInfo qr) { this.qr = qr; }
|
|
||||||
|
|
||||||
public String getView() { return view; }
|
|
||||||
public void setView(String view) { this.view = view; }
|
|
||||||
|
|
||||||
public AssetsInfo getAssets() { return assets; }
|
|
||||||
public void setAssets(AssetsInfo assets) { this.assets = assets; }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,9 @@ public class LinkTask {
|
|||||||
|
|
||||||
@TableField("qr_expire_at")
|
@TableField("qr_expire_at")
|
||||||
private LocalDateTime qrExpireAt;
|
private LocalDateTime qrExpireAt;
|
||||||
|
|
||||||
|
@TableField("first_region_select_at")
|
||||||
|
private LocalDateTime firstRegionSelectAt;
|
||||||
|
|
||||||
public Long getId() { return id; }
|
public Long getId() { return id; }
|
||||||
public void setId(Long id) { this.id = id; }
|
public void setId(Long id) { this.id = id; }
|
||||||
@@ -112,4 +115,7 @@ public class LinkTask {
|
|||||||
|
|
||||||
public LocalDateTime getQrExpireAt() { return qrExpireAt; }
|
public LocalDateTime getQrExpireAt() { return qrExpireAt; }
|
||||||
public void setQrExpireAt(LocalDateTime qrExpireAt) { this.qrExpireAt = qrExpireAt; }
|
public void setQrExpireAt(LocalDateTime qrExpireAt) { this.qrExpireAt = qrExpireAt; }
|
||||||
|
|
||||||
|
public LocalDateTime getFirstRegionSelectAt() { return firstRegionSelectAt; }
|
||||||
|
public void setFirstRegionSelectAt(LocalDateTime firstRegionSelectAt) { this.firstRegionSelectAt = firstRegionSelectAt; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ public class SecurityConfig {
|
|||||||
.pathMatchers(HttpMethod.GET, "/api/link/status").permitAll() // 用户端获取链接状态接口,公开访问
|
.pathMatchers(HttpMethod.GET, "/api/link/status").permitAll() // 用户端获取链接状态接口,公开访问
|
||||||
.pathMatchers(HttpMethod.POST, "/api/link/select-region").permitAll() // 用户端选区接口,公开访问
|
.pathMatchers(HttpMethod.POST, "/api/link/select-region").permitAll() // 用户端选区接口,公开访问
|
||||||
.pathMatchers(HttpMethod.GET, "/api/link/poll-login").permitAll() // 用户端轮询登录接口,公开访问
|
.pathMatchers(HttpMethod.GET, "/api/link/poll-login").permitAll() // 用户端轮询登录接口,公开访问
|
||||||
|
.pathMatchers(HttpMethod.GET, "/api/link/qr/**").permitAll() // 二维码获取接口,公开访问
|
||||||
|
.pathMatchers(HttpMethod.GET, "/api/link/*/game-interface").permitAll() // 游戏界面数据接口,公开访问
|
||||||
.pathMatchers("/api/link/**").authenticated() // 其他链接接口需要认证
|
.pathMatchers("/api/link/**").authenticated() // 其他链接接口需要认证
|
||||||
.anyExchange().permitAll() // 其他接口后续再收紧
|
.anyExchange().permitAll() // 其他接口后续再收紧
|
||||||
)
|
)
|
||||||
@@ -65,6 +67,8 @@ public class SecurityConfig {
|
|||||||
log.info(" * GET /api/link/status -> 允许所有 (用户端公开接口)");
|
log.info(" * GET /api/link/status -> 允许所有 (用户端公开接口)");
|
||||||
log.info(" * POST /api/link/select-region -> 允许所有 (用户端公开接口)");
|
log.info(" * POST /api/link/select-region -> 允许所有 (用户端公开接口)");
|
||||||
log.info(" * GET /api/link/poll-login -> 允许所有 (用户端公开接口)");
|
log.info(" * GET /api/link/poll-login -> 允许所有 (用户端公开接口)");
|
||||||
|
log.info(" * GET /api/link/qr/** -> 允许所有 (二维码获取接口)");
|
||||||
|
log.info(" * GET /api/link/*/game-interface -> 允许所有 (游戏界面数据接口)");
|
||||||
log.info(" * /api/link/** -> 需要认证");
|
log.info(" * /api/link/** -> 需要认证");
|
||||||
log.info(" * 其他路径 -> 允许所有");
|
log.info(" * 其他路径 -> 允许所有");
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,23 @@ public class ScriptClient {
|
|||||||
.doOnError(e -> log.warn("ScriptClient.getQrPng error path={} err={}", path, e.toString()));
|
.doOnError(e -> log.warn("ScriptClient.getQrPng error path={} err={}", path, e.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用图片获取方法
|
||||||
|
* @param path 图片路径,如 /{codeNo}/首次主页.png
|
||||||
|
* @return 图片数据
|
||||||
|
*/
|
||||||
|
public Mono<byte[]> getImagePng(String path) {
|
||||||
|
log.debug("获取图片: path={}", path);
|
||||||
|
return webClient.get()
|
||||||
|
.uri(path)
|
||||||
|
.accept(MediaType.IMAGE_PNG)
|
||||||
|
.retrieve()
|
||||||
|
.bodyToMono(byte[].class)
|
||||||
|
.timeout(Duration.ofSeconds(10))
|
||||||
|
.doOnSuccess(data -> log.debug("获取图片成功: path={}, 数据大小={}字节", path, data != null ? data.length : 0))
|
||||||
|
.doOnError(e -> log.warn("获取图片失败: path={}, error={}", path, e.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
public Mono<String> getText(String path) {
|
public Mono<String> getText(String path) {
|
||||||
return webClient.get()
|
return webClient.get()
|
||||||
.uri(path)
|
.uri(path)
|
||||||
@@ -138,6 +155,22 @@ public class ScriptClient {
|
|||||||
.doOnError(e -> log.warn("刷新操作失败: codeNo={}, error={}", codeNo, e.toString()));
|
.doOnError(e -> log.warn("刷新操作失败: codeNo={}, error={}", codeNo, e.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断刷新接口 - 统一管理刷新判断逻辑
|
||||||
|
*/
|
||||||
|
public Mono<String> checkRefresh() {
|
||||||
|
String url = "http://36.138.184.60:1234/yijianwan_netfile/saveMsg?文件名=判断刷新&f4=刷新";
|
||||||
|
log.info("调用判断刷新接口: {}", url);
|
||||||
|
return webClient.get()
|
||||||
|
.uri(url)
|
||||||
|
.accept(MediaType.TEXT_PLAIN)
|
||||||
|
.retrieve()
|
||||||
|
.bodyToMono(String.class)
|
||||||
|
.timeout(Duration.ofSeconds(10))
|
||||||
|
.doOnSuccess(result -> log.info("判断刷新接口调用成功: result={}", result))
|
||||||
|
.doOnError(e -> log.warn("判断刷新接口调用失败: {}", e.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查设备是否已上号 - 根据您提供的API示例
|
* 检查设备是否已上号 - 根据您提供的API示例
|
||||||
* URL格式: http://36.138.184.60:1234/yijianwan_netfile/readMsg?文件名=判断上号&对象名=f1
|
* URL格式: http://36.138.184.60:1234/yijianwan_netfile/readMsg?文件名=判断上号&对象名=f1
|
||||||
@@ -229,6 +262,24 @@ public class ScriptClient {
|
|||||||
.doOnSuccess(result -> log.debug("设置次数成功: codeNo={}, times={}, result={}", codeNo, times, result))
|
.doOnSuccess(result -> log.debug("设置次数成功: codeNo={}, times={}, result={}", codeNo, times, result))
|
||||||
.doOnError(e -> log.warn("设置次数失败: codeNo={}, times={}, error={}", codeNo, times, e.toString()));
|
.doOnError(e -> log.warn("设置次数失败: codeNo={}, times={}, error={}", codeNo, times, e.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存总次数(使用f4参数格式)
|
||||||
|
* @param times 总次数
|
||||||
|
* @return 保存结果
|
||||||
|
*/
|
||||||
|
public Mono<String> saveTotalTimes(int times) {
|
||||||
|
String url = String.format("http://36.138.184.60:1234/yijianwan_netfile/saveMsg?文件名=总次数&f4=%d", times);
|
||||||
|
log.info("开始调用保存总次数接口: times={}, url={}", times, url);
|
||||||
|
return webClient.get()
|
||||||
|
.uri(url)
|
||||||
|
.accept(MediaType.TEXT_PLAIN)
|
||||||
|
.retrieve()
|
||||||
|
.bodyToMono(String.class)
|
||||||
|
.timeout(Duration.ofSeconds(10))
|
||||||
|
.doOnSuccess(result -> log.info("保存总次数接口调用成功: url={}, result={}", url, result))
|
||||||
|
.doOnError(e -> log.warn("保存总次数接口调用失败: times={}, error={}", times, e.toString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.core.scheduler.Schedulers;
|
import reactor.core.scheduler.Schedulers;
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@ public class LinkStatusService {
|
|||||||
private final ScriptClient scriptClient;
|
private final ScriptClient scriptClient;
|
||||||
private final DeviceCodeMappingService deviceCodeMappingService;
|
private final DeviceCodeMappingService deviceCodeMappingService;
|
||||||
|
|
||||||
|
|
||||||
// 状态描述映射
|
// 状态描述映射
|
||||||
private static final Map<String, String> STATUS_DESC_MAP = new HashMap<>();
|
private static final Map<String, String> STATUS_DESC_MAP = new HashMap<>();
|
||||||
static {
|
static {
|
||||||
@@ -47,12 +49,13 @@ public class LinkStatusService {
|
|||||||
STATUS_DESC_MAP.put("EXPIRED", "已过期");
|
STATUS_DESC_MAP.put("EXPIRED", "已过期");
|
||||||
}
|
}
|
||||||
|
|
||||||
public LinkStatusService(LinkTaskMapper linkTaskMapper, LinkBatchMapper linkBatchMapper,
|
public LinkStatusService(LinkTaskMapper linkTaskMapper, LinkBatchMapper linkBatchMapper,
|
||||||
ScriptClient scriptClient, DeviceCodeMappingService deviceCodeMappingService) {
|
ScriptClient scriptClient, DeviceCodeMappingService deviceCodeMappingService) {
|
||||||
this.linkTaskMapper = linkTaskMapper;
|
this.linkTaskMapper = linkTaskMapper;
|
||||||
this.linkBatchMapper = linkBatchMapper;
|
this.linkBatchMapper = linkBatchMapper;
|
||||||
this.scriptClient = scriptClient;
|
this.scriptClient = scriptClient;
|
||||||
this.deviceCodeMappingService = deviceCodeMappingService;
|
this.deviceCodeMappingService = deviceCodeMappingService;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -92,7 +95,6 @@ public class LinkStatusService {
|
|||||||
response.setQuantity(linkBatch.getQuantity());
|
response.setQuantity(linkBatch.getQuantity());
|
||||||
response.setTimes(linkBatch.getTimes());
|
response.setTimes(linkBatch.getTimes());
|
||||||
response.setTotalPoints(linkBatch.getQuantity() * linkBatch.getTimes());
|
response.setTotalPoints(linkBatch.getQuantity() * linkBatch.getTimes());
|
||||||
response.setRegion(linkTask.getRegion());
|
|
||||||
response.setMachineId(linkTask.getMachineId());
|
response.setMachineId(linkTask.getMachineId());
|
||||||
response.setLoginAt(linkTask.getLoginAt());
|
response.setLoginAt(linkTask.getLoginAt());
|
||||||
response.setCreatedAt(linkTask.getCreatedAt());
|
response.setCreatedAt(linkTask.getCreatedAt());
|
||||||
@@ -289,21 +291,27 @@ public class LinkStatusService {
|
|||||||
|
|
||||||
UserLinkStatusResponse response = new UserLinkStatusResponse();
|
UserLinkStatusResponse response = new UserLinkStatusResponse();
|
||||||
response.setStatus("EXPIRED");
|
response.setStatus("EXPIRED");
|
||||||
response.setView("EXPIRED");
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 根据状态执行相应逻辑
|
// 3. 检查USING状态的10分钟过期逻辑
|
||||||
if ("USING".equals(linkTask.getStatus())) {
|
if ("USING".equals(linkTask.getStatus())) {
|
||||||
// 如果是USING状态,检查二维码是否过期,过期则刷新
|
// 检查是否超过10分钟未登录
|
||||||
if (linkTask.getQrExpireAt() != null && linkTask.getQrExpireAt().isBefore(LocalDateTime.now())) {
|
if (linkTask.getQrCreatedAt() != null &&
|
||||||
log.info("二维码已过期,执行自动刷新重置选区状态");
|
linkTask.getQrCreatedAt().isBefore(LocalDateTime.now().minusMinutes(10))) {
|
||||||
performAutoRefresh(linkTask);
|
log.warn("选择设备已超过10分钟未登录,链接过期: qrCreatedAt={}", linkTask.getQrCreatedAt());
|
||||||
} else {
|
linkTask.setStatus("EXPIRED");
|
||||||
// 二维码还未过期,更新二维码信息
|
linkTask.setUpdatedAt(LocalDateTime.now());
|
||||||
log.info("链接状态是USING,重新获取二维码");
|
linkTaskMapper.update(linkTask);
|
||||||
updateQrCodeInfo(linkTask);
|
|
||||||
|
UserLinkStatusResponse response = new UserLinkStatusResponse();
|
||||||
|
response.setStatus("EXPIRED");
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果未超过10分钟,执行自动刷新
|
||||||
|
log.info("链接状态是USING,执行自动刷新");
|
||||||
|
performAutoRefresh(linkTask);
|
||||||
} else if ("LOGGED_IN".equals(linkTask.getStatus()) || "REFUNDED".equals(linkTask.getStatus())) {
|
} else if ("LOGGED_IN".equals(linkTask.getStatus()) || "REFUNDED".equals(linkTask.getStatus())) {
|
||||||
// 已上号或已退款状态,不需要刷新
|
// 已上号或已退款状态,不需要刷新
|
||||||
log.info("链接状态为 {},不需要刷新", linkTask.getStatus());
|
log.info("链接状态为 {},不需要刷新", linkTask.getStatus());
|
||||||
@@ -312,8 +320,7 @@ public class LinkStatusService {
|
|||||||
// 4. 构建响应
|
// 4. 构建响应
|
||||||
UserLinkStatusResponse response = buildUserStatusResponse(linkTask);
|
UserLinkStatusResponse response = buildUserStatusResponse(linkTask);
|
||||||
log.info("=== 用户端链接状态查询完成 ===");
|
log.info("=== 用户端链接状态查询完成 ===");
|
||||||
log.info("返回状态: {}, view: {}", response.getStatus(), response.getView());
|
log.info("返回状态: {}", response.getStatus());
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -325,61 +332,23 @@ public class LinkStatusService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行自动刷新逻辑
|
* 执行自动刷新逻辑
|
||||||
* 刷新时会重置选区状态,让用户重新选择区域
|
* 只调用判断刷新接口,不更新数据库状态
|
||||||
*/
|
*/
|
||||||
private void performAutoRefresh(LinkTask linkTask) {
|
private void performAutoRefresh(LinkTask linkTask) {
|
||||||
try {
|
try {
|
||||||
log.info("开始执行刷新操作,将重置选区状态");
|
log.info("开始执行刷新操作");
|
||||||
|
|
||||||
// 1. 调用脚本端刷新
|
// 调用判断刷新接口(通过ScriptClient统一管理)
|
||||||
String refreshResult = scriptClient.refresh(linkTask.getCodeNo()).block();
|
String refreshResult = scriptClient.checkRefresh().block();
|
||||||
log.info("脚本端刷新结果: {}", refreshResult);
|
log.info("判断刷新接口调用完成: result={}", refreshResult);
|
||||||
|
|
||||||
// 2. 重置选区状态,删除已有选区让用户重新选择
|
|
||||||
log.info("重置选区状态: 从 {} 重置为 NEW", linkTask.getStatus());
|
|
||||||
|
|
||||||
LocalDateTime now = LocalDateTime.now();
|
|
||||||
linkTask.setStatus("NEW"); // 重置状态为NEW,允许重新选区
|
|
||||||
linkTask.setRegion(null); // 清空已选择的区域
|
|
||||||
linkTask.setQrCreatedAt(null); // 清空二维码创建时间
|
|
||||||
linkTask.setQrExpireAt(null); // 清空二维码过期时间
|
|
||||||
linkTask.setNeedRefresh(true); // 标记为刷新状态
|
|
||||||
linkTask.setRefreshTime(now); // 记录刷新时间
|
|
||||||
linkTask.setUpdatedAt(now); // 更新修改时间
|
|
||||||
linkTaskMapper.update(linkTask);
|
|
||||||
|
|
||||||
log.info("选区状态重置完成: status=NEW, region=null, needRefresh=true");
|
|
||||||
|
|
||||||
// 3. 等待10秒后允许重新选区
|
|
||||||
log.info("刷新完成,等待10秒后允许重新选区...");
|
|
||||||
Thread.sleep(10000);
|
|
||||||
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
log.warn("刷新等待被中断: {}", e.getMessage());
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("执行刷新操作失败: {}", e.getMessage());
|
log.warn("执行刷新操作失败: {}", e.getMessage());
|
||||||
// 刷新失败不影响后续流程,但记录错误日志
|
// 刷新失败不影响后续流程,只记录警告日志
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新二维码信息
|
|
||||||
*/
|
|
||||||
private void updateQrCodeInfo(LinkTask linkTask) {
|
|
||||||
try {
|
|
||||||
LocalDateTime now = LocalDateTime.now();
|
|
||||||
linkTask.setQrCreatedAt(now);
|
|
||||||
linkTask.setQrExpireAt(now.plusSeconds(60)); // 60秒后过期
|
|
||||||
linkTask.setUpdatedAt(now);
|
|
||||||
linkTaskMapper.update(linkTask);
|
|
||||||
|
|
||||||
log.info("更新二维码信息成功: qrCreatedAt={}, qrExpireAt={}",
|
|
||||||
linkTask.getQrCreatedAt(), linkTask.getQrExpireAt());
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("更新二维码信息失败: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建用户端状态响应
|
* 构建用户端状态响应
|
||||||
@@ -387,58 +356,14 @@ public class LinkStatusService {
|
|||||||
private UserLinkStatusResponse buildUserStatusResponse(LinkTask linkTask) {
|
private UserLinkStatusResponse buildUserStatusResponse(LinkTask linkTask) {
|
||||||
UserLinkStatusResponse response = new UserLinkStatusResponse();
|
UserLinkStatusResponse response = new UserLinkStatusResponse();
|
||||||
|
|
||||||
// 基本状态信息
|
// 如果状态是USING,返回NEW给用户端
|
||||||
response.setStatus(linkTask.getStatus());
|
String statusToReturn = "USING".equals(linkTask.getStatus()) ? "NEW" : linkTask.getStatus();
|
||||||
response.setNeedRefresh(Boolean.TRUE.equals(linkTask.getNeedRefresh()));
|
response.setStatus(statusToReturn);
|
||||||
response.setRegion(linkTask.getRegion());
|
|
||||||
|
|
||||||
// 确定视图类型
|
|
||||||
String view = determineView(linkTask.getStatus(), response.getNeedRefresh());
|
|
||||||
response.setView(view);
|
|
||||||
|
|
||||||
// 如果状态是USING,设置二维码信息
|
|
||||||
if ("USING".equals(linkTask.getStatus()) && linkTask.getQrCreatedAt() != null) {
|
|
||||||
UserLinkStatusResponse.QrInfo qrInfo = new UserLinkStatusResponse.QrInfo();
|
|
||||||
qrInfo.setUrl(scriptClient.getProxyQrCodeUrl(linkTask.getCodeNo()));
|
|
||||||
qrInfo.setCreatedAt(java.sql.Timestamp.valueOf(linkTask.getQrCreatedAt()).getTime());
|
|
||||||
if (linkTask.getQrExpireAt() != null) {
|
|
||||||
qrInfo.setExpireAt(java.sql.Timestamp.valueOf(linkTask.getQrExpireAt()).getTime());
|
|
||||||
}
|
|
||||||
response.setQr(qrInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果状态是LOGGED_IN,设置资源信息
|
|
||||||
if ("LOGGED_IN".equals(linkTask.getStatus())) {
|
|
||||||
UserLinkStatusResponse.AssetsInfo assets = new UserLinkStatusResponse.AssetsInfo();
|
|
||||||
assets.setBase(String.format("http://36.138.184.60:12345/%s/", linkTask.getCodeNo()));
|
|
||||||
assets.setFirstHome("首次主页.png");
|
|
||||||
assets.setFirstBonus("首次赏金.png");
|
|
||||||
assets.setMidBonus("中途赏金.png");
|
|
||||||
assets.setEndBonus("结束赏金.png");
|
|
||||||
response.setAssets(assets);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 确定视图类型
|
|
||||||
*/
|
|
||||||
private String determineView(String status, boolean needRefresh) {
|
|
||||||
switch (status) {
|
|
||||||
case "NEW":
|
|
||||||
return needRefresh ? "REFRESH" : "FIRST";
|
|
||||||
case "USING":
|
|
||||||
return "SCAN";
|
|
||||||
case "LOGGED_IN":
|
|
||||||
return "SECOND";
|
|
||||||
case "REFUNDED":
|
|
||||||
case "EXPIRED":
|
|
||||||
return "EXPIRED";
|
|
||||||
default:
|
|
||||||
return "FIRST";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 选区操作
|
* 选区操作
|
||||||
@@ -471,6 +396,14 @@ public class LinkStatusService {
|
|||||||
log.info("查询到链接任务: id={}, codeNo={}, status={}, needRefresh={}",
|
log.info("查询到链接任务: id={}, codeNo={}, status={}, needRefresh={}",
|
||||||
linkTask.getId(), linkTask.getCodeNo(), linkTask.getStatus(), linkTask.getNeedRefresh());
|
linkTask.getId(), linkTask.getCodeNo(), linkTask.getStatus(), linkTask.getNeedRefresh());
|
||||||
|
|
||||||
|
// 查询批次信息获取times参数
|
||||||
|
LinkBatch linkBatch = linkBatchMapper.findById(linkTask.getBatchId());
|
||||||
|
if (linkBatch == null) {
|
||||||
|
log.error("批次信息不存在: batchId={}", linkTask.getBatchId());
|
||||||
|
throw new IllegalStateException("批次信息不存在");
|
||||||
|
}
|
||||||
|
log.info("查询到批次信息: batchId={}, times={}", linkBatch.getId(), linkBatch.getTimes());
|
||||||
|
|
||||||
// 3. 检查链接状态,只有NEW状态才能选区
|
// 3. 检查链接状态,只有NEW状态才能选区
|
||||||
if (!"NEW".equals(linkTask.getStatus())) {
|
if (!"NEW".equals(linkTask.getStatus())) {
|
||||||
log.error("链接状态不正确,无法选区: status={}", linkTask.getStatus());
|
log.error("链接状态不正确,无法选区: status={}", linkTask.getStatus());
|
||||||
@@ -509,29 +442,42 @@ public class LinkStatusService {
|
|||||||
deviceStatus.getTotalDevices(), deviceStatus.getAvailableCount(), deviceStatus.getAvailableDevices());
|
deviceStatus.getTotalDevices(), deviceStatus.getAvailableCount(), deviceStatus.getAvailableDevices());
|
||||||
|
|
||||||
// 7. 选择一个空闲设备
|
// 7. 选择一个空闲设备
|
||||||
String selectedDevice = deviceStatus.getAvailableDevices().get(0); // 选择第一个空闲设备
|
// String selectedDevice = deviceStatus.getAvailableDevices().get(0); // 选择第一个空闲设备
|
||||||
log.info("选择设备: {}", selectedDevice);
|
String selectedDevice = "cc2";
|
||||||
|
log.info("从空闲设备列表中选择设备: {}", selectedDevice);
|
||||||
|
log.info("设备选择详情: 可用设备总数={}, 选择了第一个设备={}",
|
||||||
|
deviceStatus.getAvailableDevices().size(), selectedDevice);
|
||||||
|
|
||||||
// 8. 为选中的设备创建代理code
|
// 8. 调用保存总次数接口
|
||||||
|
try {
|
||||||
|
scriptClient.saveTotalTimes(linkBatch.getTimes()).block();
|
||||||
|
// saveTotalTimes方法已经包含了详细的日志记录
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("保存总次数接口调用失败: {}", e.getMessage());
|
||||||
|
// 不影响后续流程,只记录警告日志
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9. 为选中的设备创建代理code
|
||||||
String proxyCode = deviceCodeMappingService.createProxyCode(selectedDevice);
|
String proxyCode = deviceCodeMappingService.createProxyCode(selectedDevice);
|
||||||
log.info("为设备 {} 创建代理code: {}", selectedDevice, proxyCode);
|
log.info("为设备 {} 创建代理code: {}", selectedDevice, proxyCode);
|
||||||
|
|
||||||
// 9. 调用脚本端选区,使用选中的设备
|
// 10. 调用脚本端选区,使用选中的设备
|
||||||
log.info("开始调用脚本端选区,设备={}, 区域={}", selectedDevice, region);
|
log.info("开始调用脚本端选区,设备={}, 区域={}", selectedDevice, region);
|
||||||
String selectResult = scriptClient.selectRegion(selectedDevice, region).block();
|
String selectResult = scriptClient.selectRegion(selectedDevice, region).block();
|
||||||
log.info("脚本端选区结果: {}", selectResult);
|
log.info("脚本端选区结果: {}", selectResult);
|
||||||
|
|
||||||
// 10. 等待脚本端生成二维码(这里可以添加轮询逻辑)
|
// 11. 等待脚本端生成二维码(这里可以添加轮询逻辑)
|
||||||
log.info("等待脚本端生成二维码,等待3秒...");
|
log.info("等待脚本端生成二维码,等待3秒...");
|
||||||
Thread.sleep(3000);
|
Thread.sleep(3000);
|
||||||
|
|
||||||
// 11. 更新数据库状态为USING,保存设备信息和代理code
|
// 12. 更新数据库状态为USING,保存设备信息和代理code
|
||||||
LocalDateTime now = LocalDateTime.now();
|
LocalDateTime now = LocalDateTime.now();
|
||||||
linkTask.setStatus("USING");
|
linkTask.setStatus("USING");
|
||||||
linkTask.setRegion(region);
|
linkTask.setRegion(region);
|
||||||
linkTask.setCodeNo(proxyCode); // 使用代理code替换原来的codeNo
|
linkTask.setCodeNo(proxyCode); // 使用代理code替换原来的codeNo
|
||||||
linkTask.setQrCreatedAt(now);
|
linkTask.setQrCreatedAt(now);
|
||||||
linkTask.setQrExpireAt(now.plusSeconds(60)); // 60秒后过期
|
linkTask.setQrExpireAt(now.plusSeconds(60)); // 60秒后过期
|
||||||
|
linkTask.setFirstRegionSelectAt(now); // 记录首次选区时间
|
||||||
linkTask.setNeedRefresh(false);
|
linkTask.setNeedRefresh(false);
|
||||||
linkTask.setUpdatedAt(now);
|
linkTask.setUpdatedAt(now);
|
||||||
// 在machineId字段保存真实设备编号,便于调试和维护
|
// 在machineId字段保存真实设备编号,便于调试和维护
|
||||||
@@ -540,13 +486,13 @@ public class LinkStatusService {
|
|||||||
|
|
||||||
log.info("链接状态更新成功: status=USING, region={}, proxyCode={}, device={}", region, proxyCode, selectedDevice);
|
log.info("链接状态更新成功: status=USING, region={}, proxyCode={}, device={}", region, proxyCode, selectedDevice);
|
||||||
|
|
||||||
// 12. 构建响应
|
// 13. 构建响应
|
||||||
SelectRegionResponse response = new SelectRegionResponse(true, "选区成功");
|
SelectRegionResponse response = new SelectRegionResponse(true, "选区成功");
|
||||||
response.setQrCodeUrl(scriptClient.getProxyQrCodeUrl(proxyCode));
|
response.setQrCodeUrl(scriptClient.getProxyQrCodeUrl(proxyCode));
|
||||||
response.setQrCreatedAt(now);
|
response.setQrCreatedAt(now);
|
||||||
response.setQrExpireAt(linkTask.getQrExpireAt());
|
response.setQrExpireAt(linkTask.getQrExpireAt());
|
||||||
response.setStatus("USING");
|
response.setStatus("USING");
|
||||||
response.setRegion(region);
|
// 不返回选区字段:response.setRegion(region);
|
||||||
response.setQrDelaySeconds(5); // 客户端收到响应后,等待5秒再请求二维码
|
response.setQrDelaySeconds(5); // 客户端收到响应后,等待5秒再请求二维码
|
||||||
|
|
||||||
log.info("=== 选区操作完成 ===");
|
log.info("=== 选区操作完成 ===");
|
||||||
|
|||||||
@@ -16,32 +16,37 @@
|
|||||||
<result property="revokedAt" column="revoked_at" />
|
<result property="revokedAt" column="revoked_at" />
|
||||||
<result property="createdAt" column="created_at" />
|
<result property="createdAt" column="created_at" />
|
||||||
<result property="updatedAt" column="updated_at" />
|
<result property="updatedAt" column="updated_at" />
|
||||||
|
<result property="needRefresh" column="need_refresh" />
|
||||||
|
<result property="refreshTime" column="refresh_time" />
|
||||||
|
<result property="qrCreatedAt" column="qr_created_at" />
|
||||||
|
<result property="qrExpireAt" column="qr_expire_at" />
|
||||||
|
<result property="firstRegionSelectAt" column="first_region_select_at" />
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<select id="findById" parameterType="long" resultMap="LinkTaskMap">
|
<select id="findById" parameterType="long" resultMap="LinkTaskMap">
|
||||||
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at
|
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at
|
||||||
FROM link_task
|
FROM link_task
|
||||||
WHERE id = #{id}
|
WHERE id = #{id}
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="findByCodeNo" parameterType="string" resultMap="LinkTaskMap">
|
<select id="findByCodeNo" parameterType="string" resultMap="LinkTaskMap">
|
||||||
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at
|
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at
|
||||||
FROM link_task
|
FROM link_task
|
||||||
WHERE code_no = #{codeNo}
|
WHERE code_no = #{codeNo}
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="findByTokenHash" parameterType="string" resultMap="LinkTaskMap">
|
<select id="findByTokenHash" parameterType="string" resultMap="LinkTaskMap">
|
||||||
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at
|
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at
|
||||||
FROM link_task
|
FROM link_task
|
||||||
WHERE token_hash = #{tokenHash}
|
WHERE token_hash = #{tokenHash}
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<insert id="insert" parameterType="com.gameplatform.server.model.entity.agent.LinkTask" useGeneratedKeys="true" keyProperty="id">
|
<insert id="insert" parameterType="com.gameplatform.server.model.entity.agent.LinkTask" useGeneratedKeys="true" keyProperty="id">
|
||||||
INSERT INTO link_task (batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at)
|
INSERT INTO link_task (batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at)
|
||||||
VALUES (#{batchId}, #{agentId}, #{codeNo}, #{tokenHash}, #{expireAt}, #{status}, #{region}, #{machineId}, #{loginAt}, #{refundAt}, #{revokedAt})
|
VALUES (#{batchId}, #{agentId}, #{codeNo}, #{tokenHash}, #{expireAt}, #{status}, #{region}, #{machineId}, #{loginAt}, #{refundAt}, #{revokedAt}, #{needRefresh}, #{refreshTime}, #{qrCreatedAt}, #{qrExpireAt}, #{firstRegionSelectAt})
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
<update id="update" parameterType="com.gameplatform.server.model.entity.agent.LinkTask">
|
<update id="update" parameterType="com.gameplatform.server.model.entity.agent.LinkTask">
|
||||||
@@ -53,6 +58,11 @@
|
|||||||
<if test="loginAt != null">login_at = #{loginAt},</if>
|
<if test="loginAt != null">login_at = #{loginAt},</if>
|
||||||
<if test="refundAt != null">refund_at = #{refundAt},</if>
|
<if test="refundAt != null">refund_at = #{refundAt},</if>
|
||||||
<if test="revokedAt != null">revoked_at = #{revokedAt},</if>
|
<if test="revokedAt != null">revoked_at = #{revokedAt},</if>
|
||||||
|
<if test="needRefresh != null">need_refresh = #{needRefresh},</if>
|
||||||
|
<if test="refreshTime != null">refresh_time = #{refreshTime},</if>
|
||||||
|
<if test="qrCreatedAt != null">qr_created_at = #{qrCreatedAt},</if>
|
||||||
|
<if test="qrExpireAt != null">qr_expire_at = #{qrExpireAt},</if>
|
||||||
|
<if test="firstRegionSelectAt != null">first_region_select_at = #{firstRegionSelectAt},</if>
|
||||||
</set>
|
</set>
|
||||||
WHERE id = #{id}
|
WHERE id = #{id}
|
||||||
</update>
|
</update>
|
||||||
@@ -68,7 +78,7 @@
|
|||||||
</update>
|
</update>
|
||||||
|
|
||||||
<select id="findByAgentId" resultMap="LinkTaskMap">
|
<select id="findByAgentId" resultMap="LinkTaskMap">
|
||||||
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at
|
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at
|
||||||
FROM link_task
|
FROM link_task
|
||||||
WHERE agent_id = #{agentId}
|
WHERE agent_id = #{agentId}
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
@@ -80,7 +90,7 @@
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="findByAgentIdAndStatus" resultMap="LinkTaskMap">
|
<select id="findByAgentIdAndStatus" resultMap="LinkTaskMap">
|
||||||
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at
|
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at
|
||||||
FROM link_task
|
FROM link_task
|
||||||
WHERE agent_id = #{agentId} AND status = #{status}
|
WHERE agent_id = #{agentId} AND status = #{status}
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
@@ -92,7 +102,7 @@
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="findByBatchId" resultMap="LinkTaskMap">
|
<select id="findByBatchId" resultMap="LinkTaskMap">
|
||||||
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at
|
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at
|
||||||
FROM link_task
|
FROM link_task
|
||||||
WHERE batch_id = #{batchId}
|
WHERE batch_id = #{batchId}
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
@@ -104,7 +114,7 @@
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="findExpiredTasks" resultMap="LinkTaskMap">
|
<select id="findExpiredTasks" resultMap="LinkTaskMap">
|
||||||
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at
|
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at
|
||||||
FROM link_task
|
FROM link_task
|
||||||
WHERE expire_at <= #{expireTime} AND status IN ('NEW', 'USING')
|
WHERE expire_at <= #{expireTime} AND status IN ('NEW', 'USING')
|
||||||
ORDER BY expire_at ASC
|
ORDER BY expire_at ASC
|
||||||
@@ -112,7 +122,7 @@
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="findLinkTasksWithConditions" resultMap="LinkTaskMap">
|
<select id="findLinkTasksWithConditions" resultMap="LinkTaskMap">
|
||||||
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at
|
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at
|
||||||
FROM link_task
|
FROM link_task
|
||||||
<where>
|
<where>
|
||||||
agent_id = #{agentId}
|
agent_id = #{agentId}
|
||||||
@@ -189,7 +199,7 @@
|
|||||||
</delete>
|
</delete>
|
||||||
|
|
||||||
<select id="findByCodeNosAndAgentId" resultMap="LinkTaskMap">
|
<select id="findByCodeNosAndAgentId" resultMap="LinkTaskMap">
|
||||||
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at
|
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at
|
||||||
FROM link_task
|
FROM link_task
|
||||||
WHERE agent_id = #{agentId}
|
WHERE agent_id = #{agentId}
|
||||||
AND code_no IN
|
AND code_no IN
|
||||||
|
|||||||
Reference in New Issue
Block a user