Files
game_server/docs/前端链接访问示例.md

12 KiB
Raw Permalink Blame History

前端链接访问示例

概述

当用户访问链接页面时(如 https://你的域名/ABC12345),前端需要自动请求后端获取链接的详细信息,并根据状态显示相应的内容。

接口说明

1. 获取链接状态(主要接口)

推荐格式(路径参数):

GET /api/link/{code}/status

兼容格式(查询参数,兼容旧版):

GET /api/link/status?code={code}
GET /api/link/status?codeNo={codeNo}
GET /api/link/status?linkId={linkId}

💡 推荐使用路径参数格式,因为复制粘贴时不容易丢失参数,更符合 RESTful 规范。查询参数格式保留用于兼容已生成的旧链接。

响应示例:

{
    "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

响应: truefalse

3. 检查链接是否有效

GET /api/link/{codeNo}/valid

响应: truefalse

前端实现示例

React 组件示例

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 组件示例

<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

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

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. 实时更新:可以定时刷新状态,显示剩余时间等

接口格式说明

系统同时支持两种访问格式,保证新旧链接都能正常使用:

方式一:路径参数格式(推荐

GET /api/link/{code}/status

示例:

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}

示例:

fetch('/api/link/status?code=ABC12345')
fetch('/api/link/status?codeNo=ABC12345')
fetch('/api/link/status?linkId=123')

说明:

  • 保留此格式用于兼容已生成的旧链接
  • 支持 codecodeNolinkId 三种参数名
  • linkIdcode/codeNo 至少提供一个即可

兼容性保证

  • 两种格式返回完全相同的数据结构
  • 旧链接继续有效,无需修改
  • 新生成的链接推荐使用路径参数格式

注意事项

  1. 错误处理:处理链接不存在、已过期等情况
  2. 加载状态:显示加载中的状态,提升用户体验
  3. 响应式设计:确保在不同设备上都能正常显示
  4. 缓存策略:可以适当缓存链接状态,减少请求次数
  5. 实时更新:对于进行中的任务,可以定时刷新状态