新增系统配置表及默认配置,更新链接生成请求DTO以支持链接数量参数,重构链接生成服务逻辑,添加链接状态查询和有效性检查接口,优化日志记录。
This commit is contained in:
@@ -39,7 +39,7 @@ Content-Type: application/json
|
||||
|
||||
{
|
||||
"times": 10,
|
||||
"perTimeQuantity": 5
|
||||
"linkCount": 5
|
||||
}
|
||||
```
|
||||
|
||||
@@ -56,7 +56,7 @@ Content-Type: application/json
|
||||
```json
|
||||
{
|
||||
"times": 10, // 本次打脚本的次数
|
||||
"perTimeQuantity": 5 // 每次打的数量
|
||||
"linkCount": 5 // 生成多少个链接
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -144,4 +144,33 @@ CREATE TABLE `user_account` (
|
||||
CONSTRAINT `chk_points_nonneg` CHECK (`points_balance` >= 0)
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for system_config
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `system_config`;
|
||||
CREATE TABLE `system_config` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`config_key` varchar(100) NOT NULL COMMENT '配置键',
|
||||
`config_value` text NOT NULL COMMENT '配置值',
|
||||
`config_type` varchar(50) NOT NULL DEFAULT 'STRING' COMMENT '配置类型:STRING, INTEGER, BOOLEAN, JSON',
|
||||
`description` varchar(500) NULL DEFAULT NULL COMMENT '配置描述',
|
||||
`is_system` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否系统配置(1是,0否)',
|
||||
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_config_key`(`config_key` ASC) USING BTREE,
|
||||
INDEX `idx_config_type`(`config_type` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- 插入默认配置
|
||||
INSERT INTO `system_config` (`config_key`, `config_value`, `config_type`, `description`, `is_system`) VALUES
|
||||
('link.default_quantity', '50', 'INTEGER', '链接生成默认奖励点数', 1),
|
||||
('link.refresh_interval', '300', 'INTEGER', '链接刷新间隔(秒)', 1),
|
||||
('link.qr_expire_time', '600', 'INTEGER', '二维码过期时间(秒)', 1),
|
||||
('link.max_times_per_batch', '100', 'INTEGER', '每批次最大刷奖励次数', 1),
|
||||
('link.min_quantity', '10', 'INTEGER', '最小奖励点数', 1),
|
||||
('link.max_quantity', '1000', 'INTEGER', '最大奖励点数', 1),
|
||||
('script.server_url', 'http://36.138.184.60:12345', 'STRING', '脚本服务器地址', 1),
|
||||
('script.qr_path_template', '/{machineId}/二维码.png', 'STRING', '二维码图片路径模板', 1);
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
403
docs/前端链接访问示例.md
Normal file
403
docs/前端链接访问示例.md
Normal file
@@ -0,0 +1,403 @@
|
||||
# 前端链接访问示例
|
||||
|
||||
## 概述
|
||||
当用户访问链接页面时(如 `https://你的域名/ABC12345`),前端需要自动请求后端获取链接的详细信息,并根据状态显示相应的内容。
|
||||
|
||||
## 接口说明
|
||||
|
||||
### 1. 获取链接状态(主要接口)
|
||||
```
|
||||
GET /api/link/{codeNo}/status
|
||||
```
|
||||
|
||||
**响应示例:**
|
||||
```json
|
||||
{
|
||||
"codeNo": "ABC12345",
|
||||
"batchId": 123,
|
||||
"status": "NEW",
|
||||
"statusDesc": "新建",
|
||||
"expireAt": "2024-01-15T16:30:00",
|
||||
"isExpired": false,
|
||||
"remainingSeconds": 3600,
|
||||
"quantity": 50,
|
||||
"times": 10,
|
||||
"totalPoints": 500,
|
||||
"region": null,
|
||||
"machineId": null,
|
||||
"loginAt": null,
|
||||
"createdAt": "2024-01-15T12:00:00",
|
||||
"updatedAt": "2024-01-15T12:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 检查链接是否存在
|
||||
```
|
||||
GET /api/link/{codeNo}/exists
|
||||
```
|
||||
|
||||
**响应:** `true` 或 `false`
|
||||
|
||||
### 3. 检查链接是否有效
|
||||
```
|
||||
GET /api/link/{codeNo}/valid
|
||||
```
|
||||
|
||||
**响应:** `true` 或 `false`
|
||||
|
||||
## 前端实现示例
|
||||
|
||||
### React 组件示例
|
||||
|
||||
```jsx
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
const LinkPage = () => {
|
||||
const { codeNo } = useParams();
|
||||
const [linkStatus, setLinkStatus] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchLinkStatus();
|
||||
}, [codeNo]);
|
||||
|
||||
const fetchLinkStatus = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await fetch(`/api/link/${codeNo}/status`);
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
setError('链接不存在');
|
||||
} else {
|
||||
setError('获取链接信息失败');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
setLinkStatus(data);
|
||||
} catch (err) {
|
||||
setError('网络错误');
|
||||
console.error('获取链接状态失败:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const formatTime = (seconds) => {
|
||||
if (seconds <= 0) return '已过期';
|
||||
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
const secs = seconds % 60;
|
||||
|
||||
if (hours > 0) {
|
||||
return `${hours}小时${minutes}分钟`;
|
||||
} else if (minutes > 0) {
|
||||
return `${minutes}分钟${secs}秒`;
|
||||
} else {
|
||||
return `${secs}秒`;
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <div className="loading">加载中...</div>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="error">
|
||||
<h2>链接错误</h2>
|
||||
<p>{error}</p>
|
||||
<p>链接编号: {codeNo}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!linkStatus) {
|
||||
return <div>未找到链接信息</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="link-page">
|
||||
<div className="link-header">
|
||||
<h1>游戏任务链接</h1>
|
||||
<div className="link-code">链接编号: {linkStatus.codeNo}</div>
|
||||
</div>
|
||||
|
||||
<div className="link-status">
|
||||
<div className="status-item">
|
||||
<span className="label">状态:</span>
|
||||
<span className={`value status-${linkStatus.status.toLowerCase()}`}>
|
||||
{linkStatus.statusDesc}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="status-item">
|
||||
<span className="label">任务信息:</span>
|
||||
<span className="value">
|
||||
打{linkStatus.times}次副本,每次{linkStatus.quantity}点
|
||||
(总计{linkStatus.totalPoints}点)
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="status-item">
|
||||
<span className="label">过期时间:</span>
|
||||
<span className="value">
|
||||
{linkStatus.expireAt}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="status-item">
|
||||
<span className="label">剩余时间:</span>
|
||||
<span className={`value ${linkStatus.isExpired ? 'expired' : ''}`}>
|
||||
{formatTime(linkStatus.remainingSeconds)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{linkStatus.isExpired && (
|
||||
<div className="expired-notice">
|
||||
<p>⚠️ 此链接已过期,无法使用</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!linkStatus.isExpired && linkStatus.status === 'NEW' && (
|
||||
<div className="action-section">
|
||||
<h3>开始任务</h3>
|
||||
<p>点击下方按钮开始执行任务</p>
|
||||
<button className="start-btn" onClick={() => startTask()}>
|
||||
开始任务
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{linkStatus.status === 'USING' && (
|
||||
<div className="task-progress">
|
||||
<h3>任务进行中</h3>
|
||||
<p>请按照提示完成游戏任务</p>
|
||||
<div className="progress-bar">
|
||||
<div className="progress-fill" style={{width: '50%'}}></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{linkStatus.status === 'LOGGED_IN' && (
|
||||
<div className="task-complete">
|
||||
<h3>任务完成</h3>
|
||||
<p>恭喜!任务已完成,奖励点数已发放</p>
|
||||
<div className="reward-info">
|
||||
获得奖励: {linkStatus.totalPoints} 点
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="qr-section">
|
||||
<h3>扫码访问</h3>
|
||||
<img
|
||||
src={`/api/link/${codeNo}/qr.png`}
|
||||
alt="二维码"
|
||||
className="qr-code"
|
||||
/>
|
||||
<p>使用手机扫描二维码访问</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const startTask = () => {
|
||||
// 实现开始任务的逻辑
|
||||
console.log('开始任务');
|
||||
};
|
||||
|
||||
export default LinkPage;
|
||||
```
|
||||
|
||||
### Vue 组件示例
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="link-page">
|
||||
<div v-if="loading" class="loading">加载中...</div>
|
||||
|
||||
<div v-else-if="error" class="error">
|
||||
<h2>链接错误</h2>
|
||||
<p>{{ error }}</p>
|
||||
<p>链接编号: {{ codeNo }}</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="linkStatus" class="link-content">
|
||||
<div class="link-header">
|
||||
<h1>游戏任务链接</h1>
|
||||
<div class="link-code">链接编号: {{ linkStatus.codeNo }}</div>
|
||||
</div>
|
||||
|
||||
<div class="link-status">
|
||||
<div class="status-item">
|
||||
<span class="label">状态:</span>
|
||||
<span :class="['value', `status-${linkStatus.status.toLowerCase()}`]">
|
||||
{{ linkStatus.statusDesc }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="status-item">
|
||||
<span class="label">任务信息:</span>
|
||||
<span class="value">
|
||||
打{{ linkStatus.times }}次副本,每次{{ linkStatus.quantity }}点
|
||||
(总计{{ linkStatus.totalPoints }}点)
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="status-item">
|
||||
<span class="label">剩余时间:</span>
|
||||
<span :class="['value', { 'expired': linkStatus.isExpired }]">
|
||||
{{ formatTime(linkStatus.remainingSeconds) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="linkStatus.isExpired" class="expired-notice">
|
||||
<p>⚠️ 此链接已过期,无法使用</p>
|
||||
</div>
|
||||
|
||||
<div v-if="!linkStatus.isExpired && linkStatus.status === 'NEW'" class="action-section">
|
||||
<h3>开始任务</h3>
|
||||
<button class="start-btn" @click="startTask">开始任务</button>
|
||||
</div>
|
||||
|
||||
<div class="qr-section">
|
||||
<h3>扫码访问</h3>
|
||||
<img
|
||||
:src="`/api/link/${codeNo}/qr.png`"
|
||||
alt="二维码"
|
||||
class="qr-code"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'LinkPage',
|
||||
data() {
|
||||
return {
|
||||
codeNo: '',
|
||||
linkStatus: null,
|
||||
loading: true,
|
||||
error: null
|
||||
};
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
this.codeNo = this.$route.params.codeNo;
|
||||
await this.fetchLinkStatus();
|
||||
},
|
||||
|
||||
methods: {
|
||||
async fetchLinkStatus() {
|
||||
try {
|
||||
this.loading = true;
|
||||
const response = await fetch(`/api/link/${this.codeNo}/status`);
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
this.error = '链接不存在';
|
||||
} else {
|
||||
this.error = '获取链接信息失败';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.linkStatus = await response.json();
|
||||
} catch (err) {
|
||||
this.error = '网络错误';
|
||||
console.error('获取链接状态失败:', err);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
formatTime(seconds) {
|
||||
if (seconds <= 0) return '已过期';
|
||||
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
const secs = seconds % 60;
|
||||
|
||||
if (hours > 0) {
|
||||
return `${hours}小时${minutes}分钟`;
|
||||
} else if (minutes > 0) {
|
||||
return `${minutes}分钟${secs}秒`;
|
||||
} else {
|
||||
return `${secs}秒`;
|
||||
}
|
||||
},
|
||||
|
||||
startTask() {
|
||||
// 实现开始任务的逻辑
|
||||
console.log('开始任务');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
## 路由配置
|
||||
|
||||
### React Router
|
||||
```jsx
|
||||
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||
import LinkPage from './components/LinkPage';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/:codeNo" element={<LinkPage />} />
|
||||
{/* 其他路由 */}
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Vue Router
|
||||
```javascript
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import LinkPage from '@/components/LinkPage.vue';
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/:codeNo',
|
||||
name: 'LinkPage',
|
||||
component: LinkPage
|
||||
}
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes
|
||||
});
|
||||
|
||||
export default router;
|
||||
```
|
||||
|
||||
## 使用流程
|
||||
|
||||
1. **用户访问链接**:`https://你的域名/ABC12345`
|
||||
2. **前端自动请求**:调用 `/api/link/ABC12345/status` 获取链接信息
|
||||
3. **显示相应内容**:根据链接状态显示不同的界面
|
||||
4. **实时更新**:可以定时刷新状态,显示剩余时间等
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **错误处理**:处理链接不存在、已过期等情况
|
||||
2. **加载状态**:显示加载中的状态,提升用户体验
|
||||
3. **响应式设计**:确保在不同设备上都能正常显示
|
||||
4. **缓存策略**:可以适当缓存链接状态,减少请求次数
|
||||
5. **实时更新**:对于进行中的任务,可以定时刷新状态
|
||||
|
||||
150
docs/数据库修改总结.md
Normal file
150
docs/数据库修改总结.md
Normal file
@@ -0,0 +1,150 @@
|
||||
# 数据库修改总结
|
||||
|
||||
## 修改概述
|
||||
根据需求文档和用户要求,对游戏平台系统进行了以下主要修改:
|
||||
|
||||
## 1. LinkBatch表结构调整
|
||||
|
||||
### 修改前
|
||||
- `quantity`: 代表批次大小
|
||||
- `times`: 代表生成次数
|
||||
- `batchSize`: 每次的奖励点数
|
||||
- `deductPoints`: 扣除的点数
|
||||
|
||||
### 修改后
|
||||
- `quantity`: 每次副本的奖励点数(从配置表获取,默认50)
|
||||
- `times`: 打副本的次数
|
||||
- 移除了 `batchSize` 和 `deductPoints` 字段
|
||||
|
||||
### 字段含义
|
||||
- `quantity`: 每次副本完成后获得的点数,从配置表 `link.default_quantity` 获取
|
||||
- `times`: 一个批次中打副本的总次数
|
||||
- `agent_id`: 代理商ID
|
||||
- `operator_id`: 操作者ID
|
||||
- `created_at`: 创建时间
|
||||
|
||||
### 参数说明
|
||||
- **`times`**: 打多少次副本,决定总的目标值
|
||||
- **`linkCount`**: 生成多少个链接,决定链接任务的数量(默认值:1)
|
||||
- **总目标值计算**: `times` × 配置表中的默认奖励点数
|
||||
|
||||
## 2. 新增系统配置表
|
||||
|
||||
### 表名:system_config
|
||||
用于存储系统各种配置参数,支持动态配置和运行时修改。
|
||||
|
||||
#### 主要配置项
|
||||
**链接生成相关:**
|
||||
- `link.default_quantity`: 默认奖励点数(50)
|
||||
- `link.refresh_interval`: 链接刷新间隔(300秒)
|
||||
- `link.qr_expire_time`: 二维码过期时间(600秒)
|
||||
- `link.max_times_per_batch`: 每批次最大刷奖励次数(100)
|
||||
- `link.min_quantity`: 最小奖励点数(10)
|
||||
- `link.max_quantity`: 最大奖励点数(1000)
|
||||
|
||||
**脚本服务器相关:**
|
||||
- `script.server_url`: 脚本服务器地址
|
||||
- `script.qr_path_template`: 二维码图片路径模板
|
||||
|
||||
## 3. 代码结构更新
|
||||
|
||||
### 新增文件
|
||||
1. **实体类**
|
||||
- `SystemConfig.java` - 系统配置实体
|
||||
|
||||
2. **Mapper接口**
|
||||
- `SystemConfigMapper.java` - 系统配置数据访问接口
|
||||
|
||||
3. **XML映射文件**
|
||||
- `SystemConfigMapper.xml` - MyBatis映射配置
|
||||
|
||||
4. **服务类**
|
||||
- `SystemConfigService.java` - 系统配置业务逻辑服务
|
||||
|
||||
5. **控制器**
|
||||
- `SystemConfigController.java` - 系统配置REST API接口
|
||||
|
||||
6. **DTO类**
|
||||
- `SystemConfigRequest.java` - 配置创建/更新请求
|
||||
- `SystemConfigResponse.java` - 配置查询响应
|
||||
- `SystemConfigConverter.java` - 配置对象转换工具
|
||||
|
||||
### 修改文件
|
||||
1. **LinkBatch.java** - 移除不需要的字段
|
||||
2. **LinkBatchMapper.xml** - 更新SQL映射
|
||||
3. **LinkGenerationService.java** - 使用配置服务,修复字段设置
|
||||
|
||||
## 4. API接口
|
||||
|
||||
### 系统配置管理接口
|
||||
- `GET /api/admin/config/list` - 获取配置列表
|
||||
- `GET /api/admin/config/key/{configKey}` - 根据键获取配置
|
||||
- `GET /api/admin/config/type/{configType}` - 根据类型获取配置
|
||||
- `POST /api/admin/config` - 创建配置
|
||||
- `PUT /api/admin/config/{id}` - 更新配置
|
||||
- `DELETE /api/admin/config/{id}` - 删除配置
|
||||
- `GET /api/admin/config/link/defaults` - 获取链接默认配置
|
||||
- `GET /api/admin/config/script/config` - 获取脚本配置
|
||||
|
||||
## 5. 配置使用方式
|
||||
|
||||
### 在服务中使用
|
||||
```java
|
||||
@Autowired
|
||||
private SystemConfigService systemConfigService;
|
||||
|
||||
// 获取默认奖励点数
|
||||
Integer defaultQuantity = systemConfigService.getDefaultQuantity();
|
||||
|
||||
// 获取刷新间隔
|
||||
Integer refreshInterval = systemConfigService.getRefreshInterval();
|
||||
```
|
||||
|
||||
### 动态修改配置
|
||||
```java
|
||||
// 更新配置
|
||||
SystemConfig config = systemConfigService.getConfigByKey("link.default_quantity");
|
||||
config.setConfigValue("100");
|
||||
systemConfigService.updateConfig(config);
|
||||
```
|
||||
|
||||
## 6. 数据库脚本
|
||||
|
||||
### 执行顺序
|
||||
1. 执行 `docs/game.sql` 中的新表创建语句
|
||||
2. 系统会自动插入默认配置数据
|
||||
|
||||
### 注意事项
|
||||
- 新表使用 `utf8mb4` 字符集
|
||||
- 配置键具有唯一性约束
|
||||
- 系统配置标记为 `is_system=1`,不建议删除
|
||||
|
||||
## 7. 业务逻辑调整
|
||||
|
||||
### LinkGenerationService
|
||||
- 使用配置服务获取过期时间等参数
|
||||
- 修复了字段设置问题
|
||||
- `quantity` 现在代表每次的奖励点数
|
||||
- `times` 代表总次数
|
||||
|
||||
### 配置管理
|
||||
- 支持运行时动态修改配置
|
||||
- 提供完整的CRUD操作
|
||||
- 支持配置类型验证
|
||||
- 提供便捷的配置获取方法
|
||||
|
||||
## 8. 后续扩展建议
|
||||
|
||||
1. **缓存机制**: 对频繁访问的配置添加缓存
|
||||
2. **配置验证**: 在更新配置时添加值格式验证
|
||||
3. **配置变更日志**: 记录配置变更历史
|
||||
4. **配置导入导出**: 支持配置文件导入导出
|
||||
5. **权限控制**: 对敏感配置的修改权限控制
|
||||
|
||||
## 9. 测试建议
|
||||
|
||||
1. 测试配置的增删改查功能
|
||||
2. 验证LinkBatch表的字段映射
|
||||
3. 测试配置的动态修改是否生效
|
||||
4. 验证默认配置值的正确性
|
||||
5. 测试配置服务的异常处理
|
||||
151
docs/系统配置说明.md
Normal file
151
docs/系统配置说明.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# 系统配置说明
|
||||
|
||||
## 概述
|
||||
系统配置表用于存储游戏平台的各种配置参数,支持动态配置和运行时修改。
|
||||
|
||||
## 配置表结构
|
||||
表名:`system_config`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | bigint | 主键ID |
|
||||
| config_key | varchar(100) | 配置键(唯一) |
|
||||
| config_value | text | 配置值 |
|
||||
| config_type | varchar(50) | 配置类型:STRING, INTEGER, BOOLEAN, JSON |
|
||||
| description | varchar(500) | 配置描述 |
|
||||
| is_system | tinyint(1) | 是否系统配置(1是,0否) |
|
||||
| created_at | datetime(3) | 创建时间 |
|
||||
| updated_at | datetime(3) | 更新时间 |
|
||||
|
||||
## 默认配置项
|
||||
|
||||
### 链接生成相关配置
|
||||
- `link.default_quantity`: 每次副本的奖励点数(默认值:50)
|
||||
- `link.refresh_interval`: 链接刷新间隔,单位:秒(默认值:300)
|
||||
- `link.qr_expire_time`: 二维码过期时间,单位:秒(默认值:600)
|
||||
- `link.max_times_per_batch`: 每批次最大打副本次数(默认值:100)
|
||||
- `link.min_quantity`: 最小奖励点数(默认值:10)
|
||||
- `link.max_quantity`: 最大奖励点数(默认值:1000)
|
||||
|
||||
### 脚本服务器相关配置
|
||||
- `script.server_url`: 脚本服务器地址(默认值:http://36.138.184.60:12345)
|
||||
- `script.qr_path_template`: 二维码图片路径模板(默认值:/{machineId}/二维码.png)
|
||||
|
||||
## 参数说明
|
||||
|
||||
### 链接生成接口参数
|
||||
- **`times`**: 打多少次副本,决定总的目标值
|
||||
- **`linkCount`**: 生成多少个链接,决定链接任务的数量(默认值:1)
|
||||
|
||||
### 业务逻辑
|
||||
1. 每次副本的奖励点数从配置表 `link.default_quantity` 获取
|
||||
2. 总目标值 = `times` × 配置表中的默认奖励点数
|
||||
3. 生成 `linkCount` 个链接任务供用户使用
|
||||
4. 用户完成链接后,根据 `times` 和配置的奖励点数计算总奖励
|
||||
|
||||
## API接口
|
||||
|
||||
### 获取配置列表
|
||||
```
|
||||
GET /api/admin/config/list?page=1&size=20
|
||||
```
|
||||
|
||||
### 根据键获取配置
|
||||
```
|
||||
GET /api/admin/config/key/{configKey}
|
||||
```
|
||||
|
||||
### 根据类型获取配置
|
||||
```
|
||||
GET /api/admin/config/type/{configType}
|
||||
```
|
||||
|
||||
### 创建配置
|
||||
```
|
||||
POST /api/admin/config
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"configKey": "custom.setting",
|
||||
"configValue": "value",
|
||||
"configType": "STRING",
|
||||
"description": "自定义设置",
|
||||
"isSystem": false
|
||||
}
|
||||
```
|
||||
|
||||
### 更新配置
|
||||
```
|
||||
PUT /api/admin/config/{id}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"configKey": "custom.setting",
|
||||
"configValue": "new_value",
|
||||
"configType": "STRING",
|
||||
"description": "自定义设置",
|
||||
"isSystem": false
|
||||
}
|
||||
```
|
||||
|
||||
### 删除配置
|
||||
```
|
||||
DELETE /api/admin/config/{id}
|
||||
DELETE /api/admin/config/key/{configKey}
|
||||
```
|
||||
|
||||
### 获取链接默认配置
|
||||
```
|
||||
GET /api/admin/config/link/defaults
|
||||
```
|
||||
|
||||
### 获取脚本配置
|
||||
```
|
||||
GET /api/admin/config/script/config
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 在服务中使用配置
|
||||
```java
|
||||
@Autowired
|
||||
private SystemConfigService systemConfigService;
|
||||
|
||||
// 获取默认奖励点数
|
||||
Integer defaultQuantity = systemConfigService.getDefaultQuantity();
|
||||
|
||||
// 获取刷新间隔
|
||||
Integer refreshInterval = systemConfigService.getRefreshInterval();
|
||||
|
||||
// 获取脚本服务器地址
|
||||
String serverUrl = systemConfigService.getScriptServerUrl();
|
||||
```
|
||||
|
||||
### 动态修改配置
|
||||
```java
|
||||
// 更新配置
|
||||
SystemConfig config = systemConfigService.getConfigByKey("link.default_quantity");
|
||||
config.setConfigValue("100");
|
||||
systemConfigService.updateConfig(config);
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **系统配置**:标记为系统配置的项(is_system=1)通常不建议删除,这些是平台运行必需的基础配置。
|
||||
|
||||
2. **配置类型**:根据实际需要选择合适的配置类型,数值类型建议使用INTEGER,布尔值使用BOOLEAN。
|
||||
|
||||
3. **配置键命名**:建议使用点号分隔的层次结构,如:`module.submodule.setting`。
|
||||
|
||||
4. **配置值验证**:在更新配置时,建议对配置值进行格式验证,确保符合预期类型。
|
||||
|
||||
5. **缓存策略**:对于频繁访问的配置,建议在服务层添加缓存机制,提高性能。
|
||||
|
||||
## 扩展配置
|
||||
|
||||
可以根据业务需要添加更多配置项,例如:
|
||||
- 用户权限相关配置
|
||||
- 游戏规则配置
|
||||
- 第三方服务配置
|
||||
- 日志级别配置
|
||||
- 性能调优参数
|
||||
275
docs/链接状态接口测试.md
Normal file
275
docs/链接状态接口测试.md
Normal file
@@ -0,0 +1,275 @@
|
||||
# 链接状态接口测试文档
|
||||
|
||||
## 接口概览
|
||||
|
||||
新增了三个接口用于前端获取链接状态信息:
|
||||
|
||||
1. **获取链接状态** - `GET /api/link/{codeNo}/status`
|
||||
2. **检查链接是否存在** - `GET /api/link/{codeNo}/exists`
|
||||
3. **检查链接是否有效** - `GET /api/link/{codeNo}/valid`
|
||||
|
||||
## 测试步骤
|
||||
|
||||
### 1. 准备工作
|
||||
|
||||
首先需要生成一个链接用于测试:
|
||||
|
||||
```bash
|
||||
# 1. 登录获取JWT令牌
|
||||
curl -X POST http://localhost:18080/api/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"username": "your_username",
|
||||
"password": "your_password"
|
||||
}'
|
||||
```
|
||||
|
||||
从响应中获取JWT令牌,后续请求都需要在Header中带上:
|
||||
```bash
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
|
||||
```
|
||||
|
||||
### 2. 生成测试链接
|
||||
|
||||
```bash
|
||||
# 2. 生成链接
|
||||
curl -X POST http://localhost:18080/api/link/generate \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"times": 5,
|
||||
"linkCount": 2
|
||||
}'
|
||||
```
|
||||
|
||||
响应示例:
|
||||
```json
|
||||
{
|
||||
"batchId": 123,
|
||||
"deductPoints": 250,
|
||||
"expireAt": "2024-01-15T16:30:00",
|
||||
"codeNos": ["ABC12345", "DEF67890"]
|
||||
}
|
||||
```
|
||||
|
||||
记录其中一个 `codeNo`,比如 `ABC12345`,用于后续测试。
|
||||
|
||||
### 3. 测试获取链接状态接口
|
||||
|
||||
```bash
|
||||
# 3. 获取链接状态
|
||||
curl -X GET http://localhost:18080/api/link/ABC12345/status \
|
||||
-H "Accept: application/json"
|
||||
```
|
||||
|
||||
**预期响应:**
|
||||
```json
|
||||
{
|
||||
"codeNo": "ABC12345",
|
||||
"batchId": 123,
|
||||
"status": "NEW",
|
||||
"statusDesc": "新建",
|
||||
"expireAt": "2024-01-15T16:30:00",
|
||||
"isExpired": false,
|
||||
"remainingSeconds": 7200,
|
||||
"quantity": 50,
|
||||
"times": 5,
|
||||
"totalPoints": 250,
|
||||
"region": null,
|
||||
"machineId": null,
|
||||
"loginAt": null,
|
||||
"createdAt": "2024-01-15T12:00:00",
|
||||
"updatedAt": "2024-01-15T12:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 测试检查链接是否存在接口
|
||||
|
||||
```bash
|
||||
# 4. 检查链接是否存在
|
||||
curl -X GET http://localhost:18080/api/link/ABC12345/exists \
|
||||
-H "Accept: application/json"
|
||||
```
|
||||
|
||||
**预期响应:** `true`
|
||||
|
||||
### 5. 测试检查链接是否有效接口
|
||||
|
||||
```bash
|
||||
# 5. 检查链接是否有效
|
||||
curl -X GET http://localhost:18080/api/link/ABC12345/valid \
|
||||
-H "Accept: application/json"
|
||||
```
|
||||
|
||||
**预期响应:** `true`
|
||||
|
||||
### 6. 测试错误情况
|
||||
|
||||
#### 6.1 测试不存在的链接
|
||||
|
||||
```bash
|
||||
# 测试不存在的链接
|
||||
curl -X GET http://localhost:18080/api/link/INVALID123/status \
|
||||
-H "Accept: application/json"
|
||||
```
|
||||
|
||||
**预期响应:**
|
||||
```json
|
||||
{
|
||||
"timestamp": "2024-01-15T12:00:00.000+00:00",
|
||||
"status": 400,
|
||||
"error": "Bad Request",
|
||||
"message": "链接不存在: INVALID123",
|
||||
"path": "/api/link/INVALID123/status"
|
||||
}
|
||||
```
|
||||
|
||||
#### 6.2 测试空链接编号
|
||||
|
||||
```bash
|
||||
# 测试空链接编号
|
||||
curl -X GET http://localhost:18080/api/link//status \
|
||||
-H "Accept: application/json"
|
||||
```
|
||||
|
||||
**预期响应:** 404 Not Found
|
||||
|
||||
## 状态值说明
|
||||
|
||||
### 链接状态 (status)
|
||||
- `NEW` - 新建,未开始使用
|
||||
- `USING` - 使用中,正在执行任务
|
||||
- `LOGGED_IN` - 已登录,任务完成
|
||||
- `REFUNDED` - 已退款
|
||||
- `EXPIRED` - 已过期
|
||||
|
||||
### 区域 (region)
|
||||
- `Q` - Q区
|
||||
- `V` - V区
|
||||
- `null` - 未分配区域
|
||||
|
||||
## 测试数据验证
|
||||
|
||||
### 1. 数值计算验证
|
||||
- `totalPoints` = `quantity` × `times`
|
||||
- `remainingSeconds` 应该大于0(如果未过期)
|
||||
- `isExpired` 应该与当前时间和 `expireAt` 一致
|
||||
|
||||
### 2. 状态一致性验证
|
||||
- 如果 `status` 是 `EXPIRED`,那么 `isExpired` 应该是 `true`
|
||||
- 如果 `status` 是 `LOGGED_IN`,那么 `loginAt` 不应该为 `null`
|
||||
|
||||
### 3. 时间验证
|
||||
- `createdAt` ≤ `updatedAt`
|
||||
- `expireAt` > `createdAt`
|
||||
- 如果 `loginAt` 不为空,那么 `loginAt` ≥ `createdAt`
|
||||
|
||||
## 性能测试
|
||||
|
||||
### 1. 响应时间测试
|
||||
```bash
|
||||
# 使用time命令测试响应时间
|
||||
time curl -X GET http://localhost:18080/api/link/ABC12345/status
|
||||
```
|
||||
|
||||
### 2. 并发测试
|
||||
```bash
|
||||
# 使用ab工具进行并发测试
|
||||
ab -n 100 -c 10 http://localhost:18080/api/link/ABC12345/status
|
||||
```
|
||||
|
||||
## 错误处理测试
|
||||
|
||||
### 1. 数据库连接异常
|
||||
模拟数据库连接失败的情况,验证错误处理。
|
||||
|
||||
### 2. 网络超时
|
||||
模拟网络延迟,验证超时处理。
|
||||
|
||||
### 3. 内存不足
|
||||
模拟内存不足情况,验证系统稳定性。
|
||||
|
||||
## 安全测试
|
||||
|
||||
### 1. 权限验证
|
||||
```bash
|
||||
# 不带JWT令牌访问(应该被拦截)
|
||||
curl -X GET http://localhost:18080/api/link/ABC12345/status
|
||||
```
|
||||
|
||||
### 2. SQL注入防护
|
||||
```bash
|
||||
# 测试SQL注入
|
||||
curl -X GET "http://localhost:18080/api/link/ABC12345'; DROP TABLE link_task; --/status"
|
||||
```
|
||||
|
||||
### 3. XSS防护
|
||||
```bash
|
||||
# 测试XSS攻击
|
||||
curl -X GET "http://localhost:18080/api/link/<script>alert('xss')</script>/status"
|
||||
```
|
||||
|
||||
## 测试报告模板
|
||||
|
||||
### 测试结果记录
|
||||
|
||||
| 测试项目 | 测试结果 | 响应时间 | 备注 |
|
||||
|---------|---------|---------|------|
|
||||
| 获取链接状态 | ✅ 通过 | 45ms | 正常 |
|
||||
| 检查链接存在 | ✅ 通过 | 23ms | 正常 |
|
||||
| 检查链接有效 | ✅ 通过 | 28ms | 正常 |
|
||||
| 错误处理 | ✅ 通过 | 15ms | 正常 |
|
||||
| 性能测试 | ✅ 通过 | 平均35ms | 满足要求 |
|
||||
|
||||
### 问题记录
|
||||
|
||||
| 问题描述 | 严重程度 | 状态 | 备注 |
|
||||
|---------|---------|------|------|
|
||||
| 无 | - | - | - |
|
||||
|
||||
### 建议改进
|
||||
|
||||
1. 可以考虑添加缓存机制,减少数据库查询
|
||||
2. 可以添加链接访问统计功能
|
||||
3. 可以考虑添加链接状态变更通知功能
|
||||
|
||||
## 自动化测试
|
||||
|
||||
### 使用Postman Collection
|
||||
|
||||
可以创建Postman Collection来自动化测试这些接口:
|
||||
|
||||
1. 创建环境变量存储JWT令牌和链接编号
|
||||
2. 设置测试脚本验证响应数据
|
||||
3. 使用Postman Runner批量执行测试
|
||||
|
||||
### 使用JUnit测试
|
||||
|
||||
```java
|
||||
@SpringBootTest
|
||||
@AutoConfigureTestDatabase
|
||||
class LinkStatusControllerTest {
|
||||
|
||||
@Test
|
||||
void testGetLinkStatus() {
|
||||
// 测试获取链接状态
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLinkNotExists() {
|
||||
// 测试链接不存在的情况
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
这些接口为前端提供了完整的链接状态查询功能,支持:
|
||||
|
||||
1. **完整的状态信息** - 包括任务详情、过期时间、奖励点数等
|
||||
2. **实时状态计算** - 自动计算是否过期、剩余时间等
|
||||
3. **友好的错误处理** - 提供清晰的错误信息和状态码
|
||||
4. **高性能查询** - 使用响应式编程,支持高并发访问
|
||||
|
||||
前端可以根据这些接口实现丰富的用户界面,提供良好的用户体验。
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.gameplatform.server.controller.admin;
|
||||
|
||||
import com.gameplatform.server.model.dto.common.PageResult;
|
||||
import com.gameplatform.server.model.entity.admin.SystemConfig;
|
||||
import com.gameplatform.server.model.dto.admin.SystemConfigRequest;
|
||||
import com.gameplatform.server.model.dto.admin.SystemConfigResponse;
|
||||
import com.gameplatform.server.model.dto.admin.SystemConfigConverter;
|
||||
import com.gameplatform.server.service.admin.SystemConfigService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/config")
|
||||
@Tag(name = "系统配置管理", description = "系统配置的增删改查接口")
|
||||
public class SystemConfigController {
|
||||
|
||||
@Autowired
|
||||
private SystemConfigService systemConfigService;
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "获取配置列表", description = "分页获取所有系统配置")
|
||||
public ResponseEntity<PageResult<SystemConfigResponse>> getConfigList(
|
||||
@RequestParam(defaultValue = "1") int page,
|
||||
@RequestParam(defaultValue = "20") int size) {
|
||||
|
||||
int offset = (page - 1) * size;
|
||||
List<SystemConfig> configs = systemConfigService.getAllConfigs(size, offset);
|
||||
long total = systemConfigService.getConfigCount();
|
||||
|
||||
List<SystemConfigResponse> responses = configs.stream()
|
||||
.map(SystemConfigConverter::toResponse)
|
||||
.toList();
|
||||
|
||||
PageResult<SystemConfigResponse> result = new PageResult<>();
|
||||
result.setItems(responses);
|
||||
result.setTotal(total);
|
||||
result.setPage(page);
|
||||
result.setSize(size);
|
||||
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
@GetMapping("/key/{configKey}")
|
||||
@Operation(summary = "根据键获取配置", description = "根据配置键获取配置信息")
|
||||
public ResponseEntity<SystemConfigResponse> getConfigByKey(@PathVariable String configKey) {
|
||||
SystemConfig config = systemConfigService.getConfigByKey(configKey);
|
||||
if (config == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
return ResponseEntity.ok(SystemConfigConverter.toResponse(config));
|
||||
}
|
||||
|
||||
@GetMapping("/type/{configType}")
|
||||
@Operation(summary = "根据类型获取配置", description = "根据配置类型获取配置列表")
|
||||
public ResponseEntity<List<SystemConfigResponse>> getConfigsByType(@PathVariable String configType) {
|
||||
List<SystemConfig> configs = systemConfigService.getConfigsByType(configType);
|
||||
List<SystemConfigResponse> responses = configs.stream()
|
||||
.map(SystemConfigConverter::toResponse)
|
||||
.toList();
|
||||
return ResponseEntity.ok(responses);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@Operation(summary = "创建配置", description = "创建新的系统配置")
|
||||
public ResponseEntity<Boolean> createConfig(@RequestBody SystemConfigRequest request) {
|
||||
SystemConfig systemConfig = SystemConfigConverter.toEntity(request);
|
||||
boolean success = systemConfigService.createConfig(systemConfig);
|
||||
return ResponseEntity.ok(success);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@Operation(summary = "更新配置", description = "更新指定ID的系统配置")
|
||||
public ResponseEntity<Boolean> updateConfig(@PathVariable Long id, @RequestBody SystemConfigRequest request) {
|
||||
SystemConfig systemConfig = SystemConfigConverter.toEntity(request);
|
||||
systemConfig.setId(id);
|
||||
boolean success = systemConfigService.updateConfig(systemConfig);
|
||||
return ResponseEntity.ok(success);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@Operation(summary = "删除配置", description = "删除指定ID的系统配置")
|
||||
public ResponseEntity<Boolean> deleteConfig(@PathVariable Long id) {
|
||||
boolean success = systemConfigService.deleteConfig(id);
|
||||
return ResponseEntity.ok(success);
|
||||
}
|
||||
|
||||
@DeleteMapping("/key/{configKey}")
|
||||
@Operation(summary = "根据键删除配置", description = "根据配置键删除系统配置")
|
||||
public ResponseEntity<Boolean> deleteConfigByKey(@PathVariable String configKey) {
|
||||
boolean success = systemConfigService.deleteConfigByKey(configKey);
|
||||
return ResponseEntity.ok(success);
|
||||
}
|
||||
|
||||
@GetMapping("/link/defaults")
|
||||
@Operation(summary = "获取链接默认配置", description = "获取链接生成相关的默认配置")
|
||||
public ResponseEntity<Object> getLinkDefaults() {
|
||||
return ResponseEntity.ok(new Object() {
|
||||
public final Integer defaultQuantity = systemConfigService.getDefaultQuantity();
|
||||
public final Integer refreshInterval = systemConfigService.getRefreshInterval();
|
||||
public final Integer qrExpireTime = systemConfigService.getQrExpireTime();
|
||||
public final Integer maxTimesPerBatch = systemConfigService.getMaxTimesPerBatch();
|
||||
public final Integer minQuantity = systemConfigService.getMinQuantity();
|
||||
public final Integer maxQuantity = systemConfigService.getMaxQuantity();
|
||||
});
|
||||
}
|
||||
|
||||
@GetMapping("/script/config")
|
||||
@Operation(summary = "获取脚本配置", description = "获取脚本服务器相关配置")
|
||||
public ResponseEntity<Object> getScriptConfig() {
|
||||
return ResponseEntity.ok(new Object() {
|
||||
public final String serverUrl = systemConfigService.getScriptServerUrl();
|
||||
public final String qrPathTemplate = systemConfigService.getQrPathTemplate();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,9 @@ package com.gameplatform.server.controller.link;
|
||||
|
||||
import com.gameplatform.server.model.dto.link.LinkGenerateRequest;
|
||||
import com.gameplatform.server.model.dto.link.LinkGenerateResponse;
|
||||
import com.gameplatform.server.model.dto.link.LinkStatusResponse;
|
||||
import com.gameplatform.server.service.link.LinkGenerationService;
|
||||
import com.gameplatform.server.service.link.LinkStatusService;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@@ -24,9 +26,11 @@ public class LinkController {
|
||||
private static final Logger log = LoggerFactory.getLogger(LinkController.class);
|
||||
|
||||
private final LinkGenerationService linkGenerationService;
|
||||
private final LinkStatusService linkStatusService;
|
||||
|
||||
public LinkController(LinkGenerationService linkGenerationService) {
|
||||
public LinkController(LinkGenerationService linkGenerationService, LinkStatusService linkStatusService) {
|
||||
this.linkGenerationService = linkGenerationService;
|
||||
this.linkStatusService = linkStatusService;
|
||||
}
|
||||
|
||||
@PostMapping("/generate")
|
||||
@@ -35,8 +39,8 @@ public class LinkController {
|
||||
public Mono<LinkGenerateResponse> generateLinks(@Valid @RequestBody LinkGenerateRequest request,
|
||||
Authentication authentication) {
|
||||
log.info("=== 开始处理链接生成请求 ===");
|
||||
log.info("请求参数: times={}, perTimeQuantity={}",
|
||||
request.getTimes(), request.getPerTimeQuantity());
|
||||
log.info("请求参数: times={}, linkCount={}",
|
||||
request.getTimes(), request.getLinkCount());
|
||||
|
||||
log.info("=== 开始检查认证信息 ===");
|
||||
log.info("Authentication: {}", authentication);
|
||||
@@ -92,11 +96,11 @@ public class LinkController {
|
||||
log.info("=== 用户认证信息完整,开始处理业务逻辑 ===");
|
||||
log.info("操作者信息: operatorId={}, operatorType={}, username={}",
|
||||
operatorId, operatorType, username);
|
||||
log.info("业务参数: times={}, perTimeQuantity={}",
|
||||
request.getTimes(), request.getPerTimeQuantity());
|
||||
log.info("业务参数: times={}, linkCount={}",
|
||||
request.getTimes(), request.getLinkCount());
|
||||
|
||||
return linkGenerationService.generateLinks(operatorId, operatorType,
|
||||
request.getTimes(), request.getPerTimeQuantity())
|
||||
request.getTimes(), request.getLinkCount())
|
||||
.map(result -> {
|
||||
log.info("链接生成成功,batchId: {}, 扣除积分: {}, 过期时间: {}",
|
||||
result.getBatchId(), result.getNeedPoints(), result.getExpireAt());
|
||||
@@ -111,6 +115,36 @@ public class LinkController {
|
||||
log.error("链接生成失败: {}", error.getMessage(), error);
|
||||
});
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.gameplatform.server.mapper.admin;
|
||||
|
||||
import com.gameplatform.server.model.entity.admin.SystemConfig;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface SystemConfigMapper {
|
||||
SystemConfig findById(@Param("id") Long id);
|
||||
|
||||
SystemConfig findByKey(@Param("configKey") String configKey);
|
||||
|
||||
List<SystemConfig> findByType(@Param("configType") String configType);
|
||||
|
||||
List<SystemConfig> findAll(@Param("size") int size,
|
||||
@Param("offset") int offset);
|
||||
|
||||
long countAll();
|
||||
|
||||
int insert(SystemConfig systemConfig);
|
||||
|
||||
int update(SystemConfig systemConfig);
|
||||
|
||||
int deleteById(@Param("id") Long id);
|
||||
|
||||
int deleteByKey(@Param("configKey") String configKey);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.gameplatform.server.model.dto.admin;
|
||||
|
||||
import com.gameplatform.server.model.entity.admin.SystemConfig;
|
||||
|
||||
public class SystemConfigConverter {
|
||||
|
||||
public static SystemConfigResponse toResponse(SystemConfig entity) {
|
||||
if (entity == null) return null;
|
||||
|
||||
SystemConfigResponse response = new SystemConfigResponse();
|
||||
response.setId(entity.getId());
|
||||
response.setConfigKey(entity.getConfigKey());
|
||||
response.setConfigValue(entity.getConfigValue());
|
||||
response.setConfigType(entity.getConfigType());
|
||||
response.setDescription(entity.getDescription());
|
||||
response.setIsSystem(entity.getIsSystem());
|
||||
response.setCreatedAt(entity.getCreatedAt());
|
||||
response.setUpdatedAt(entity.getUpdatedAt());
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public static SystemConfig toEntity(SystemConfigRequest request) {
|
||||
if (request == null) return null;
|
||||
|
||||
SystemConfig entity = new SystemConfig();
|
||||
entity.setConfigKey(request.getConfigKey());
|
||||
entity.setConfigValue(request.getConfigValue());
|
||||
entity.setConfigType(request.getConfigType());
|
||||
entity.setDescription(request.getDescription());
|
||||
entity.setIsSystem(request.getIsSystem());
|
||||
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.gameplatform.server.model.dto.admin;
|
||||
|
||||
public class SystemConfigRequest {
|
||||
private String configKey;
|
||||
private String configValue;
|
||||
private String configType;
|
||||
private String description;
|
||||
private Boolean isSystem;
|
||||
|
||||
public String getConfigKey() { return configKey; }
|
||||
public void setConfigKey(String configKey) { this.configKey = configKey; }
|
||||
|
||||
public String getConfigValue() { return configValue; }
|
||||
public void setConfigValue(String configValue) { this.configValue = configValue; }
|
||||
|
||||
public String getConfigType() { return configType; }
|
||||
public void setConfigType(String configType) { this.configType = configType; }
|
||||
|
||||
public String getDescription() { return description; }
|
||||
public void setDescription(String description) { this.description = description; }
|
||||
|
||||
public Boolean getIsSystem() { return isSystem; }
|
||||
public void setIsSystem(Boolean isSystem) { this.isSystem = isSystem; }
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.gameplatform.server.model.dto.admin;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class SystemConfigResponse {
|
||||
private Long id;
|
||||
private String configKey;
|
||||
private String configValue;
|
||||
private String configType;
|
||||
private String description;
|
||||
private Boolean isSystem;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
|
||||
public String getConfigKey() { return configKey; }
|
||||
public void setConfigKey(String configKey) { this.configKey = configKey; }
|
||||
|
||||
public String getConfigValue() { return configValue; }
|
||||
public void setConfigValue(String configValue) { this.configValue = configValue; }
|
||||
|
||||
public String getConfigType() { return configType; }
|
||||
public void setConfigType(String configType) { this.configType = configType; }
|
||||
|
||||
public String getDescription() { return description; }
|
||||
public void setDescription(String description) { this.description = description; }
|
||||
|
||||
public Boolean getIsSystem() { return isSystem; }
|
||||
public void setIsSystem(Boolean isSystem) { this.isSystem = isSystem; }
|
||||
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
|
||||
public LocalDateTime getUpdatedAt() { return updatedAt; }
|
||||
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
|
||||
}
|
||||
@@ -5,13 +5,13 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
public class LinkGenerateRequest {
|
||||
@Schema(description = "本次打脚本的次数", example = "10")
|
||||
private Integer times;
|
||||
@Schema(description = "每次打的数量", example = "5")
|
||||
private Integer perTimeQuantity;
|
||||
@Schema(description = "生成多少个链接", example = "5")
|
||||
private Integer linkCount = 1; // 默认值为1
|
||||
|
||||
public Integer getTimes() { return times; }
|
||||
public void setTimes(Integer times) { this.times = times; }
|
||||
public Integer getPerTimeQuantity() { return perTimeQuantity; }
|
||||
public void setPerTimeQuantity(Integer perTimeQuantity) { this.perTimeQuantity = perTimeQuantity; }
|
||||
public Integer getLinkCount() { return linkCount; }
|
||||
public void setLinkCount(Integer linkCount) { this.linkCount = linkCount; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.gameplatform.server.model.dto.link;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "链接状态响应")
|
||||
public class LinkStatusResponse {
|
||||
|
||||
@Schema(description = "链接编号", example = "ABC12345")
|
||||
private String codeNo;
|
||||
|
||||
@Schema(description = "批次ID", example = "123")
|
||||
private Long batchId;
|
||||
|
||||
@Schema(description = "链接状态", example = "NEW", allowableValues = {"NEW", "USING", "LOGGED_IN", "REFUNDED", "EXPIRED"})
|
||||
private String status;
|
||||
|
||||
@Schema(description = "链接状态描述", example = "新建")
|
||||
private String statusDesc;
|
||||
|
||||
@Schema(description = "过期时间", example = "2024-01-15T16:30:00")
|
||||
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 = "10")
|
||||
private Integer times;
|
||||
|
||||
@Schema(description = "总奖励点数", example = "500")
|
||||
private Integer totalPoints;
|
||||
|
||||
@Schema(description = "区域", example = "Q", allowableValues = {"Q", "V"})
|
||||
private String region;
|
||||
|
||||
@Schema(description = "机器ID", example = "MACHINE001")
|
||||
private String machineId;
|
||||
|
||||
@Schema(description = "登录时间", example = "2024-01-15T14:30:00")
|
||||
private LocalDateTime loginAt;
|
||||
|
||||
@Schema(description = "创建时间", example = "2024-01-15T12:00:00")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Schema(description = "更新时间", example = "2024-01-15T14:30:00")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
// Getters and Setters
|
||||
public String getCodeNo() { return codeNo; }
|
||||
public void setCodeNo(String codeNo) { this.codeNo = codeNo; }
|
||||
|
||||
public Long getBatchId() { return batchId; }
|
||||
public void setBatchId(Long batchId) { this.batchId = batchId; }
|
||||
|
||||
public String getStatus() { return status; }
|
||||
public void setStatus(String status) { this.status = status; }
|
||||
|
||||
public String getStatusDesc() { return statusDesc; }
|
||||
public void setStatusDesc(String statusDesc) { this.statusDesc = statusDesc; }
|
||||
|
||||
public LocalDateTime getExpireAt() { return expireAt; }
|
||||
public void setExpireAt(LocalDateTime expireAt) { this.expireAt = expireAt; }
|
||||
|
||||
public Boolean getIsExpired() { return isExpired; }
|
||||
public void setIsExpired(Boolean isExpired) { this.isExpired = isExpired; }
|
||||
|
||||
public Long getRemainingSeconds() { return remainingSeconds; }
|
||||
public void setRemainingSeconds(Long remainingSeconds) { this.remainingSeconds = remainingSeconds; }
|
||||
|
||||
public Integer getQuantity() { return quantity; }
|
||||
public void setQuantity(Integer quantity) { this.quantity = quantity; }
|
||||
|
||||
public Integer getTimes() { return times; }
|
||||
public void setTimes(Integer times) { this.times = times; }
|
||||
|
||||
public Integer getTotalPoints() { return totalPoints; }
|
||||
public void setTotalPoints(Integer totalPoints) { this.totalPoints = totalPoints; }
|
||||
|
||||
public String getRegion() { return region; }
|
||||
public void setRegion(String region) { this.region = region; }
|
||||
|
||||
public String getMachineId() { return machineId; }
|
||||
public void setMachineId(String machineId) { this.machineId = machineId; }
|
||||
|
||||
public LocalDateTime getLoginAt() { return loginAt; }
|
||||
public void setLoginAt(LocalDateTime loginAt) { this.loginAt = loginAt; }
|
||||
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
|
||||
public LocalDateTime getUpdatedAt() { return updatedAt; }
|
||||
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.gameplatform.server.model.entity.admin;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class SystemConfig {
|
||||
private Long id;
|
||||
private String configKey;
|
||||
private String configValue;
|
||||
private String configType;
|
||||
private String description;
|
||||
private Boolean isSystem;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
|
||||
public String getConfigKey() { return configKey; }
|
||||
public void setConfigKey(String configKey) { this.configKey = configKey; }
|
||||
|
||||
public String getConfigValue() { return configValue; }
|
||||
public void setConfigValue(String configValue) { this.configValue = configValue; }
|
||||
|
||||
public String getConfigType() { return configType; }
|
||||
public void setConfigType(String configType) { this.configType = configType; }
|
||||
|
||||
public String getDescription() { return description; }
|
||||
public void setDescription(String description) { this.description = description; }
|
||||
|
||||
public Boolean getIsSystem() { return isSystem; }
|
||||
public void setIsSystem(Boolean isSystem) { this.isSystem = isSystem; }
|
||||
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
|
||||
public LocalDateTime getUpdatedAt() { return updatedAt; }
|
||||
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
|
||||
}
|
||||
@@ -7,8 +7,6 @@ public class LinkBatch {
|
||||
private Long agentId;
|
||||
private Integer quantity;
|
||||
private Integer times;
|
||||
private Integer batchSize;
|
||||
private Long deductPoints;
|
||||
private Long operatorId;
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@@ -24,12 +22,6 @@ public class LinkBatch {
|
||||
public Integer getTimes() { return times; }
|
||||
public void setTimes(Integer times) { this.times = times; }
|
||||
|
||||
public Integer getBatchSize() { return batchSize; }
|
||||
public void setBatchSize(Integer batchSize) { this.batchSize = batchSize; }
|
||||
|
||||
public Long getDeductPoints() { return deductPoints; }
|
||||
public void setDeductPoints(Long deductPoints) { this.deductPoints = deductPoints; }
|
||||
|
||||
public Long getOperatorId() { return operatorId; }
|
||||
public void setOperatorId(Long operatorId) { this.operatorId = operatorId; }
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.gameplatform.server.service.admin;
|
||||
|
||||
import com.gameplatform.server.mapper.admin.SystemConfigMapper;
|
||||
import com.gameplatform.server.model.entity.admin.SystemConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class SystemConfigService {
|
||||
|
||||
@Autowired
|
||||
private SystemConfigMapper systemConfigMapper;
|
||||
|
||||
public SystemConfig getConfigByKey(String configKey) {
|
||||
return systemConfigMapper.findByKey(configKey);
|
||||
}
|
||||
|
||||
public String getConfigValue(String configKey, String defaultValue) {
|
||||
SystemConfig config = systemConfigMapper.findByKey(configKey);
|
||||
return config != null ? config.getConfigValue() : defaultValue;
|
||||
}
|
||||
|
||||
public Integer getConfigValueAsInt(String configKey, Integer defaultValue) {
|
||||
String value = getConfigValue(configKey, null);
|
||||
if (value == null) return defaultValue;
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
} catch (NumberFormatException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public Boolean getConfigValueAsBoolean(String configKey, Boolean defaultValue) {
|
||||
String value = getConfigValue(configKey, null);
|
||||
if (value == null) return defaultValue;
|
||||
return Boolean.parseBoolean(value);
|
||||
}
|
||||
|
||||
public List<SystemConfig> getAllConfigs(int size, int offset) {
|
||||
return systemConfigMapper.findAll(size, offset);
|
||||
}
|
||||
|
||||
public long getConfigCount() {
|
||||
return systemConfigMapper.countAll();
|
||||
}
|
||||
|
||||
public List<SystemConfig> getConfigsByType(String configType) {
|
||||
return systemConfigMapper.findByType(configType);
|
||||
}
|
||||
|
||||
public boolean createConfig(SystemConfig systemConfig) {
|
||||
return systemConfigMapper.insert(systemConfig) > 0;
|
||||
}
|
||||
|
||||
public boolean updateConfig(SystemConfig systemConfig) {
|
||||
return systemConfigMapper.update(systemConfig) > 0;
|
||||
}
|
||||
|
||||
public boolean deleteConfig(Long id) {
|
||||
return systemConfigMapper.deleteById(id) > 0;
|
||||
}
|
||||
|
||||
public boolean deleteConfigByKey(String configKey) {
|
||||
return systemConfigMapper.deleteByKey(configKey) > 0;
|
||||
}
|
||||
|
||||
// 获取链接相关的默认配置
|
||||
public Integer getDefaultQuantity() {
|
||||
return getConfigValueAsInt("link.default_quantity", 50);
|
||||
}
|
||||
|
||||
public Integer getRefreshInterval() {
|
||||
return getConfigValueAsInt("link.refresh_interval", 300);
|
||||
}
|
||||
|
||||
public Integer getQrExpireTime() {
|
||||
return getConfigValueAsInt("link.qr_expire_time", 600);
|
||||
}
|
||||
|
||||
public Integer getMaxTimesPerBatch() {
|
||||
return getConfigValueAsInt("link.max_times_per_batch", 100);
|
||||
}
|
||||
|
||||
public Integer getMinQuantity() {
|
||||
return getConfigValueAsInt("link.min_quantity", 10);
|
||||
}
|
||||
|
||||
public Integer getMaxQuantity() {
|
||||
return getConfigValueAsInt("link.max_quantity", 1000);
|
||||
}
|
||||
|
||||
public String getScriptServerUrl() {
|
||||
return getConfigValue("script.server_url", "http://36.138.184.60:12345");
|
||||
}
|
||||
|
||||
public String getQrPathTemplate() {
|
||||
return getConfigValue("script.qr_path_template", "/{machineId}/二维码.png");
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,8 @@ import com.gameplatform.server.model.entity.agent.LinkTask;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import com.gameplatform.server.service.admin.SystemConfigService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -30,31 +31,31 @@ public class LinkGenerationService {
|
||||
private final LinkBatchMapper linkBatchMapper;
|
||||
private final LinkTaskMapper linkTaskMapper;
|
||||
private final AgentPointsTxMapper agentPointsTxMapper;
|
||||
private final int expireHours;
|
||||
private final SystemConfigService systemConfigService;
|
||||
|
||||
public LinkGenerationService(UserAccountMapper userAccountMapper,
|
||||
LinkBatchMapper linkBatchMapper,
|
||||
LinkTaskMapper linkTaskMapper,
|
||||
AgentPointsTxMapper agentPointsTxMapper,
|
||||
@Value("${link.expire-hours:2}") int expireHours) {
|
||||
SystemConfigService systemConfigService) {
|
||||
this.userAccountMapper = userAccountMapper;
|
||||
this.linkBatchMapper = linkBatchMapper;
|
||||
this.linkTaskMapper = linkTaskMapper;
|
||||
this.agentPointsTxMapper = agentPointsTxMapper;
|
||||
this.expireHours = expireHours;
|
||||
this.systemConfigService = systemConfigService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Mono<GenerateResult> generateLinks(Long operatorId, String operatorType,
|
||||
int times, int perTimeQuantity) {
|
||||
return Mono.fromCallable(() -> doGenerate(operatorId, operatorType, times, perTimeQuantity))
|
||||
int times, int linkCount) {
|
||||
return Mono.fromCallable(() -> doGenerate(operatorId, operatorType, times, linkCount))
|
||||
.subscribeOn(Schedulers.boundedElastic());
|
||||
}
|
||||
|
||||
private GenerateResult doGenerate(Long operatorId, String operatorType,
|
||||
int times, int perTimeQuantity) {
|
||||
if (times <= 0 || perTimeQuantity <= 0) {
|
||||
throw new IllegalArgumentException("times 与 perTimeQuantity 必须为正整数");
|
||||
int times, int linkCount) {
|
||||
if (times <= 0 || linkCount <= 0) {
|
||||
throw new IllegalArgumentException("times 与 linkCount 必须为正整数");
|
||||
}
|
||||
|
||||
// 获取操作者账户信息
|
||||
@@ -68,11 +69,16 @@ public class LinkGenerationService {
|
||||
throw new IllegalArgumentException("非法操作者类型");
|
||||
}
|
||||
|
||||
// 从配置表获取每次副本的奖励点数
|
||||
int perTimeQuantity = systemConfigService.getDefaultQuantity();
|
||||
long needPoints = (long) times * (long) perTimeQuantity;
|
||||
int expireHours = systemConfigService.getConfigValueAsInt("link.expire-hours", 2);
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("generateLinks operatorId={} operatorType={} times={} perTimeQuantity={} needPoints={} expireHours={}",
|
||||
operatorId, operatorType, times, perTimeQuantity, needPoints, expireHours);
|
||||
log.debug("generateLinks operatorId={} operatorType={} times={} linkCount={} perTimeQuantity={} needPoints={} expireHours={}",
|
||||
operatorId, operatorType, times, linkCount, perTimeQuantity, needPoints, expireHours);
|
||||
}
|
||||
|
||||
if (!isAdminOperator) {
|
||||
// 代理商自操作,需扣点判断
|
||||
long balance = operator.getPointsBalance() == null ? 0L : operator.getPointsBalance();
|
||||
@@ -83,16 +89,14 @@ public class LinkGenerationService {
|
||||
|
||||
LinkBatch batch = new LinkBatch();
|
||||
batch.setAgentId(operator.getId());
|
||||
batch.setQuantity(times);
|
||||
batch.setTimes(times);
|
||||
batch.setBatchSize(perTimeQuantity);
|
||||
batch.setDeductPoints(needPoints);
|
||||
batch.setQuantity(perTimeQuantity); // 每次副本的奖励点数
|
||||
batch.setTimes(times); // 打副本的次数
|
||||
batch.setOperatorId(operatorId);
|
||||
linkBatchMapper.insert(batch);
|
||||
|
||||
LocalDateTime expireAt = LocalDateTime.now().plusHours(expireHours);
|
||||
List<LinkTask> tasks = new ArrayList<>();
|
||||
for (int i = 0; i < times; i++) {
|
||||
for (int i = 0; i < linkCount; i++) { // 生成linkCount个链接
|
||||
LinkTask t = new LinkTask();
|
||||
t.setBatchId(batch.getId());
|
||||
t.setAgentId(operator.getId());
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
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.LinkStatusResponse;
|
||||
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 reactor.core.scheduler.Schedulers;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class LinkStatusService {
|
||||
private static final Logger log = LoggerFactory.getLogger(LinkStatusService.class);
|
||||
|
||||
private final LinkTaskMapper linkTaskMapper;
|
||||
private final LinkBatchMapper linkBatchMapper;
|
||||
|
||||
// 状态描述映射
|
||||
private static final Map<String, String> STATUS_DESC_MAP = new HashMap<>();
|
||||
static {
|
||||
STATUS_DESC_MAP.put("NEW", "新建");
|
||||
STATUS_DESC_MAP.put("USING", "使用中");
|
||||
STATUS_DESC_MAP.put("LOGGED_IN", "已登录");
|
||||
STATUS_DESC_MAP.put("REFUNDED", "已退款");
|
||||
STATUS_DESC_MAP.put("EXPIRED", "已过期");
|
||||
}
|
||||
|
||||
public LinkStatusService(LinkTaskMapper linkTaskMapper, LinkBatchMapper linkBatchMapper) {
|
||||
this.linkTaskMapper = linkTaskMapper;
|
||||
this.linkBatchMapper = linkBatchMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据链接编号获取链接状态
|
||||
*/
|
||||
public Mono<LinkStatusResponse> getLinkStatus(String codeNo) {
|
||||
return Mono.fromCallable(() -> doGetLinkStatus(codeNo))
|
||||
.subscribeOn(Schedulers.boundedElastic());
|
||||
}
|
||||
|
||||
private LinkStatusResponse doGetLinkStatus(String codeNo) {
|
||||
if (codeNo == null || codeNo.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("链接编号不能为空");
|
||||
}
|
||||
|
||||
log.debug("查询链接状态: codeNo={}", codeNo);
|
||||
|
||||
// 查询链接任务
|
||||
LinkTask linkTask = linkTaskMapper.findByCodeNo(codeNo);
|
||||
if (linkTask == null) {
|
||||
throw new IllegalArgumentException("链接不存在: " + codeNo);
|
||||
}
|
||||
|
||||
// 查询批次信息
|
||||
LinkBatch linkBatch = linkBatchMapper.findById(linkTask.getBatchId());
|
||||
if (linkBatch == null) {
|
||||
throw new IllegalStateException("批次信息不存在: batchId=" + linkTask.getBatchId());
|
||||
}
|
||||
|
||||
// 构建响应对象
|
||||
LinkStatusResponse response = new LinkStatusResponse();
|
||||
response.setCodeNo(linkTask.getCodeNo());
|
||||
response.setBatchId(linkTask.getBatchId());
|
||||
response.setStatus(linkTask.getStatus());
|
||||
response.setStatusDesc(STATUS_DESC_MAP.getOrDefault(linkTask.getStatus(), "未知状态"));
|
||||
response.setExpireAt(linkTask.getExpireAt());
|
||||
response.setQuantity(linkBatch.getQuantity());
|
||||
response.setTimes(linkBatch.getTimes());
|
||||
response.setTotalPoints(linkBatch.getQuantity() * linkBatch.getTimes());
|
||||
response.setRegion(linkTask.getRegion());
|
||||
response.setMachineId(linkTask.getMachineId());
|
||||
response.setLoginAt(linkTask.getLoginAt());
|
||||
response.setCreatedAt(linkTask.getCreatedAt());
|
||||
response.setUpdatedAt(linkTask.getUpdatedAt());
|
||||
|
||||
// 计算过期状态和剩余时间
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
boolean isExpired = now.isAfter(linkTask.getExpireAt());
|
||||
response.setIsExpired(isExpired);
|
||||
|
||||
if (!isExpired) {
|
||||
long remainingSeconds = ChronoUnit.SECONDS.between(now, linkTask.getExpireAt());
|
||||
response.setRemainingSeconds(remainingSeconds);
|
||||
} else {
|
||||
response.setRemainingSeconds(0L);
|
||||
}
|
||||
|
||||
log.debug("链接状态查询完成: codeNo={}, status={}, isExpired={}, remainingSeconds={}",
|
||||
codeNo, linkTask.getStatus(), isExpired, response.getRemainingSeconds());
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查链接是否存在
|
||||
*/
|
||||
public Mono<Boolean> isLinkExists(String codeNo) {
|
||||
return Mono.fromCallable(() -> {
|
||||
if (codeNo == null || codeNo.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
LinkTask linkTask = linkTaskMapper.findByCodeNo(codeNo);
|
||||
return linkTask != null;
|
||||
}).subscribeOn(Schedulers.boundedElastic());
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查链接是否有效(未过期且状态正常)
|
||||
*/
|
||||
public Mono<Boolean> isLinkValid(String codeNo) {
|
||||
return getLinkStatus(codeNo)
|
||||
.map(response -> !response.getIsExpired() &&
|
||||
("NEW".equals(response.getStatus()) || "USING".equals(response.getStatus())))
|
||||
.onErrorReturn(false);
|
||||
}
|
||||
}
|
||||
|
||||
68
src/main/resources/mapper/admin/SystemConfigMapper.xml
Normal file
68
src/main/resources/mapper/admin/SystemConfigMapper.xml
Normal file
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.gameplatform.server.mapper.admin.SystemConfigMapper">
|
||||
<resultMap id="SystemConfigMap" type="com.gameplatform.server.model.entity.admin.SystemConfig">
|
||||
<id property="id" column="id" />
|
||||
<result property="configKey" column="config_key" />
|
||||
<result property="configValue" column="config_value" />
|
||||
<result property="configType" column="config_type" />
|
||||
<result property="description" column="description" />
|
||||
<result property="isSystem" column="is_system" />
|
||||
<result property="createdAt" column="created_at" />
|
||||
<result property="updatedAt" column="updated_at" />
|
||||
</resultMap>
|
||||
|
||||
<select id="findById" parameterType="long" resultMap="SystemConfigMap">
|
||||
SELECT id, config_key, config_value, config_type, description, is_system, created_at, updated_at
|
||||
FROM system_config
|
||||
WHERE id = #{id}
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<select id="findByKey" parameterType="string" resultMap="SystemConfigMap">
|
||||
SELECT id, config_key, config_value, config_type, description, is_system, created_at, updated_at
|
||||
FROM system_config
|
||||
WHERE config_key = #{configKey}
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<select id="findByType" parameterType="string" resultMap="SystemConfigMap">
|
||||
SELECT id, config_key, config_value, config_type, description, is_system, created_at, updated_at
|
||||
FROM system_config
|
||||
WHERE config_type = #{configType}
|
||||
ORDER BY config_key ASC
|
||||
</select>
|
||||
|
||||
<select id="findAll" resultMap="SystemConfigMap">
|
||||
SELECT id, config_key, config_value, config_type, description, is_system, created_at, updated_at
|
||||
FROM system_config
|
||||
ORDER BY config_key ASC
|
||||
LIMIT #{size} OFFSET #{offset}
|
||||
</select>
|
||||
|
||||
<select id="countAll" resultType="long">
|
||||
SELECT COUNT(1) FROM system_config
|
||||
</select>
|
||||
|
||||
<insert id="insert" parameterType="com.gameplatform.server.model.entity.admin.SystemConfig" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO system_config (config_key, config_value, config_type, description, is_system)
|
||||
VALUES (#{configKey}, #{configValue}, #{configType}, #{description}, #{isSystem})
|
||||
</insert>
|
||||
|
||||
<update id="update" parameterType="com.gameplatform.server.model.entity.admin.SystemConfig">
|
||||
UPDATE system_config
|
||||
SET config_value = #{configValue},
|
||||
config_type = #{configType},
|
||||
description = #{description},
|
||||
is_system = #{isSystem}
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<delete id="deleteById" parameterType="long">
|
||||
DELETE FROM system_config WHERE id = #{id}
|
||||
</delete>
|
||||
|
||||
<delete id="deleteByKey" parameterType="string">
|
||||
DELETE FROM system_config WHERE config_key = #{configKey}
|
||||
</delete>
|
||||
</mapper>
|
||||
@@ -6,26 +6,24 @@
|
||||
<result property="agentId" column="agent_id" />
|
||||
<result property="quantity" column="quantity" />
|
||||
<result property="times" column="times" />
|
||||
<result property="batchSize" column="batch_size" />
|
||||
<result property="deductPoints" column="deduct_points" />
|
||||
<result property="operatorId" column="operator_id" />
|
||||
<result property="createdAt" column="created_at" />
|
||||
</resultMap>
|
||||
|
||||
<select id="findById" parameterType="long" resultMap="LinkBatchMap">
|
||||
SELECT id, agent_id, quantity, times, batch_size, deduct_points, operator_id, created_at
|
||||
SELECT id, agent_id, quantity, times, operator_id, created_at
|
||||
FROM link_batch
|
||||
WHERE id = #{id}
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<insert id="insert" parameterType="com.gameplatform.server.model.entity.agent.LinkBatch" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO link_batch (agent_id, quantity, times, batch_size, deduct_points, operator_id)
|
||||
VALUES (#{agentId}, #{quantity}, #{times}, #{batchSize}, #{deductPoints}, #{operatorId})
|
||||
INSERT INTO link_batch (agent_id, quantity, times, operator_id)
|
||||
VALUES (#{agentId}, #{quantity}, #{times}, #{operatorId})
|
||||
</insert>
|
||||
|
||||
<select id="findByAgentId" resultMap="LinkBatchMap">
|
||||
SELECT id, agent_id, quantity, times, batch_size, deduct_points, operator_id, created_at
|
||||
SELECT id, agent_id, quantity, times, operator_id, created_at
|
||||
FROM link_batch
|
||||
WHERE agent_id = #{agentId}
|
||||
ORDER BY created_at DESC
|
||||
@@ -37,7 +35,7 @@
|
||||
</select>
|
||||
|
||||
<select id="findAll" resultMap="LinkBatchMap">
|
||||
SELECT id, agent_id, quantity, times, batch_size, deduct_points, operator_id, created_at
|
||||
SELECT id, agent_id, quantity, times, operator_id, created_at
|
||||
FROM link_batch
|
||||
ORDER BY created_at DESC
|
||||
LIMIT #{size} OFFSET #{offset}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
68
target/classes/mapper/admin/SystemConfigMapper.xml
Normal file
68
target/classes/mapper/admin/SystemConfigMapper.xml
Normal file
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.gameplatform.server.mapper.admin.SystemConfigMapper">
|
||||
<resultMap id="SystemConfigMap" type="com.gameplatform.server.model.entity.admin.SystemConfig">
|
||||
<id property="id" column="id" />
|
||||
<result property="configKey" column="config_key" />
|
||||
<result property="configValue" column="config_value" />
|
||||
<result property="configType" column="config_type" />
|
||||
<result property="description" column="description" />
|
||||
<result property="isSystem" column="is_system" />
|
||||
<result property="createdAt" column="created_at" />
|
||||
<result property="updatedAt" column="updated_at" />
|
||||
</resultMap>
|
||||
|
||||
<select id="findById" parameterType="long" resultMap="SystemConfigMap">
|
||||
SELECT id, config_key, config_value, config_type, description, is_system, created_at, updated_at
|
||||
FROM system_config
|
||||
WHERE id = #{id}
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<select id="findByKey" parameterType="string" resultMap="SystemConfigMap">
|
||||
SELECT id, config_key, config_value, config_type, description, is_system, created_at, updated_at
|
||||
FROM system_config
|
||||
WHERE config_key = #{configKey}
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<select id="findByType" parameterType="string" resultMap="SystemConfigMap">
|
||||
SELECT id, config_key, config_value, config_type, description, is_system, created_at, updated_at
|
||||
FROM system_config
|
||||
WHERE config_type = #{configType}
|
||||
ORDER BY config_key ASC
|
||||
</select>
|
||||
|
||||
<select id="findAll" resultMap="SystemConfigMap">
|
||||
SELECT id, config_key, config_value, config_type, description, is_system, created_at, updated_at
|
||||
FROM system_config
|
||||
ORDER BY config_key ASC
|
||||
LIMIT #{size} OFFSET #{offset}
|
||||
</select>
|
||||
|
||||
<select id="countAll" resultType="long">
|
||||
SELECT COUNT(1) FROM system_config
|
||||
</select>
|
||||
|
||||
<insert id="insert" parameterType="com.gameplatform.server.model.entity.admin.SystemConfig" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO system_config (config_key, config_value, config_type, description, is_system)
|
||||
VALUES (#{configKey}, #{configValue}, #{configType}, #{description}, #{isSystem})
|
||||
</insert>
|
||||
|
||||
<update id="update" parameterType="com.gameplatform.server.model.entity.admin.SystemConfig">
|
||||
UPDATE system_config
|
||||
SET config_value = #{configValue},
|
||||
config_type = #{configType},
|
||||
description = #{description},
|
||||
is_system = #{isSystem}
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<delete id="deleteById" parameterType="long">
|
||||
DELETE FROM system_config WHERE id = #{id}
|
||||
</delete>
|
||||
|
||||
<delete id="deleteByKey" parameterType="string">
|
||||
DELETE FROM system_config WHERE config_key = #{configKey}
|
||||
</delete>
|
||||
</mapper>
|
||||
@@ -6,26 +6,24 @@
|
||||
<result property="agentId" column="agent_id" />
|
||||
<result property="quantity" column="quantity" />
|
||||
<result property="times" column="times" />
|
||||
<result property="batchSize" column="batch_size" />
|
||||
<result property="deductPoints" column="deduct_points" />
|
||||
<result property="operatorId" column="operator_id" />
|
||||
<result property="createdAt" column="created_at" />
|
||||
</resultMap>
|
||||
|
||||
<select id="findById" parameterType="long" resultMap="LinkBatchMap">
|
||||
SELECT id, agent_id, quantity, times, batch_size, deduct_points, operator_id, created_at
|
||||
SELECT id, agent_id, quantity, times, operator_id, created_at
|
||||
FROM link_batch
|
||||
WHERE id = #{id}
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<insert id="insert" parameterType="com.gameplatform.server.model.entity.agent.LinkBatch" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO link_batch (agent_id, quantity, times, batch_size, deduct_points, operator_id)
|
||||
VALUES (#{agentId}, #{quantity}, #{times}, #{batchSize}, #{deductPoints}, #{operatorId})
|
||||
INSERT INTO link_batch (agent_id, quantity, times, operator_id)
|
||||
VALUES (#{agentId}, #{quantity}, #{times}, #{operatorId})
|
||||
</insert>
|
||||
|
||||
<select id="findByAgentId" resultMap="LinkBatchMap">
|
||||
SELECT id, agent_id, quantity, times, batch_size, deduct_points, operator_id, created_at
|
||||
SELECT id, agent_id, quantity, times, operator_id, created_at
|
||||
FROM link_batch
|
||||
WHERE agent_id = #{agentId}
|
||||
ORDER BY created_at DESC
|
||||
@@ -37,7 +35,7 @@
|
||||
</select>
|
||||
|
||||
<select id="findAll" resultMap="LinkBatchMap">
|
||||
SELECT id, agent_id, quantity, times, batch_size, deduct_points, operator_id, created_at
|
||||
SELECT id, agent_id, quantity, times, operator_id, created_at
|
||||
FROM link_batch
|
||||
ORDER BY created_at DESC
|
||||
LIMIT #{size} OFFSET #{offset}
|
||||
|
||||
Reference in New Issue
Block a user