This commit is contained in:
zyh
2025-08-26 15:18:14 +08:00
parent 599ec0a36b
commit d3fe8fda7d
77 changed files with 1149 additions and 60 deletions

1
.idea/compiler.xml generated
View File

@@ -2,6 +2,7 @@
<project version="4"> <project version="4">
<component name="CompilerConfiguration"> <component name="CompilerConfiguration">
<annotationProcessing> <annotationProcessing>
<profile default="true" name="Default" enabled="true" />
<profile name="Maven default annotation processors profile" enabled="true"> <profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" /> <sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" /> <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />

View File

@@ -400,4 +400,3 @@ export default router;
3. **响应式设计**:确保在不同设备上都能正常显示 3. **响应式设计**:确保在不同设备上都能正常显示
4. **缓存策略**:可以适当缓存链接状态,减少请求次数 4. **缓存策略**:可以适当缓存链接状态,减少请求次数
5. **实时更新**:对于进行中的任务,可以定时刷新状态 5. **实时更新**:对于进行中的任务,可以定时刷新状态

View File

@@ -0,0 +1,414 @@
# 链接列表接口测试文档
## 接口概览
**接口地址**: `GET /api/link/list`
**功能描述**: 分页查询用户生成的链接列表支持按状态、批次ID等条件过滤和排序
## 请求参数
### URL参数
| 参数名 | 类型 | 必填 | 默认值 | 说明 | 示例 |
|-------|------|------|--------|------|------|
| page | Integer | 否 | 1 | 页码从1开始 | 1 |
| pageSize | Integer | 否 | 20 | 每页大小1-100 | 20 |
| status | String | 否 | - | 链接状态过滤 | NEW |
| batchId | Long | 否 | - | 批次ID过滤 | 123 |
| isExpired | Boolean | 否 | - | 是否过期过滤 | false |
| sortBy | String | 否 | createdAt | 排序字段 | createdAt |
| sortDir | String | 否 | DESC | 排序方向 | DESC |
### 状态值说明
- `NEW` - 新建
- `USING` - 使用中
- `LOGGED_IN` - 已登录
- `REFUNDED` - 已退款
- `EXPIRED` - 已过期
### 排序字段说明
- `createdAt` - 创建时间
- `updatedAt` - 更新时间
- `expireAt` - 过期时间
## 响应格式
```json
{
"items": [
{
"codeNo": "ABC12345",
"batchId": 123,
"status": "NEW",
"statusDesc": "新建",
"expireAt": "2024-01-15 16:30:00",
"isExpired": false,
"remainingSeconds": 7200,
"quantity": 50,
"times": 5,
"totalPoints": 250,
"region": null,
"machineId": null,
"loginAt": null,
"createdAt": "2024-01-15 12:00:00",
"updatedAt": "2024-01-15 12:00:00"
}
],
"total": 150,
"page": 1,
"pageSize": 20,
"totalPages": 8,
"hasNext": true,
"hasPrev": false
}
```
## 测试用例
### 1. 基本分页查询
```bash
# 查询第一页每页20条
curl -X GET "http://localhost:18080/api/link/list?page=1&pageSize=20" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Accept: application/json"
```
**预期结果**:
- 返回最新的20条链接记录
- 按创建时间倒序排列
- 包含完整的分页信息
### 2. 按状态过滤
```bash
# 查询所有新建状态的链接
curl -X GET "http://localhost:18080/api/link/list?status=NEW" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Accept: application/json"
```
**预期结果**:
- 只返回状态为NEW的链接
- 其他字段正常显示
### 3. 按批次ID过滤
```bash
# 查询指定批次的所有链接
curl -X GET "http://localhost:18080/api/link/list?batchId=123" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Accept: application/json"
```
**预期结果**:
- 只返回指定批次的链接
- 显示该批次的所有链接状态
### 4. 按过期状态过滤
```bash
# 查询未过期的链接
curl -X GET "http://localhost:18080/api/link/list?isExpired=false" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Accept: application/json"
# 查询已过期的链接
curl -X GET "http://localhost:18080/api/link/list?isExpired=true" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Accept: application/json"
```
**预期结果**:
- isExpired=false: 只返回未过期的链接
- isExpired=true: 只返回已过期的链接
### 5. 排序测试
```bash
# 按过期时间升序排列
curl -X GET "http://localhost:18080/api/link/list?sortBy=expireAt&sortDir=ASC" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Accept: application/json"
# 按更新时间降序排列
curl -X GET "http://localhost:18080/api/link/list?sortBy=updatedAt&sortDir=DESC" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Accept: application/json"
```
**预期结果**:
- 按指定字段和方向正确排序
- 数据顺序符合预期
### 6. 组合条件查询
```bash
# 查询未过期的新建状态链接,按过期时间升序
curl -X GET "http://localhost:18080/api/link/list?status=NEW&isExpired=false&sortBy=expireAt&sortDir=ASC&page=1&pageSize=10" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Accept: application/json"
```
**预期结果**:
- 同时满足多个条件的链接
- 正确的排序和分页
### 7. 大页码测试
```bash
# 查询第100页
curl -X GET "http://localhost:18080/api/link/list?page=100&pageSize=20" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Accept: application/json"
```
**预期结果**:
- 如果数据不足,返回空数组
- 分页信息正确显示
### 8. 边界值测试
```bash
# 最小页码和页大小
curl -X GET "http://localhost:18080/api/link/list?page=1&pageSize=1" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Accept: application/json"
# 最大页大小
curl -X GET "http://localhost:18080/api/link/list?page=1&pageSize=100" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Accept: application/json"
```
**预期结果**:
- pageSize=1: 只返回1条记录
- pageSize=100: 返回最多100条记录
## 错误测试
### 1. 无效参数测试
```bash
# 无效的页码
curl -X GET "http://localhost:18080/api/link/list?page=0" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Accept: application/json"
# 页大小超出限制
curl -X GET "http://localhost:18080/api/link/list?pageSize=101" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Accept: application/json"
# 无效的排序字段
curl -X GET "http://localhost:18080/api/link/list?sortBy=invalidField" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Accept: application/json"
# 无效的排序方向
curl -X GET "http://localhost:18080/api/link/list?sortDir=INVALID" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Accept: application/json"
```
**预期结果**:
- 参数验证失败返回400错误
- 或者使用默认值处理
### 2. 认证失败测试
```bash
# 无JWT令牌
curl -X GET "http://localhost:18080/api/link/list" \
-H "Accept: application/json"
# 无效JWT令牌
curl -X GET "http://localhost:18080/api/link/list" \
-H "Authorization: Bearer INVALID_TOKEN" \
-H "Accept: application/json"
```
**预期结果**:
- 返回401未授权错误
- 不返回任何数据
## 性能测试
### 1. 响应时间测试
```bash
# 测试响应时间
time curl -X GET "http://localhost:18080/api/link/list?page=1&pageSize=20" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Accept: application/json"
```
**性能要求**:
- 响应时间 < 100ms正常数据量
- 响应时间 < 500ms大数据量
### 2. 并发测试
```bash
# 使用ab工具进行并发测试
ab -n 100 -c 10 -H "Authorization: Bearer YOUR_JWT_TOKEN" \
"http://localhost:18080/api/link/list?page=1&pageSize=20"
```
**性能要求**:
- 支持至少10个并发用户
- 无明显性能衰减
## 数据验证
### 1. 分页计算验证
检查以下字段的计算是否正确
- `totalPages` = Math.ceil(total / pageSize)
- `hasNext` = (page * pageSize < total)
- `hasPrev` = (page > 1)
### 2. 时间计算验证
检查以下时间字段:
- `isExpired` 与当前时间和 `expireAt` 的比较
- `remainingSeconds` 的计算精度
- 时间格式是否正确
### 3. 数据一致性验证
- 批次信息与链接任务的关联是否正确
- `totalPoints` = `quantity` × `times`
- 状态描述与状态码的对应关系
## 测试数据准备
### 1. 创建测试批次
```bash
# 生成多个批次的链接用于测试
curl -X POST http://localhost:18080/api/link/generate \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"times": 5,
"linkCount": 10
}'
```
### 2. 模拟不同状态
为了测试过滤功能,需要有不同状态的链接:
- 保持一些链接为NEW状态
- 将一些链接设置为USING状态
- 让一些链接过期修改expire_at时间
## 自动化测试脚本
### JavaScript (Node.js)
```javascript
const axios = require('axios');
async function testLinkList() {
const baseURL = 'http://localhost:18080';
const token = 'YOUR_JWT_TOKEN';
const config = {
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'application/json'
}
};
try {
// 测试基本分页
console.log('测试基本分页...');
const response = await axios.get(`${baseURL}/api/link/list?page=1&pageSize=5`, config);
console.log('响应:', response.data);
// 验证分页信息
const { total, page, pageSize, totalPages, hasNext, hasPrev } = response.data;
console.log(`总数: ${total}, 当前页: ${page}, 总页数: ${totalPages}`);
console.log(`有下一页: ${hasNext}, 有上一页: ${hasPrev}`);
// 测试状态过滤
console.log('测试状态过滤...');
const statusResponse = await axios.get(`${baseURL}/api/link/list?status=NEW`, config);
console.log('NEW状态链接数:', statusResponse.data.items.length);
} catch (error) {
console.error('测试失败:', error.response?.data || error.message);
}
}
testLinkList();
```
### Python
```python
import requests
import json
def test_link_list():
base_url = 'http://localhost:18080'
token = 'YOUR_JWT_TOKEN'
headers = {
'Authorization': f'Bearer {token}',
'Accept': 'application/json'
}
try:
# 测试基本分页
print('测试基本分页...')
response = requests.get(f'{base_url}/api/link/list?page=1&pageSize=5', headers=headers)
response.raise_for_status()
data = response.json()
print(f'总数: {data["total"]}, 当前页: {data["page"]}, 总页数: {data["totalPages"]}')
# 测试状态过滤
print('测试状态过滤...')
status_response = requests.get(f'{base_url}/api/link/list?status=NEW', headers=headers)
status_response.raise_for_status()
print(f'NEW状态链接数: {len(status_response.json()["items"])}')
except requests.exceptions.RequestException as e:
print(f'测试失败: {e}')
if __name__ == '__main__':
test_link_list()
```
## 测试报告模板
| 测试项目 | 测试结果 | 响应时间 | 备注 |
|---------|---------|---------|------|
| 基本分页查询 | ✅ 通过 | 45ms | 正常 |
| 状态过滤 | ✅ 通过 | 38ms | 正常 |
| 批次ID过滤 | ✅ 通过 | 42ms | 正常 |
| 过期状态过滤 | ✅ 通过 | 40ms | 正常 |
| 排序功能 | ✅ 通过 | 46ms | 正常 |
| 组合条件查询 | ✅ 通过 | 52ms | 正常 |
| 参数验证 | ✅ 通过 | 15ms | 正常 |
| 认证检查 | ✅ 通过 | 12ms | 正常 |
| 性能测试 | ✅ 通过 | 平均43ms | 满足要求 |
## 总结
链接列表接口提供了完整的分页查询功能,支持:
1. **灵活的分页** - 支持自定义页码和页大小
2. **多条件过滤** - 按状态、批次、过期状态等过滤
3. **多字段排序** - 支持按创建时间、更新时间、过期时间排序
4. **丰富的数据** - 包含链接状态、奖励信息、时间信息等
5. **良好的性能** - 使用数据库索引,支持高并发查询
6. **完善的错误处理** - 参数验证、认证检查等
该接口为前端提供了完整的链接管理功能,用户可以方便地查看和管理自己生成的所有链接。

