feat: 增强二维码和图片代理功能

主要修改:
1. 在QrProxyController中新增多个图片代理接口,包括首页、首次赏金、中途赏金和结束赏金图片的获取。
2. 更新LinkController中的链接状态查询逻辑,简化日志输出。
3. 在LinkStatusService中优化链接状态处理逻辑,增加对USING状态的过期检查。
4. 在ScriptClient中新增通用图片获取方法,支持从脚本端获取图片数据。
5. 更新SecurityConfig,允许公开访问二维码和游戏界面数据接口。

技术细节:
- 新增GameInterfaceResponse DTO以支持游戏界面数据的返回格式。
- 通过脚本端接口实现图片的动态获取和链接状态的自动刷新。
This commit is contained in:
zyh
2025-08-26 23:11:01 +08:00
parent 400d6757c8
commit bb4136b4ab
10 changed files with 478 additions and 217 deletions

View 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';

View File

@@ -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);
// 如果是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()); return Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build());
}
}); });
} }
} }

View File

@@ -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 final ScriptClient scriptClient; private static final Logger log = LoggerFactory.getLogger(QrProxyController.class);
public QrProxyController(ScriptClient scriptClient) { private final ScriptClient scriptClient;
private final String appBaseUrl;
private final LinkTaskMapper linkTaskMapper;
private final LinkBatchMapper linkBatchMapper;
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 "未知区域";
}
}
} }

View File

@@ -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;
}
}

View File

@@ -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; }
} }

View File

@@ -59,6 +59,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; }
} }

View File

@@ -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(" * 其他路径 -> 允许所有");

View File

@@ -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()));
}
} }

View File

@@ -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 {
@@ -53,6 +55,7 @@ public class LinkStatusService {
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("=== 选区操作完成 ===");

View File

@@ -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 &lt;= #{expireTime} AND status IN ('NEW', 'USING') WHERE expire_at &lt;= #{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