# 评委邀请码生成方案 - 实施指南 > **实施日期**: 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 judgeIds; private String role = "judge"; private Integer expireDays = 30; } ``` --- ### 2. Service 层 #### IMartialJudgeInviteService.java **新增方法**: ```java // 生成邀请码 MartialJudgeInvite generateInviteCode(GenerateInviteDTO dto); // 批量生成邀请码 List 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.lambdaQuery() .eq(MartialJudgeInvite::getInviteCode, inviteCode) .eq(MartialJudgeInvite::getIsDeleted, 0) ); ``` 3. **防止重复生成**: ```java // 检查评委是否已有有效邀请码 MartialJudgeInvite existInvite = this.getOne( Wrappers.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 ``` ### 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. 上线部署 --- **祝您实施顺利!** 🚀 如有问题,请查看代码注释或联系技术支持。