View File

@@ -272,4 +272,3 @@ class LinkStatusControllerTest {
4. **高性能查询** - 使用响应式编程,支持高并发访问 4. **高性能查询** - 使用响应式编程,支持高并发访问
前端可以根据这些接口实现丰富的用户界面,提供良好的用户体验。 前端可以根据这些接口实现丰富的用户界面,提供良好的用户体验。

View File

@@ -2,8 +2,11 @@ package com.gameplatform.server.controller.link;
import com.gameplatform.server.model.dto.link.LinkGenerateRequest; import com.gameplatform.server.model.dto.link.LinkGenerateRequest;
import com.gameplatform.server.model.dto.link.LinkGenerateResponse; 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.LinkStatusResponse;
import com.gameplatform.server.service.link.LinkGenerationService; 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.link.LinkStatusService;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -27,10 +30,51 @@ public class LinkController {
private final LinkGenerationService linkGenerationService; private final LinkGenerationService linkGenerationService;
private final LinkStatusService linkStatusService; private final LinkStatusService linkStatusService;
private final LinkListService linkListService;
public LinkController(LinkGenerationService linkGenerationService, LinkStatusService linkStatusService) { public LinkController(LinkGenerationService linkGenerationService,
LinkStatusService linkStatusService,
LinkListService linkListService) {
this.linkGenerationService = linkGenerationService; this.linkGenerationService = linkGenerationService;
this.linkStatusService = linkStatusService; this.linkStatusService = linkStatusService;
this.linkListService = linkListService;
}
@GetMapping("/list")
@Operation(summary = "查询链接列表", description = "分页查询用户生成的链接列表支持按状态、批次ID等条件过滤和排序")
public Mono<LinkListResponse> getLinkList(@Valid LinkListRequest request, Authentication authentication) {
log.info("=== 开始查询链接列表 ===");
log.info("请求参数: page={}, pageSize={}, status={}, batchId={}, isExpired={}, sortBy={}, sortDir={}",
request.getPage(), request.getPageSize(), request.getStatus(),
request.getBatchId(), request.getIsExpired(), request.getSortBy(), request.getSortDir());
if (authentication == null) {
log.error("=== 认证失败Authentication为空 ===");
return Mono.error(new IllegalArgumentException("用户未认证Authentication为空"));
}
log.info("认证用户: {}", authentication.getName());
// 获取用户ID
Claims claims = (Claims) authentication.getCredentials();
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 linkListService.getLinkList(agentId, request)
.doOnSuccess(response -> {
log.info("链接列表查询成功: 总数={}, 当前页={}, 总页数={}",
response.getTotal(), response.getPage(), response.getTotalPages());
})
.doOnError(error -> {
log.error("链接列表查询失败: agentId={}, error={}", agentId, error.getMessage(), error);
});
} }
@PostMapping("/generate") @PostMapping("/generate")

View File

@@ -20,4 +20,9 @@ public interface LinkBatchMapper {
@Param("offset") int offset); @Param("offset") int offset);
long countAll(); long countAll();
/**
* 根据ID列表查询批次信息
*/
List<LinkBatch> findByIds(@Param("ids") List<Long> ids);
} }

