feat: 添加用户端链接状态查询接口及自动刷新逻辑
主要修改: 1. 在LinkController中新增获取用户链接状态的接口,支持通过linkId或codeNo查询。 2. 在LinkStatusService中实现用户链接状态查询逻辑,包含自动刷新和二维码更新功能。 3. 更新LinkTask实体,添加needRefresh、refreshTime、qrCreatedAt和qrExpireAt字段以支持新功能。 4. 在ScriptClient中新增检查空闲设备、选区、刷新、检查上号状态等操作的实现。 5. 更新SecurityConfig,允许用户端获取链接状态接口公开访问。 技术细节: - 新增UserLinkStatusResponse DTO以支持用户链接状态的返回格式。 - 通过脚本端接口实现链接状态的自动刷新和二维码信息更新。
This commit is contained in:
@@ -7,6 +7,7 @@ import com.gameplatform.server.model.dto.link.LinkGenerateResponse;
|
||||
import com.gameplatform.server.model.dto.link.LinkListRequest;
|
||||
import com.gameplatform.server.model.dto.link.LinkListResponse;
|
||||
import com.gameplatform.server.model.dto.link.LinkStatusResponse;
|
||||
import com.gameplatform.server.model.dto.link.UserLinkStatusResponse;
|
||||
import com.gameplatform.server.service.link.LinkGenerationService;
|
||||
import com.gameplatform.server.service.link.LinkListService;
|
||||
import com.gameplatform.server.service.link.LinkStatusService;
|
||||
@@ -195,6 +196,37 @@ public class LinkController {
|
||||
return linkStatusService.isLinkValid(codeNo);
|
||||
}
|
||||
|
||||
@GetMapping("/status")
|
||||
@Operation(summary = "用户端获取链接状态", description = "根据链接ID或链接编号获取链接状态,包含自动刷新逻辑,用于用户端页面")
|
||||
public Mono<UserLinkStatusResponse> getUserLinkStatus(
|
||||
@RequestParam(value = "linkId", required = false) Long linkId,
|
||||
@RequestParam(value = "codeNo", required = false) String codeNo,
|
||||
@RequestParam(value = "code", required = false) String code) {
|
||||
log.info("=== 用户端获取链接状态 ===");
|
||||
log.info("linkId: {}, codeNo: {}, code: {}", linkId, codeNo, code);
|
||||
|
||||
// 如果提供了code参数,则将其作为codeNo使用
|
||||
String actualCodeNo = codeNo;
|
||||
if (actualCodeNo == null || actualCodeNo.trim().isEmpty()) {
|
||||
actualCodeNo = code;
|
||||
}
|
||||
|
||||
// 验证参数:linkId和实际的codeNo至少提供一个
|
||||
if (linkId == null && (actualCodeNo == null || actualCodeNo.trim().isEmpty())) {
|
||||
log.error("参数错误:linkId、codeNo或code至少提供一个");
|
||||
return Mono.error(new IllegalArgumentException("参数错误:linkId、codeNo或code至少提供一个"));
|
||||
}
|
||||
|
||||
return linkStatusService.getUserLinkStatus(linkId, actualCodeNo)
|
||||
.doOnSuccess(response -> {
|
||||
log.info("用户端链接状态查询成功: status={}, view={}, needRefresh={}",
|
||||
response.getStatus(), response.getView(), response.getNeedRefresh());
|
||||
})
|
||||
.doOnError(error -> {
|
||||
log.error("用户端链接状态查询失败: {}", error.getMessage(), error);
|
||||
});
|
||||
}
|
||||
|
||||
@DeleteMapping("/{codeNo}")
|
||||
@Operation(summary = "删除链接", description = "删除指定的链接,用户只能删除自己创建的链接")
|
||||
public Mono<Boolean> deleteLink(@PathVariable("codeNo") String codeNo, Authentication authentication) {
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.gameplatform.server.model.dto.link;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "用户端链接状态响应")
|
||||
public class UserLinkStatusResponse {
|
||||
|
||||
@Schema(description = "链接状态", example = "NEW", allowableValues = {"NEW", "USING", "LOGGED_IN", "REFUNDED", "EXPIRED"})
|
||||
private String status;
|
||||
|
||||
@Schema(description = "是否需要刷新", example = "false")
|
||||
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 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; }
|
||||
}
|
||||
@@ -46,6 +46,18 @@ public class LinkTask {
|
||||
|
||||
@TableField("updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@TableField("need_refresh")
|
||||
private Boolean needRefresh;
|
||||
|
||||
@TableField("refresh_time")
|
||||
private LocalDateTime refreshTime;
|
||||
|
||||
@TableField("qr_created_at")
|
||||
private LocalDateTime qrCreatedAt;
|
||||
|
||||
@TableField("qr_expire_at")
|
||||
private LocalDateTime qrExpireAt;
|
||||
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
@@ -88,4 +100,16 @@ public class LinkTask {
|
||||
|
||||
public LocalDateTime getUpdatedAt() { return updatedAt; }
|
||||
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
|
||||
|
||||
public Boolean getNeedRefresh() { return needRefresh; }
|
||||
public void setNeedRefresh(Boolean needRefresh) { this.needRefresh = needRefresh; }
|
||||
|
||||
public LocalDateTime getRefreshTime() { return refreshTime; }
|
||||
public void setRefreshTime(LocalDateTime refreshTime) { this.refreshTime = refreshTime; }
|
||||
|
||||
public LocalDateTime getQrCreatedAt() { return qrCreatedAt; }
|
||||
public void setQrCreatedAt(LocalDateTime qrCreatedAt) { this.qrCreatedAt = qrCreatedAt; }
|
||||
|
||||
public LocalDateTime getQrExpireAt() { return qrExpireAt; }
|
||||
public void setQrExpireAt(LocalDateTime qrExpireAt) { this.qrExpireAt = qrExpireAt; }
|
||||
}
|
||||
|
||||
@@ -58,6 +58,102 @@ public class JwtService {
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成链接code(用于用户端访问链接)
|
||||
*/
|
||||
public String generateLinkCode(Long linkId, String codeNo, int expireHours) {
|
||||
log.info("=== 开始生成链接code ===");
|
||||
log.info("生成参数: linkId={}, codeNo={}, expireHours={}", linkId, codeNo, expireHours);
|
||||
|
||||
Instant now = Instant.now();
|
||||
var builder = Jwts.builder()
|
||||
.setSubject("link_access")
|
||||
.setIssuedAt(Date.from(now))
|
||||
.setExpiration(Date.from(now.plus(expireHours, ChronoUnit.HOURS)))
|
||||
.claim("linkId", linkId)
|
||||
.claim("codeNo", codeNo)
|
||||
.claim("type", "link_access");
|
||||
|
||||
String code = builder.signWith(key, SignatureAlgorithm.HS256).compact();
|
||||
|
||||
log.info("=== 链接code生成成功 ===");
|
||||
log.info("code长度: {} 字符", code.length());
|
||||
log.info("过期时间: {} 小时后", expireHours);
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析链接code获取链接信息
|
||||
*/
|
||||
public LinkCodeInfo parseLinkCode(String code) {
|
||||
log.debug("=== 开始解析链接code ===");
|
||||
log.debug("code长度: {} 字符", code.length());
|
||||
|
||||
try {
|
||||
io.jsonwebtoken.Claims claims = Jwts.parserBuilder()
|
||||
.setSigningKey(key)
|
||||
.build()
|
||||
.parseClaimsJws(code)
|
||||
.getBody();
|
||||
|
||||
// 检查是否是链接类型的token
|
||||
String type = claims.get("type", String.class);
|
||||
if (!"link_access".equals(type)) {
|
||||
throw new IllegalArgumentException("无效的链接code类型");
|
||||
}
|
||||
|
||||
Long linkId = claims.get("linkId", Long.class);
|
||||
String codeNo = claims.get("codeNo", String.class);
|
||||
|
||||
if (linkId == null || codeNo == null) {
|
||||
throw new IllegalArgumentException("链接code格式错误");
|
||||
}
|
||||
|
||||
LinkCodeInfo info = new LinkCodeInfo();
|
||||
info.setLinkId(linkId);
|
||||
info.setCodeNo(codeNo);
|
||||
info.setIssuedAt(claims.getIssuedAt());
|
||||
info.setExpireAt(claims.getExpiration());
|
||||
|
||||
log.debug("=== 链接code解析成功 ===");
|
||||
log.debug("linkId: {}, codeNo: {}", linkId, codeNo);
|
||||
|
||||
return info;
|
||||
} catch (Exception e) {
|
||||
log.warn("=== 链接code解析失败 ===");
|
||||
log.warn("code: {}", code);
|
||||
log.warn("错误详情: {}", e.getMessage());
|
||||
throw new IllegalArgumentException("无效的链接code", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 链接code信息
|
||||
*/
|
||||
public static class LinkCodeInfo {
|
||||
private Long linkId;
|
||||
private String codeNo;
|
||||
private Date issuedAt;
|
||||
private Date expireAt;
|
||||
|
||||
public Long getLinkId() { return linkId; }
|
||||
public void setLinkId(Long linkId) { this.linkId = linkId; }
|
||||
|
||||
public String getCodeNo() { return codeNo; }
|
||||
public void setCodeNo(String codeNo) { this.codeNo = codeNo; }
|
||||
|
||||
public Date getIssuedAt() { return issuedAt; }
|
||||
public void setIssuedAt(Date issuedAt) { this.issuedAt = issuedAt; }
|
||||
|
||||
public Date getExpireAt() { return expireAt; }
|
||||
public void setExpireAt(Date expireAt) { this.expireAt = expireAt; }
|
||||
|
||||
public boolean isExpired() {
|
||||
return expireAt != null && new Date().after(expireAt);
|
||||
}
|
||||
}
|
||||
|
||||
public io.jsonwebtoken.Claims parse(String token) {
|
||||
log.info("=== 开始解析JWT token ===");
|
||||
log.info("token长度: {} 字符", token.length());
|
||||
|
||||
@@ -41,7 +41,8 @@ public class SecurityConfig {
|
||||
.pathMatchers("/actuator/**").permitAll()
|
||||
.pathMatchers(HttpMethod.POST, "/api/auth/login").permitAll()
|
||||
.pathMatchers(HttpMethod.GET, "/api/auth/me").permitAll()
|
||||
.pathMatchers("/api/link/**").authenticated() // 链接接口需要认证
|
||||
.pathMatchers(HttpMethod.GET, "/api/link/status").permitAll() // 用户端获取链接状态接口,公开访问
|
||||
.pathMatchers("/api/link/**").authenticated() // 其他链接接口需要认证
|
||||
.anyExchange().permitAll() // 其他接口后续再收紧
|
||||
)
|
||||
// 关键:将JWT过滤器集成到Security过滤链中,放在AUTHENTICATION位置
|
||||
@@ -59,6 +60,7 @@ public class SecurityConfig {
|
||||
log.info(" * /actuator/** -> 允许所有");
|
||||
log.info(" * POST /api/auth/login -> 允许所有");
|
||||
log.info(" * GET /api/auth/me -> 允许所有");
|
||||
log.info(" * GET /api/link/status -> 允许所有 (用户端公开接口)");
|
||||
log.info(" * /api/link/** -> 需要认证");
|
||||
log.info(" * 其他路径 -> 允许所有");
|
||||
|
||||
|
||||
@@ -55,6 +55,110 @@ public class ScriptClient {
|
||||
.timeout(Duration.ofSeconds(5))
|
||||
.doOnError(e -> log.warn("ScriptClient.getText error path={} err={}", path, e.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查空闲设备
|
||||
*/
|
||||
public Mono<String> checkAvailableDevice() {
|
||||
String url = "http://36.138.184.60:1234/yijianwan_netfile/readAllMsg?文件名=判断分数";
|
||||
log.debug("检查空闲设备: {}", url);
|
||||
return webClient.get()
|
||||
.uri(url)
|
||||
.accept(MediaType.TEXT_PLAIN)
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.timeout(Duration.ofSeconds(10))
|
||||
.doOnSuccess(result -> log.debug("检查空闲设备成功: {}", result))
|
||||
.doOnError(e -> log.warn("检查空闲设备失败: {}", e.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 选区操作
|
||||
*/
|
||||
public Mono<String> selectRegion(String codeNo, String region) {
|
||||
String url = String.format("http://36.138.184.60:1234/yijianwan_netfile/saveMsg?文件名=判断系统&编号=%s", region);
|
||||
log.debug("选区操作: codeNo={}, region={}, url={}", codeNo, region, url);
|
||||
return webClient.post()
|
||||
.uri(url)
|
||||
.accept(MediaType.TEXT_PLAIN)
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.timeout(Duration.ofSeconds(10))
|
||||
.doOnSuccess(result -> log.debug("选区操作成功: codeNo={}, region={}, result={}", codeNo, region, result))
|
||||
.doOnError(e -> log.warn("选区操作失败: codeNo={}, region={}, error={}", codeNo, region, e.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新操作
|
||||
*/
|
||||
public Mono<String> refresh(String codeNo) {
|
||||
String url = "http://36.138.184.60:1234/yijianwan_netfile/saveMsg?文件名=判断刷新&编号=刷新";
|
||||
log.debug("刷新操作: codeNo={}, url={}", codeNo, url);
|
||||
return webClient.post()
|
||||
.uri(url)
|
||||
.accept(MediaType.TEXT_PLAIN)
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.timeout(Duration.ofSeconds(10))
|
||||
.doOnSuccess(result -> log.debug("刷新操作成功: codeNo={}, result={}", codeNo, result))
|
||||
.doOnError(e -> log.warn("刷新操作失败: codeNo={}, error={}", codeNo, e.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查上号状态
|
||||
*/
|
||||
public Mono<String> checkLoginStatus(String codeNo) {
|
||||
String url = String.format("http://36.138.184.60:1234/yijianwan_netfile/readMsg?文件名=判断上号&对象名=%s", codeNo);
|
||||
log.debug("检查上号状态: codeNo={}, url={}", codeNo, url);
|
||||
return webClient.get()
|
||||
.uri(url)
|
||||
.accept(MediaType.TEXT_PLAIN)
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.timeout(Duration.ofSeconds(5))
|
||||
.doOnSuccess(result -> log.debug("检查上号状态成功: codeNo={}, result={}", codeNo, result))
|
||||
.doOnError(e -> log.warn("检查上号状态失败: codeNo={}, error={}", codeNo, e.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取二维码URL(带时间戳防缓存)
|
||||
*/
|
||||
public String getQrCodeUrl(String codeNo) {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
return String.format("http://36.138.184.60:12345/%s/二维码.png?t=%d", codeNo, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取目标分数
|
||||
*/
|
||||
public Mono<String> getTargetScore(String codeNo) {
|
||||
String url = String.format("http://36.138.184.60:1234/yijianwan_netfile/readMsg?文件名=判断分数&对象名=%s", codeNo);
|
||||
log.debug("获取目标分数: codeNo={}, url={}", codeNo, url);
|
||||
return webClient.get()
|
||||
.uri(url)
|
||||
.accept(MediaType.TEXT_PLAIN)
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.timeout(Duration.ofSeconds(5))
|
||||
.doOnSuccess(result -> log.debug("获取目标分数成功: codeNo={}, result={}", codeNo, result))
|
||||
.doOnError(e -> log.warn("获取目标分数失败: codeNo={}, error={}", codeNo, e.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置次数(生成链接时调用)
|
||||
*/
|
||||
public Mono<String> setTimes(String codeNo, int times) {
|
||||
String url = String.format("http://36.138.184.60:1234/yijianwan_netfile/saveMsg?文件名=总次数&编号=%d", times);
|
||||
log.debug("设置次数: codeNo={}, times={}, url={}", codeNo, times, url);
|
||||
return webClient.post()
|
||||
.uri(url)
|
||||
.accept(MediaType.TEXT_PLAIN)
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.timeout(Duration.ofSeconds(10))
|
||||
.doOnSuccess(result -> log.debug("设置次数成功: codeNo={}, times={}, result={}", codeNo, times, result))
|
||||
.doOnError(e -> log.warn("设置次数失败: codeNo={}, times={}, error={}", codeNo, times, e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,8 +4,11 @@ import com.gameplatform.server.mapper.agent.LinkBatchMapper;
|
||||
import com.gameplatform.server.mapper.agent.LinkTaskMapper;
|
||||
import com.gameplatform.server.model.dto.link.BatchDeleteResponse;
|
||||
import com.gameplatform.server.model.dto.link.LinkStatusResponse;
|
||||
import com.gameplatform.server.model.dto.link.UserLinkStatusResponse;
|
||||
import com.gameplatform.server.model.entity.agent.LinkBatch;
|
||||
import com.gameplatform.server.model.entity.agent.LinkTask;
|
||||
|
||||
import com.gameplatform.server.service.external.ScriptClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -28,6 +31,8 @@ public class LinkStatusService {
|
||||
private final LinkTaskMapper linkTaskMapper;
|
||||
private final LinkBatchMapper linkBatchMapper;
|
||||
|
||||
private final ScriptClient scriptClient;
|
||||
|
||||
// 状态描述映射
|
||||
private static final Map<String, String> STATUS_DESC_MAP = new HashMap<>();
|
||||
static {
|
||||
@@ -38,9 +43,11 @@ public class LinkStatusService {
|
||||
STATUS_DESC_MAP.put("EXPIRED", "已过期");
|
||||
}
|
||||
|
||||
public LinkStatusService(LinkTaskMapper linkTaskMapper, LinkBatchMapper linkBatchMapper) {
|
||||
public LinkStatusService(LinkTaskMapper linkTaskMapper, LinkBatchMapper linkBatchMapper,
|
||||
ScriptClient scriptClient) {
|
||||
this.linkTaskMapper = linkTaskMapper;
|
||||
this.linkBatchMapper = linkBatchMapper;
|
||||
this.scriptClient = scriptClient;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -236,4 +243,181 @@ public class LinkStatusService {
|
||||
return response;
|
||||
}).subscribeOn(Schedulers.boundedElastic());
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户端获取链接状态(支持linkId或codeNo参数,带自动刷新逻辑)
|
||||
*/
|
||||
public Mono<UserLinkStatusResponse> getUserLinkStatus(Long linkId, String codeNo) {
|
||||
return Mono.fromCallable(() -> doGetUserLinkStatus(linkId, codeNo))
|
||||
.subscribeOn(Schedulers.boundedElastic());
|
||||
}
|
||||
|
||||
private UserLinkStatusResponse doGetUserLinkStatus(Long linkId, String codeNo) {
|
||||
log.info("=== 开始处理用户端链接状态查询 ===");
|
||||
log.info("linkId: {}, codeNo: {}", linkId, codeNo);
|
||||
|
||||
try {
|
||||
// 1. 查询链接任务
|
||||
LinkTask linkTask = null;
|
||||
if (linkId != null) {
|
||||
linkTask = linkTaskMapper.findById(linkId);
|
||||
log.info("通过linkId查询链接任务: id={}", linkId);
|
||||
} else if (codeNo != null && !codeNo.trim().isEmpty()) {
|
||||
linkTask = linkTaskMapper.findByCodeNo(codeNo.trim());
|
||||
log.info("通过codeNo查询链接任务: codeNo={}", codeNo);
|
||||
}
|
||||
|
||||
if (linkTask == null) {
|
||||
log.error("链接任务不存在: linkId={}, codeNo={}", linkId, codeNo);
|
||||
throw new IllegalArgumentException("链接不存在");
|
||||
}
|
||||
|
||||
log.info("查询到链接任务: id={}, codeNo={}, status={}, needRefresh={}",
|
||||
linkTask.getId(), linkTask.getCodeNo(), linkTask.getStatus(), linkTask.getNeedRefresh());
|
||||
|
||||
// 2. 检查链接任务是否过期
|
||||
if (linkTask.getExpireAt() != null && linkTask.getExpireAt().isBefore(LocalDateTime.now())) {
|
||||
log.warn("链接任务已过期: expireAt={}", linkTask.getExpireAt());
|
||||
linkTask.setStatus("EXPIRED");
|
||||
linkTask.setUpdatedAt(LocalDateTime.now());
|
||||
linkTaskMapper.update(linkTask);
|
||||
|
||||
UserLinkStatusResponse response = new UserLinkStatusResponse();
|
||||
response.setStatus("EXPIRED");
|
||||
response.setView("EXPIRED");
|
||||
return response;
|
||||
}
|
||||
|
||||
// 3. 如果状态不是NEW,执行自动刷新逻辑
|
||||
if (!"NEW".equals(linkTask.getStatus())) {
|
||||
log.info("链接状态不是NEW,执行自动刷新逻辑");
|
||||
performAutoRefresh(linkTask);
|
||||
}
|
||||
|
||||
// 4. 如果状态是USING,重新获取二维码
|
||||
if ("USING".equals(linkTask.getStatus())) {
|
||||
log.info("链接状态是USING,重新获取二维码");
|
||||
updateQrCodeInfo(linkTask);
|
||||
}
|
||||
|
||||
// 5. 构建响应
|
||||
UserLinkStatusResponse response = buildUserStatusResponse(linkTask);
|
||||
log.info("=== 用户端链接状态查询完成 ===");
|
||||
log.info("返回状态: {}, view: {}", response.getStatus(), response.getView());
|
||||
|
||||
return response;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("=== 用户端链接状态查询失败 ===");
|
||||
log.error("错误详情: {}", e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行自动刷新逻辑
|
||||
*/
|
||||
private void performAutoRefresh(LinkTask linkTask) {
|
||||
try {
|
||||
log.info("开始执行刷新操作");
|
||||
|
||||
// 调用脚本端刷新
|
||||
String refreshResult = scriptClient.refresh(linkTask.getCodeNo()).block();
|
||||
log.info("脚本端刷新结果: {}", refreshResult);
|
||||
|
||||
// 更新刷新状态
|
||||
linkTask.setNeedRefresh(true);
|
||||
linkTask.setRefreshTime(LocalDateTime.now());
|
||||
linkTask.setUpdatedAt(LocalDateTime.now());
|
||||
linkTaskMapper.update(linkTask);
|
||||
|
||||
// 等待10秒
|
||||
log.info("刷新完成,等待10秒...");
|
||||
Thread.sleep(10000);
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.warn("等待被中断: {}", e.getMessage());
|
||||
} catch (Exception e) {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建用户端状态响应
|
||||
*/
|
||||
private UserLinkStatusResponse buildUserStatusResponse(LinkTask linkTask) {
|
||||
UserLinkStatusResponse response = new UserLinkStatusResponse();
|
||||
|
||||
// 基本状态信息
|
||||
response.setStatus(linkTask.getStatus());
|
||||
response.setNeedRefresh(Boolean.TRUE.equals(linkTask.getNeedRefresh()));
|
||||
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.getQrCodeUrl(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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定视图类型
|
||||
*/
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user