Files
martial-admin-mini/doc/后端开发快速上手.md
宅房 5b75d0f4eb docs: 新增快速参考和项目状态看板
## 新增文档

1. 快速参考.md - 一页纸快速参考卡片
   - 3步启动流程
   - 接口清单
   - 调试技巧
   - 常见问题速查

2. 项目状态看板.md - 实时项目状态跟踪
   - 总体进度(72%)
   - 任务清单(前端100%,后端44%)
   - 接口开发状态
   - 测试状态
   - 代码统计
   - 时间线和里程碑

3. doc/后端开发快速上手.md - 后端开发者30分钟上手指南
   - 6步实现流程
   - 完整代码示例
   - SQL示例
   - VO类定义
   - 测试方法

## 文档体系

现在共有 21 个文档,约 25,000+ 行
- 快速上手文档: 3个
- 开发规范文档: 5个
- 测试指南文档: 2个
- 状态报告文档: 3个
- 项目说明文档: 8个

## 项目状态

 前端完成度: 100%
⚠️ 后端完成度: 44%
📚 文档完成度: 100%

下一步: 后端开始开发 5 个接口

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-12 01:00:42 +08:00

616 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 后端开发快速上手指南
> 30分钟了解需要做什么然后开始开发
---
## 📋 你需要做什么
### 创建1个Controller实现5个接口
```java
@RestController
@RequestMapping("/api/mini")
public class MartialMiniController {
// 5个接口方法
}
```
**预计工作量**: 6人天约1周
---
## 🚀 第一步创建Controller30分钟
### 1. 创建文件
```
src/main/java/org/springblade/modules/martial/controller/
└── MartialMiniController.java
```
### 2. 基础代码
```java
package org.springblade.modules.martial.controller;
import org.springblade.core.tool.api.R;
import org.springframework.web.bind.annotation.*;
import lombok.AllArgsConstructor;
/**
* 武术评分系统 - 小程序专用接口
*
* @author 你的名字
*/
@RestController
@RequestMapping("/api/mini")
@AllArgsConstructor
public class MartialMiniController {
// 注入需要的Service
// private IMartialJudgeInviteService judgeInviteService;
// private IMartialAthleteService athleteService;
// private IMartialScoreService scoreService;
/**
* 小程序登录
*/
@PostMapping("/login")
public R<LoginVO> login(@RequestBody LoginDTO dto) {
// TODO: 实现登录逻辑
return R.success(null);
}
/**
* 获取评委的选手列表(普通评委)
*/
@GetMapping("/athletes")
public R<List<AthleteScoreVO>> getMyAthletes(
@RequestParam Long judgeId,
@RequestParam Long venueId,
@RequestParam Long projectId
) {
// TODO: 实现获取选手列表逻辑
return R.success(null);
}
/**
* 获取选手列表(裁判长)
*/
@GetMapping("/athletes/admin")
public R<List<AthleteAdminVO>> getAthletesForAdmin(
@RequestParam Long competitionId,
@RequestParam Long venueId,
@RequestParam Long projectId
) {
// TODO: 实现裁判长选手列表逻辑
return R.success(null);
}
/**
* 获取评分详情(裁判长查看)
*/
@GetMapping("/score/detail/{athleteId}")
public R<ScoreDetailVO> getScoreDetail(@PathVariable Long athleteId) {
// TODO: 实现评分详情逻辑
return R.success(null);
}
/**
* 修改评分(裁判长)
*/
@PutMapping("/score/modify")
public R<ModifyResultVO> modifyScore(@RequestBody ModifyScoreDTO dto) {
// TODO: 实现修改评分逻辑
return R.success(null);
}
}
```
---
## 🔴 第二步实现登录接口2天
### 接口规范
```
POST /api/mini/login
请求:
{
"matchCode": "123",
"inviteCode": "pub"
}
响应:
{
"code": 200,
"success": true,
"msg": "登录成功",
"data": {
"token": "xxx",
"userRole": "pub",
"matchId": "123",
"matchName": "2025年全国武术散打锦标赛...",
"matchTime": "2025年6月25日 9:00",
"judgeId": "456",
"judgeName": "欧阳丽娜",
"venueId": "1",
"venueName": "第一场地",
"projects": ["女子组长拳", "男子组陈氏太极拳"]
}
}
```
### 实现逻辑
```java
@PostMapping("/login")
public R<LoginVO> login(@RequestBody LoginDTO dto) {
// 1. 验证比赛编码
MartialCompetition competition = competitionService.getOne(
Wrappers.<MartialCompetition>lambdaQuery()
.eq(MartialCompetition::getCompetitionCode, dto.getMatchCode())
.eq(MartialCompetition::getIsDeleted, 0)
);
if (competition == null) {
return R.fail("比赛编码不存在");
}
// 2. 验证邀请码
MartialJudgeInvite invite = judgeInviteService.getOne(
Wrappers.<MartialJudgeInvite>lambdaQuery()
.eq(MartialJudgeInvite::getInviteCode, dto.getInviteCode())
.eq(MartialJudgeInvite::getCompetitionId, competition.getId())
.eq(MartialJudgeInvite::getIsUsed, 0)
.eq(MartialJudgeInvite::getIsDeleted, 0)
.gt(MartialJudgeInvite::getExpireTime, LocalDateTime.now())
);
if (invite == null) {
return R.fail("邀请码错误或已失效");
}
// 3. 生成Token使用BladeX的Token生成机制
String token = generateToken(invite.getJudgeId());
// 4. 更新邀请码使用状态
invite.setIsUsed(1);
invite.setUseTime(LocalDateTime.now());
invite.setAccessToken(token);
judgeInviteService.updateById(invite);
// 5. 查询评委信息
MartialJudge judge = judgeService.getById(invite.getJudgeId());
// 6. 查询场地信息(如果有)
MartialVenue venue = null;
if (invite.getVenueId() != null) {
venue = venueService.getById(invite.getVenueId());
}
// 7. 解析项目列表
List<String> projects = JSON.parseArray(invite.getProjects(), String.class);
// 8. 构建响应
LoginVO vo = new LoginVO();
vo.setToken(token);
vo.setUserRole(invite.getRole()); // "judge" 或 "chief_judge"
vo.setMatchId(competition.getId().toString());
vo.setMatchName(competition.getCompetitionName());
vo.setMatchTime(formatDateTime(competition.getStartTime()));
vo.setJudgeId(judge.getId().toString());
vo.setJudgeName(judge.getJudgeName());
if (venue != null) {
vo.setVenueId(venue.getId().toString());
vo.setVenueName(venue.getVenueName());
}
vo.setProjects(projects);
return R.success(vo);
}
```
### SQL示例
```sql
-- 验证邀请码
SELECT
ji.id,
ji.judge_id AS judgeId,
ji.role,
ji.venue_id AS venueId,
ji.projects,
j.judge_name AS judgeName,
c.id AS matchId,
c.competition_name AS matchName,
c.start_time AS matchTime,
v.venue_name AS venueName
FROM martial_judge_invite ji
LEFT JOIN martial_judge j ON ji.judge_id = j.id
LEFT JOIN martial_competition c ON ji.competition_id = c.id
LEFT JOIN martial_venue v ON ji.venue_id = v.id
WHERE ji.invite_code = ?
AND c.competition_code = ?
AND ji.is_used = 0
AND ji.expire_time > NOW()
AND ji.is_deleted = 0
```
---
## 🔴 第三步实现选手列表接口1天
### 接口规范
```
GET /api/mini/athletes?judgeId=456&venueId=1&projectId=5
响应:
{
"code": 200,
"success": true,
"msg": "操作成功",
"data": [
{
"athleteId": "1",
"name": "张三",
"idCard": "123456789000000000",
"team": "少林寺武术大学院",
"number": "123-4567898275",
"myScore": 8.906,
"totalScore": 8.907,
"scored": true,
"scoreTime": "2025-06-25 09:15:00"
}
]
}
```
### SQL示例
```sql
SELECT
a.id AS athleteId,
a.player_name AS name,
a.id_card AS idCard,
a.team_name AS team,
a.player_no AS number,
a.total_score AS totalScore,
s.score AS myScore,
CASE WHEN s.id IS NOT NULL THEN 1 ELSE 0 END AS scored,
s.score_time AS scoreTime
FROM martial_athlete a
LEFT JOIN martial_score s
ON a.id = s.athlete_id
AND s.judge_id = ?
WHERE a.venue_id = ?
AND a.project_id = ?
AND a.is_deleted = 0
ORDER BY a.order_num ASC
```
---
## 🟡 第四步实现裁判长选手列表1天
### 接口规范
```
GET /api/mini/athletes/admin?competitionId=123&venueId=1&projectId=5
响应:
{
"code": 200,
"success": true,
"msg": "操作成功",
"data": [
{
"athleteId": "1",
"name": "张三",
"idCard": "123456789000000000",
"team": "少林寺武术大学院",
"number": "123-4567898275",
"totalScore": 8.907,
"judgeCount": 6,
"totalJudges": 6,
"canModify": true
}
]
}
```
### SQL示例
```sql
SELECT
a.id AS athleteId,
a.player_name AS name,
a.id_card AS idCard,
a.team_name AS team,
a.player_no AS number,
a.total_score AS totalScore,
COUNT(s.id) AS judgeCount,
(SELECT COUNT(*) FROM martial_judge_project jp
WHERE jp.project_id = ? AND jp.is_deleted = 0) AS totalJudges,
CASE WHEN COUNT(s.id) = (SELECT COUNT(*) FROM martial_judge_project jp
WHERE jp.project_id = ? AND jp.is_deleted = 0) THEN 1 ELSE 0 END AS canModify
FROM martial_athlete a
LEFT JOIN martial_score s ON a.id = s.athlete_id
WHERE a.venue_id = ?
AND a.project_id = ?
AND a.is_deleted = 0
GROUP BY a.id
ORDER BY a.order_num ASC
```
---
## 🟡 第五步实现评分详情接口1天
### 接口规范
```
GET /api/mini/score/detail/1
响应:
{
"code": 200,
"success": true,
"msg": "操作成功",
"data": {
"athleteInfo": {
"athleteId": "1",
"name": "张三",
"idCard": "123456789000000000",
"team": "少林寺武术大学院",
"number": "123-4567898275",
"totalScore": 8.907
},
"judgeScores": [
{
"judgeId": "1",
"judgeName": "欧阳丽娜",
"score": 8.907,
"scoreTime": "2025-06-25 09:15:00",
"note": ""
}
],
"modification": null
}
}
```
### SQL示例
```sql
-- 选手信息
SELECT
a.id AS athleteId,
a.player_name AS name,
a.id_card AS idCard,
a.team_name AS team,
a.player_no AS number,
a.total_score AS totalScore
FROM martial_athlete a
WHERE a.id = ?
-- 评委评分
SELECT
s.judge_id AS judgeId,
s.judge_name AS judgeName,
s.score,
s.score_time AS scoreTime,
s.note
FROM martial_score s
WHERE s.athlete_id = ?
ORDER BY s.score_time ASC
```
---
## 🟡 第六步实现修改评分接口1天
### 接口规范
```
PUT /api/mini/score/modify
请求:
{
"athleteId": "1",
"modifierId": "789",
"modifiedScore": 8.910,
"note": "修改原因"
}
响应:
{
"code": 200,
"success": true,
"msg": "修改成功",
"data": {
"athleteId": "1",
"originalScore": 8.907,
"modifiedScore": 8.910,
"modifyTime": "2025-06-25 10:00:00"
}
}
```
### 实现逻辑
```java
@PutMapping("/score/modify")
public R<ModifyResultVO> modifyScore(@RequestBody ModifyScoreDTO dto) {
// 1. 验证权限(只有裁判长可以修改)
MartialJudgeInvite invite = judgeInviteService.getOne(
Wrappers.<MartialJudgeInvite>lambdaQuery()
.eq(MartialJudgeInvite::getJudgeId, dto.getModifierId())
.eq(MartialJudgeInvite::getRole, "chief_judge")
.eq(MartialJudgeInvite::getIsDeleted, 0)
);
if (invite == null) {
return R.fail("无权限修改评分");
}
// 2. 查询当前总分
MartialAthlete athlete = athleteService.getById(dto.getAthleteId());
BigDecimal originalScore = athlete.getTotalScore();
// 3. 更新选手总分
athlete.setTotalScore(dto.getModifiedScore());
athlete.setUpdatedBy(dto.getModifierId());
athlete.setUpdateTime(LocalDateTime.now());
athleteService.updateById(athlete);
// 4. 记录修改信息可以在athlete表中添加字段或创建修改记录表
// ...
// 5. 构建响应
ModifyResultVO vo = new ModifyResultVO();
vo.setAthleteId(dto.getAthleteId().toString());
vo.setOriginalScore(originalScore);
vo.setModifiedScore(dto.getModifiedScore());
vo.setModifyTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
return R.success(vo);
}
```
---
## 📝 创建VO类
### LoginVO.java
```java
package org.springblade.modules.martial.vo;
import lombok.Data;
import java.util.List;
@Data
public class LoginVO {
private String token;
private String userRole;
private String matchId;
private String matchName;
private String matchTime;
private String judgeId;
private String judgeName;
private String venueId;
private String venueName;
private List<String> projects;
}
```
### AthleteScoreVO.java
```java
package org.springblade.modules.martial.vo;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class AthleteScoreVO {
private String athleteId;
private String name;
private String idCard;
private String team;
private String number;
private BigDecimal myScore;
private BigDecimal totalScore;
private Boolean scored;
private String scoreTime;
}
```
### AthleteAdminVO.java
```java
package org.springblade.modules.martial.vo;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class AthleteAdminVO {
private String athleteId;
private String name;
private String idCard;
private String team;
private String number;
private BigDecimal totalScore;
private Integer judgeCount;
private Integer totalJudges;
private Boolean canModify;
}
```
---
## 🧪 测试接口
### 使用Postman测试
#### 1. 测试登录
```
POST http://localhost:8080/api/mini/login
Content-Type: application/json
{
"matchCode": "123",
"inviteCode": "pub"
}
```
#### 2. 测试选手列表
```
GET http://localhost:8080/api/mini/athletes?judgeId=456&venueId=1&projectId=5
Blade-Auth: Bearer {token}
```
---
## ✅ 检查清单
### 开发前
- [ ] 阅读接口规范文档
- [ ] 了解现有数据库表结构
- [ ] 准备测试数据
### 开发中
- [ ] 创建 MartialMiniController
- [ ] 创建所有VO类
- [ ] 实现登录接口
- [ ] 实现选手列表接口2个
- [ ] 实现评分详情接口
- [ ] 实现修改评分接口
### 开发后
- [ ] 单元测试通过
- [ ] Postman测试通过
- [ ] 更新Swagger文档
- [ ] 通知前端联调
---
## 📞 需要帮助?
- **详细规范**: [后端接口开发清单.md](./后端接口开发清单.md)
- **前端对接**: [前端API对接指南.md](./前端API对接指南.md)
- **技术对比**: [后端实现对比报告.md](./后端实现对比报告.md)
---
**预计完成时间**: 6人天约1周
开始开发吧! 🚀