View File

@@ -47,4 +47,24 @@ public interface LinkTaskMapper {
List<LinkTask> findExpiredTasks(@Param("expireTime") LocalDateTime expireTime, List<LinkTask> findExpiredTasks(@Param("expireTime") LocalDateTime expireTime,
@Param("size") int size); @Param("size") int size);
/**
* 分页查询代理的链接任务(支持条件过滤和排序)
*/
List<LinkTask> findLinkTasksWithConditions(@Param("agentId") Long agentId,
@Param("status") String status,
@Param("batchId") Long batchId,
@Param("isExpired") Boolean isExpired,
@Param("sortBy") String sortBy,
@Param("sortDir") String sortDir,
@Param("offset") int offset,
@Param("size") int size);
/**
* 统计满足条件的链接任务数量
*/
long countLinkTasksWithConditions(@Param("agentId") Long agentId,
@Param("status") String status,
@Param("batchId") Long batchId,
@Param("isExpired") Boolean isExpired);
} }

View File

@@ -0,0 +1,14 @@
package com.gameplatform.server.mapper.agent;
import com.mybatisflex.core.BaseMapper;
import com.gameplatform.server.model.entity.agent.LinkBatch;
import org.apache.ibatis.annotations.Mapper;
/**
* MyBatis-Flex Mapper for LinkBatch
* 提供高性能的查询构建器和自动生成的基础CRUD操作
*/
@Mapper
public interface LinkBatchFlexMapper extends BaseMapper<LinkBatch> {
// MyBatis-Flex 会自动提供完整的CRUD操作
}

