9.2 KiB
9.2 KiB
Game Interface 接口新增完成时间
✅ 修改完成
在 /api/link/{codeNo}/game-interface 接口响应中新增了 completedAt(完成时间)和 status(任务状态)字段。
📦 修改的文件
-
GameInterfaceResponse.java - 响应DTO
- 新增
status字段(任务状态) - 新增
completedAt字段(完成时间)
- 新增
-
QrProxyController.java - 控制器
- 设置
status字段 - 仅当任务完成时设置
completedAt字段
- 设置
📊 接口响应示例
任务进行中(NEW / USING / LOGGED_IN)
{
"codeNo": "MYNM5JHA",
"totalPoints": 1000,
"quantity": 100,
"times": 10,
"region": "Q",
"regionDesc": "QQ区",
"machineId": "rr3",
"completedPoints": null,
"status": "LOGGED_IN",
"completedAt": null,
"qrCodeUrl": "https://uzi1.cn/api/link/image/MYNM5JHA/qr.png",
"homepageUrl": "https://uzi1.cn/api/link/image/MYNM5JHA/homepage.png",
"firstRewardUrl": "https://uzi1.cn/api/link/image/MYNM5JHA/first-reward.png",
"midRewardUrl": "https://uzi1.cn/api/link/image/MYNM5JHA/mid-reward.png",
"endRewardUrl": "https://uzi1.cn/api/link/image/MYNM5JHA/end-reward.png",
"progressDisplayFormat": "percent"
}
任务已完成(COMPLETED)✨
{
"codeNo": "MYNM5JHA",
"totalPoints": 1000,
"quantity": 100,
"times": 10,
"region": "Q",
"regionDesc": "QQ区",
"machineId": "rr3",
"completedPoints": 1000,
"status": "COMPLETED",
"completedAt": 1730644245,
"qrCodeUrl": "https://uzi1.cn/api/link/image/MYNM5JHA/qr.png",
"homepageUrl": "https://uzi1.cn/api/link/completion/MYNM5JHA/homepage.png",
"firstRewardUrl": "https://uzi1.cn/api/link/completion/MYNM5JHA/first-reward.png",
"midRewardUrl": "https://uzi1.cn/api/link/completion/MYNM5JHA/mid-reward.png",
"endRewardUrl": "https://uzi1.cn/api/link/completion/MYNM5JHA/end-reward.png",
"progressDisplayFormat": "percent"
}
🎯 新增字段说明
status(任务状态)
| 字段名 | 类型 | 说明 | 示例 |
|---|---|---|---|
status |
String | 任务当前状态 | "COMPLETED" |
可能的值:
NEW: 新建USING: 使用中LOGGED_IN: 已登录COMPLETED: 已完成 ✨REFUNDED: 已退款EXPIRED: 已过期
completedAt(完成时间戳)
| 字段名 | 类型 | 说明 | 示例 |
|---|---|---|---|
completedAt |
Long | 任务完成时间戳(秒级) | 1730644245 |
特点:
- ✅ 仅当
status为COMPLETED时有值 - ✅ 使用 Unix 时间戳(秒级,10位数字)
- ✅ 其他状态下为
null - ✅ 可直接用于前端时间处理
🔧 实现逻辑
// 设置状态
response.setStatus(linkTask.getStatus());
// 设置完成时间戳-秒级(仅当任务完成时)
if ("COMPLETED".equals(linkTask.getStatus()) && linkTask.getUpdatedAt() != null) {
// 转换为秒级时间戳
long epochSecond = linkTask.getUpdatedAt()
.atZone(java.time.ZoneId.systemDefault())
.toEpochSecond();
response.setCompletedAt(epochSecond);
}
💡 前端使用示例
JavaScript/TypeScript
// 获取游戏界面数据
fetch(`/api/link/${codeNo}/game-interface`)
.then(res => res.json())
.then(data => {
console.log('任务状态:', data.status);
// 判断任务是否完成
if (data.status === 'COMPLETED' && data.completedAt) {
console.log('完成时间戳:', data.completedAt);
// 将秒级时间戳转换为毫秒(JavaScript Date需要毫秒)
const completedTime = new Date(data.completedAt * 1000);
console.log('格式化时间:', completedTime.toLocaleString('zh-CN'));
// 输出: 2025/11/3 20:30:45
// 计算完成了多久
const now = new Date();
const diffMs = now - completedTime;
const diffMins = Math.floor(diffMs / 60000);
console.log(`${diffMins} 分钟前完成`);
// 显示完成图片
document.getElementById('homepage').src = data.homepageUrl;
document.getElementById('firstReward').src = data.firstRewardUrl;
document.getElementById('midReward').src = data.midRewardUrl;
document.getElementById('endReward').src = data.endRewardUrl;
} else {
console.log('任务进行中...');
}
});
Vue/React 组件示例
// Vue 3 Composition API
import { ref, computed } from 'vue';
const gameData = ref(null);
// 格式化完成时间
const formattedCompletedTime = computed(() => {
if (gameData.value?.completedAt) {
// 秒级时间戳转毫秒
const date = new Date(gameData.value.completedAt * 1000);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
}
return null;
});
// 计算完成多久
const completedAgo = computed(() => {
if (gameData.value?.completedAt) {
// 秒级时间戳转毫秒
const completedTime = new Date(gameData.value.completedAt * 1000);
const now = new Date();
const diffMs = now - completedTime;
const diffMins = Math.floor(diffMs / 60000);
if (diffMins < 1) return '刚刚完成';
if (diffMins < 60) return `${diffMins} 分钟前`;
const diffHours = Math.floor(diffMins / 60);
if (diffHours < 24) return `${diffHours} 小时前`;
const diffDays = Math.floor(diffHours / 24);
return `${diffDays} 天前`;
}
return null;
});
📱 UI 显示建议
完成状态显示
<!-- 任务完成提示 -->
<div v-if="gameData.status === 'COMPLETED'" class="completion-banner">
<span class="status-badge">✅ 已完成</span>
<span class="completion-time">
完成时间: {{ formattedCompletedTime }}
</span>
<span class="time-ago">
({{ completedAgo }})
</span>
</div>
<!-- 完成点数 -->
<div class="points-display">
<span>完成点数: {{ gameData.completedPoints }} / {{ gameData.totalPoints }}</span>
<progress :value="gameData.completedPoints" :max="gameData.totalPoints"></progress>
</div>
不同状态的UI提示
const statusDisplay = {
'NEW': { text: '等待开始', color: 'gray', icon: '⏳' },
'USING': { text: '使用中', color: 'blue', icon: '🎮' },
'LOGGED_IN': { text: '已登录', color: 'green', icon: '✓' },
'COMPLETED': { text: '已完成', color: 'success', icon: '✅' },
'REFUNDED': { text: '已退款', color: 'warning', icon: '↩️' },
'EXPIRED': { text: '已过期', color: 'danger', icon: '⏰' }
};
const currentStatus = statusDisplay[gameData.status];
🎨 样式建议
.completion-banner {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 16px;
border-radius: 8px;
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 12px;
}
.status-badge {
background: rgba(255, 255, 255, 0.2);
padding: 4px 12px;
border-radius: 20px;
font-weight: 600;
}
.completion-time {
flex: 1;
font-size: 14px;
}
.time-ago {
opacity: 0.8;
font-size: 12px;
}
.points-display {
background: #f8f9fa;
padding: 16px;
border-radius: 8px;
margin-bottom: 16px;
}
.points-display progress {
width: 100%;
height: 8px;
margin-top: 8px;
}
📊 字段对比
| 场景 | status | completedAt | completedPoints | 图片URL前缀 |
|---|---|---|---|---|
| 新建任务 | NEW |
null |
null |
/api/link/image/ |
| 使用中 | USING |
null |
null |
/api/link/image/ |
| 已登录 | LOGGED_IN |
null |
可能有值 | /api/link/image/ |
| 已完成 ✨ | COMPLETED |
有值 | 有值 | /api/link/completion/ |
| 已退款 | REFUNDED |
null |
可能有值 | /api/link/image/ |
| 已过期 | EXPIRED |
null |
null |
/api/link/image/ |
⚙️ 时间戳格式说明
Unix 时间戳(秒级)
1730644245
│
└─ 10位数字,表示自1970-01-01 00:00:00 UTC以来的秒数
示例值:
1730644245= 2025-11-03 20:30:45 (北京时间)
解析示例
// JavaScript - 秒级时间戳需要乘以1000转换为毫秒
const completedAt = 1730644245;
const date = new Date(completedAt * 1000); // 注意:乘以1000
console.log(date.toLocaleString('zh-CN'));
// 输出: 2025/11/3 20:30:45
console.log(date.toLocaleDateString('zh-CN'));
// 输出: 2025/11/3
console.log(date.toLocaleTimeString('zh-CN'));
// 输出: 20:30:45
// 或者使用时间库(如 dayjs)
import dayjs from 'dayjs';
console.log(dayjs.unix(completedAt).format('YYYY-MM-DD HH:mm:ss'));
// 输出: 2025-11-03 20:30:45
🧪 测试验证
1. 任务进行中
curl "http://localhost:18080/api/link/MYNM5JHA/game-interface" | jq .
# 预期结果
{
"status": "LOGGED_IN",
"completedAt": null,
...
}
2. 任务完成后
curl "http://localhost:18080/api/link/MYNM5JHA/game-interface" | jq .
# 预期结果
{
"status": "COMPLETED",
"completedAt": 1730644245,
"completedPoints": 1000,
...
}
📖 相关文档
- 完成图片保存功能:
COMPLETION_IMAGE_FEATURE_SUMMARY.md - 图片URL优化:
GAME_INTERFACE_IMAGE_UPDATE.md - 所有完成触发点:
COMPLETION_IMAGE_ALL_TRIGGERS.md
更新时间: 2025-11-03
版本: v1.3.0
状态: ✅ 已完成