feat: 增加选区和轮询上号功能

主要修改:
1. 在LinkController中新增选区和轮询上号接口,支持用户选择游戏区域和检查上号状态。
2. 在LinkStatusService中实现选区操作逻辑,包含空闲设备检查和状态更新。
3. 更新ScriptClient,增加获取设备二维码和检查设备状态的功能。
4. 修改SecurityConfig,允许选区和轮询上号接口公开访问。
5. 更新application.yml,添加应用基础URL配置。

技术细节:
- 新增SelectRegionResponse和PollLoginResponse DTO以支持新功能的返回格式。
- 通过脚本端接口实现选区和上号状态的检查与更新。
This commit is contained in:
zyh
2025-08-26 20:29:27 +08:00
parent 3847250c2b
commit 400d6757c8
12 changed files with 1288 additions and 143 deletions

View File

@@ -6,18 +6,26 @@ import com.gameplatform.server.model.dto.link.LinkGenerateRequest;
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.PollLoginResponse;
import com.gameplatform.server.model.dto.link.SelectRegionRequest;
import com.gameplatform.server.model.dto.link.SelectRegionResponse;
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;
import com.gameplatform.server.service.device.DeviceCodeMappingService;
import com.gameplatform.server.service.external.ScriptClient;
import io.jsonwebtoken.Claims;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import javax.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
@@ -31,13 +39,19 @@ public class LinkController {
private final LinkGenerationService linkGenerationService;
private final LinkStatusService linkStatusService;
private final LinkListService linkListService;
private final DeviceCodeMappingService deviceCodeMappingService;
private final ScriptClient scriptClient;
public LinkController(LinkGenerationService linkGenerationService,
LinkStatusService linkStatusService,
LinkListService linkListService) {
LinkListService linkListService,
DeviceCodeMappingService deviceCodeMappingService,
ScriptClient scriptClient) {
this.linkGenerationService = linkGenerationService;
this.linkStatusService = linkStatusService;
this.linkListService = linkListService;
this.deviceCodeMappingService = deviceCodeMappingService;
this.scriptClient = scriptClient;
}
@GetMapping("/list")
@@ -166,36 +180,124 @@ public class LinkController {
});
}
@GetMapping("/{codeNo}/status")
@Operation(summary = "获取链接状态", description = "根据链接编号获取链接的详细状态信息,包括过期时间、奖励点数、当前状态等")
public Mono<LinkStatusResponse> getLinkStatus(@PathVariable("codeNo") String codeNo) {
log.info("=== 开始查询链接状态 ===");
log.info("链接编号: {}", codeNo);
return linkStatusService.getLinkStatus(codeNo)
// @GetMapping("/{codeNo}/status")
// @Operation(summary = "获取链接状态", description = "根据链接编号获取链接的详细状态信息,包括过期时间、奖励点数、当前状态等")
// public Mono<LinkStatusResponse> getLinkStatus(@PathVariable("codeNo") String codeNo) {
// log.info("=== 开始查询链接状态 ===");
// log.info("链接编号: {}", codeNo);
//
// return linkStatusService.getLinkStatus(codeNo)
// .doOnSuccess(response -> {
// log.info("链接状态查询成功: codeNo={}, status={}, isExpired={}",
// codeNo, response.getStatus(), response.getIsExpired());
// })
// .doOnError(error -> {
// log.error("链接状态查询失败: codeNo={}, error={}", codeNo, error.getMessage(), error);
// });
// }
//
// @GetMapping("/{codeNo}/exists")
// @Operation(summary = "检查链接是否存在", description = "检查指定链接编号是否存在")
// public Mono<Boolean> isLinkExists(@PathVariable("codeNo") String codeNo) {
// log.debug("检查链接是否存在: codeNo={}", codeNo);
// return linkStatusService.isLinkExists(codeNo);
// }
//
// @GetMapping("/{codeNo}/valid")
// @Operation(summary = "检查链接是否有效", description = "检查指定链接是否有效(未过期且状态正常)")
// public Mono<Boolean> isLinkValid(@PathVariable("codeNo") String codeNo) {
// log.debug("检查链接是否有效: codeNo={}", codeNo);
// return linkStatusService.isLinkValid(codeNo);
// }
@DeleteMapping("/{codeNo}")
@Operation(summary = "删除链接", description = "删除指定的链接,用户只能删除自己创建的链接")
public Mono<Boolean> deleteLink(@PathVariable("codeNo") String codeNo, Authentication authentication) {
log.info("=== 开始删除链接 ===");
log.info("链接编号: {}", codeNo);
if (authentication == null) {
log.error("=== 认证失败Authentication为空 ===");
return Mono.error(new IllegalArgumentException("用户未认证Authentication为空"));
}
// 获取用户ID
Claims claims = (Claims) authentication.getDetails();
if (claims == null) {
log.error("=== 认证失败Claims为空 ===");
log.error("Authentication details: {}", authentication.getDetails());
return Mono.error(new IllegalArgumentException("用户未认证Claims为空"));
}
Long agentId = claims.get("userId", Long.class);
String userType = claims.get("userType", String.class);
log.info("用户信息: agentId={}, userType={}", agentId, userType);
if (agentId == null) {
log.error("=== 无法获取用户ID ===");
return Mono.error(new IllegalArgumentException("无法获取用户ID"));
}
return linkStatusService.deleteLink(codeNo, agentId)
.doOnSuccess(success -> {
if (success) {
log.info("链接删除成功: codeNo={}, agentId={}", codeNo, agentId);
} else {
log.warn("链接删除失败: codeNo={}, agentId={}", codeNo, agentId);
}
})
.doOnError(error -> {
log.error("删除链接时发生错误: codeNo={}, agentId={}, error={}",
codeNo, agentId, error.getMessage(), error);
});
}
@PostMapping("/batch-delete")
@Operation(summary = "批量删除链接", description = "批量删除指定的链接用户只能删除自己创建的链接最多一次删除100个")
public Mono<BatchDeleteResponse> batchDeleteLinks(@RequestBody BatchDeleteRequest request, Authentication authentication) {
log.info("=== 开始批量删除链接 ===");
log.info("要删除的链接数量: {}", request.getCodeNos().size());
log.info("链接编号列表: {}", request.getCodeNos());
if (authentication == null) {
log.error("=== 认证失败Authentication为空 ===");
return Mono.error(new IllegalArgumentException("用户未认证Authentication为空"));
}
// 获取用户ID
Claims claims = (Claims) authentication.getDetails();
if (claims == null) {
log.error("=== 认证失败Claims为空 ===");
log.error("Authentication details: {}", authentication.getDetails());
return Mono.error(new IllegalArgumentException("用户未认证Claims为空"));
}
Long agentId = claims.get("userId", Long.class);
String userType = claims.get("userType", String.class);
log.info("用户信息: agentId={}, userType={}", agentId, userType);
if (agentId == null) {
log.error("=== 无法获取用户ID ===");
return Mono.error(new IllegalArgumentException("无法获取用户ID"));
}
return linkStatusService.batchDeleteLinks(request.getCodeNos(), agentId)
.doOnSuccess(response -> {
log.info("链接状态查询成功: codeNo={}, status={}, isExpired={}",
codeNo, response.getStatus(), response.getIsExpired());
log.info("批量删除链接完成: 总数={}, 成功={}, 失败={}, agentId={}",
response.getTotalCount(), response.getSuccessCount(),
response.getFailedCount(), agentId);
if (!response.isAllSuccess()) {
log.warn("部分链接删除失败: 失败的链接={}, 失败原因={}",
response.getFailedCodeNos(), response.getFailedReasons());
}
})
.doOnError(error -> {
log.error("链接状态查询失败: codeNo={}, error={}", codeNo, error.getMessage(), error);
log.error("批量删除链接时发生错误: agentId={}, codeNos={}, error={}",
agentId, request.getCodeNos(), error.getMessage(), error);
});
}
@GetMapping("/{codeNo}/exists")
@Operation(summary = "检查链接是否存在", description = "检查指定链接编号是否存在")
public Mono<Boolean> isLinkExists(@PathVariable("codeNo") String codeNo) {
log.debug("检查链接是否存在: codeNo={}", codeNo);
return linkStatusService.isLinkExists(codeNo);
}
@GetMapping("/{codeNo}/valid")
@Operation(summary = "检查链接是否有效", description = "检查指定链接是否有效(未过期且状态正常)")
public Mono<Boolean> isLinkValid(@PathVariable("codeNo") String codeNo) {
log.debug("检查链接是否有效: codeNo={}", codeNo);
return linkStatusService.isLinkValid(codeNo);
}
@GetMapping("/status")
@Operation(summary = "用户端获取链接状态", description = "根据链接ID或链接编号获取链接状态包含自动刷新逻辑用于用户端页面")
public Mono<UserLinkStatusResponse> getUserLinkStatus(
@@ -227,92 +329,85 @@ public class LinkController {
});
}
@DeleteMapping("/{codeNo}")
@Operation(summary = "删除链接", description = "删除指定的链接,用户只能删除自己创建的链接")
public Mono<Boolean> deleteLink(@PathVariable("codeNo") String codeNo, Authentication authentication) {
log.info("=== 开始删除链接 ===");
log.info("链接编号: {}", codeNo);
@PostMapping("/select-region")
@Operation(summary = "选择区域", description = "用户选择游戏区域Q或V选区成功后生成二维码")
public Mono<SelectRegionResponse> selectRegion(@Valid @RequestBody SelectRegionRequest request) {
log.info("=== 开始处理选区请求 ===");
log.info("请求参数: code={}, region={}", request.getCode(), request.getRegion());
if (authentication == null) {
log.error("=== 认证失败Authentication为空 ===");
return Mono.error(new IllegalArgumentException("用户未认证Authentication为空"));
}
// 获取用户ID
Claims claims = (Claims) authentication.getDetails();
if (claims == null) {
log.error("=== 认证失败Claims为空 ===");
log.error("Authentication details: {}", authentication.getDetails());
return Mono.error(new IllegalArgumentException("用户未认证Claims为空"));
}
Long agentId = claims.get("userId", Long.class);
String userType = claims.get("userType", String.class);
log.info("用户信息: agentId={}, userType={}", agentId, userType);
if (agentId == null) {
log.error("=== 无法获取用户ID ===");
return Mono.error(new IllegalArgumentException("无法获取用户ID"));
}
return linkStatusService.deleteLink(codeNo, agentId)
.doOnSuccess(success -> {
if (success) {
log.info("链接删除成功: codeNo={}, agentId={}", codeNo, agentId);
} else {
log.warn("链接删除失败: codeNo={}, agentId={}", codeNo, agentId);
}
return linkStatusService.selectRegion(request.getCode(), request.getRegion())
.doOnSuccess(response -> {
log.info("选区请求处理成功: success={}, status={}, region={}",
response.isSuccess(), response.getStatus(), response.getRegion());
})
.doOnError(error -> {
log.error("删除链接时发生错误: codeNo={}, agentId={}, error={}",
codeNo, agentId, error.getMessage(), error);
log.error("选区请求处理失败: code={}, region={}, error={}",
request.getCode(), request.getRegion(), error.getMessage(), error);
});
}
@PostMapping("/batch-delete")
@Operation(summary = "批量删除链接", description = "批量删除指定的链接用户只能删除自己创建的链接最多一次删除100个")
public Mono<BatchDeleteResponse> batchDeleteLinks(@RequestBody BatchDeleteRequest request, Authentication authentication) {
log.info("=== 开始批量删除链接 ===");
log.info("要删除的链接数量: {}", request.getCodeNos().size());
log.info("链接编号列表: {}", request.getCodeNos());
@GetMapping("/poll-login")
@Operation(summary = "轮询上号", description = "轮询检查用户是否已上号仅在USING状态下有效")
public Mono<PollLoginResponse> pollLogin(@RequestParam("code") String code) {
log.info("=== 开始轮询上号 ===");
log.info("请求参数: code={}", code);
if (authentication == null) {
log.error("=== 认证失败Authentication为空 ===");
return Mono.error(new IllegalArgumentException("用户未认证Authentication为空"));
}
// 获取用户ID
Claims claims = (Claims) authentication.getDetails();
if (claims == null) {
log.error("=== 认证失败Claims为空 ===");
log.error("Authentication details: {}", authentication.getDetails());
return Mono.error(new IllegalArgumentException("用户未认证Claims为空"));
}
Long agentId = claims.get("userId", Long.class);
String userType = claims.get("userType", String.class);
log.info("用户信息: agentId={}, userType={}", agentId, userType);
if (agentId == null) {
log.error("=== 无法获取用户ID ===");
return Mono.error(new IllegalArgumentException("无法获取用户ID"));
}
return linkStatusService.batchDeleteLinks(request.getCodeNos(), agentId)
return linkStatusService.pollLogin(code)
.doOnSuccess(response -> {
log.info("批量删除链接完成: 总数={}, 成功={}, 失败={}, agentId={}",
response.getTotalCount(), response.getSuccessCount(),
response.getFailedCount(), agentId);
if (!response.isAllSuccess()) {
log.warn("部分链接删除失败: 失败的链接={}, 失败原因={}",
response.getFailedCodeNos(), response.getFailedReasons());
}
log.info("轮询上号完成: success={}, status={}, view={}",
response.isSuccess(), response.getStatus(), response.getView());
})
.doOnError(error -> {
log.error("批量删除链接时发生错误: agentId={}, codeNos={}, error={}",
agentId, request.getCodeNos(), error.getMessage(), error);
log.error("轮询上号失败: code={}, error={}",
code, error.getMessage(), error);
});
}
/**
* 代理二维码获取接口
* 通过代理code获取真实设备的二维码避免暴露设备编号
*/
@GetMapping("/qr/{proxyCode}")
@Operation(summary = "获取二维码", description = "通过代理code获取设备二维码用于扫码上号")
public Mono<ResponseEntity<byte[]>> getProxyQrCode(@PathVariable("proxyCode") String proxyCode) {
log.info("=== 获取代理二维码 ===");
log.info("代理code: {}", proxyCode);
// 验证代理code是否有效
if (!deviceCodeMappingService.isValidProxyCode(proxyCode)) {
log.warn("无效的代理code: {}", proxyCode);
return Mono.just(ResponseEntity.notFound().build());
}
// 获取真实设备编号
String deviceId = deviceCodeMappingService.getDeviceId(proxyCode);
if (deviceId == null) {
log.warn("代理code对应的设备不存在: {}", proxyCode);
return Mono.just(ResponseEntity.notFound().build());
}
log.info("代理code {} 对应设备: {}", proxyCode, deviceId);
// 获取真实设备的二维码
return scriptClient.getDeviceQrCode(deviceId)
.map(qrData -> {
log.info("获取设备 {} 二维码成功,大小: {} 字节", deviceId, qrData.length);
// 设置响应头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.IMAGE_PNG);
headers.setCacheControl("no-cache, no-store, must-revalidate");
headers.setPragma("no-cache");
headers.setExpires(0);
return ResponseEntity.ok()
.headers(headers)
.body(qrData);
})
.onErrorResume(error -> {
log.error("获取设备 {} 二维码失败: {}", deviceId, error.getMessage(), error);
return Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build());
});
}
}