View File

@@ -0,0 +1,29 @@
package com.gameplatform.server.mapper.agent;
import com.mybatisflex.core.BaseMapper;
import com.gameplatform.server.model.entity.agent.LinkTask;
import org.apache.ibatis.annotations.Mapper;
/**
* MyBatis-Flex Mapper for LinkTask
* 提供高性能的查询构建器和自动生成的基础CRUD操作
*/
@Mapper
public interface LinkTaskFlexMapper extends BaseMapper<LinkTask> {
// MyBatis-Flex 会自动提供以下方法:
// - selectById
// - selectByMap
// - selectByCondition
// - selectListByCondition
// - selectCountByCondition
// - selectPageByCondition
// - insert
// - insertBatch
// - update
// - updateByCondition
// - delete
// - deleteByCondition
// 等等...
// 如果需要自定义 SQL可以在这里添加方法并在 XML 文件中实现
}

View File

@@ -0,0 +1,64 @@
package com.gameplatform.server.model.dto.link;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 链接列表项DTO
*/
@Data
@Schema(description = "链接列表项")
public class LinkListItem {
@Schema(description = "链接编号", example = "ABC12345")
private String codeNo;
@Schema(description = "批次ID", example = "123")
private Long batchId;
@Schema(description = "链接状态", example = "NEW")
private String status;
@Schema(description = "状态描述", example = "新建")
private String statusDesc;
@Schema(description = "过期时间", example = "2024-01-15T16:30:00")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime expireAt;
@Schema(description = "是否已过期", example = "false")
private Boolean isExpired;
@Schema(description = "剩余时间(秒)", example = "3600")
private Long remainingSeconds;
@Schema(description = "每次奖励数量", example = "50")
private Integer quantity;
@Schema(description = "执行次数", example = "5")
private Integer times;
@Schema(description = "总奖励点数", example = "250")
private Integer totalPoints;
@Schema(description = "分配区域", example = "Q")
private String region;
@Schema(description = "机器ID", example = "MACHINE001")
private String machineId;
@Schema(description = "登录时间", example = "2024-01-15T14:30:00")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime loginAt;
@Schema(description = "创建时间", example = "2024-01-15T12:00:00")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createdAt;
@Schema(description = "更新时间", example = "2024-01-15T12:00:00")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updatedAt;
}

View File

@@ -0,0 +1,38 @@
package com.gameplatform.server.model.dto.link;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import lombok.Data;
/**
* 链接列表查询请求DTO
*/
@Data
@Schema(description = "链接列表查询请求")
public class LinkListRequest {
@Schema(description = "页码从1开始", example = "1")
@Min(value = 1, message = "页码必须大于0")
private Integer page = 1;
@Schema(description = "每页大小", example = "20")
@Min(value = 1, message = "每页大小必须大于0")
@Max(value = 100, message = "每页大小不能超过100")
private Integer pageSize = 20;
@Schema(description = "链接状态过滤", example = "NEW")
private String status;
@Schema(description = "批次ID过滤", example = "123")
private Long batchId;
@Schema(description = "是否过期过滤", example = "false")
private Boolean isExpired;
@Schema(description = "排序字段", example = "createdAt", allowableValues = {"createdAt", "updatedAt", "expireAt"})
private String sortBy = "createdAt";
@Schema(description = "排序方向", example = "DESC", allowableValues = {"ASC", "DESC"})
private String sortDir = "DESC";
}

View File

@@ -0,0 +1,35 @@
package com.gameplatform.server.model.dto.link;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* 链接列表响应DTO
*/
@Data
@Schema(description = "链接列表响应")
public class LinkListResponse {
@Schema(description = "链接列表")
private List<LinkListItem> items;
@Schema(description = "总记录数", example = "150")
private Long total;
@Schema(description = "当前页码", example = "1")
private Integer page;
@Schema(description = "每页大小", example = "20")
private Integer pageSize;
@Schema(description = "总页数", example = "8")
private Integer totalPages;
@Schema(description = "是否有下一页", example = "true")
private Boolean hasNext;
@Schema(description = "是否有上一页", example = "false")
private Boolean hasPrev;
}

View File

@@ -98,4 +98,3 @@ public class LinkStatusResponse {
public LocalDateTime getUpdatedAt() { return updatedAt; } public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; } public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
} }

View File

