feat: 更新公告和链接状态接口,增强参数校验,支持跳转链接最大长度为5000字符,添加异步保存完成图片功能,优化接口文档和数据库结构

This commit is contained in:
yahaozhang
2025-11-03 20:56:34 +08:00
parent f43320138a
commit cadf8d98cb
40 changed files with 3148 additions and 17 deletions

View File

@@ -48,11 +48,12 @@ CREATE TABLE `announcement` (
`title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`enabled` tinyint(1) NOT NULL DEFAULT 1,
`jump_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
`jump_url` varchar(5000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
`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),
`belong_id` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = DYNAMIC;
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Table structure for link_batch

View File

@@ -184,6 +184,22 @@ curl -X PUT "http://localhost:8080/api/admin/announcement/1/enabled?enabled=fals
}
```
**400 Bad Request** - 字段长度超限:
```json
{
"timestamp": "2023-12-01T10:00:00.000+00:00",
"status": 400,
"error": "Bad Request",
"message": "Validation failed",
"errors": [
{
"field": "jumpUrl",
"message": "跳转链接长度不能超过5000个字符"
}
]
}
```
**404 Not Found** - 公告不存在:
```json
{
@@ -200,17 +216,19 @@ curl -X PUT "http://localhost:8080/api/admin/announcement/1/enabled?enabled=fals
2. 公告标题和内容不能为空
3. `enabled` 字段默认为 `false`
4. `jumpUrl` 字段可选,用于设置点击公告后的跳转链接
5. 获取启用公告的接口最多返回10条记录
6. 所有时间字段使用 ISO 8601 格式
5. `jumpUrl` 字段最大长度为 **5000个字符**,超过此限制将返回验证错误
6. 获取启用公告的接口最多返回10条记录
7. 所有时间字段使用 ISO 8601 格式
## 数据库表结构
公告数据存储在 `announcement` 表中,包含以下字段:
- `id` - 主键,自增
- `title` - 公告标题
- `content` - 公告内容
- `enabled` - 启用状态
- `jump_url` - 跳转链接
- `created_at` - 创建时间
- `updated_at` - 更新时间
- `title` - 公告标题 (VARCHAR(100))
- `content` - 公告内容 (TEXT)
- `enabled` - 启用状态 (TINYINT(1))
- `jump_url` - 跳转链接 (VARCHAR(5000)),可选
- `belong_id` - 归属ID (INT)关联用户ID
- `created_at` - 创建时间 (DATETIME(3))
- `updated_at` - 更新时间 (DATETIME(3))

View File

@@ -6,10 +6,21 @@
## 接口说明
### 1. 获取链接状态(主要接口)
**推荐格式(路径参数):**
```
GET /api/link/{codeNo}/status
GET /api/link/{code}/status
```
**兼容格式(查询参数,兼容旧版):**
```
GET /api/link/status?code={code}
GET /api/link/status?codeNo={codeNo}
GET /api/link/status?linkId={linkId}
```
> 💡 **推荐使用路径参数格式**,因为复制粘贴时不容易丢失参数,更符合 RESTful 规范。查询参数格式保留用于兼容已生成的旧链接。
**响应示例:**
```json
{
@@ -66,6 +77,7 @@ const LinkPage = () => {
const fetchLinkStatus = async () => {
try {
setLoading(true);
// 使用路径参数格式,更不容易丢失链接信息
const response = await fetch(`/api/link/${codeNo}/status`);
if (!response.ok) {
@@ -301,6 +313,7 @@ export default {
async fetchLinkStatus() {
try {
this.loading = true;
// 使用路径参数格式,更不容易丢失链接信息
const response = await fetch(`/api/link/${this.codeNo}/status`);
if (!response.ok) {
@@ -389,10 +402,57 @@ export default router;
## 使用流程
1. **用户访问链接**`https://你的域名/ABC12345`
2. **前端自动请求**:调用 `/api/link/ABC12345/status` 获取链接信息
2. **前端自动请求**:调用 `/api/link/ABC12345/status` 获取链接信息(使用路径参数,更不容易丢失)
3. **显示相应内容**:根据链接状态显示不同的界面
4. **实时更新**:可以定时刷新状态,显示剩余时间等
## 接口格式说明
系统同时支持两种访问格式,保证新旧链接都能正常使用:
### 方式一:路径参数格式(推荐 ⭐)
```
GET /api/link/{code}/status
```
**示例:**
```javascript
fetch('/api/link/ABC12345/status')
```
**优势:**
- ✅ 复制粘贴时不会丢失参数
- ✅ 符合 RESTful 设计规范
- ✅ URL 结构更清晰
- ✅ 浏览器地址栏直接可见完整路径
### 方式二:查询参数格式(兼容旧版)
```
GET /api/link/status?code={code}
GET /api/link/status?codeNo={codeNo}
GET /api/link/status?linkId={linkId}
```
**示例:**
```javascript
fetch('/api/link/status?code=ABC12345')
fetch('/api/link/status?codeNo=ABC12345')
fetch('/api/link/status?linkId=123')
```
**说明:**
- 保留此格式用于兼容已生成的旧链接
- 支持 `code``codeNo``linkId` 三种参数名
- `linkId``code/codeNo` 至少提供一个即可
### 兼容性保证
- ✅ 两种格式返回完全相同的数据结构
- ✅ 旧链接继续有效,无需修改
- ✅ 新生成的链接推荐使用路径参数格式
## 注意事项
1. **错误处理**:处理链接不存在、已过期等情况

View File

@@ -0,0 +1,384 @@
# 完成图片保存功能说明
## 📋 功能概述
当游戏任务完成时系统会自动保存4张关键截图到本地文件系统并保留24小时。这些图片可以作为任务完成的证明和记录。
## 🎯 保存的图片
任务完成时会保存以下4张图片
1. **首次主页.png** (homepage)
2. **首次赏金.png** (first-reward)
3. **中途赏金.png** (mid-reward)
4. **结束赏金.png** (end-reward)
## 🔧 技术实现
### 1. 核心服务组件
#### **CompletionImageService**
- 负责从脚本端下载图片并保存到本地文件系统
- 并发下载4张图片提高效率
- 提供图片访问和清理功能
#### **GameCompletionDetectionService**
- 在任务完成时触发图片保存
- 异步执行,不阻塞主流程
- 保存成功后更新数据库记录
#### **CompletionImageController**
- 提供HTTP接口访问已保存的图片
- 支持单张图片访问和批量URL获取
#### **CompletionImageCleanupTask**
- 定时清理任务(每小时执行)
- 自动删除超过24小时的图片文件夹
### 2. 文件存储结构
```
completion-images/
├── 20251103/ # 日期文件夹yyyyMMdd
│ ├── ABC123XYZ/ # 链接编号codeNo
│ │ ├── homepage.png
│ │ ├── first-reward.png
│ │ ├── mid-reward.png
│ │ └── end-reward.png
│ └── DEF456UVW/
│ └── ...
└── 20251104/
└── ...
```
### 3. 数据库字段
`link_task` 表中新增两个字段:
| 字段名 | 类型 | 说明 |
|--------|------|------|
| `completion_images` | TEXT | JSON格式存储图片信息 |
| `completion_images_saved_at` | DATETIME | 图片保存时间 |
**completion_images JSON 示例:**
```json
{
"saveTime": "2025-11-03T10:30:45",
"codeNo": "ABC123XYZ",
"machineId": "f1",
"dateFolder": "20251103",
"images": {
"homepage": "20251103/ABC123XYZ/homepage.png",
"first-reward": "20251103/ABC123XYZ/first-reward.png",
"mid-reward": "20251103/ABC123XYZ/mid-reward.png",
"end-reward": "20251103/ABC123XYZ/end-reward.png"
},
"totalCount": 4
}
```
## 📡 API 接口
### 1. 获取单张图片
**首次主页图片**
```http
GET /api/link/completion/{codeNo}/homepage.png
```
**首次赏金图片**
```http
GET /api/link/completion/{codeNo}/first-reward.png
```
**中途赏金图片**
```http
GET /api/link/completion/{codeNo}/mid-reward.png
```
**结束赏金图片**
```http
GET /api/link/completion/{codeNo}/end-reward.png
```
**响应示例:**
- 成功返回图片数据image/png
- 失败404 Not Found
### 2. 获取所有图片URL列表
```http
GET /api/link/completion/{codeNo}/images
```
**响应示例:**
```json
{
"homepage": "https://uzi1.cn/api/link/completion/ABC123XYZ/homepage.png",
"firstReward": "https://uzi1.cn/api/link/completion/ABC123XYZ/first-reward.png",
"midReward": "https://uzi1.cn/api/link/completion/ABC123XYZ/mid-reward.png",
"endReward": "https://uzi1.cn/api/link/completion/ABC123XYZ/end-reward.png"
}
```
## ⚙️ 配置说明
`application.yml` 中配置:
```yaml
# 完成图片存储配置
completion:
image:
storage:
path: "./completion-images" # 图片存储路径
retention-hours: 24 # 图片保留时间(小时)
```
### 配置项说明
| 配置项 | 说明 | 默认值 |
|--------|------|--------|
| `path` | 图片存储路径,支持相对路径和绝对路径 | `./completion-images` |
| `retention-hours` | 图片保留时间(小时) | 24 |
### 生产环境建议
**推荐配置绝对路径:**
```yaml
completion:
image:
storage:
path: "/data/gameplatform/completion-images"
```
**磁盘空间预估:**
- 单个任务4张图片约 800KB - 2MB
- 每天100个任务约 80MB - 200MB
- 24小时滚动约 80MB - 200MB
## 🔄 执行流程
### 1. 图片保存流程
```mermaid
sequenceDiagram
participant Detection as 完成检测服务
participant ImageService as 图片服务
participant ScriptClient as 脚本客户端
participant FileSystem as 文件系统
participant Database as 数据库
Detection->>Detection: 检测到任务完成
Detection->>ImageService: 异步保存图片
ImageService->>ScriptClient: 并发下载4张图片
ScriptClient-->>ImageService: 返回图片数据
ImageService->>FileSystem: 保存到本地
FileSystem-->>ImageService: 保存成功
ImageService->>Database: 更新图片信息
Database-->>ImageService: 更新完成
```
### 2. 清理流程
```
每小时第5分钟执行
计算过期时间(当前时间 - 24小时
查找过期的日期文件夹
递归删除过期文件夹
记录清理日志
```
## 🔒 安全配置
`SecurityConfig.java` 中已配置公开访问权限:
```java
.pathMatchers(HttpMethod.GET, "/api/link/completion/**").permitAll()
.pathMatchers(HttpMethod.HEAD, "/api/link/completion/**").permitAll()
```
**说明:**
- 完成图片可以公开访问(无需认证)
- 图片URL包含链接编号具有一定的私密性
- 24小时后自动删除减少泄露风险
## 📊 监控和日志
### 关键日志
**图片保存成功:**
```
INFO - 完成图片保存成功: codeNo=ABC123XYZ, imageInfo={...}
```
**图片保存失败:**
```
ERROR - 完成图片保存失败: codeNo=ABC123XYZ, error=...
```
**定时清理:**
```
INFO - === 完成图片清理任务完成:删除文件夹数=5, 耗时=234ms ===
```
### 监控指标
- 图片保存成功率
- 图片下载耗时
- 磁盘空间使用
- 清理任务执行情况
## 🚀 部署步骤
### 1. 数据库迁移
执行迁移脚本:
```bash
# 文件位置: src/main/resources/db/migration/V20251103__add_completion_images_saved_at.sql
mysql -u username -p database_name < V20251103__add_completion_images_saved_at.sql
```
或者使用 Flyway 自动迁移(推荐)。
### 2. 创建存储目录
```bash
# 创建图片存储目录
mkdir -p /data/gameplatform/completion-images
# 设置权限
chown -R app_user:app_group /data/gameplatform/completion-images
chmod 755 /data/gameplatform/completion-images
```
### 3. 更新配置文件
修改 `application.yml`
```yaml
completion:
image:
storage:
path: "/data/gameplatform/completion-images"
```
### 4. 重启应用
```bash
systemctl restart gameplatform-server
```
### 5. 验证功能
查看日志确认功能正常:
```bash
tail -f logs/server.log | grep "完成图片"
```
## 🔍 故障排查
### 问题1图片保存失败
**可能原因:**
1. 存储目录不存在或无写权限
2. 脚本端图片不存在
3. 网络连接问题
**排查步骤:**
```bash
# 1. 检查目录权限
ls -la /data/gameplatform/completion-images
# 2. 检查磁盘空间
df -h
# 3. 查看详细日志
grep "完成图片保存失败" logs/server.log
```
### 问题2图片无法访问
**可能原因:**
1. 图片已被清理超过24小时
2. 图片保存时失败
3. 文件路径错误
**排查步骤:**
```bash
# 查找特定任务的图片
find /data/gameplatform/completion-images -name "*ABC123XYZ*"
# 检查数据库记录
SELECT code_no, completion_images, completion_images_saved_at
FROM link_task
WHERE code_no = 'ABC123XYZ';
```
### 问题3磁盘空间不足
**解决方案:**
1. 调整保留时间减少到12小时
2. 增加磁盘空间
3. 配置日志轮转和压缩
## 📝 注意事项
1. **异步执行**:图片保存是异步的,不会阻塞任务完成流程
2. **容错机制**:单张图片下载失败不影响其他图片
3. **自动清理**超过24小时的图片会自动删除无需手动维护
4. **并发安全**:使用日期文件夹隔离,避免并发冲突
5. **存储规划**:建议预留至少 500MB 磁盘空间
## 🎓 使用示例
### 前端获取完成图片
```javascript
// 获取所有图片URL
fetch('/api/link/completion/ABC123XYZ/images')
.then(res => res.json())
.then(urls => {
console.log('首次主页:', urls.homepage);
console.log('首次赏金:', urls.firstReward);
console.log('中途赏金:', urls.midReward);
console.log('结束赏金:', urls.endReward);
});
// 直接显示图片
<img src="/api/link/completion/ABC123XYZ/homepage.png" alt="首次主页" />
```
### 查询数据库中的图片信息
```sql
-- 查询最近完成且有图片的任务
SELECT
code_no,
status,
completed_points,
completion_images_saved_at,
JSON_EXTRACT(completion_images, '$.totalCount') as image_count
FROM link_task
WHERE status = 'COMPLETED'
AND completion_images IS NOT NULL
AND completion_images_saved_at > DATE_SUB(NOW(), INTERVAL 24 HOUR)
ORDER BY completion_images_saved_at DESC
LIMIT 10;
```
## 🔮 未来优化方向
1. **CDN 集成**:将图片上传到 CDN提高访问速度
2. **压缩优化**:自动压缩图片,减少存储空间
3. **备份机制**:定期备份重要图片到对象存储
4. **统计分析**:添加图片访问统计和热度分析
5. **批量下载**:支持批量导出完成图片
---
**最后更新时间:** 2025-11-03
**版本:** v1.0.0

View File

@@ -0,0 +1,252 @@
# 链接状态接口兼容性说明
## 概述
为了解决链接复制粘贴时参数丢失的问题,同时保证向后兼容,系统现在支持两种访问格式:
1. **路径参数格式**(推荐):`/api/link/{code}/status`
2. **查询参数格式**(兼容旧版):`/api/link/status?code={code}`
## 支持的访问方式
### 方式一:路径参数(推荐 ⭐)
**接口:** `GET /api/link/{code}/status`
**示例:**
```bash
curl http://localhost:8080/api/link/ABC12345/status
```
```javascript
// JavaScript
fetch('/api/link/ABC12345/status')
```
**优势:**
- ✅ 复制粘贴时不会丢失参数
- ✅ 符合 RESTful 设计规范
- ✅ URL 结构更清晰
- ✅ 浏览器地址栏直接可见完整路径
---
### 方式二:查询参数(兼容旧版)
**接口:** `GET /api/link/status`
**支持的参数:**
- `code` - 链接编号(推荐)
- `codeNo` - 链接编号(别名)
- `linkId` - 链接数据库ID
**示例:**
```bash
# 使用 code 参数
curl "http://localhost:8080/api/link/status?code=ABC12345"
# 使用 codeNo 参数
curl "http://localhost:8080/api/link/status?codeNo=ABC12345"
# 使用 linkId 参数
curl "http://localhost:8080/api/link/status?linkId=123"
```
```javascript
// JavaScript
fetch('/api/link/status?code=ABC12345')
fetch('/api/link/status?codeNo=ABC12345')
fetch('/api/link/status?linkId=123')
```
**说明:**
- `linkId``code/codeNo` 至少提供一个即可
- 如果同时提供,优先使用 `codeNo`,其次是 `code`
---
## 响应格式
两种方式返回完全相同的数据结构:
```json
{
"status": "NEW",
"machineId": null
}
```
**status 可能的值:**
- `NEW` - 新建
- `USING` - 使用中(前端会显示为 NEW
- `LOGGED_IN` - 已登录
- `COMPLETED` - 已完成
- `REFUNDED` - 已退款
- `EXPIRED` - 已过期
---
## 测试用例
### 测试 1路径参数方式
```bash
# 假设有一个 codeNo 为 ABC12345 的链接
curl http://localhost:8080/api/link/ABC12345/status
```
**期望结果:** 返回链接状态信息
---
### 测试 2查询参数方式code
```bash
curl "http://localhost:8080/api/link/status?code=ABC12345"
```
**期望结果:** 返回与测试1相同的结果
---
### 测试 3查询参数方式codeNo
```bash
curl "http://localhost:8080/api/link/status?codeNo=ABC12345"
```
**期望结果:** 返回与测试1相同的结果
---
### 测试 4查询参数方式linkId
```bash
# 假设链接的数据库 ID 为 123
curl "http://localhost:8080/api/link/status?linkId=123"
```
**期望结果:** 返回链接状态信息
---
### 测试 5错误处理
```bash
# 不存在的链接
curl http://localhost:8080/api/link/INVALID/status
# 空参数
curl "http://localhost:8080/api/link/status?code="
# 缺少参数
curl "http://localhost:8080/api/link/status"
```
**期望结果:** 返回错误信息
---
## 迁移建议
### 对于新开发的前端
**推荐使用路径参数格式:**
```javascript
const codeNo = 'ABC12345';
const response = await fetch(`/api/link/${codeNo}/status`);
```
### 对于现有系统
**无需修改,查询参数格式继续有效:**
```javascript
// 继续使用旧格式
const response = await fetch(`/api/link/status?code=${codeNo}`);
```
### 渐进式迁移
可以逐步将旧代码迁移到新格式:
```javascript
// 旧代码
async function getLinkStatus_Old(codeNo) {
return fetch(`/api/link/status?code=${codeNo}`);
}
// 新代码(推荐)
async function getLinkStatus_New(codeNo) {
return fetch(`/api/link/${codeNo}/status`);
}
```
---
## 后端实现说明
### Controller 方法
系统提供了两个独立的 Controller 方法:
1. **getUserLinkStatusByPath** - 处理路径参数请求
- 路由:`GET /api/link/{code}/status`
- 参数:`@PathVariable String code`
2. **getUserLinkStatusByQuery** - 处理查询参数请求
- 路由:`GET /api/link/status`
- 参数:`@RequestParam Long linkId`, `@RequestParam String codeNo`, `@RequestParam String code`
### 日志区分
两个方法使用不同的日志标识,便于问题排查:
- 路径参数:`=== 用户端获取链接状态(路径参数) ===`
- 查询参数:`=== 用户端获取链接状态(查询参数,兼容模式) ===`
---
## 兼容性保证
- ✅ 两种格式返回完全相同的数据结构
- ✅ 旧链接继续有效,无需修改
- ✅ 新生成的链接推荐使用路径参数格式
- ✅ 系统会长期维护两种格式的支持
- ✅ 不会影响现有功能和性能
---
## 常见问题
### Q1: 为什么推荐使用路径参数格式?
**A:** 路径参数格式的优势:
1. 复制粘贴 URL 时不会丢失参数(查询参数容易在 `?` 后被截断)
2. 符合 RESTful API 设计规范
3. URL 更清晰,更容易阅读和理解
4. 浏览器地址栏显示更完整
### Q2: 旧链接会失效吗?
**A:** 不会。查询参数格式会长期保持支持,确保兼容性。
### Q3: 能否混合使用两种格式?
**A:** 可以。同一个应用中可以同时使用两种格式,系统都会正确处理。
### Q4: 性能上有区别吗?
**A:** 没有。两种格式调用相同的底层服务方法,性能完全一致。
### Q5: 如何在 Swagger/OpenAPI 中查看?
**A:** Swagger UI 会显示两个独立的接口:
- `GET /api/link/{code}/status` - 推荐格式
- `GET /api/link/status` - 兼容格式
---
## 更新日志
- **2025-10-21**:添加路径参数格式支持,同时保留查询参数格式兼容性
- 旧的查询参数格式标记为"兼容模式",推荐新项目使用路径参数格式