Files
game_server/GAME_INTERFACE_COMPLETED_TIME_UPDATE.md

9.2 KiB
Raw Permalink Blame History

Game Interface 接口新增完成时间

修改完成

/api/link/{codeNo}/game-interface 接口响应中新增了 completedAt(完成时间)和 status(任务状态)字段。

📦 修改的文件

  1. GameInterfaceResponse.java - 响应DTO

    • 新增 status 字段(任务状态)
    • 新增 completedAt 字段(完成时间)
  2. 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

特点:

  • 仅当 statusCOMPLETED 时有值
  • 使用 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
状态: 已完成