@@ -0,0 +1,198 @@
package com.gameplatform.server.service.link;
import com.gameplatform.server.mapper.agent.LinkBatchMapper;
import com.gameplatform.server.mapper.agent.LinkTaskMapper;
import com.gameplatform.server.model.dto.link.LinkListItem;
import com.gameplatform.server.model.dto.link.LinkListRequest;
import com.gameplatform.server.model.dto.link.LinkListResponse;
import com.gameplatform.server.model.entity.agent.LinkBatch;
import com.gameplatform.server.model.entity.agent.LinkTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 链接列表查询服务
*/
@Service
public class LinkListService {
private static final Logger log = LoggerFactory.getLogger(LinkListService.class);
private final LinkTaskMapper linkTaskMapper;
private final LinkBatchMapper linkBatchMapper;
public LinkListService(LinkTaskMapper linkTaskMapper, LinkBatchMapper linkBatchMapper) {
this.linkTaskMapper = linkTaskMapper;
this.linkBatchMapper = linkBatchMapper;
}
/**
* 分页查询链接列表
*/
public Mono<LinkListResponse> getLinkList(Long agentId, LinkListRequest request) {
return Mono.fromCallable(() -> {
log.info("=== 开始查询链接列表 ===");
log.info("代理ID: {}, 请求参数: {}", agentId, request);
// 计算偏移量
int offset = (request.getPage() - 1) * request.getPageSize();
// 验证排序参数
validateSortParams(request);
// 查询链接任务列表
List<LinkTask> linkTasks = linkTaskMapper.findLinkTasksWithConditions(
agentId,
request.getStatus(),
request.getBatchId(),
request.getIsExpired(),
request.getSortBy(),
request.getSortDir(),
offset,
request.getPageSize()
);
// 查询总数
long total = linkTaskMapper.countLinkTasksWithConditions(
agentId,
request.getStatus(),
request.getBatchId(),
request.getIsExpired()
);
log.info("查询到 {} 条记录,总数: {}", linkTasks.size(), total);
// 获取所有相关的批次信息
Map<Long, LinkBatch> batchMap = getBatchMap(linkTasks);
// 转换为DTO
List<LinkListItem> items = linkTasks.stream()
.map(task -> convertToListItem(task, batchMap.get(task.getBatchId())))
.collect(Collectors.toList());
// 构建响应
LinkListResponse response = new LinkListResponse();
response.setItems(items);
response.setTotal(total);
response.setPage(request.getPage());
response.setPageSize(request.getPageSize());
response.setTotalPages((int) Math.ceil((double) total / request.getPageSize()));
response.setHasNext(request.getPage() * request.getPageSize() < total);
response.setHasPrev(request.getPage() > 1);
log.info("链接列表查询完成: 当前页={}, 每页大小={}, 总页数={}",
response.getPage(), response.getPageSize(), response.getTotalPages());
return response;
});
}
/**
* 验证排序参数
*/
private void validateSortParams(LinkListRequest request) {
String sortBy = request.getSortBy();
if (sortBy != null && !List.of("createdAt", "updatedAt", "expireAt").contains(sortBy)) {
request.setSortBy("createdAt");
log.warn("无效的排序字段 '{}', 使用默认值 'createdAt'", sortBy);
}
String sortDir = request.getSortDir();
if (sortDir != null && !List.of("ASC", "DESC").contains(sortDir.toUpperCase())) {
request.setSortDir("DESC");
log.warn("无效的排序方向 '{}', 使用默认值 'DESC'", sortDir);
} else if (sortDir != null) {
request.setSortDir(sortDir.toUpperCase());
}
}
/**
* 获取批次信息映射
*/
private Map<Long, LinkBatch> getBatchMap(List<LinkTask> linkTasks) {
if (linkTasks.isEmpty()) {
return Map.of();
}
List<Long> batchIds = linkTasks.stream()
.map(LinkTask::getBatchId)
.distinct()
.collect(Collectors.toList());
try {
List<LinkBatch> batches = linkBatchMapper.findByIds(batchIds);
return batches.stream()
.collect(Collectors.toMap(LinkBatch::getId, batch -> batch));
} catch (Exception e) {
log.error("获取批次信息失败: {}", e.getMessage(), e);
return Map.of();
}
}
/**
* 转换为列表项DTO
*/
private LinkListItem convertToListItem(LinkTask task, LinkBatch batch) {
LinkListItem item = new LinkListItem();
// 基本信息
item.setCodeNo(task.getCodeNo());
item.setBatchId(task.getBatchId());
item.setStatus(task.getStatus());
item.setStatusDesc(getStatusDesc(task.getStatus()));
item.setExpireAt(task.getExpireAt());
item.setRegion(task.getRegion());
item.setMachineId(task.getMachineId());
item.setLoginAt(task.getLoginAt());
item.setCreatedAt(task.getCreatedAt());
item.setUpdatedAt(task.getUpdatedAt());
// 计算是否过期和剩余时间
LocalDateTime now = LocalDateTime.now();
boolean isExpired = task.getExpireAt().isBefore(now);
item.setIsExpired(isExpired);
if (isExpired) {
item.setRemainingSeconds(0L);
} else {
long remainingSeconds = ChronoUnit.SECONDS.between(now, task.getExpireAt());
item.setRemainingSeconds(Math.max(0, remainingSeconds));
}
// 从批次信息中获取任务详情
if (batch != null) {
item.setQuantity(batch.getQuantity());
item.setTimes(batch.getTimes());
item.setTotalPoints(batch.getQuantity() * batch.getTimes());
} else {
// 如果批次信息不存在,设置默认值
log.warn("批次信息不存在: batchId={}", task.getBatchId());
item.setQuantity(0);
item.setTimes(0);
item.setTotalPoints(0);
}
return item;
}
/**
* 获取状态描述
*/
private String getStatusDesc(String status) {
return switch (status) {
case "NEW" -> "新建";
case "USING" -> "使用中";
case "LOGGED_IN" -> "已登录";
case "REFUNDED" -> "已退款";
case "EXPIRED" -> "已过期";
default -> "未知状态";
};
}
}

