This commit is contained in:
581
doc/评委邀请码功能实现指南.md
Normal file
581
doc/评委邀请码功能实现指南.md
Normal file
@@ -0,0 +1,581 @@
|
||||
# 评委邀请码功能实现指南
|
||||
|
||||
> **实施日期**: 2025-12-12
|
||||
> **页面路径**: `src/views/martial/judgeInvite/index.vue`
|
||||
> **与赛事绑定**: ✅ 已通过 `competitionId` 实现
|
||||
|
||||
---
|
||||
|
||||
## 📋 实现方案
|
||||
|
||||
### 一、需求分析
|
||||
|
||||
根据文档,评委邀请码功能需要实现:
|
||||
|
||||
1. **单个生成**:为单个评委生成6位邀请码
|
||||
2. **批量生成**:为多个评委批量生成邀请码
|
||||
3. **重新生成**:已有邀请码时可重新生成
|
||||
4. **复制功能**:点击邀请码可复制
|
||||
5. **赛事绑定**:所有操作都与选中的赛事绑定
|
||||
|
||||
### 二、后端接口(已完成)
|
||||
|
||||
后端接口已在 `src/api/martial/judgeInvite.js` 中添加:
|
||||
|
||||
```javascript
|
||||
// 1. 生成邀请码
|
||||
export const generateInviteCode = (data) => {
|
||||
return request({
|
||||
url: '/api/blade-martial/judgeInvite/generate',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 2. 批量生成邀请码
|
||||
export const batchGenerateInviteCode = (data) => {
|
||||
return request({
|
||||
url: '/api/blade-martial/judgeInvite/generate/batch',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 3. 重新生成邀请码
|
||||
export const regenerateInviteCode = (inviteId) => {
|
||||
return request({
|
||||
url: `/api/blade-martial/judgeInvite/regenerate/${inviteId}`,
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
// 4. 查询评委邀请码
|
||||
export const getInviteByJudge = (competitionId, judgeId) => {
|
||||
return request({
|
||||
url: '/api/blade-martial/judgeInvite/byJudge',
|
||||
method: 'get',
|
||||
params: { competitionId, judgeId }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、前端实现步骤
|
||||
|
||||
### 步骤1:导入新增的API
|
||||
|
||||
在 `src/views/martial/judgeInvite/index.vue` 第281-292行,修改导入语句:
|
||||
|
||||
```javascript
|
||||
import {
|
||||
getJudgeInviteList,
|
||||
sendInvite,
|
||||
batchSendInvites,
|
||||
resendInvite,
|
||||
cancelInvite,
|
||||
confirmInvite,
|
||||
getInviteStatistics,
|
||||
importFromJudgePool,
|
||||
exportInvites,
|
||||
sendReminder,
|
||||
generateInviteCode, // 新增
|
||||
batchGenerateInviteCode, // 新增
|
||||
regenerateInviteCode // 新增
|
||||
} from '@/api/martial/judgeInvite'
|
||||
```
|
||||
|
||||
### 步骤2:修改邀请码列显示(第165-179行)
|
||||
|
||||
将现有的邀请码列替换为:
|
||||
|
||||
```vue
|
||||
<el-table-column prop="inviteCode" label="邀请码" width="200" align="center">
|
||||
<template #default="{ row }">
|
||||
<!-- 已有邀请码:显示邀请码 + 重新生成按钮 -->
|
||||
<div v-if="row.inviteCode" style="display: flex; align-items: center; justify-content: center; gap: 8px;">
|
||||
<el-tag
|
||||
type="warning"
|
||||
effect="dark"
|
||||
size="default"
|
||||
style="font-family: monospace; font-weight: bold; cursor: pointer;"
|
||||
@click="copyToClipboard(row.inviteCode, '邀请码')"
|
||||
title="点击复制"
|
||||
>
|
||||
{{ row.inviteCode }}
|
||||
</el-tag>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleRegenerateCode(row)"
|
||||
title="重新生成邀请码"
|
||||
>
|
||||
<el-icon><Refresh /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 未生成邀请码:显示生成按钮 -->
|
||||
<el-button
|
||||
v-else
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleGenerateCode(row)"
|
||||
>
|
||||
生成邀请码
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
```
|
||||
|
||||
### 步骤3:添加批量生成按钮(第129-131行)
|
||||
|
||||
修改工具栏的"批量邀请"按钮功能:
|
||||
|
||||
```vue
|
||||
<el-button type="success" :icon="DocumentCopy" @click="handleBatchGenerateCode">
|
||||
批量生成邀请码
|
||||
</el-button>
|
||||
```
|
||||
|
||||
### 步骤4:添加方法实现
|
||||
|
||||
在 `<script setup>` 部分,在第456行之后添加以下方法:
|
||||
|
||||
```javascript
|
||||
// ==================== 邀请码生成功能 ====================
|
||||
|
||||
/**
|
||||
* 生成单个邀请码
|
||||
*/
|
||||
const handleGenerateCode = async (row) => {
|
||||
if (!queryParams.competitionId) {
|
||||
ElMessage.warning('请先选择赛事')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await generateInviteCode({
|
||||
competitionId: queryParams.competitionId,
|
||||
judgeId: row.judgeId,
|
||||
role: row.refereeType === 1 ? 'chief_judge' : 'judge', // 根据评委类型设置角色
|
||||
venueId: row.venueId || null,
|
||||
projects: row.projects ? JSON.stringify(row.projects) : null,
|
||||
expireDays: 30
|
||||
})
|
||||
|
||||
if (res.data && res.data.inviteCode) {
|
||||
ElMessage.success(`邀请码生成成功:${res.data.inviteCode}`)
|
||||
// 自动复制到剪贴板
|
||||
copyToClipboard(res.data.inviteCode, '邀请码')
|
||||
// 刷新列表
|
||||
await fetchData()
|
||||
await loadStatistics()
|
||||
} else {
|
||||
ElMessage.error(res.msg || '生成失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('生成邀请码失败:', error)
|
||||
ElMessage.error(error.response?.data?.msg || error.message || '生成邀请码失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新生成邀请码
|
||||
*/
|
||||
const handleRegenerateCode = async (row) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'重新生成后,旧邀请码将失效。确定继续吗?',
|
||||
'重新生成邀请码',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
|
||||
loading.value = true
|
||||
const res = await regenerateInviteCode(row.id)
|
||||
|
||||
if (res.data && res.data.inviteCode) {
|
||||
ElMessage.success(`邀请码已重新生成:${res.data.inviteCode}`)
|
||||
// 自动复制到剪贴板
|
||||
copyToClipboard(res.data.inviteCode, '邀请码')
|
||||
// 刷新列表
|
||||
await fetchData()
|
||||
await loadStatistics()
|
||||
} else {
|
||||
ElMessage.error(res.msg || '重新生成失败')
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('重新生成邀请码失败:', error)
|
||||
ElMessage.error(error.response?.data?.msg || error.message || '重新生成邀请码失败')
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量生成邀请码
|
||||
*/
|
||||
const handleBatchGenerateCode = async () => {
|
||||
if (!queryParams.competitionId) {
|
||||
ElMessage.warning('请先选择赛事')
|
||||
return
|
||||
}
|
||||
|
||||
if (selection.value.length === 0) {
|
||||
ElMessage.warning('请先选择评委')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定为选中的 ${selection.value.length} 位评委批量生成邀请码吗?`,
|
||||
'批量生成邀请码',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'info'
|
||||
}
|
||||
)
|
||||
|
||||
loading.value = true
|
||||
const judgeIds = selection.value.map(item => item.judgeId)
|
||||
|
||||
const res = await batchGenerateInviteCode({
|
||||
competitionId: queryParams.competitionId,
|
||||
judgeIds: judgeIds,
|
||||
role: 'judge',
|
||||
expireDays: 30
|
||||
})
|
||||
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
ElMessage.success(`成功生成 ${res.data.length} 个邀请码`)
|
||||
// 刷新列表
|
||||
await fetchData()
|
||||
await loadStatistics()
|
||||
} else {
|
||||
ElMessage.error(res.msg || '批量生成失败')
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('批量生成邀请码失败:', error)
|
||||
ElMessage.error(error.response?.data?.msg || error.message || '批量生成邀请码失败')
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、数据流转说明
|
||||
|
||||
### 1. 赛事绑定流程
|
||||
|
||||
```
|
||||
用户操作:
|
||||
1. 选择赛事(下拉框)
|
||||
↓
|
||||
2. queryParams.competitionId 更新
|
||||
↓
|
||||
3. 触发 handleCompetitionChange
|
||||
↓
|
||||
4. 加载该赛事的评委列表
|
||||
↓
|
||||
5. 显示评委及其邀请码状态
|
||||
```
|
||||
|
||||
### 2. 生成邀请码流程
|
||||
|
||||
```
|
||||
单个生成:
|
||||
1. 点击"生成邀请码"按钮
|
||||
↓
|
||||
2. 调用 generateInviteCode API
|
||||
↓
|
||||
3. 传入:
|
||||
- competitionId: 当前选中的赛事ID
|
||||
- judgeId: 评委ID
|
||||
- role: 评委角色
|
||||
- venueId: 场地ID(可选)
|
||||
- projects: 项目列表(可选)
|
||||
- expireDays: 30天
|
||||
↓
|
||||
4. 后端生成6位随机码
|
||||
↓
|
||||
5. 返回邀请码
|
||||
↓
|
||||
6. 前端自动复制到剪贴板
|
||||
↓
|
||||
7. 刷新列表显示
|
||||
```
|
||||
|
||||
### 3. 批量生成流程
|
||||
|
||||
```
|
||||
批量生成:
|
||||
1. 选择多个评委(勾选)
|
||||
↓
|
||||
2. 点击"批量生成邀请码"按钮
|
||||
↓
|
||||
3. 确认操作
|
||||
↓
|
||||
4. 调用 batchGenerateInviteCode API
|
||||
↓
|
||||
5. 传入:
|
||||
- competitionId: 当前选中的赛事ID
|
||||
- judgeIds: 评委ID数组
|
||||
- role: 'judge'
|
||||
- expireDays: 30天
|
||||
↓
|
||||
6. 后端循环为每个评委生成邀请码
|
||||
↓
|
||||
7. 返回生成的邀请码列表
|
||||
↓
|
||||
8. 刷新列表显示
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、关键字段说明
|
||||
|
||||
### 1. 数据表字段(martial_judge_invite)
|
||||
|
||||
| 字段 | 类型 | 说明 | 示例值 |
|
||||
|------|------|------|--------|
|
||||
| `id` | bigint | 主键ID | 1001 |
|
||||
| `competition_id` | bigint | **赛事ID(绑定)** | 1 |
|
||||
| `judge_id` | bigint | 评委ID | 5 |
|
||||
| `invite_code` | varchar(50) | 邀请码 | ABC123 |
|
||||
| `role` | varchar(20) | 角色 | judge/chief_judge |
|
||||
| `venue_id` | bigint | 场地ID | 2 |
|
||||
| `projects` | varchar(500) | 项目列表 | ["太极拳","长拳"] |
|
||||
| `expire_time` | datetime | 过期时间 | 2026-01-11 |
|
||||
| `is_used` | int | 是否已使用 | 0/1 |
|
||||
| `status` | int | 状态 | 1-启用,0-禁用 |
|
||||
|
||||
### 2. 前端查询参数
|
||||
|
||||
```javascript
|
||||
queryParams = {
|
||||
current: 1, // 当前页
|
||||
size: 10, // 每页条数
|
||||
competitionId: '', // ⭐ 赛事ID(核心绑定字段)
|
||||
judgeName: '', // 评委姓名
|
||||
judgeLevel: '', // 评委等级
|
||||
inviteStatus: '' // 邀请状态
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、测试验证
|
||||
|
||||
### 测试场景1:单个生成邀请码
|
||||
|
||||
**前置条件**:
|
||||
- 已选择赛事
|
||||
- 列表中有评委记录
|
||||
- 评委未生成邀请码
|
||||
|
||||
**操作步骤**:
|
||||
1. 在评委列表中找到一个未生成邀请码的评委
|
||||
2. 点击"生成邀请码"按钮
|
||||
3. 等待生成完成
|
||||
|
||||
**预期结果**:
|
||||
- ✅ 显示成功提示:`邀请码生成成功:ABC123`
|
||||
- ✅ 邀请码自动复制到剪贴板
|
||||
- ✅ 列表刷新,显示生成的邀请码
|
||||
- ✅ 邀请码列变为:邀请码 + 重新生成按钮
|
||||
|
||||
### 测试场景2:重新生成邀请码
|
||||
|
||||
**前置条件**:
|
||||
- 已选择赛事
|
||||
- 评委已有邀请码
|
||||
|
||||
**操作步骤**:
|
||||
1. 点击邀请码旁边的重新生成按钮
|
||||
2. 确认操作
|
||||
3. 等待生成完成
|
||||
|
||||
**预期结果**:
|
||||
- ✅ 显示警告提示框
|
||||
- ✅ 确认后生成新邀请码
|
||||
- ✅ 旧邀请码失效
|
||||
- ✅ 新邀请码自动复制到剪贴板
|
||||
|
||||
### 测试场景3:批量生成邀请码
|
||||
|
||||
**前置条件**:
|
||||
- 已选择赛事
|
||||
- 列表中有多个未生成邀请码的评委
|
||||
|
||||
**操作步骤**:
|
||||
1. 勾选多个评委(如3个)
|
||||
2. 点击"批量生成邀请码"按钮
|
||||
3. 确认操作
|
||||
4. 等待生成完成
|
||||
|
||||
**预期结果**:
|
||||
- ✅ 显示确认提示框:`确定为选中的 3 位评委批量生成邀请码吗?`
|
||||
- ✅ 确认后批量生成
|
||||
- ✅ 显示成功提示:`成功生成 3 个邀请码`
|
||||
- ✅ 列表刷新,所有评委都显示邀请码
|
||||
|
||||
### 测试场景4:复制邀请码
|
||||
|
||||
**前置条件**:
|
||||
- 评委已有邀请码
|
||||
|
||||
**操作步骤**:
|
||||
1. 点击邀请码标签
|
||||
2. 粘贴到其他地方验证
|
||||
|
||||
**预期结果**:
|
||||
- ✅ 显示成功提示:`邀请码已复制:ABC123`
|
||||
- ✅ 剪贴板中有邀请码内容
|
||||
|
||||
### 测试场景5:赛事切换
|
||||
|
||||
**操作步骤**:
|
||||
1. 在赛事A生成邀请码
|
||||
2. 切换到赛事B
|
||||
3. 查看列表
|
||||
|
||||
**预期结果**:
|
||||
- ✅ 只显示赛事B的评委列表
|
||||
- ✅ 赛事A的邀请码不显示
|
||||
- ✅ 每个赛事的邀请码独立管理
|
||||
|
||||
---
|
||||
|
||||
## 七、错误处理
|
||||
|
||||
### 错误1:未选择赛事
|
||||
|
||||
```javascript
|
||||
if (!queryParams.competitionId) {
|
||||
ElMessage.warning('请先选择赛事')
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
### 错误2:评委已有有效邀请码
|
||||
|
||||
后端会返回错误信息:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"msg": "该评委已有有效邀请码,请使用重新生成功能"
|
||||
}
|
||||
```
|
||||
|
||||
前端显示:
|
||||
```javascript
|
||||
ElMessage.error(error.response?.data?.msg || '生成邀请码失败')
|
||||
```
|
||||
|
||||
### 错误3:邀请码重复
|
||||
|
||||
后端会自动重试(最多10次),前端无需处理
|
||||
|
||||
---
|
||||
|
||||
## 八、UI/UX优化建议
|
||||
|
||||
### 1. 视觉优化
|
||||
|
||||
- ✅ 邀请码使用等宽字体(`monospace`)
|
||||
- ✅ 使用警告色标签(`type="warning"`)
|
||||
- ✅ 添加复制提示(`title="点击复制"`)
|
||||
- ✅ 按钮使用图标(`<Refresh />`)
|
||||
|
||||
### 2. 交互优化
|
||||
|
||||
- ✅ 点击邀请码自动复制
|
||||
- ✅ 重新生成前确认提示
|
||||
- ✅ 批量生成前确认提示
|
||||
- ✅ 生成成功后自动复制到剪贴板
|
||||
- ✅ 操作成功后自动刷新列表
|
||||
|
||||
### 3. 加载状态
|
||||
|
||||
- ✅ 生成时显示 loading
|
||||
- ✅ 防止重复点击
|
||||
- ✅ 异常时显示错误提示
|
||||
|
||||
---
|
||||
|
||||
## 九、实施检查清单
|
||||
|
||||
### 后端准备
|
||||
|
||||
- [x] DTO类创建完成
|
||||
- [x] Service方法实现完成
|
||||
- [x] Controller接口添加完成
|
||||
- [x] 数据库表结构正确
|
||||
- [x] 唯一索引配置正确
|
||||
|
||||
### 前端准备
|
||||
|
||||
- [x] API接口添加完成(`judgeInvite.js`)
|
||||
- [ ] 导入新增API
|
||||
- [ ] 修改邀请码列UI
|
||||
- [ ] 添加生成邀请码方法
|
||||
- [ ] 添加重新生成方法
|
||||
- [ ] 添加批量生成方法
|
||||
- [ ] 修改批量邀请按钮功能
|
||||
|
||||
### 测试验证
|
||||
|
||||
- [ ] 单个生成测试通过
|
||||
- [ ] 重新生成测试通过
|
||||
- [ ] 批量生成测试通过
|
||||
- [ ] 复制功能测试通过
|
||||
- [ ] 赛事切换测试通过
|
||||
- [ ] 错误处理测试通过
|
||||
|
||||
---
|
||||
|
||||
## 十、总结
|
||||
|
||||
### 核心要点
|
||||
|
||||
1. **赛事绑定**:所有操作都基于 `queryParams.competitionId`
|
||||
2. **数据隔离**:不同赛事的邀请码完全独立
|
||||
3. **用户友好**:自动复制、确认提示、状态反馈
|
||||
4. **错误处理**:完善的错误提示和异常处理
|
||||
5. **性能优化**:操作后自动刷新,保持数据同步
|
||||
|
||||
### 实施时间
|
||||
|
||||
- 前端修改:30分钟
|
||||
- 测试验证:20分钟
|
||||
- **总计**:50分钟
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 按照本文档修改前端代码
|
||||
2. 启动项目测试各功能
|
||||
3. 根据测试结果调整优化
|
||||
4. 上线部署
|
||||
|
||||
---
|
||||
|
||||
**祝您实施顺利!** 🚀
|
||||
|
||||
如有问题,请参考:
|
||||
- 后端实施文档:`评委邀请码生成方案实施指南.md`
|
||||
- API文档:`src/api/martial/judgeInvite.js`
|
||||
- 页面代码:`src/views/martial/judgeInvite/index.vue`
|
||||
554
doc/评委邀请码生成方案实施指南.md
Normal file
554
doc/评委邀请码生成方案实施指南.md
Normal file
@@ -0,0 +1,554 @@
|
||||
# 评委邀请码生成方案 - 实施指南
|
||||
|
||||
> **实施日期**: 2025-12-12
|
||||
> **实施方式**: 管理员生成 → 复制发送 → 评委使用
|
||||
> **状态**: ✅ 代码已完成,可立即测试
|
||||
|
||||
---
|
||||
|
||||
## 📋 方案概述
|
||||
|
||||
### 核心流程
|
||||
|
||||
```
|
||||
管理员操作:
|
||||
1. 进入评委管理页面
|
||||
2. 选择评委,点击"生成邀请码"
|
||||
3. 系统生成6位随机码(如:ABC123)
|
||||
4. 复制邀请码
|
||||
5. 通过微信/短信发送给评委
|
||||
|
||||
评委使用:
|
||||
1. 收到邀请码
|
||||
2. 打开小程序登录页
|
||||
3. 输入比赛编码 + 邀请码
|
||||
4. 登录成功,开始评分
|
||||
```
|
||||
|
||||
### 技术特点
|
||||
|
||||
- ✅ **无需改表** - 使用现有字段
|
||||
- ✅ **6位随机码** - 大写字母+数字组合
|
||||
- ✅ **唯一性保证** - 数据库唯一索引
|
||||
- ✅ **有效期管理** - 默认30天
|
||||
- ✅ **状态管理** - 待使用/已使用/已禁用
|
||||
|
||||
---
|
||||
|
||||
## 🚀 已完成的代码
|
||||
|
||||
### 1. DTO 类
|
||||
|
||||
#### GenerateInviteDTO.java
|
||||
**路径**: `src/main/java/org/springblade/modules/martial/pojo/dto/GenerateInviteDTO.java`
|
||||
|
||||
```java
|
||||
@Data
|
||||
@ApiModel("生成邀请码DTO")
|
||||
public class GenerateInviteDTO {
|
||||
@NotNull(message = "赛事ID不能为空")
|
||||
private Long competitionId;
|
||||
|
||||
@NotNull(message = "评委ID不能为空")
|
||||
private Long judgeId;
|
||||
|
||||
@NotBlank(message = "角色不能为空")
|
||||
private String role; // judge 或 chief_judge
|
||||
|
||||
private Long venueId; // 场地ID(普通评委必填)
|
||||
private String projects; // 项目列表(JSON)
|
||||
private Integer expireDays = 30; // 过期天数
|
||||
}
|
||||
```
|
||||
|
||||
#### BatchGenerateInviteDTO.java
|
||||
**路径**: `src/main/java/org/springblade/modules/martial/pojo/dto/BatchGenerateInviteDTO.java`
|
||||
|
||||
```java
|
||||
@Data
|
||||
@ApiModel("批量生成邀请码DTO")
|
||||
public class BatchGenerateInviteDTO {
|
||||
@NotNull(message = "赛事ID不能为空")
|
||||
private Long competitionId;
|
||||
|
||||
@NotEmpty(message = "评委列表不能为空")
|
||||
private List<Long> judgeIds;
|
||||
|
||||
private String role = "judge";
|
||||
private Integer expireDays = 30;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Service 层
|
||||
|
||||
#### IMartialJudgeInviteService.java
|
||||
**新增方法**:
|
||||
|
||||
```java
|
||||
// 生成邀请码
|
||||
MartialJudgeInvite generateInviteCode(GenerateInviteDTO dto);
|
||||
|
||||
// 批量生成邀请码
|
||||
List<MartialJudgeInvite> batchGenerateInviteCode(BatchGenerateInviteDTO dto);
|
||||
|
||||
// 重新生成邀请码
|
||||
MartialJudgeInvite regenerateInviteCode(Long inviteId);
|
||||
|
||||
// 生成唯一邀请码
|
||||
String generateUniqueInviteCode();
|
||||
```
|
||||
|
||||
#### MartialJudgeInviteServiceImpl.java
|
||||
**核心实现**:
|
||||
|
||||
1. **生成唯一邀请码**:
|
||||
```java
|
||||
// 6位随机字符串(大写字母+数字)
|
||||
String inviteCode = UUID.randomUUID().toString()
|
||||
.replaceAll("-", "")
|
||||
.substring(0, 6)
|
||||
.toUpperCase();
|
||||
```
|
||||
|
||||
2. **检查重复**:
|
||||
```java
|
||||
// 检查邀请码是否已存在
|
||||
long count = this.count(
|
||||
Wrappers.<MartialJudgeInvite>lambdaQuery()
|
||||
.eq(MartialJudgeInvite::getInviteCode, inviteCode)
|
||||
.eq(MartialJudgeInvite::getIsDeleted, 0)
|
||||
);
|
||||
```
|
||||
|
||||
3. **防止重复生成**:
|
||||
```java
|
||||
// 检查评委是否已有有效邀请码
|
||||
MartialJudgeInvite existInvite = this.getOne(
|
||||
Wrappers.<MartialJudgeInvite>lambdaQuery()
|
||||
.eq(MartialJudgeInvite::getCompetitionId, competitionId)
|
||||
.eq(MartialJudgeInvite::getJudgeId, judgeId)
|
||||
.eq(MartialJudgeInvite::getStatus, 1)
|
||||
.gt(MartialJudgeInvite::getExpireTime, LocalDateTime.now())
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Controller 层
|
||||
|
||||
#### MartialJudgeInviteController.java
|
||||
**新增接口**:
|
||||
|
||||
| 接口 | 方法 | 路径 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 生成邀请码 | POST | `/martial/judgeInvite/generate` | 为单个评委生成 |
|
||||
| 批量生成 | POST | `/martial/judgeInvite/generate/batch` | 批量生成 |
|
||||
| 重新生成 | PUT | `/martial/judgeInvite/regenerate/{id}` | 重新生成(旧码失效) |
|
||||
| 查询邀请码 | GET | `/martial/judgeInvite/byJudge` | 查询评委的邀请码 |
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试指南
|
||||
|
||||
### 1. 使用 Postman 测试
|
||||
|
||||
#### 测试1:生成邀请码
|
||||
|
||||
```http
|
||||
POST http://localhost:8080/martial/judgeInvite/generate
|
||||
Content-Type: application/json
|
||||
Blade-Auth: Bearer {token}
|
||||
|
||||
{
|
||||
"competitionId": 1,
|
||||
"judgeId": 1,
|
||||
"role": "judge",
|
||||
"venueId": 1,
|
||||
"projects": "[\"女子组长拳\",\"男子组陈氏太极拳\"]",
|
||||
"expireDays": 30
|
||||
}
|
||||
```
|
||||
|
||||
**预期响应**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": 1001,
|
||||
"competitionId": 1,
|
||||
"judgeId": 1,
|
||||
"inviteCode": "ABC123",
|
||||
"role": "judge",
|
||||
"venueId": 1,
|
||||
"projects": "[\"女子组长拳\",\"男子组陈氏太极拳\"]",
|
||||
"expireTime": "2026-01-11 10:00:00",
|
||||
"isUsed": 0,
|
||||
"status": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 测试2:批量生成邀请码
|
||||
|
||||
```http
|
||||
POST http://localhost:8080/martial/judgeInvite/generate/batch
|
||||
Content-Type: application/json
|
||||
Blade-Auth: Bearer {token}
|
||||
|
||||
{
|
||||
"competitionId": 1,
|
||||
"judgeIds": [1, 2, 3, 4, 5],
|
||||
"role": "judge",
|
||||
"expireDays": 30
|
||||
}
|
||||
```
|
||||
|
||||
#### 测试3:查询评委邀请码
|
||||
|
||||
```http
|
||||
GET http://localhost:8080/martial/judgeInvite/byJudge?competitionId=1&judgeId=1
|
||||
Blade-Auth: Bearer {token}
|
||||
```
|
||||
|
||||
#### 测试4:重新生成邀请码
|
||||
|
||||
```http
|
||||
PUT http://localhost:8080/martial/judgeInvite/regenerate/1001
|
||||
Blade-Auth: Bearer {token}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. 使用 SQL 测试
|
||||
|
||||
#### 执行测试脚本
|
||||
|
||||
```bash
|
||||
# 进入数据库
|
||||
mysql -u root -p blade
|
||||
|
||||
# 执行测试脚本
|
||||
source database/martial-db/test_invite_code_generation.sql
|
||||
```
|
||||
|
||||
#### 查询有效邀请码
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
ji.id,
|
||||
ji.invite_code,
|
||||
ji.role,
|
||||
j.name AS judge_name,
|
||||
ji.expire_time,
|
||||
ji.is_used,
|
||||
CASE
|
||||
WHEN ji.is_used = 1 THEN '已使用'
|
||||
WHEN ji.expire_time < NOW() THEN '已过期'
|
||||
WHEN ji.status = 0 THEN '已禁用'
|
||||
ELSE '待使用'
|
||||
END AS status_text
|
||||
FROM martial_judge_invite ji
|
||||
LEFT JOIN martial_judge j ON ji.judge_id = j.id
|
||||
WHERE ji.competition_id = 1
|
||||
AND ji.is_deleted = 0
|
||||
ORDER BY ji.create_time DESC;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 数据库字段说明
|
||||
|
||||
### martial_judge_invite 表
|
||||
|
||||
| 字段 | 类型 | 说明 | 使用方式 |
|
||||
|------|------|------|----------|
|
||||
| `invite_code` | varchar(50) | 邀请码 | 6位随机码 |
|
||||
| `status` | int | 状态 | 1-启用,0-禁用 |
|
||||
| `is_used` | int | 是否已使用 | 0-未使用,1-已使用 |
|
||||
| `expire_time` | datetime | 过期时间 | 默认30天后 |
|
||||
| `use_time` | datetime | 使用时间 | 登录时记录 |
|
||||
| `role` | varchar(20) | 角色 | judge/chief_judge |
|
||||
| `venue_id` | bigint | 场地ID | 普通评委必填 |
|
||||
| `projects` | varchar(500) | 项目列表 | JSON数组 |
|
||||
|
||||
### 状态判断逻辑
|
||||
|
||||
```
|
||||
有效邀请码:status=1 AND is_used=0 AND expire_time>NOW()
|
||||
已使用:is_used=1
|
||||
已过期:expire_time<=NOW()
|
||||
已禁用:status=0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 前端集成建议
|
||||
|
||||
### 1. 在评委管理页面添加按钮
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-table :data="judgeList">
|
||||
<el-table-column label="操作">
|
||||
<template #default="{ row }">
|
||||
<!-- 生成邀请码按钮 -->
|
||||
<el-button
|
||||
v-if="!row.inviteCode"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="generateInviteCode(row)"
|
||||
>
|
||||
生成邀请码
|
||||
</el-button>
|
||||
|
||||
<!-- 显示邀请码 -->
|
||||
<div v-else>
|
||||
<el-tag>{{ row.inviteCode }}</el-tag>
|
||||
<el-button
|
||||
type="text"
|
||||
size="small"
|
||||
@click="copyInviteCode(row.inviteCode)"
|
||||
>
|
||||
复制
|
||||
</el-button>
|
||||
<el-button
|
||||
type="text"
|
||||
size="small"
|
||||
@click="regenerateInviteCode(row)"
|
||||
>
|
||||
重新生成
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 2. 生成邀请码方法
|
||||
|
||||
```javascript
|
||||
async generateInviteCode(judge) {
|
||||
try {
|
||||
const res = await this.$http.post('/martial/judgeInvite/generate', {
|
||||
competitionId: this.competitionId,
|
||||
judgeId: judge.id,
|
||||
role: judge.refereeType === 1 ? 'chief_judge' : 'judge',
|
||||
venueId: judge.venueId,
|
||||
projects: JSON.stringify(judge.projects),
|
||||
expireDays: 30
|
||||
});
|
||||
|
||||
if (res.success) {
|
||||
this.$message.success('邀请码生成成功:' + res.data.inviteCode);
|
||||
// 复制到剪贴板
|
||||
this.copyToClipboard(res.data.inviteCode);
|
||||
// 刷新列表
|
||||
this.loadJudgeList();
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error(error.message || '生成失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 复制到剪贴板
|
||||
copyToClipboard(text) {
|
||||
const input = document.createElement('input');
|
||||
input.value = text;
|
||||
document.body.appendChild(input);
|
||||
input.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(input);
|
||||
this.$message.success('已复制到剪贴板');
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 批量生成
|
||||
|
||||
```javascript
|
||||
async batchGenerate() {
|
||||
const selectedJudges = this.$refs.table.selection;
|
||||
if (selectedJudges.length === 0) {
|
||||
this.$message.warning('请选择评委');
|
||||
return;
|
||||
}
|
||||
|
||||
const judgeIds = selectedJudges.map(j => j.id);
|
||||
|
||||
try {
|
||||
const res = await this.$http.post('/martial/judgeInvite/generate/batch', {
|
||||
competitionId: this.competitionId,
|
||||
judgeIds: judgeIds,
|
||||
role: 'judge',
|
||||
expireDays: 30
|
||||
});
|
||||
|
||||
if (res.success) {
|
||||
this.$message.success(`成功生成${res.data.length}个邀请码`);
|
||||
this.loadJudgeList();
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error(error.message || '批量生成失败');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 验证清单
|
||||
|
||||
### 后端验证
|
||||
|
||||
- [ ] DTO类创建成功
|
||||
- [ ] Service方法实现完成
|
||||
- [ ] Controller接口添加完成
|
||||
- [ ] 编译无错误
|
||||
- [ ] Swagger文档生成正常
|
||||
|
||||
### 功能验证
|
||||
|
||||
- [ ] 单个生成邀请码成功
|
||||
- [ ] 邀请码格式正确(6位大写字母+数字)
|
||||
- [ ] 邀请码唯一性验证通过
|
||||
- [ ] 批量生成成功
|
||||
- [ ] 重新生成成功(旧码失效)
|
||||
- [ ] 查询邀请码成功
|
||||
- [ ] 防止重复生成(已有有效邀请码时报错)
|
||||
|
||||
### 数据库验证
|
||||
|
||||
- [ ] 邀请码保存成功
|
||||
- [ ] 过期时间设置正确
|
||||
- [ ] 状态字段正确
|
||||
- [ ] 唯一索引生效
|
||||
|
||||
### 小程序验证
|
||||
|
||||
- [ ] 使用邀请码登录成功
|
||||
- [ ] 登录后权限正确
|
||||
- [ ] 场地和项目信息正确
|
||||
|
||||
---
|
||||
|
||||
## 🔧 常见问题
|
||||
|
||||
### 问题1:邀请码重复
|
||||
|
||||
**现象**: 生成的邀请码已存在
|
||||
|
||||
**原因**: 随机生成时碰撞
|
||||
|
||||
**解决**: 代码已实现重试机制(最多10次)
|
||||
|
||||
---
|
||||
|
||||
### 问题2:评委已有邀请码
|
||||
|
||||
**现象**: 提示"该评委已有有效邀请码"
|
||||
|
||||
**原因**: 防止重复生成
|
||||
|
||||
**解决**:
|
||||
- 使用"重新生成"功能
|
||||
- 或等待旧邀请码过期
|
||||
|
||||
---
|
||||
|
||||
### 问题3:邀请码过期
|
||||
|
||||
**现象**: 登录时提示邀请码已过期
|
||||
|
||||
**原因**: 超过30天有效期
|
||||
|
||||
**解决**: 使用"重新生成"功能
|
||||
|
||||
---
|
||||
|
||||
## 📈 后续优化建议
|
||||
|
||||
### 短期优化(可选)
|
||||
|
||||
1. **邀请码格式优化**
|
||||
- 添加前缀(如:WS-ABC123)
|
||||
- 区分角色(J-评委,C-裁判长)
|
||||
|
||||
2. **批量导出**
|
||||
- 导出Excel:评委信息+邀请码
|
||||
- 生成PDF邀请函
|
||||
|
||||
3. **统计报表**
|
||||
- 邀请码使用率
|
||||
- 过期邀请码数量
|
||||
|
||||
### 长期优化(可选)
|
||||
|
||||
1. **短信/邮件发送**
|
||||
- 集成短信服务
|
||||
- 自动发送邀请码
|
||||
|
||||
2. **二维码生成**
|
||||
- 生成邀请二维码
|
||||
- 扫码直接登录
|
||||
|
||||
3. **邀请码管理**
|
||||
- 批量禁用
|
||||
- 批量延期
|
||||
|
||||
---
|
||||
|
||||
## 📞 技术支持
|
||||
|
||||
### 代码位置
|
||||
|
||||
| 文件 | 路径 |
|
||||
|------|------|
|
||||
| DTO类 | `src/main/java/org/springblade/modules/martial/pojo/dto/` |
|
||||
| Service接口 | `src/main/java/org/springblade/modules/martial/service/IMartialJudgeInviteService.java` |
|
||||
| Service实现 | `src/main/java/org/springblade/modules/martial/service/impl/MartialJudgeInviteServiceImpl.java` |
|
||||
| Controller | `src/main/java/org/springblade/modules/martial/controller/MartialJudgeInviteController.java` |
|
||||
| 测试SQL | `database/martial-db/test_invite_code_generation.sql` |
|
||||
|
||||
### Swagger 文档
|
||||
|
||||
启动后端服务后访问:
|
||||
```
|
||||
http://localhost:8080/doc.html
|
||||
```
|
||||
|
||||
搜索"裁判邀请码管理"查看所有接口。
|
||||
|
||||
---
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
### 已完成
|
||||
|
||||
✅ DTO类创建
|
||||
✅ Service层实现
|
||||
✅ Controller接口
|
||||
✅ 测试SQL脚本
|
||||
✅ 实施文档
|
||||
|
||||
### 工作量
|
||||
|
||||
- 后端开发:2小时
|
||||
- 测试验证:1小时
|
||||
- 文档编写:1小时
|
||||
- **总计**:4小时
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 启动后端服务
|
||||
2. 使用Postman测试接口
|
||||
3. 前端集成(如需要)
|
||||
4. 联调测试
|
||||
5. 上线部署
|
||||
|
||||
---
|
||||
|
||||
**祝您实施顺利!** 🚀
|
||||
|
||||
如有问题,请查看代码注释或联系技术支持。
|
||||
Reference in New Issue
Block a user