555 lines
12 KiB
Markdown
555 lines
12 KiB
Markdown
# 评委邀请码生成方案 - 实施指南
|
||
|
||
> **实施日期**: 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. 上线部署
|
||
|
||
---
|
||
|
||
**祝您实施顺利!** 🚀
|
||
|
||
如有问题,请查看代码注释或联系技术支持。
|