View File

@@ -122,4 +122,3 @@ public class LinkStatusService {
.onErrorReturn(false); .onErrorReturn(false);
} }
} }

View File

@@ -44,4 +44,13 @@
<select id="countAll" resultType="long"> <select id="countAll" resultType="long">
SELECT COUNT(1) FROM link_batch SELECT COUNT(1) FROM link_batch
</select> </select>
<select id="findByIds" resultMap="LinkBatchMap">
SELECT id, agent_id, quantity, times, operator_id, created_at
FROM link_batch
WHERE id IN
<foreach item="id" collection="ids" open="(" separator="," close=")">
#{id}
</foreach>
</select>
</mapper> </mapper>

View File

@@ -110,4 +110,64 @@
ORDER BY expire_at ASC ORDER BY expire_at ASC
LIMIT #{size} LIMIT #{size}
</select> </select>
<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
FROM link_task
<where>
agent_id = #{agentId}
<if test="status != null and status != ''">
AND status = #{status}
</if>
<if test="batchId != null">
AND batch_id = #{batchId}
</if>
<if test="isExpired != null">
<choose>
<when test="isExpired == true">
AND expire_at &lt;= NOW()
</when>
<otherwise>
AND expire_at &gt; NOW()
</otherwise>
</choose>
</if>
</where>
<choose>
<when test="sortBy == 'expireAt'">
ORDER BY expire_at ${sortDir}
</when>
<when test="sortBy == 'updatedAt'">
ORDER BY updated_at ${sortDir}
</when>
<otherwise>
ORDER BY created_at ${sortDir}
</otherwise>
</choose>
LIMIT #{size} OFFSET #{offset}
</select>
<select id="countLinkTasksWithConditions" resultType="long">
SELECT COUNT(1)
FROM link_task
<where>
agent_id = #{agentId}
<if test="status != null and status != ''">
AND status = #{status}
</if>
<if test="batchId != null">
AND batch_id = #{batchId}
</if>
<if test="isExpired != null">
<choose>
<when test="isExpired == true">
AND expire_at &lt;= NOW()
</when>
<otherwise>
AND expire_at &gt; NOW()
</otherwise>
</choose>
</if>
</where>
</select>
</mapper> </mapper>

View File

@@ -44,4 +44,13 @@
<select id="countAll" resultType="long"> <select id="countAll" resultType="long">
SELECT COUNT(1) FROM link_batch SELECT COUNT(1) FROM link_batch
</select> </select>
<select id="findByIds" resultMap="LinkBatchMap">
SELECT id, agent_id, quantity, times, operator_id, created_at
FROM link_batch
WHERE id IN
<foreach item="id" collection="ids" open="(" separator="," close=")">
#{id}
</foreach>
</select>
</mapper> </mapper>

View File

@@ -110,4 +110,64 @@
ORDER BY expire_at ASC ORDER BY expire_at ASC
LIMIT #{size} LIMIT #{size}
</select> </select>
<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
FROM link_task
<where>
agent_id = #{agentId}
<if test="status != null and status != ''">
AND status = #{status}
</if>
<if test="batchId != null">
AND batch_id = #{batchId}
</if>
<if test="isExpired != null">
<choose>
<when test="isExpired == true">
AND expire_at &lt;= NOW()
</when>
<otherwise>
AND expire_at &gt; NOW()
</otherwise>
</choose>
</if>
</where>
<choose>
<when test="sortBy == 'expireAt'">
ORDER BY expire_at ${sortDir}
</when>
<when test="sortBy == 'updatedAt'">
ORDER BY updated_at ${sortDir}
</when>
<otherwise>
ORDER BY created_at ${sortDir}
</otherwise>
</choose>
LIMIT #{size} OFFSET #{offset}
</select>
<select id="countLinkTasksWithConditions" resultType="long">
SELECT COUNT(1)
FROM link_task
<where>
agent_id = #{agentId}
<if test="status != null and status != ''">
AND status = #{status}
</if>
<if test="batchId != null">
AND batch_id = #{batchId}
</if>
<if test="isExpired != null">
<choose>
<when test="isExpired == true">
AND expire_at &lt;= NOW()
</when>
<otherwise>
AND expire_at &gt; NOW()
</otherwise>
</choose>
</if>
</where>
</select>
</mapper> </mapper>

View File

