新增系统配置表及默认配置,更新链接生成请求DTO以支持链接数量参数,重构链接生成服务逻辑,添加链接状态查询和有效性检查接口,优化日志记录。
This commit is contained in:
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. **实时更新**:对于进行中的任务,可以定时刷新状态
|
||||
|
||||
Reference in New Issue
Block a user