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>
This commit is contained in:
615
doc/后端开发快速上手.md
Normal file
615
doc/后端开发快速上手.md
Normal file
@@ -0,0 +1,615 @@
|
||||
# 后端开发快速上手指南
|
||||
|
||||
> 30分钟了解需要做什么,然后开始开发
|
||||
|
||||
---
|
||||
|
||||
## 📋 你需要做什么
|
||||
|
||||
### 创建1个Controller,实现5个接口
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/api/mini")
|
||||
public class MartialMiniController {
|
||||
// 5个接口方法
|
||||
}
|
||||
```
|
||||
|
||||
**预计工作量**: 6人天(约1周)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 第一步:创建Controller(30分钟)
|
||||
|
||||
### 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周)
|
||||
|
||||
开始开发吧! 🚀
|
||||
Reference in New Issue
Block a user