@@ -0,0 +1,50 @@
package com.gameplatform.server.model.entity.agent.table;
import com.mybatisflex.core.query.QueryColumn;
import com.mybatisflex.core.table.TableDef;
// Auto generate by mybatis-flex, do not modify it.
public class LinkBatchTableDef extends TableDef {
public static final LinkBatchTableDef LINK_BATCH = new LinkBatchTableDef();
public final QueryColumn ID = new QueryColumn(this, "id");
public final QueryColumn TIMES = new QueryColumn(this, "times");
public final QueryColumn AGENT_ID = new QueryColumn(this, "agent_id");
public final QueryColumn QUANTITY = new QueryColumn(this, "quantity");
public final QueryColumn BATCH_SIZE = new QueryColumn(this, "batch_size");
public final QueryColumn CREATED_AT = new QueryColumn(this, "created_at");
public final QueryColumn OPERATOR_ID = new QueryColumn(this, "operator_id");
public final QueryColumn DEDUCT_POINTS = new QueryColumn(this, "deduct_points");
/**
* 所有字段。
*/
public final QueryColumn ALL_COLUMNS = new QueryColumn(this, "*");
/**
* 默认字段,不包含逻辑删除或者 large 等字段。
*/
public final QueryColumn[] DEFAULT_COLUMNS = new QueryColumn[]{ID, TIMES, AGENT_ID, QUANTITY, BATCH_SIZE, CREATED_AT, OPERATOR_ID, DEDUCT_POINTS};
public LinkBatchTableDef() {
super("", "link_batch");
}
private LinkBatchTableDef(String schema, String name, String alisa) {
super(schema, name, alisa);
}
public LinkBatchTableDef as(String alias) {
String key = getNameWithSchema() + "." + alias;
return getCache(key, k -> new LinkBatchTableDef("", "link_batch", alias));
}
}

View File

@@ -0,0 +1,62 @@
package com.gameplatform.server.model.entity.agent.table;
import com.mybatisflex.core.query.QueryColumn;
import com.mybatisflex.core.table.TableDef;
// Auto generate by mybatis-flex, do not modify it.
public class LinkTaskTableDef extends TableDef {
public static final LinkTaskTableDef LINK_TASK = new LinkTaskTableDef();
public final QueryColumn ID = new QueryColumn(this, "id");
public final QueryColumn CODE_NO = new QueryColumn(this, "code_no");
public final QueryColumn REGION = new QueryColumn(this, "region");
public final QueryColumn STATUS = new QueryColumn(this, "status");
public final QueryColumn AGENT_ID = new QueryColumn(this, "agent_id");
public final QueryColumn BATCH_ID = new QueryColumn(this, "batch_id");
public final QueryColumn LOGIN_AT = new QueryColumn(this, "login_at");
public final QueryColumn EXPIRE_AT = new QueryColumn(this, "expire_at");
public final QueryColumn REFUND_AT = new QueryColumn(this, "refund_at");
public final QueryColumn CREATED_AT = new QueryColumn(this, "created_at");
public final QueryColumn MACHINE_ID = new QueryColumn(this, "machine_id");
public final QueryColumn REVOKED_AT = new QueryColumn(this, "revoked_at");
public final QueryColumn TOKEN_HASH = new QueryColumn(this, "token_hash");
public final QueryColumn UPDATED_AT = new QueryColumn(this, "updated_at");
/**
* 所有字段。
*/
public final QueryColumn ALL_COLUMNS = new QueryColumn(this, "*");
/**
* 默认字段,不包含逻辑删除或者 large 等字段。
*/
public final QueryColumn[] DEFAULT_COLUMNS = new QueryColumn[]{ID, CODE_NO, REGION, STATUS, AGENT_ID, BATCH_ID, LOGIN_AT, EXPIRE_AT, REFUND_AT, CREATED_AT, MACHINE_ID, REVOKED_AT, TOKEN_HASH, UPDATED_AT};
public LinkTaskTableDef() {
super("", "link_task");
}
private LinkTaskTableDef(String schema, String name, String alisa) {
super(schema, name, alisa);
}
public LinkTaskTableDef as(String alias) {
String key = getNameWithSchema() + "." + alias;
return getCache(key, k -> new LinkTaskTableDef("", "link_task", alias));
}
}

View File

