feat: 增加选区和轮询上号功能
主要修改: 1. 在LinkController中新增选区和轮询上号接口,支持用户选择游戏区域和检查上号状态。 2. 在LinkStatusService中实现选区操作逻辑,包含空闲设备检查和状态更新。 3. 更新ScriptClient,增加获取设备二维码和检查设备状态的功能。 4. 修改SecurityConfig,允许选区和轮询上号接口公开访问。 5. 更新application.yml,添加应用基础URL配置。 技术细节: - 新增SelectRegionResponse和PollLoginResponse DTO以支持新功能的返回格式。 - 通过脚本端接口实现选区和上号状态的检查与更新。
This commit is contained in:
@@ -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());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user