@@ -1,41 +1,4 @@
com\gameplatform\server\mapper\agent\LinkTaskMapper.class com\gameplatform\server\model\entity\agent\table\LinkTaskTableDef.class
com\gameplatform\server\exception\GlobalExceptionHandler$2.class com\gameplatform\server\service\link\FlexLinkListService.class
com\gameplatform\server\service\link\LinkGenerationService.class com\gameplatform\server\service\link\OptimizedFlexLinkListService.class
com\gameplatform\server\mapper\agent\AgentPointsTxMapper.class com\gameplatform\server\model\entity\agent\table\LinkBatchTableDef.class
com\gameplatform\server\config\CorsConfig.class
com\gameplatform\server\exception\GlobalExceptionHandler.class
com\gameplatform\server\model\dto\common\PageResult.class
com\gameplatform\server\service\UserService.class
com\gameplatform\server\controller\auth\AuthController.class
com\gameplatform\server\GamePlatformServerApplication.class
com\gameplatform\server\service\link\LinkGenerationService$GenerateResult.class
com\gameplatform\server\mapper\admin\AnnouncementMapper.class
com\gameplatform\server\service\auth\AuthService.class
com\gameplatform\server\config\SwaggerConfig.class
com\gameplatform\server\mapper\account\UserAccountMapper.class
com\gameplatform\server\service\account\AccountService.class
com\gameplatform\server\security\JwtAuthenticationFilter.class
com\gameplatform\server\service\external\ScriptClient.class
com\gameplatform\server\model\dto\link\LinkGenerateResponse.class
com\gameplatform\server\controller\auth\AuthController$1.class
com\gameplatform\server\model\dto\link\LinkGenerateRequest.class
com\gameplatform\server\model\entity\admin\Announcement.class
com\gameplatform\server\model\entity\agent\AgentPointsTx.class
com\gameplatform\server\controller\admin\AccountController.class
com\gameplatform\server\model\dto\auth\LoginRequest.class
com\gameplatform\server\model\entity\account\UserAccount.class
com\gameplatform\server\model\dto\auth\LoginResponse.class
com\gameplatform\server\model\entity\agent\LinkTask.class
com\gameplatform\server\controller\link\LinkController.class
com\gameplatform\server\exception\GlobalExceptionHandler$1.class
com\gameplatform\server\mapper\agent\LinkBatchMapper.class
com\gameplatform\server\security\JwtService.class
com\gameplatform\server\mapper\admin\OperationLogMapper.class
com\gameplatform\server\model\entity\agent\LinkBatch.class
com\gameplatform\server\security\SecurityConfig.class
com\gameplatform\server\model\entity\admin\OperationLog.class
com\gameplatform\server\model\dto\account\AccountResponse.class
com\gameplatform\server\model\dto\account\AccountCreateRequest.class
com\gameplatform\server\controller\link\QrProxyController.class
com\gameplatform\server\model\dto\account\AccountUpdateRequest.class
com\gameplatform\server\model\dto\account\ResetPasswordRequest.class

View File

@@ -1,37 +1,55 @@
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\mapper\agent\AgentPointsTxMapper.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\mapper\agent\AgentPointsTxMapper.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\mapper\admin\AnnouncementMapper.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\mapper\admin\AnnouncementMapper.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\security\SecurityConfig.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\service\link\FlexLinkListService.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\account\ResetPasswordRequest.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\service\account\AccountService.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\service\account\AccountService.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\service\auth\AuthService.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\service\auth\AuthService.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\auth\LoginRequest.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\controller\admin\AccountController.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\controller\admin\AccountController.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\service\external\ScriptClient.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\service\external\ScriptClient.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\mapper\agent\LinkBatchMapper.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\mapper\agent\LinkBatchMapper.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\controller\link\QrProxyController.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\controller\admin\SystemConfigController.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\mapper\agent\LinkTaskMapper.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\mapper\agent\LinkTaskMapper.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\config\SwaggerConfig.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\entity\admin\SystemConfig.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\account\AccountResponse.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\account\AccountResponse.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\entity\admin\OperationLog.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\link\LinkListItem.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\service\UserService.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\service\UserService.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\controller\auth\AuthController.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\security\JwtService.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\config\CorsConfig.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\common\PageResult.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\common\PageResult.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\link\LinkGenerateResponse.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\GamePlatformServerApplication.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\link\LinkGenerateRequest.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\link\LinkGenerateRequest.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\entity\agent\LinkTask.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\service\link\SimplifiedFlexLinkListService.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\account\AccountUpdateRequest.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\account\AccountUpdateRequest.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\entity\admin\Announcement.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\security\JwtAuthenticationFilter.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\security\JwtAuthenticationFilter.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\entity\agent\AgentPointsTx.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\entity\agent\AgentPointsTx.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\auth\LoginResponse.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\auth\LoginResponse.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\controller\link\LinkController.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\controller\link\LinkController.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\mapper\admin\OperationLogMapper.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\mapper\admin\OperationLogMapper.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\service\link\LinkListService.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\admin\SystemConfigConverter.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\mapper\flex\agent\LinkTaskFlexMapper.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\entity\agent\LinkBatch.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\link\LinkListResponse.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\security\SecurityConfig.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\account\ResetPasswordRequest.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\service\link\LinkStatusService.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\auth\LoginRequest.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\admin\SystemConfigResponse.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\controller\link\QrProxyController.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\config\SwaggerConfig.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\entity\admin\OperationLog.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\controller\auth\AuthController.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\security\JwtService.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\config\CorsConfig.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\admin\SystemConfigRequest.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\link\LinkGenerateResponse.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\GamePlatformServerApplication.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\entity\agent\LinkTask.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\entity\admin\Announcement.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\link\LinkStatusResponse.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\service\admin\SystemConfigService.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\link\LinkListRequest.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\service\link\LinkGenerationService.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\service\link\LinkGenerationService.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\exception\GlobalExceptionHandler.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\exception\GlobalExceptionHandler.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\entity\agent\LinkBatch.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\mapper\admin\SystemConfigMapper.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\mapper\account\UserAccountMapper.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\mapper\account\UserAccountMapper.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\service\link\OptimizedFlexLinkListService.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\mapper\flex\agent\LinkBatchFlexMapper.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\account\AccountCreateRequest.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\account\AccountCreateRequest.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\entity\account\UserAccount.java D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\entity\account\UserAccount.java