Compare commits
57 Commits
67908a4dd0
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| df7efac819 | |||
| 559dea702a | |||
| c40ca5b35b | |||
| 742272026b | |||
| 496537ceef | |||
| e0d3572e34 | |||
| a262ca9279 | |||
| b94ac501de | |||
| ea50330a5d | |||
| e3f158985a | |||
| eefe7167ee | |||
| 550802a029 | |||
| ac44bd45fa | |||
| 8193baf314 | |||
| 3af34506ba | |||
| 55ccf08246 | |||
| 29e9fb4e0a | |||
| 9fa5eb46df | |||
| d3c7dccf05 | |||
| 370cdc8e1e | |||
| e70dbd1144 | |||
| 760b7d0039 | |||
| e50b71a13d | |||
| e1bf9a4351 | |||
| 2f9fbbb2aa | |||
| f45fee050e | |||
| 18895dcb76 | |||
| 89962c69e6 | |||
| 45758108a8 | |||
| 19e3d94a33 | |||
| 7fae2f0ff8 | |||
| fe5ddfa253 | |||
| c7038a5883 | |||
| 87a05df04f | |||
| b7ad819a29 | |||
| 6db9a1e51d | |||
| 0539152dbb | |||
| c7058b8b07 | |||
| 16b55adf81 | |||
| 0b5fc9fb71 | |||
| 86e4580e5d | |||
| 47d0b70a9c | |||
| 105e457f7c | |||
| d583bdc5c8 | |||
| 07845f3a4f | |||
| ec2382b447 | |||
| bcba649b02 | |||
| a19baf3907 | |||
| 301bb7a227 | |||
| fdd346b27f | |||
| 1d5ac896dd | |||
| aab66f79fe | |||
| 491c8db26c | |||
| 4a2071ddda | |||
| 559e97b672 | |||
| 35a5369e81 | |||
| dca5e5050f |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -39,3 +39,6 @@ PORT_FORWARD.md
|
||||
QUICKSTART.md
|
||||
SERVICE_CONFIG.md
|
||||
nul
|
||||
|
||||
# MinIO 运行时数据
|
||||
minio_data/
|
||||
|
||||
50
Dockerfile.fullbuild
Normal file
50
Dockerfile.fullbuild
Normal file
@@ -0,0 +1,50 @@
|
||||
# ============================================
|
||||
# 武术赛事管理系统 - 完整构建 Dockerfile
|
||||
# 包含 martial-tool 编译 + martial-master 编译
|
||||
# ============================================
|
||||
|
||||
# 构建阶段:使用 Maven + JDK 镜像
|
||||
FROM maven:3.9-eclipse-temurin-17 AS builder
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
# 复制 martial-tool(BladeX 框架)
|
||||
COPY martial-tool /build/martial-tool
|
||||
|
||||
# 编译 martial-tool 并安装到本地仓库
|
||||
RUN cd /build/martial-tool && \
|
||||
mvn clean install -DskipTests -q
|
||||
|
||||
# 复制 martial-master(后端项目)
|
||||
COPY martial-master /build/martial-master
|
||||
|
||||
# 编译 martial-master
|
||||
RUN cd /build/martial-master && \
|
||||
mvn clean package -DskipTests -q
|
||||
|
||||
# ============================================
|
||||
# 运行阶段:使用轻量级 JRE 镜像
|
||||
# ============================================
|
||||
FROM eclipse-temurin:17-jre-jammy
|
||||
|
||||
LABEL maintainer="JohnSion"
|
||||
LABEL description="武术比赛管理系统后端服务"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 从构建阶段复制 JAR 文件
|
||||
COPY --from=builder /build/martial-master/target/blade-api.jar /app/blade-api.jar
|
||||
|
||||
# 暴露端口
|
||||
EXPOSE 8123
|
||||
|
||||
# 健康检查
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
|
||||
CMD curl -f http://localhost:8123/actuator/health || exit 1
|
||||
|
||||
# JVM 参数配置
|
||||
ENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC"
|
||||
ENV SPRING_PROFILE="dev"
|
||||
|
||||
# 启动命令
|
||||
CMD ["sh", "-c", "java ${JAVA_OPTS} -jar /app/blade-api.jar --spring.profiles.active=${SPRING_PROFILE}"]
|
||||
429
README.md
429
README.md
@@ -2,348 +2,145 @@
|
||||
|
||||
基于 BladeX 4.0.1 企业级框架构建的武术比赛管理系统后端服务。
|
||||
|
||||
## 🌐 在线访问
|
||||
## 在线访问
|
||||
|
||||
- **生产环境 API**: https://martial-api.johnsion.club
|
||||
- **API 文档**: https://martial-doc.johnsion.club
|
||||
- **前端系统**: https://martial.johnsion.club
|
||||
- **CI/CD 管理**: https://martial-ci.johnsion.club
|
||||
| 服务 | 地址 | 说明 |
|
||||
|------|------|------|
|
||||
| 后端 API | https://martial-api.aitisai.com | Spring Boot 服务 |
|
||||
| 管理后台 | https://martial-admin.aitisai.com | Web 管理端 |
|
||||
| 用户端 | https://martial.aitisai.com | 报名小程序 H5 |
|
||||
| 裁判端 | https://martial-mini.aitisai.com | 裁判评分小程序 |
|
||||
| OSS 存储 | https://martial-oss.aitisai.com | MinIO 对象存储 |
|
||||
| MinIO 控制台 | https://martial-minio.aitisai.com | MinIO 管理界面 |
|
||||
|
||||
## 📦 技术栈
|
||||
## 技术栈
|
||||
|
||||
- **框架**: Spring Boot 3.2.4
|
||||
- **框架**: Spring Boot 3.2.4 + BladeX 4.0.1
|
||||
- **语言**: Java 17
|
||||
- **数据库**: MySQL 8.0 + Redis 7
|
||||
- **ORM**: MyBatis-Plus
|
||||
- **数据库**: MySQL 8.0
|
||||
- **缓存**: Redis 7
|
||||
- **API 文档**: Knife4j (Swagger)
|
||||
- **企业框架**: BladeX 4.0.1 RELEASE
|
||||
- **数据库迁移**: Flyway
|
||||
- **对象存储**: MinIO
|
||||
- **反向代理**: Caddy
|
||||
- **容器化**: Docker Compose
|
||||
|
||||
## 📁 项目结构
|
||||
## 快速开始
|
||||
|
||||
### 环境要求
|
||||
|
||||
- Docker & Docker Compose
|
||||
|
||||
### 一键部署(推荐)
|
||||
|
||||
确保目录结构如下:
|
||||
```
|
||||
martial/
|
||||
├── martial-tool/ # BladeX 框架(必需)
|
||||
├── martial-master/ # 后端项目
|
||||
├── martial-web/ # 管理后台前端
|
||||
├── martial-mini/ # 用户端小程序
|
||||
└── martial-admin-mini/ # 裁判端小程序
|
||||
```
|
||||
|
||||
```bash
|
||||
cd martial/martial-master
|
||||
|
||||
# 首次部署(完整构建,约5-6分钟)
|
||||
docker compose up -d
|
||||
|
||||
# 查看构建日志
|
||||
docker compose logs -f martial-api
|
||||
|
||||
# 查看服务状态
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
服务启动后:
|
||||
- API 服务: http://localhost:8123
|
||||
- API 文档: http://localhost:8123/doc.html
|
||||
|
||||
### 快速构建(开发迭代)
|
||||
|
||||
如果已经手动编译过 JAR,可以使用快速构建:
|
||||
|
||||
```bash
|
||||
# 先编译 martial-tool(首次)
|
||||
cd ../martial-tool && mvn clean install -DskipTests
|
||||
|
||||
# 编译 martial-master
|
||||
cd ../martial-master && mvn clean package -DskipTests
|
||||
|
||||
# 使用快速构建 Dockerfile
|
||||
docker compose build martial-api --build-arg DOCKERFILE=Dockerfile.quick
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
martial-master/
|
||||
├── src/main/java/org/springblade/
|
||||
│ ├── Application.java # 主启动类
|
||||
│ ├── common/ # 公共工具和配置
|
||||
│ ├── modules/ # 业务模块
|
||||
│ │ ├── auth/ # 认证授权
|
||||
│ │ ├── system/ # 系统管理
|
||||
│ │ ├── resource/ # 资源管理
|
||||
│ │ ├── desk/ # 工作台
|
||||
│ │ ├── develop/ # 代码生成
|
||||
│ │ └── martial/ # ⭐ 武术比赛业务(核心)
|
||||
│ └── job/ # 定时任务
|
||||
│ ├── modules/martial/ # 武术比赛核心业务
|
||||
│ │ ├── controller/ # 接口控制器
|
||||
│ │ ├── service/ # 业务逻辑
|
||||
│ │ ├── mapper/ # 数据访问
|
||||
│ │ └── pojo/ # 实体类
|
||||
│ └── ... # BladeX 框架模块
|
||||
├── src/main/resources/
|
||||
│ ├── application.yml # 主配置
|
||||
│ ├── application-dev.yml # 开发环境
|
||||
│ ├── application-test.yml # 测试环境
|
||||
│ └── application-prod.yml # 生产环境
|
||||
├── database/ # 数据库脚本
|
||||
│ ├── bladex/ # BladeX 框架表
|
||||
│ ├── flowable/ # 工作流表
|
||||
│ ├── martial-db/ # 武术业务表
|
||||
│ └── upgrade/ # 升级脚本
|
||||
├── docs/ # 项目文档
|
||||
│ ├── README.md # 文档索引
|
||||
│ ├── 架构说明.md # 架构设计
|
||||
│ ├── 前后端架构说明.md # 前后端交互
|
||||
│ ├── 开发指南.md # 开发规范
|
||||
│ └── CI-CD部署总结.md # 部署文档
|
||||
├── scripts/ # 运维脚本
|
||||
│ ├── docker/ # Docker 部署
|
||||
│ └── fatjar/ # JAR 启动脚本
|
||||
├── .drone.yml # CI/CD 配置
|
||||
├── Dockerfile # Docker 镜像构建
|
||||
└── CLAUDE.md # 项目完整说明
|
||||
|
||||
│ ├── application.yml # 主配置
|
||||
│ ├── application-dev.yml # 开发环境
|
||||
│ ├── application-prod.yml # 生产环境
|
||||
│ └── db/migration/ # Flyway 迁移脚本
|
||||
├── database/ # 数据库初始化脚本
|
||||
├── docs/ # 项目文档
|
||||
├── docker-compose.yml # Docker 编排配置
|
||||
├── Dockerfile.fullbuild # 完整构建(含 martial-tool)
|
||||
└── Dockerfile.quick # 快速构建(需预编译 JAR)
|
||||
```
|
||||
|
||||
## 🚀 快速开始
|
||||
## Docker Compose 服务
|
||||
|
||||
### 环境要求
|
||||
| 服务 | 端口 | 说明 |
|
||||
|------|------|------|
|
||||
| martial-api | 8123 | 后端 API 服务 |
|
||||
| martial-mysql | 3306 | MySQL 数据库 |
|
||||
| martial-redis | 6379 | Redis 缓存 |
|
||||
| minio | 9000/9001 | 对象存储 |
|
||||
|
||||
- **JDK**: 17+
|
||||
- **Maven**: 3.8+
|
||||
- **MySQL**: 8.0+
|
||||
- **Redis**: 6.0+
|
||||
## 数据库迁移
|
||||
|
||||
### 本地开发
|
||||
项目使用 Flyway 管理数据库版本,应用启动时自动执行迁移。
|
||||
|
||||
**添加新迁移:**
|
||||
```bash
|
||||
# 1. 克隆项目
|
||||
git clone https://git.waypeak.work/martial/martial-master.git
|
||||
cd martial-master
|
||||
|
||||
# 2. 编译 BladeX 框架(首次必须)
|
||||
cd /path/to/martial-tool
|
||||
mvn clean install -DskipTests
|
||||
|
||||
# 3. 编译并运行
|
||||
cd /path/to/martial-master
|
||||
mvn clean package -DskipTests -Dmaven.test.skip=true
|
||||
mvn spring-boot:run
|
||||
|
||||
# 4. 访问应用
|
||||
# API: http://localhost:8123
|
||||
# 文档: http://localhost:8123/doc.html
|
||||
# 在 src/main/resources/db/migration/ 创建脚本
|
||||
# 命名规范: V{版本号}__{描述}.sql
|
||||
# 示例: V3__add_new_table.sql
|
||||
```
|
||||
|
||||
详细说明请参考:[CLAUDE.md](./CLAUDE.md)
|
||||
详细说明:[docs/DATABASE_MIGRATION.md](./docs/DATABASE_MIGRATION.md)
|
||||
|
||||
## 🔄 自动化部署
|
||||
## 开发文档
|
||||
|
||||
### CI/CD 架构
|
||||
| 文档 | 说明 |
|
||||
|------|------|
|
||||
| [CLAUDE.md](./CLAUDE.md) | 项目完整说明 |
|
||||
| [docs/开发指南.md](./docs/开发指南.md) | 开发规范 |
|
||||
| [docs/架构说明.md](./docs/架构说明.md) | 架构设计 |
|
||||
| [docs/DATABASE_MIGRATION.md](./docs/DATABASE_MIGRATION.md) | 数据库迁移指南 |
|
||||
|
||||
本项目已配置 Drone CI/CD 实现代码推送后的全自动编译、部署流程。
|
||||
## 相关仓库
|
||||
|
||||
```
|
||||
开发者 Push 代码
|
||||
↓
|
||||
Gitea 仓库(git.waypeak.work)
|
||||
↓ [Webhook 触发]
|
||||
Drone CI Server(martial-ci.johnsion.club)
|
||||
↓ [Runner 执行]
|
||||
编译 BladeX 框架 → 编译后端项目 → 构建 Docker 镜像 → 部署容器 → 健康检查
|
||||
↓
|
||||
生产服务器部署完成(martial-api.johnsion.club)
|
||||
```
|
||||
| 仓库 | 说明 |
|
||||
|------|------|
|
||||
| [martial-master](https://git.waypeak.work/martial/martial-master) | 后端 API |
|
||||
| [martial-web](https://git.waypeak.work/martial/martial-web) | 管理后台前端 |
|
||||
| [martial-mini](https://git.waypeak.work/martial/martial-mini) | 用户端小程序 |
|
||||
| [martial-admin-mini](https://git.waypeak.work/martial/martial-admin-mini) | 裁判端小程序 |
|
||||
|
||||
### 部署流程
|
||||
## 许可协议
|
||||
|
||||
**日常开发(不触发部署):**
|
||||
|
||||
```bash
|
||||
# 1. 切换到开发分支
|
||||
git checkout dev
|
||||
|
||||
# 2. 修改代码并提交
|
||||
git add .
|
||||
git commit -m "feat: 添加新功能"
|
||||
git push origin dev
|
||||
|
||||
# ✅ 推送到 dev 分支不会触发自动部署
|
||||
```
|
||||
|
||||
**发布到生产环境:**
|
||||
|
||||
```bash
|
||||
# 1. 合并开发分支到 main
|
||||
git checkout main
|
||||
git merge dev
|
||||
|
||||
# 2. 推送到 main 分支(自动触发部署)
|
||||
git push origin main
|
||||
|
||||
# 3. 查看部署进度
|
||||
# 访问 Drone UI: https://martial-ci.johnsion.club
|
||||
# 或等待约 5-6 分钟后直接访问生产环境
|
||||
```
|
||||
|
||||
### 部署步骤(全自动)
|
||||
|
||||
1. **编译完整项目**(约4-5分钟)
|
||||
- 克隆 BladeX 框架代码(martial-tool)
|
||||
- 编译框架并安装到 Maven 本地仓库
|
||||
- 编译后端项目(martial-master)
|
||||
- 生成 blade-api.jar(约236MB)
|
||||
|
||||
2. **构建 Docker 镜像**(约1分钟)
|
||||
- 基于 eclipse-temurin:17-jre-alpine
|
||||
- 复制 JAR 文件和配置
|
||||
- 构建轻量化镜像
|
||||
|
||||
3. **部署到生产环境**(约30秒)
|
||||
- 停止旧容器
|
||||
- 启动新容器
|
||||
- 连接数据库和 Redis
|
||||
|
||||
4. **健康检查**(约45秒)
|
||||
- 等待 Spring Boot 应用完全启动
|
||||
- 检查健康端点: `/actuator/health`
|
||||
- 验证部署成功
|
||||
|
||||
**总耗时:** 约 6-7 分钟
|
||||
|
||||
### 访问地址
|
||||
|
||||
**部署完成后:**
|
||||
- 后端 API: https://martial-api.johnsion.club
|
||||
- API 文档: https://martial-doc.johnsion.club
|
||||
- 健康检查: https://martial-api.johnsion.club/actuator/health
|
||||
- 前端系统: https://martial.johnsion.club
|
||||
|
||||
**CI/CD 管理:**
|
||||
- Drone UI: https://martial-ci.johnsion.club
|
||||
|
||||
### 部署配置
|
||||
|
||||
**生产服务器:**
|
||||
- MySQL 8.0 (Docker 容器)
|
||||
- Redis 7 (Docker 容器)
|
||||
- Docker Network: martial_martial-network
|
||||
|
||||
**环境变量配置在 docker-compose.yml:**
|
||||
```yaml
|
||||
SPRING_PROFILE: prod
|
||||
JAVA_OPTS: "-Xms512m -Xmx1024m"
|
||||
```
|
||||
|
||||
### 故障排查
|
||||
|
||||
**查看部署日志:**
|
||||
```bash
|
||||
# Drone 构建日志
|
||||
访问: https://martial-ci.johnsion.club
|
||||
|
||||
# 应用日志
|
||||
ssh root@154.30.6.21
|
||||
docker logs -f martial-backend
|
||||
```
|
||||
|
||||
**检查服务状态:**
|
||||
```bash
|
||||
# 查看容器状态
|
||||
docker ps | grep martial
|
||||
|
||||
# 查看健康状态
|
||||
curl https://martial-api.johnsion.club/actuator/health
|
||||
|
||||
# 重启服务
|
||||
cd /app/martial && docker-compose restart backend
|
||||
```
|
||||
|
||||
详细部署文档请参考:[docs/CI-CD部署总结.md](./docs/CI-CD部署总结.md)
|
||||
|
||||
## 📚 开发文档
|
||||
|
||||
- **[CLAUDE.md](./CLAUDE.md)** - 项目完整说明、构建命令、技术栈
|
||||
- **[docs/README.md](./docs/README.md)** - 文档索引和快速导航
|
||||
- **[docs/架构说明.md](./docs/架构说明.md)** - BladeX 架构设计说明
|
||||
- **[docs/前后端架构说明.md](./docs/前后端架构说明.md)** - 前后端分离架构
|
||||
- **[docs/开发指南.md](./docs/开发指南.md)** - 开发规范和最佳实践
|
||||
- **[docs/CI-CD部署总结.md](./docs/CI-CD部署总结.md)** - CI/CD 配置和运维
|
||||
|
||||
## 🗄️ 数据库
|
||||
|
||||
**连接信息(生产环境):**
|
||||
- Host: 容器内使用 `martial-mysql`
|
||||
- Port: 3306
|
||||
- Database: martial_db
|
||||
- Username: root
|
||||
- Password: WtcSecure901faf1ac4d32e2bPwd
|
||||
|
||||
**数据库脚本:**
|
||||
- BladeX 框架表: `database/bladex/bladex.mysql.all.create.sql`
|
||||
- Flowable 工作流表: `database/flowable/flowable.mysql.all.create.sql`
|
||||
- 武术业务表: `database/martial-db/martial_db.sql`
|
||||
|
||||
## 🔧 配置说明
|
||||
|
||||
**配置文件优先级:**
|
||||
```
|
||||
application.yml (基础配置)
|
||||
↓
|
||||
application-{profile}.yml (环境配置)
|
||||
↓
|
||||
环境变量 (Docker 容器配置)
|
||||
```
|
||||
|
||||
**环境切换:**
|
||||
```bash
|
||||
# 开发环境
|
||||
mvn spring-boot:run -Dspring-boot.run.profiles=dev
|
||||
|
||||
# 测试环境
|
||||
java -jar blade-api.jar --spring.profiles.active=test
|
||||
|
||||
# 生产环境(Docker)
|
||||
SPRING_PROFILE=prod
|
||||
```
|
||||
|
||||
## 🔐 安全配置
|
||||
|
||||
- **Token 认证**: 无状态 Token 机制
|
||||
- **多租户隔离**: 基于 tenant_id 的数据隔离
|
||||
- **权限控制**: RBAC 角色权限体系
|
||||
- **SQL 监控**: Druid 数据库连接池监控
|
||||
- **API 文档**: 生产环境可配置访问控制
|
||||
|
||||
## 📊 监控和管理
|
||||
|
||||
- **API 文档**: https://martial-doc.johnsion.club
|
||||
- **Druid 监控**: https://martial-api.johnsion.club/druid
|
||||
- 用户名: blade
|
||||
- 密码: 1qaz@WSX
|
||||
- **健康检查**: https://martial-api.johnsion.club/actuator/health
|
||||
- **CI/CD 管理**: https://martial-ci.johnsion.club
|
||||
|
||||
## 🤝 贡献指南
|
||||
|
||||
1. Fork 本仓库
|
||||
2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
|
||||
3. 提交更改 (`git commit -m 'feat: Add some AmazingFeature'`)
|
||||
4. 推送到分支 (`git push origin feature/AmazingFeature`)
|
||||
5. 提交 Pull Request
|
||||
|
||||
**提交规范:**
|
||||
```
|
||||
feat: 新功能
|
||||
fix: 修复 Bug
|
||||
docs: 文档更新
|
||||
style: 代码格式调整
|
||||
refactor: 重构
|
||||
perf: 性能优化
|
||||
test: 测试相关
|
||||
chore: 构建/工具配置
|
||||
```
|
||||
|
||||
## 👥 开发团队
|
||||
|
||||
- **开发者**: JohnSion
|
||||
- **AI 助手**: Claude Code
|
||||
- **基础框架**: BladeX 4.0.1 (上海布雷德科技有限公司)
|
||||
|
||||
## 📄 许可协议
|
||||
|
||||
### BladeX 商业授权
|
||||
|
||||
本项目基于 **BladeX 商业框架** 构建,需遵守以下协议:
|
||||
|
||||
#### 版权声明
|
||||
- BladeX 是一个商业化软件,系列产品知识产权归**上海布雷德科技有限公司**独立所有
|
||||
- 您一旦开始复制、下载、安装或者使用本产品,即被视为完全理解并接受本协议的各项条款
|
||||
- 更多详情请看:[BladeX商业授权许可协议](https://license.bladex.cn)
|
||||
|
||||
#### 授权范围
|
||||
- **专业版**:只可用于**个人学习**及**个人私活**项目,不可用于公司或团队,不可泄露给任何第三方
|
||||
- **企业版**:可用于**企业名下**的任何项目,企业版员工在**未购买**专业版授权前,只授权开发**所在授权企业名下**的项目,**不得将BladeX用于个人私活**
|
||||
- **共同遵守**:若甲方需要您提供项目源码,则需代为甲方购买BladeX企业授权,甲方购买后续的所有项目都无需再次购买授权
|
||||
|
||||
#### 商用权益
|
||||
- ✔️ 遵守[商业协议](https://license.bladex.cn)的前提下,将BladeX系列产品用于授权范围内的商用项目,并上线运营
|
||||
- ✔️ 遵守[商业协议](https://license.bladex.cn)的前提下,不限制项目数,不限制服务器数
|
||||
- ✔️ 遵守[商业协议](https://license.bladex.cn)的前提下,将自行编写的业务代码申请软件著作权
|
||||
|
||||
#### 何为侵权
|
||||
- ❌ 不遵守商业协议,私自销售商业源码
|
||||
- ❌ 以任何理由将BladeX源码用于申请软件著作权
|
||||
- ❌ 将商业源码以任何途径任何理由泄露给未授权的单位或个人
|
||||
- ❌ 开发完毕项目,没有为甲方购买企业授权,向甲方提供了BladeX代码
|
||||
- ❌ 基于BladeX拓展研发与BladeX有竞争关系的衍生框架,并将其开源或销售
|
||||
|
||||
#### 侵权后果
|
||||
- 情节较轻:第一次发现警告处理
|
||||
- 情节较重:封禁账号,踢出商业群,并保留追究法律责任的权利
|
||||
- 情节严重:与本地律师事务所合作,以公司名义起诉侵犯计算机软件著作权
|
||||
|
||||
#### 技术支持
|
||||
- **答疑时间**: 工作日 9:00 ~ 17:00,周末、节假日休息
|
||||
- **技术社区**: https://sns.bladex.cn
|
||||
- **官方QQ**: 1272154962
|
||||
本项目基于 **BladeX 商业框架** 构建,需遵守 [BladeX 商业授权许可协议](https://license.bladex.cn)。
|
||||
|
||||
---
|
||||
|
||||
**最后更新**: 2025-11-30
|
||||
**项目版本**: 4.0.1 RELEASE
|
||||
**部署环境**: Docker + Drone CI/CD
|
||||
**最后更新**: 2024-12-29
|
||||
|
||||
@@ -6254,6 +6254,7 @@ CREATE TABLE `martial_project` (
|
||||
`project_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '项目名称',
|
||||
`project_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '项目编码',
|
||||
`category` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '组别(男子组/女子组)',
|
||||
`event_type` int(0) NULL DEFAULT NULL COMMENT '项目类型: 1-套路 2-散打 3-器械 4-对练',
|
||||
`type` int(0) NULL DEFAULT 1 COMMENT '类型(1-个人,2-双人,3-集体)',
|
||||
`min_participants` int(0) NULL DEFAULT 1 COMMENT '最少参赛人数',
|
||||
`max_participants` int(0) NULL DEFAULT 1 COMMENT '最多参赛人数',
|
||||
@@ -6262,6 +6263,8 @@ CREATE TABLE `martial_project` (
|
||||
`gender_limit` int(0) NULL DEFAULT 0 COMMENT '性别限制(0-不限,1-仅男,2-仅女)',
|
||||
`estimated_duration` int(0) NULL DEFAULT 5 COMMENT '预估时长(分钟)',
|
||||
`price` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '报名费用',
|
||||
`registration_start_time` datetime(0) NULL DEFAULT NULL COMMENT '报名开始时间',
|
||||
`registration_end_time` datetime(0) NULL DEFAULT NULL COMMENT '报名结束时间',
|
||||
`difficulty_coefficient` decimal(5, 2) NULL DEFAULT 1.00 COMMENT '难度系数',
|
||||
`registration_deadline` datetime(0) NULL DEFAULT NULL COMMENT '报名截止时间',
|
||||
`description` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '项目描述',
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# MySQL 数据库
|
||||
mysql:
|
||||
@@ -42,21 +40,61 @@ services:
|
||||
networks:
|
||||
- martial-network
|
||||
|
||||
# 后端应用
|
||||
# MinIO 对象存储
|
||||
minio:
|
||||
image: minio/minio:RELEASE.2024-12-18T13-15-44Z
|
||||
container_name: minio
|
||||
environment:
|
||||
MINIO_ROOT_USER: "JohnSion"
|
||||
MINIO_ROOT_PASSWORD: "v!*BTket4oagDdw"
|
||||
TZ: "Asia/Shanghai"
|
||||
command: server /data --console-address ":9001"
|
||||
volumes:
|
||||
- ./minio_data:/data
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "9001:9001"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://127.0.0.1:9000/minio/health/live"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- martial-network
|
||||
|
||||
# MinIO 初始化 - 创建桶和设置策略
|
||||
minio-init:
|
||||
image: minio/mc:latest
|
||||
depends_on:
|
||||
minio:
|
||||
condition: service_healthy
|
||||
entrypoint: >
|
||||
sh -c "
|
||||
mc alias set local http://minio:9000 $${MINIO_ROOT_USER} $${MINIO_ROOT_PASSWORD} &&
|
||||
mc mb -p local/assets || true &&
|
||||
mc anonymous set download local/assets || true
|
||||
"
|
||||
environment:
|
||||
MINIO_ROOT_USER: "JohnSion"
|
||||
MINIO_ROOT_PASSWORD: "v!*BTket4oagDdw"
|
||||
restart: "no"
|
||||
networks:
|
||||
- martial-network
|
||||
|
||||
# 后端应用(完整构建模式)
|
||||
martial-api:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
dockerfile: Dockerfile.quick
|
||||
container_name: martial-api
|
||||
restart: always
|
||||
environment:
|
||||
SPRING_PROFILE: dev
|
||||
JAVA_OPTS: "-Xms512m -Xmx1024m -XX:+UseG1GC"
|
||||
# 覆盖数据库连接配置
|
||||
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/martial_db?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
|
||||
SPRING_DATASOURCE_USERNAME: root
|
||||
SPRING_DATASOURCE_PASSWORD: 123456
|
||||
# 覆盖 Redis 连接配置
|
||||
SPRING_DATA_REDIS_HOST: redis
|
||||
SPRING_DATA_REDIS_PORT: 6379
|
||||
SPRING_DATA_REDIS_PASSWORD: 123456
|
||||
|
||||
224
docs/DATABASE_MIGRATION.md
Normal file
224
docs/DATABASE_MIGRATION.md
Normal file
@@ -0,0 +1,224 @@
|
||||
# 数据库迁移指南
|
||||
|
||||
本项目使用 **Flyway** 进行数据库版本管理和自动迁移。
|
||||
|
||||
## 概述
|
||||
|
||||
Flyway 是一个数据库迁移工具,它能够:
|
||||
- 自动追踪数据库版本
|
||||
- 按顺序执行迁移脚本
|
||||
- 确保团队成员的数据库结构一致
|
||||
- 支持回滚和修复
|
||||
|
||||
## 工作原理
|
||||
|
||||
1. 应用启动时,Flyway 自动扫描 `src/main/resources/db/migration` 目录
|
||||
2. 检查 `flyway_schema_history` 表,确定已执行的版本
|
||||
3. 按版本号顺序执行未运行的迁移脚本
|
||||
4. 记录执行结果到历史表
|
||||
|
||||
## 迁移脚本命名规范
|
||||
|
||||
```
|
||||
V{版本号}__{描述}.sql
|
||||
```
|
||||
|
||||
### 命名规则
|
||||
|
||||
| 规则 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| 前缀 | 必须以 `V` 开头(大写) | V1, V2, V10 |
|
||||
| 版本号 | 数字,支持小数点 | 1, 2, 2.1, 10 |
|
||||
| 分隔符 | **两个下划线** | `__` |
|
||||
| 描述 | 用下划线连接单词 | add_user_table |
|
||||
| 后缀 | 必须是 `.sql` | .sql |
|
||||
|
||||
### 正确示例
|
||||
|
||||
```
|
||||
V1__baseline.sql # 基线版本
|
||||
V2__add_project_fields.sql # 添加项目字段
|
||||
V3__create_order_table.sql # 创建订单表
|
||||
V4__add_index_to_user.sql # 添加用户索引
|
||||
V4.1__fix_user_column_type.sql # 修复用户列类型(小版本)
|
||||
V10__major_refactor.sql # 大版本重构
|
||||
```
|
||||
|
||||
### 错误示例
|
||||
|
||||
```
|
||||
v1__init.sql # 错误:v 应该大写
|
||||
V1_init.sql # 错误:只有一个下划线
|
||||
V1-init.sql # 错误:使用了连字符
|
||||
V1__init.SQL # 错误:后缀应该小写
|
||||
init.sql # 错误:缺少版本前缀
|
||||
```
|
||||
|
||||
## 如何添加新的迁移
|
||||
|
||||
### 步骤 1:确定版本号
|
||||
|
||||
查看当前最新版本:
|
||||
```bash
|
||||
ls src/main/resources/db/migration/
|
||||
```
|
||||
|
||||
新版本号 = 最新版本号 + 1
|
||||
|
||||
### 步骤 2:创建迁移脚本
|
||||
|
||||
在 `src/main/resources/db/migration/` 目录创建新文件:
|
||||
|
||||
```sql
|
||||
-- =====================================================
|
||||
-- 迁移脚本: [描述]
|
||||
-- 版本: V{版本号}
|
||||
-- 描述: [详细说明]
|
||||
-- 日期: YYYY-MM-DD
|
||||
-- =====================================================
|
||||
|
||||
-- 你的 SQL 语句
|
||||
ALTER TABLE xxx ADD COLUMN yyy VARCHAR(100);
|
||||
```
|
||||
|
||||
### 步骤 3:测试迁移
|
||||
|
||||
本地启动应用,观察日志:
|
||||
```
|
||||
Flyway Community Edition 9.x.x
|
||||
Successfully validated 3 migrations
|
||||
Current version of schema: 2
|
||||
Migrating schema to version 3 - create_order_table
|
||||
Successfully applied 1 migration
|
||||
```
|
||||
|
||||
### 步骤 4:提交代码
|
||||
|
||||
```bash
|
||||
git add src/main/resources/db/migration/V3__xxx.sql
|
||||
git commit -m "db: 添加xxx迁移脚本"
|
||||
git push
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 幂等性脚本
|
||||
|
||||
编写可重复执行的脚本,避免重复执行报错:
|
||||
|
||||
```sql
|
||||
-- 添加列(如果不存在)
|
||||
SET @exist := (SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'your_table'
|
||||
AND column_name = 'new_column');
|
||||
SET @sql := IF(@exist = 0,
|
||||
'ALTER TABLE your_table ADD COLUMN new_column VARCHAR(100)',
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
```
|
||||
|
||||
### 2. 不要修改已执行的脚本
|
||||
|
||||
一旦迁移脚本被执行(已提交到版本库),**永远不要修改它**。
|
||||
|
||||
如果需要修复,创建新的迁移脚本:
|
||||
```
|
||||
V3__create_table.sql # 已执行,有错误
|
||||
V4__fix_v3_error.sql # 新建脚本修复错误
|
||||
```
|
||||
|
||||
### 3. 小步迁移
|
||||
|
||||
每个迁移脚本只做一件事:
|
||||
- V2__add_user_email.sql
|
||||
- V3__add_user_phone.sql
|
||||
- 不要: V2__add_user_email_and_phone_and_address.sql
|
||||
|
||||
### 4. 添加注释
|
||||
|
||||
```sql
|
||||
-- =====================================================
|
||||
-- 迁移脚本: 添加用户邮箱字段
|
||||
-- 版本: V5
|
||||
-- 描述: 为用户表添加邮箱字段,用于接收通知
|
||||
-- 作者: 张三
|
||||
-- 日期: 2024-12-29
|
||||
-- 关联需求: JIRA-123
|
||||
-- =====================================================
|
||||
```
|
||||
|
||||
### 5. 备份数据
|
||||
|
||||
生产环境执行迁移前,务必备份数据库:
|
||||
```bash
|
||||
mysqldump -u root -p martial_db > backup_$(date +%Y%m%d).sql
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 迁移失败怎么办?
|
||||
|
||||
1. 查看错误日志,定位问题
|
||||
2. 修复数据库中的问题(手动)
|
||||
3. 修复迁移脚本
|
||||
4. 执行 Flyway repair(如需要)
|
||||
|
||||
### Q2: 如何跳过某个版本?
|
||||
|
||||
不建议跳过版本。如果必须跳过,可以创建空脚本:
|
||||
```sql
|
||||
-- V3__placeholder.sql
|
||||
-- 此版本跳过
|
||||
SELECT 1;
|
||||
```
|
||||
|
||||
### Q3: 多人开发版本冲突怎么办?
|
||||
|
||||
使用日期时间作为版本号前缀:
|
||||
```
|
||||
V20241229001__add_field.sql
|
||||
V20241229002__fix_bug.sql
|
||||
```
|
||||
|
||||
### Q4: 如何查看迁移历史?
|
||||
|
||||
```sql
|
||||
SELECT * FROM flyway_schema_history ORDER BY installed_rank;
|
||||
```
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
src/main/resources/
|
||||
└── db/
|
||||
└── migration/
|
||||
├── V1__baseline.sql # 基线版本
|
||||
├── V2__add_project_fields.sql # 添加项目字段
|
||||
└── V3__xxx.sql # 后续迁移...
|
||||
```
|
||||
|
||||
## 配置说明
|
||||
|
||||
application.yml 中的 Flyway 配置:
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
flyway:
|
||||
enabled: true # 启用 Flyway
|
||||
locations: classpath:db/migration # 迁移脚本位置
|
||||
table: flyway_schema_history # 版本历史表名
|
||||
baseline-version: 0 # 基线版本号
|
||||
baseline-on-migrate: true # 自动执行基线
|
||||
validate-on-migrate: true # 校验迁移脚本
|
||||
encoding: UTF-8 # 脚本编码
|
||||
out-of-order: false # 禁止乱序执行
|
||||
clean-disabled: true # 禁用清理(生产安全)
|
||||
```
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [Flyway 官方文档](https://flywaydb.org/documentation/)
|
||||
- [Spring Boot Flyway 集成](https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.data-initialization.migration-tool.flyway)
|
||||
BIN
minio_data/.minio.sys/buckets/.bloomcycle.bin/xl.meta
Normal file
BIN
minio_data/.minio.sys/buckets/.bloomcycle.bin/xl.meta
Normal file
Binary file not shown.
BIN
minio_data/.minio.sys/buckets/.usage-cache.bin.bkp/xl.meta
Normal file
BIN
minio_data/.minio.sys/buckets/.usage-cache.bin.bkp/xl.meta
Normal file
Binary file not shown.
BIN
minio_data/.minio.sys/buckets/.usage-cache.bin/xl.meta
Normal file
BIN
minio_data/.minio.sys/buckets/.usage-cache.bin/xl.meta
Normal file
Binary file not shown.
BIN
minio_data/.minio.sys/buckets/.usage.json/xl.meta
Normal file
BIN
minio_data/.minio.sys/buckets/.usage.json/xl.meta
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
minio_data/.minio.sys/buckets/assets/.metadata.bin/xl.meta
Normal file
BIN
minio_data/.minio.sys/buckets/assets/.metadata.bin/xl.meta
Normal file
Binary file not shown.
Binary file not shown.
BIN
minio_data/.minio.sys/buckets/assets/.usage-cache.bin/xl.meta
Normal file
BIN
minio_data/.minio.sys/buckets/assets/.usage-cache.bin/xl.meta
Normal file
Binary file not shown.
BIN
minio_data/.minio.sys/config/config.json/xl.meta
Normal file
BIN
minio_data/.minio.sys/config/config.json/xl.meta
Normal file
Binary file not shown.
BIN
minio_data/.minio.sys/config/iam/format.json/xl.meta
Normal file
BIN
minio_data/.minio.sys/config/iam/format.json/xl.meta
Normal file
Binary file not shown.
1
minio_data/.minio.sys/format.json
Normal file
1
minio_data/.minio.sys/format.json
Normal file
@@ -0,0 +1 @@
|
||||
{"version":"1","format":"xl-single","id":"7aa712c5-97fa-4608-aafd-5e91b82dcaaa","xl":{"version":"3","this":"a0620b80-1f59-4689-8995-4d5bcde4044d","sets":[["a0620b80-1f59-4689-8995-4d5bcde4044d"]],"distributionAlgo":"SIPMOD+PARITY"}}
|
||||
BIN
minio_data/.minio.sys/pool.bin/xl.meta
Normal file
BIN
minio_data/.minio.sys/pool.bin/xl.meta
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
10
pom.xml
10
pom.xml
@@ -228,6 +228,16 @@
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Flyway 数据库迁移 -->
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-mysql</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.springblade.modules.martial.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 赛程编排配置
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "martial.schedule")
|
||||
public class ScheduleConfig {
|
||||
|
||||
/**
|
||||
* 个人项目每组最大人数(超过此数量将自动拆分)
|
||||
*/
|
||||
private int maxPeoplePerGroup = 35;
|
||||
|
||||
/**
|
||||
* 拆分后每组目标人数
|
||||
*/
|
||||
private int targetPeoplePerGroup = 33;
|
||||
|
||||
/**
|
||||
* 默认每人比赛时长(分钟)
|
||||
*/
|
||||
private int defaultDurationPerPerson = 5;
|
||||
|
||||
/**
|
||||
* 容量利用率警告阈值(百分比)
|
||||
*/
|
||||
private int capacityWarningThreshold = 90;
|
||||
|
||||
/**
|
||||
* 是否允许容量超载(超载时仍继续编排)
|
||||
*/
|
||||
private boolean allowOverload = false;
|
||||
|
||||
/**
|
||||
* 上午时段开始时间
|
||||
*/
|
||||
private String morningStartTime = "08:00";
|
||||
|
||||
/**
|
||||
* 上午时段结束时间
|
||||
*/
|
||||
private String morningEndTime = "12:00";
|
||||
|
||||
/**
|
||||
* 下午时段开始时间
|
||||
*/
|
||||
private String afternoonStartTime = "14:00";
|
||||
|
||||
/**
|
||||
* 下午时段结束时间
|
||||
*/
|
||||
private String afternoonEndTime = "18:00";
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import com.qiniu.util.Auth;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.boot.ctrl.BladeController;
|
||||
import org.springblade.core.mp.support.Condition;
|
||||
import org.springblade.core.mp.support.Query;
|
||||
@@ -22,6 +23,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
* @author BladeX
|
||||
*/
|
||||
@RestController
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
@RequestMapping("/martial/athlete")
|
||||
@Tag(name = "参赛选手管理", description = "参赛选手接口")
|
||||
@@ -55,8 +57,13 @@ public class MartialAthleteController extends BladeController {
|
||||
@PostMapping("/submit")
|
||||
@Operation(summary = "新增或修改", description = "传入实体")
|
||||
public R submit(@RequestBody MartialAthlete athlete) {
|
||||
athlete.setCreateUser(AuthUtil.getUserId());
|
||||
athlete.setUpdateUser(AuthUtil.getUserId());
|
||||
Long userId = AuthUtil.getUserId();
|
||||
log.info("=== 提交选手 === userId: {}, playerName: {}", userId, athlete.getPlayerName());
|
||||
// Only set createUser for new records (when id is null)
|
||||
if (athlete.getId() == null) {
|
||||
athlete.setCreateUser(userId);
|
||||
}
|
||||
athlete.setUpdateUser(userId);
|
||||
return R.status(athleteService.saveOrUpdate(athlete));
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialAthlete;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialCompetition;
|
||||
import org.springblade.modules.martial.service.IMartialAthleteService;
|
||||
import org.springblade.modules.martial.mapper.MartialAthleteMapper;
|
||||
import org.springblade.modules.martial.service.IMartialCompetitionService;
|
||||
import org.springblade.modules.system.pojo.entity.User;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -42,6 +43,11 @@ public class MartialCompetitionController extends BladeController {
|
||||
@Operation(summary = "详情", description = "传入ID")
|
||||
public R<MartialCompetition> detail(@RequestParam Long id) {
|
||||
MartialCompetition detail = competitionService.getById(id);
|
||||
if (detail != null) {
|
||||
// Count distinct participants by id_card
|
||||
Long cnt = ((MartialAthleteMapper) martialAthleteService.getBaseMapper()).countDistinctParticipants(detail.getId());
|
||||
detail.setTotalParticipants(cnt != null ? cnt.intValue() : 0);
|
||||
}
|
||||
return R.data(detail);
|
||||
}
|
||||
|
||||
@@ -54,11 +60,9 @@ public class MartialCompetitionController extends BladeController {
|
||||
IPage<MartialCompetition> pages = competitionService.page(Condition.getPage(query), Condition.getQueryWrapper(competition));
|
||||
List<MartialCompetition> pagelist = pages.getRecords();
|
||||
for (MartialCompetition martialCompetition : pagelist) {
|
||||
Long cnt = martialAthleteService.count(Wrappers.<MartialAthlete>query().lambda()
|
||||
.eq(MartialAthlete::getCompetitionId, martialCompetition.getId())
|
||||
.eq(MartialAthlete::getIsDeleted, 0)
|
||||
);
|
||||
martialCompetition.setTotalParticipants(cnt.intValue());
|
||||
// Count distinct participants by id_card
|
||||
Long cnt = ((MartialAthleteMapper) martialAthleteService.getBaseMapper()).countDistinctParticipants(martialCompetition.getId());
|
||||
martialCompetition.setTotalParticipants(cnt != null ? cnt.intValue() : 0);
|
||||
}
|
||||
return R.data(pages);
|
||||
}
|
||||
@@ -68,8 +72,9 @@ public class MartialCompetitionController extends BladeController {
|
||||
*/
|
||||
@PostMapping("/submit")
|
||||
@Operation(summary = "新增或修改", description = "传入实体")
|
||||
public R submit(@RequestBody MartialCompetition competition) {
|
||||
return R.status(competitionService.saveOrUpdate(competition));
|
||||
public R<MartialCompetition> submit(@RequestBody MartialCompetition competition) {
|
||||
competitionService.saveOrUpdate(competition);
|
||||
return R.data(competition);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,7 +5,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springblade.core.boot.ctrl.BladeController;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialCompetitionRulesAttachment;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialCompetitionAttachment;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialCompetitionRulesChapter;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialCompetitionRulesContent;
|
||||
import org.springblade.modules.martial.pojo.vo.MartialCompetitionRulesVO;
|
||||
@@ -45,8 +45,8 @@ public class MartialCompetitionRulesController extends BladeController {
|
||||
*/
|
||||
@GetMapping("/attachment/list")
|
||||
@Operation(summary = "获取附件列表", description = "管理端获取附件列表")
|
||||
public R<List<MartialCompetitionRulesAttachment>> getAttachmentList(@RequestParam Long competitionId) {
|
||||
List<MartialCompetitionRulesAttachment> list = rulesService.getAttachmentList(competitionId);
|
||||
public R<List<MartialCompetitionAttachment>> getAttachmentList(@RequestParam Long competitionId) {
|
||||
List<MartialCompetitionAttachment> list = rulesService.getAttachmentList(competitionId);
|
||||
return R.data(list);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ public class MartialCompetitionRulesController extends BladeController {
|
||||
*/
|
||||
@PostMapping("/attachment/save")
|
||||
@Operation(summary = "保存附件", description = "新增或修改附件")
|
||||
public R saveAttachment(@RequestBody MartialCompetitionRulesAttachment attachment) {
|
||||
public R saveAttachment(@RequestBody MartialCompetitionAttachment attachment) {
|
||||
return R.status(rulesService.saveAttachment(attachment));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.springblade.modules.martial.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.boot.ctrl.BladeController;
|
||||
import org.springblade.core.mp.support.Query;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialContact;
|
||||
import org.springblade.modules.martial.service.IMartialContactService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@RequestMapping("/martial/contact")
|
||||
@Tag(name = "联系人管理", description = "联系人接口")
|
||||
public class MartialContactController extends BladeController {
|
||||
|
||||
private final IMartialContactService contactService;
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "分页列表", description = "获取当前用户的联系人列表")
|
||||
public R<IPage<MartialContact>> list(Query query) {
|
||||
Long userId = AuthUtil.getUserId();
|
||||
IPage<MartialContact> pages = contactService.getContactList(userId, query.getCurrent(), query.getSize());
|
||||
return R.data(pages);
|
||||
}
|
||||
|
||||
@GetMapping("/detail")
|
||||
@Operation(summary = "详情", description = "获取联系人详情")
|
||||
public R<MartialContact> detail(@RequestParam Long id) {
|
||||
return R.data(contactService.getContactDetail(id));
|
||||
}
|
||||
|
||||
@PostMapping("/submit")
|
||||
@Operation(summary = "保存", description = "新增或修改联系人")
|
||||
public R<Boolean> submit(@RequestBody MartialContact contact) {
|
||||
Long userId = AuthUtil.getUserId();
|
||||
log.info("Contact submit - id: {}, name: {}, userId: {}, isDefault: {}",
|
||||
contact.getId(), contact.getName(), userId, contact.getIsDefault());
|
||||
|
||||
return R.data(contactService.saveContact(contact, userId));
|
||||
}
|
||||
|
||||
@PostMapping("/remove")
|
||||
@Operation(summary = "删除", description = "删除联系人")
|
||||
public R<Boolean> remove(@RequestParam String ids) {
|
||||
return R.data(contactService.removeByIds(Func.toLongList(ids)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -49,10 +49,13 @@ public class MartialDeductionItemController extends BladeController {
|
||||
@Operation(summary = "分页列表", description = "分页查询")
|
||||
public R<IPage<MartialDeductionItem>> list(MartialDeductionItem deductionItem, Query query) {
|
||||
IPage<MartialDeductionItem> pages = deductionItemService.page(Condition.getPage(query), Condition.getQueryWrapper(deductionItem));
|
||||
List<MartialDeductionItem> deductionItems = pages.getRecords();
|
||||
List<MartialDeductionItem> deductionItems = pages.getRecords();
|
||||
for (MartialDeductionItem item : deductionItems) {
|
||||
MartialProject project = martialProjectService.getById(item.getProjectId());
|
||||
item.setProjectName(project.getProjectName());
|
||||
if (project != null) {
|
||||
item.setProjectName(project.getProjectName());
|
||||
item.setCompetitionId(project.getCompetitionId());
|
||||
}
|
||||
}
|
||||
return R.data(pages);
|
||||
}
|
||||
|
||||
@@ -10,26 +10,19 @@ import org.springblade.core.tool.utils.DateUtil;
|
||||
import org.springblade.modules.martial.excel.AthleteExportExcel;
|
||||
import org.springblade.modules.martial.excel.ResultExportExcel;
|
||||
import org.springblade.modules.martial.excel.ScheduleExportExcel;
|
||||
import org.springblade.modules.martial.excel.ScheduleExportExcel2;
|
||||
import org.springblade.modules.martial.pojo.vo.CertificateVO;
|
||||
import org.springblade.modules.martial.service.IMartialAthleteService;
|
||||
import org.springblade.modules.martial.service.IMartialResultService;
|
||||
import org.springblade.modules.martial.service.IMartialScheduleService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 导出打印 控制器
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@RequestMapping("/martial/export")
|
||||
@@ -40,67 +33,47 @@ public class MartialExportController {
|
||||
private final IMartialAthleteService athleteService;
|
||||
private final IMartialScheduleService scheduleService;
|
||||
|
||||
/**
|
||||
* Task 3.1: 导出成绩单
|
||||
*/
|
||||
@GetMapping("/results")
|
||||
@Operation(summary = "导出成绩单", description = "导出指定赛事或项目的成绩单Excel")
|
||||
public void exportResults(
|
||||
@RequestParam Long competitionId,
|
||||
@RequestParam(required = false) Long projectId,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
public void exportResults(@RequestParam Long competitionId, @RequestParam(required = false) Long projectId, HttpServletResponse response) {
|
||||
List<ResultExportExcel> list = resultService.exportResults(competitionId, projectId);
|
||||
String fileName = "成绩单_" + DateUtil.today();
|
||||
String sheetName = projectId != null ? "项目成绩单" : "全部成绩";
|
||||
ExcelUtil.export(response, fileName, sheetName, list, ResultExportExcel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Task 3.2: 导出运动员名单
|
||||
*/
|
||||
@GetMapping("/athletes")
|
||||
@Operation(summary = "导出运动员名单", description = "导出指定赛事的运动员名单Excel")
|
||||
public void exportAthletes(
|
||||
@RequestParam Long competitionId,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
public void exportAthletes(@RequestParam Long competitionId, HttpServletResponse response) {
|
||||
List<AthleteExportExcel> list = athleteService.exportAthletes(competitionId);
|
||||
String fileName = "运动员名单_" + DateUtil.today();
|
||||
ExcelUtil.export(response, fileName, "运动员名单", list, AthleteExportExcel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Task 3.3: 导出赛程表
|
||||
*/
|
||||
@GetMapping("/schedule")
|
||||
@Operation(summary = "导出赛程表", description = "导出指定赛事的赛程安排Excel")
|
||||
public void exportSchedule(
|
||||
@RequestParam Long competitionId,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
public void exportSchedule(@RequestParam Long competitionId, HttpServletResponse response) {
|
||||
List<ScheduleExportExcel> list = scheduleService.exportSchedule(competitionId);
|
||||
String fileName = "赛程表_" + DateUtil.today();
|
||||
ExcelUtil.export(response, fileName, "赛程安排", list, ScheduleExportExcel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Task 3.4: 生成单个证书(HTML格式)
|
||||
*/
|
||||
@GetMapping("/certificate/{resultId}")
|
||||
@Operation(summary = "生成证书", description = "生成获奖证书HTML页面,可打印为PDF")
|
||||
public void generateCertificate(
|
||||
@PathVariable Long resultId,
|
||||
HttpServletResponse response
|
||||
) throws IOException {
|
||||
// 1. 获取证书数据
|
||||
CertificateVO certificate = resultService.generateCertificateData(resultId);
|
||||
@GetMapping("/schedule2")
|
||||
@Operation(summary = "导出赛程表-模板2", description = "按场地导出比赛时间表格式的赛程安排")
|
||||
public void exportScheduleTemplate2(@RequestParam Long competitionId, @RequestParam(required = false) Long venueId,
|
||||
@RequestParam(required = false) String venueName, @RequestParam(required = false) String timeSlot, HttpServletResponse response) {
|
||||
List<ScheduleExportExcel2> list = scheduleService.exportScheduleTemplate2(competitionId, venueId);
|
||||
String fileName = "比赛时间_" + (venueName != null ? venueName : "全部场地") + "_" + DateUtil.today();
|
||||
String sheetName = (venueName != null ? venueName : "全部场地") + (timeSlot != null ? "_" + timeSlot : "");
|
||||
ExcelUtil.export(response, fileName, sheetName, list, ScheduleExportExcel2.class);
|
||||
}
|
||||
|
||||
// 2. 读取HTML模板
|
||||
@GetMapping("/certificate/{resultId}")
|
||||
@Operation(summary = "生成证书", description = "生成获奖证书HTML页面")
|
||||
public void generateCertificate(@PathVariable Long resultId, HttpServletResponse response) throws IOException {
|
||||
CertificateVO certificate = resultService.generateCertificateData(resultId);
|
||||
Path templatePath = Path.of("src/main/resources/templates/certificate/certificate.html");
|
||||
String template = Files.readString(templatePath, StandardCharsets.UTF_8);
|
||||
|
||||
// 3. 替换模板变量
|
||||
String html = template
|
||||
.replace("${playerName}", certificate.getPlayerName())
|
||||
.replace("${competitionName}", certificate.getCompetitionName())
|
||||
@@ -109,15 +82,10 @@ public class MartialExportController {
|
||||
.replace("${medalClass}", certificate.getMedalClass())
|
||||
.replace("${organization}", certificate.getOrganization())
|
||||
.replace("${issueDate}", certificate.getIssueDate());
|
||||
|
||||
// 4. 返回HTML
|
||||
response.setContentType("text/html;charset=UTF-8");
|
||||
response.getWriter().write(html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Task 3.4: 批量生成证书数据
|
||||
*/
|
||||
@GetMapping("/certificates/batch")
|
||||
@Operation(summary = "批量生成证书数据", description = "批量获取项目获奖选手的证书数据")
|
||||
public R<List<CertificateVO>> batchGenerateCertificates(@RequestParam Long projectId) {
|
||||
@@ -125,14 +93,10 @@ public class MartialExportController {
|
||||
return R.data(certificates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Task 3.4: 获取单个证书数据(JSON格式)
|
||||
*/
|
||||
@GetMapping("/certificate/data/{resultId}")
|
||||
@Operation(summary = "获取证书数据", description = "获取证书数据(JSON格式),供前端渲染")
|
||||
@Operation(summary = "获取证书数据", description = "获取证书数据(JSON格式)")
|
||||
public R<CertificateVO> getCertificateData(@PathVariable Long resultId) {
|
||||
CertificateVO certificate = resultService.generateCertificateData(resultId);
|
||||
return R.data(certificate);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -224,4 +224,24 @@ public class MartialJudgeInviteController extends BladeController {
|
||||
// 使用EasyExcel或POI导出
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 更新邀请的项目分配
|
||||
*/
|
||||
@PutMapping("/updateProjects")
|
||||
@Operation(summary = "更新项目分配", description = "更新裁判邀请的项目分配")
|
||||
public R updateProjects(@RequestBody java.util.Map<String, Object> params) {
|
||||
Long inviteId = Long.valueOf(params.get("inviteId").toString());
|
||||
String projects = params.get("projects").toString();
|
||||
|
||||
MartialJudgeInvite invite = judgeInviteService.getById(inviteId);
|
||||
if (invite == null) {
|
||||
return R.fail("邀请记录不存在");
|
||||
}
|
||||
|
||||
invite.setProjects(projects);
|
||||
boolean success = judgeInviteService.updateById(invite);
|
||||
return success ? R.success("更新成功") : R.fail("更新失败");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,11 +19,19 @@ import org.springblade.modules.martial.pojo.vo.MiniAthleteAdminVO;
|
||||
import org.springblade.modules.martial.pojo.vo.MiniAthleteScoreVO;
|
||||
import org.springblade.modules.martial.pojo.vo.MiniLoginVO;
|
||||
import org.springblade.modules.martial.pojo.vo.MiniScoreDetailVO;
|
||||
import org.springblade.modules.martial.pojo.vo.LineupGroupVO;
|
||||
import org.springblade.modules.martial.pojo.vo.LineupParticipantVO;
|
||||
import org.springblade.modules.martial.pojo.vo.ScheduleGroupDetailVO;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import org.springblade.modules.martial.service.*;
|
||||
import org.springblade.modules.martial.pojo.dto.ChiefJudgeConfirmDTO;
|
||||
import org.springblade.modules.martial.pojo.dto.GeneralJudgeConfirmDTO;
|
||||
import org.springblade.modules.martial.pojo.entity.MtVenue;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialVenue;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialResult;
|
||||
import org.springblade.core.redis.cache.BladeRedis;
|
||||
import org.springblade.modules.martial.mapper.MartialScheduleStatusMapper;
|
||||
import org.springblade.modules.martial.mapper.MartialScheduleGroupMapper;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
@@ -31,6 +39,8 @@ import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
@@ -56,6 +66,9 @@ public class MartialMiniController extends BladeController {
|
||||
private final IMartialAthleteService athleteService;
|
||||
private final IMartialScoreService scoreService;
|
||||
private final BladeRedis bladeRedis;
|
||||
private final IMartialResultService resultService;
|
||||
private final MartialScheduleStatusMapper scheduleStatusMapper;
|
||||
private final MartialScheduleGroupMapper scheduleGroupMapper;
|
||||
|
||||
// Redis缓存key前缀
|
||||
private static final String MINI_LOGIN_CACHE_PREFIX = "mini:login:";
|
||||
@@ -110,11 +123,35 @@ public class MartialMiniController extends BladeController {
|
||||
martialVenue = venueService.getById(invite.getVenueId());
|
||||
}
|
||||
|
||||
List<MiniLoginVO.ProjectInfo> projects = parseProjects(invite.getProjects());
|
||||
// 获取项目列表:总裁判看所有项目,其他裁判根据场地获取项目
|
||||
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
||||
Integer refereeTypeVal = invite.getRefereeType();
|
||||
String roleVal = invite.getRole();
|
||||
boolean isGeneralJudge = (refereeTypeVal != null && refereeTypeVal == 3)
|
||||
|| "general_judge".equals(roleVal) || "general".equals(roleVal);
|
||||
|
||||
if (isGeneralJudge) {
|
||||
// 总裁判看所有项目
|
||||
projects = getAllProjectsByCompetition(competition.getId());
|
||||
} else if (Func.isNotEmpty(invite.getProjects())) {
|
||||
projects = parseProjects(invite.getProjects());
|
||||
} else if (invite.getVenueId() != null) {
|
||||
// 未指定项目,根据场地获取项目;如果场地没有项目则返回空列表
|
||||
projects = getProjectsByVenue(invite.getVenueId());
|
||||
}
|
||||
// 如果没有场地,projects保持为空列表
|
||||
|
||||
MiniLoginVO vo = new MiniLoginVO();
|
||||
vo.setToken(token);
|
||||
vo.setUserRole("chief_judge".equals(invite.getRole()) ? "admin" : "pub");
|
||||
String role = invite.getRole();
|
||||
Integer refereeType = invite.getRefereeType();
|
||||
if ("general_judge".equals(role) || "general".equals(role) || (refereeType != null && refereeType == 3)) {
|
||||
vo.setUserRole("general");
|
||||
} else if ("chief_judge".equals(role) || (refereeType != null && refereeType == 1)) {
|
||||
vo.setUserRole("admin");
|
||||
} else {
|
||||
vo.setUserRole("pub");
|
||||
}
|
||||
vo.setMatchId(competition.getId());
|
||||
vo.setMatchName(competition.getCompetitionName());
|
||||
vo.setMatchTime(competition.getCompetitionStartTime() != null ?
|
||||
@@ -192,18 +229,18 @@ public class MartialMiniController extends BladeController {
|
||||
*/
|
||||
private void updateAthleteTotalScore(Long athleteId, Long projectId, Long venueId) {
|
||||
try {
|
||||
// 1. 查询该场地的普通裁判数量
|
||||
int requiredJudgeCount = getRequiredJudgeCount(projectId);
|
||||
// 1. 查询该场地的裁判员数量
|
||||
int requiredJudgeCount = getRequiredJudgeCount(venueId);
|
||||
|
||||
// 2. 获取裁判长ID列表
|
||||
// 2. 获取主裁判ID列表
|
||||
List<Long> chiefJudgeIds = getChiefJudgeIds(venueId);
|
||||
|
||||
// 3. 查询该选手在该项目的所有评分(排除裁判长的评分)
|
||||
// 3. 查询该选手在该项目的所有评分(排除主裁判的评分)
|
||||
LambdaQueryWrapper<MartialScore> scoreQuery = new LambdaQueryWrapper<>();
|
||||
scoreQuery.eq(MartialScore::getAthleteId, athleteId);
|
||||
scoreQuery.eq(MartialScore::getProjectId, projectId);
|
||||
scoreQuery.eq(MartialScore::getIsDeleted, 0);
|
||||
// 排除裁判长的所有评分(包括普通评分和修改记录)
|
||||
// 排除主裁判的所有评分(包括普通评分和修改记录)
|
||||
if (!chiefJudgeIds.isEmpty()) {
|
||||
scoreQuery.notIn(MartialScore::getJudgeId, chiefJudgeIds);
|
||||
}
|
||||
@@ -243,20 +280,19 @@ public class MartialMiniController extends BladeController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目应评分的裁判数量(普通裁判,不包括裁判长)
|
||||
* 获取项目应评分的裁判数量(裁判员,不包括主裁判)
|
||||
* 按项目过滤:检查 projects JSON 字段是否包含该项目ID
|
||||
*/
|
||||
private int getRequiredJudgeCount(Long projectId) {
|
||||
if (projectId == null) {
|
||||
private int getRequiredJudgeCount(Long venueId) {
|
||||
if (venueId == null || venueId <= 0) {
|
||||
return 0;
|
||||
}
|
||||
LambdaQueryWrapper<MartialJudgeInvite> judgeQuery = new LambdaQueryWrapper<>();
|
||||
judgeQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
||||
judgeQuery.ne(MartialJudgeInvite::getRole, "chief_judge"); // 排除裁判长
|
||||
// 按项目过滤:projects字段包含该项目ID
|
||||
judgeQuery.like(MartialJudgeInvite::getProjects, projectId.toString());
|
||||
judgeQuery.eq(MartialJudgeInvite::getVenueId, venueId);
|
||||
judgeQuery.eq(MartialJudgeInvite::getRefereeType, 2); // Only count referees (type=2), exclude chief judge (type=1) and general judge (type=3)
|
||||
List<MartialJudgeInvite> judges = judgeInviteService.list(judgeQuery);
|
||||
// 使用 distinct judge_id 来计算不重复的裁判数量
|
||||
// Use distinct judge_id to count unique judges
|
||||
return (int) judges.stream()
|
||||
.map(MartialJudgeInvite::getJudgeId)
|
||||
.filter(Objects::nonNull)
|
||||
@@ -318,8 +354,8 @@ public class MartialMiniController extends BladeController {
|
||||
|
||||
/**
|
||||
* 获取选手列表(支持分页)
|
||||
* - 普通裁判:获取所有选手,标记是否已评分
|
||||
* - 裁判长:获取所有普通裁判都评分完成的选手列表
|
||||
* - 裁判员:获取所有选手,标记是否已评分
|
||||
* - 主裁判:获取所有裁判员都评分完成的选手列表
|
||||
*/
|
||||
@GetMapping("/score/athletes")
|
||||
@Operation(summary = "获取选手列表", description = "根据裁判类型获取选手列表(支持分页)")
|
||||
@@ -328,6 +364,7 @@ public class MartialMiniController extends BladeController {
|
||||
@RequestParam Integer refereeType,
|
||||
@RequestParam(required = false) Long projectId,
|
||||
@RequestParam(required = false) Long venueId,
|
||||
@RequestParam(required = false) Long competitionId,
|
||||
@RequestParam(defaultValue = "1") Integer current,
|
||||
@RequestParam(defaultValue = "10") Integer size
|
||||
) {
|
||||
@@ -335,6 +372,11 @@ public class MartialMiniController extends BladeController {
|
||||
LambdaQueryWrapper<MartialAthlete> athleteQuery = new LambdaQueryWrapper<>();
|
||||
athleteQuery.eq(MartialAthlete::getIsDeleted, 0);
|
||||
|
||||
// 按比赛ID过滤(重要:确保只显示当前比赛的选手)
|
||||
if (competitionId != null) {
|
||||
athleteQuery.eq(MartialAthlete::getCompetitionId, competitionId);
|
||||
}
|
||||
|
||||
if (projectId != null) {
|
||||
athleteQuery.eq(MartialAthlete::getProjectId, projectId);
|
||||
}
|
||||
@@ -343,10 +385,10 @@ public class MartialMiniController extends BladeController {
|
||||
|
||||
List<MartialAthlete> athletes = athleteService.list(athleteQuery);
|
||||
|
||||
// 2. 获取该场地所有裁判长的judge_id列表
|
||||
// 2. 获取该场地所有主裁判的judge_id列表
|
||||
List<Long> chiefJudgeIds = getChiefJudgeIds(venueId);
|
||||
|
||||
// 3. 获取所有评分记录(排除裁判长的评分)
|
||||
// 3. 获取所有评分记录(排除主裁判的评分)
|
||||
LambdaQueryWrapper<MartialScore> scoreQuery = new LambdaQueryWrapper<>();
|
||||
scoreQuery.eq(MartialScore::getIsDeleted, 0);
|
||||
if (projectId != null) {
|
||||
@@ -356,7 +398,7 @@ public class MartialMiniController extends BladeController {
|
||||
if (venueId != null && venueId > 0) {
|
||||
scoreQuery.eq(MartialScore::getVenueId, venueId);
|
||||
}
|
||||
// 排除裁判长的评分
|
||||
// 排除主裁判的评分
|
||||
if (!chiefJudgeIds.isEmpty()) {
|
||||
scoreQuery.notIn(MartialScore::getJudgeId, chiefJudgeIds);
|
||||
}
|
||||
@@ -367,18 +409,18 @@ public class MartialMiniController extends BladeController {
|
||||
.collect(java.util.stream.Collectors.groupingBy(MartialScore::getAthleteId));
|
||||
|
||||
// 4. 获取该场地的应评裁判数量
|
||||
int requiredJudgeCount = getRequiredJudgeCount(projectId);
|
||||
int requiredJudgeCount = getRequiredJudgeCount(venueId);
|
||||
|
||||
// 5. 根据裁判类型处理选手列表
|
||||
List<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> filteredList;
|
||||
|
||||
if (refereeType == 1) {
|
||||
// 裁判长:返回所有选手,前端根据totalScore判断是否显示修改按钮
|
||||
// 主裁判:返回所有选手,前端根据totalScore判断是否显示修改按钮
|
||||
filteredList = athletes.stream()
|
||||
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId()), judgeId, requiredJudgeCount))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
} else {
|
||||
// 普通裁判:返回所有选手,标记是否已评分
|
||||
// 裁判员:返回所有选手,标记是否已评分
|
||||
filteredList = athletes.stream()
|
||||
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId()), judgeId, requiredJudgeCount))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
@@ -404,7 +446,7 @@ public class MartialMiniController extends BladeController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取场地所有裁判长的judge_id列表
|
||||
* 获取场地所有主裁判的judge_id列表
|
||||
*/
|
||||
private List<Long> getChiefJudgeIds(Long venueId) {
|
||||
if (venueId == null) {
|
||||
@@ -432,10 +474,10 @@ public class MartialMiniController extends BladeController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改评分(裁判长)
|
||||
* 修改评分(主裁判)
|
||||
*/
|
||||
@PutMapping("/score/modify")
|
||||
@Operation(summary = "修改评分", description = "裁判长修改选手总分")
|
||||
@Operation(summary = "修改评分", description = "主裁判修改选手总分")
|
||||
public R modifyScore(@RequestBody MiniScoreModifyDTO dto) {
|
||||
boolean success = scoreService.modifyScoreByAdmin(dto);
|
||||
return success ? R.success("修改成功") : R.fail("修改失败");
|
||||
@@ -493,11 +535,35 @@ public class MartialMiniController extends BladeController {
|
||||
MartialCompetition competition = competitionService.getById(invite.getCompetitionId());
|
||||
MartialJudge judge = judgeService.getById(invite.getJudgeId());
|
||||
MartialVenue martialVenue = invite.getVenueId() != null ? venueService.getById(invite.getVenueId()) : null;
|
||||
List<MiniLoginVO.ProjectInfo> projects = parseProjects(invite.getProjects());
|
||||
// 获取项目列表:总裁判看所有项目,其他裁判根据场地获取项目
|
||||
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
||||
Integer refereeTypeVal = invite.getRefereeType();
|
||||
String roleVal = invite.getRole();
|
||||
boolean isGeneralJudge = (refereeTypeVal != null && refereeTypeVal == 3)
|
||||
|| "general_judge".equals(roleVal) || "general".equals(roleVal);
|
||||
|
||||
if (isGeneralJudge) {
|
||||
// 总裁判看所有项目
|
||||
projects = getAllProjectsByCompetition(competition.getId());
|
||||
} else if (Func.isNotEmpty(invite.getProjects())) {
|
||||
projects = parseProjects(invite.getProjects());
|
||||
} else if (invite.getVenueId() != null) {
|
||||
// 未指定项目,根据场地获取项目;如果场地没有项目则返回空列表
|
||||
projects = getProjectsByVenue(invite.getVenueId());
|
||||
}
|
||||
// 如果没有场地,projects保持为空列表
|
||||
|
||||
MiniLoginVO vo = new MiniLoginVO();
|
||||
vo.setToken(token);
|
||||
vo.setUserRole("chief_judge".equals(invite.getRole()) ? "admin" : "pub");
|
||||
String role = invite.getRole();
|
||||
Integer refereeType = invite.getRefereeType();
|
||||
if ("general_judge".equals(role) || "general".equals(role) || (refereeType != null && refereeType == 3)) {
|
||||
vo.setUserRole("general");
|
||||
} else if ("chief_judge".equals(role) || (refereeType != null && refereeType == 1)) {
|
||||
vo.setUserRole("admin");
|
||||
} else {
|
||||
vo.setUserRole("pub");
|
||||
}
|
||||
vo.setMatchId(competition != null ? competition.getId() : null);
|
||||
vo.setMatchName(competition != null ? competition.getCompetitionName() : null);
|
||||
vo.setMatchTime(competition != null && competition.getCompetitionStartTime() != null ?
|
||||
@@ -634,4 +700,271 @@ public class MartialMiniController extends BladeController {
|
||||
return projects;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取比赛的所有项目
|
||||
*/
|
||||
private List<MiniLoginVO.ProjectInfo> getAllProjectsByCompetition(Long competitionId) {
|
||||
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
||||
|
||||
LambdaQueryWrapper<MartialProject> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(MartialProject::getCompetitionId, competitionId);
|
||||
wrapper.eq(MartialProject::getIsDeleted, 0);
|
||||
|
||||
List<MartialProject> projectList = projectService.list(wrapper);
|
||||
|
||||
if (Func.isNotEmpty(projectList)) {
|
||||
projects = projectList.stream().map(project -> {
|
||||
MiniLoginVO.ProjectInfo info = new MiniLoginVO.ProjectInfo();
|
||||
info.setProjectId(project.getId());
|
||||
info.setProjectName(project.getProjectName());
|
||||
return info;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
return projects;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据场地获取项目列表
|
||||
*/
|
||||
private List<MiniLoginVO.ProjectInfo> getProjectsByVenue(Long venueId) {
|
||||
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
||||
|
||||
LambdaQueryWrapper<MartialProject> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(MartialProject::getVenueId, venueId);
|
||||
wrapper.eq(MartialProject::getIsDeleted, 0);
|
||||
|
||||
List<MartialProject> projectList = projectService.list(wrapper);
|
||||
|
||||
if (Func.isNotEmpty(projectList)) {
|
||||
projects = projectList.stream().map(project -> {
|
||||
MiniLoginVO.ProjectInfo info = new MiniLoginVO.ProjectInfo();
|
||||
info.setProjectId(project.getId());
|
||||
info.setProjectName(project.getProjectName());
|
||||
return info;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
return projects;
|
||||
}
|
||||
|
||||
|
||||
// ========== 三级裁判评分流程 API ==========
|
||||
|
||||
/**
|
||||
* 主裁判确认/修改分数
|
||||
*/
|
||||
@PostMapping("/chief/confirm")
|
||||
@Operation(summary = "主裁判确认分数", description = "主裁判确认或修改选手分数")
|
||||
public R confirmByChiefJudge(@RequestBody ChiefJudgeConfirmDTO dto) {
|
||||
Long resultId = parseLong(dto.getResultId());
|
||||
Long chiefJudgeId = parseLong(dto.getChiefJudgeId());
|
||||
if (resultId == null || chiefJudgeId == null) {
|
||||
return R.fail("参数错误");
|
||||
}
|
||||
boolean success = resultService.confirmByChiefJudge(resultId, chiefJudgeId, dto.getScore(), dto.getNote());
|
||||
return success ? R.success("确认成功") : R.fail("确认失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 总裁确认/修改分数
|
||||
*/
|
||||
@PostMapping("/general/confirm")
|
||||
@Operation(summary = "总裁确认分数", description = "总裁确认或修改选手分数")
|
||||
public R confirmByGeneralJudge(@RequestBody GeneralJudgeConfirmDTO dto) {
|
||||
Long resultId = parseLong(dto.getResultId());
|
||||
Long generalJudgeId = parseLong(dto.getGeneralJudgeId());
|
||||
if (resultId == null || generalJudgeId == null) {
|
||||
return R.fail("参数错误");
|
||||
}
|
||||
boolean success = resultService.confirmByGeneralJudge(resultId, generalJudgeId, dto.getScore(), dto.getNote());
|
||||
return success ? R.success("确认成功") : R.fail("确认失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取待主裁判确认的成绩列表
|
||||
*/
|
||||
@GetMapping("/chief/pending")
|
||||
@Operation(summary = "待主裁判确认列表", description = "获取待主裁判确认的成绩列表")
|
||||
public R<List<MartialResult>> getPendingChiefConfirmList(@RequestParam Long venueId) {
|
||||
List<MartialResult> list = resultService.getPendingChiefConfirmList(venueId);
|
||||
return R.data(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取待总裁确认的成绩列表
|
||||
*/
|
||||
@GetMapping("/general/pending")
|
||||
@Operation(summary = "待总裁确认列表", description = "获取待总裁确认的成绩列表(所有场地)")
|
||||
public R<List<MartialResult>> getPendingGeneralConfirmList(@RequestParam Long competitionId) {
|
||||
List<MartialResult> list = resultService.getPendingGeneralConfirmList(competitionId);
|
||||
return R.data(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有场地列表(总裁用)
|
||||
*/
|
||||
@GetMapping("/general/venues")
|
||||
@Operation(summary = "获取所有场地", description = "总裁获取比赛的所有场地列表")
|
||||
public R<List<MartialVenue>> getAllVenues(@RequestParam Long competitionId) {
|
||||
LambdaQueryWrapper<MartialVenue> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(MartialVenue::getCompetitionId, competitionId);
|
||||
wrapper.eq(MartialVenue::getIsDeleted, 0);
|
||||
wrapper.orderByAsc(MartialVenue::getVenueName);
|
||||
List<MartialVenue> venues = venueService.list(wrapper);
|
||||
return R.data(venues);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已总裁确认的成绩列表
|
||||
*/
|
||||
@GetMapping("/general/confirmed")
|
||||
@Operation(summary = "已总裁确认列表", description = "获取已总裁确认的成绩列表")
|
||||
public R<List<MartialResult>> getConfirmedGeneralList(@RequestParam Long competitionId) {
|
||||
List<MartialResult> list = resultService.getConfirmedGeneralList(competitionId);
|
||||
return R.data(list);
|
||||
}
|
||||
|
||||
// ========== 出场顺序相关 API ==========
|
||||
|
||||
/**
|
||||
* 获取编排状态
|
||||
*/
|
||||
@GetMapping("/schedule/status")
|
||||
@Operation(summary = "获取编排状态", description = "检查赛事编排是否完成")
|
||||
public R<Map<String, Object>> getScheduleStatus(@RequestParam Long competitionId) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
LambdaQueryWrapper<MartialScheduleStatus> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(MartialScheduleStatus::getCompetitionId, competitionId);
|
||||
wrapper.eq(MartialScheduleStatus::getIsDeleted, 0);
|
||||
wrapper.last("LIMIT 1");
|
||||
MartialScheduleStatus status = scheduleStatusMapper.selectOne(wrapper);
|
||||
|
||||
if (status == null) {
|
||||
result.put("isCompleted", false);
|
||||
result.put("scheduleStatus", 0);
|
||||
result.put("statusText", "未编排");
|
||||
return R.data(result);
|
||||
}
|
||||
|
||||
boolean isCompleted = status.getScheduleStatus() != null && status.getScheduleStatus() == 2;
|
||||
result.put("isCompleted", isCompleted);
|
||||
result.put("scheduleStatus", status.getScheduleStatus());
|
||||
result.put("statusText", getScheduleStatusText(status.getScheduleStatus()));
|
||||
result.put("totalGroups", status.getTotalGroups());
|
||||
result.put("totalParticipants", status.getTotalParticipants());
|
||||
result.put("lockedTime", status.getLockedTime());
|
||||
|
||||
return R.data(result);
|
||||
}
|
||||
|
||||
private String getScheduleStatusText(Integer status) {
|
||||
if (status == null) return "未编排";
|
||||
switch (status) {
|
||||
case 0: return "未编排";
|
||||
case 1: return "编排中";
|
||||
case 2: return "已锁定";
|
||||
default: return "未知";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取出场顺序
|
||||
*/
|
||||
@GetMapping("/schedule/lineup")
|
||||
@Operation(summary = "获取出场顺序", description = "获取已编排的出场顺序列表")
|
||||
public R<Map<String, Object>> getLineup(
|
||||
@RequestParam Long competitionId,
|
||||
@RequestParam(required = false) Long projectId
|
||||
) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
// 使用现有mapper查询编排详情
|
||||
List<ScheduleGroupDetailVO> details = scheduleGroupMapper.selectScheduleGroupDetails(competitionId);
|
||||
|
||||
if (details == null || details.isEmpty()) {
|
||||
result.put("groups", new ArrayList<>());
|
||||
return R.data(result);
|
||||
}
|
||||
|
||||
// 按项目过滤
|
||||
if (projectId != null) {
|
||||
// 需要通过groupName或其他字段判断项目,这里先获取项目名
|
||||
MartialProject project = projectService.getById(projectId);
|
||||
if (project != null) {
|
||||
String projectName = project.getProjectName();
|
||||
details = details.stream()
|
||||
.filter(d -> d.getGroupName() != null && d.getGroupName().contains(projectName))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
// 转换为LineupGroupVO格式
|
||||
Map<Long, LineupGroupVO> groupMap = new HashMap<>();
|
||||
for (ScheduleGroupDetailVO detail : details) {
|
||||
Long groupId = detail.getGroupId();
|
||||
LineupGroupVO group = groupMap.get(groupId);
|
||||
if (group == null) {
|
||||
group = new LineupGroupVO();
|
||||
group.setGroupId(groupId);
|
||||
group.setGroupName(detail.getGroupName());
|
||||
group.setCategory(detail.getCategory());
|
||||
group.setVenueName(detail.getVenueName());
|
||||
group.setTimeSlot(detail.getTimeSlot());
|
||||
group.setTableNo(generateTableNo(detail));
|
||||
group.setParticipants(new ArrayList<>());
|
||||
groupMap.put(groupId, group);
|
||||
}
|
||||
|
||||
// 添加参赛者
|
||||
if (detail.getParticipantId() != null) {
|
||||
LineupParticipantVO participant = new LineupParticipantVO();
|
||||
participant.setId(detail.getParticipantId());
|
||||
participant.setOrder(detail.getPerformanceOrder() != null ? detail.getPerformanceOrder() : group.getParticipants().size() + 1);
|
||||
participant.setPlayerName(detail.getPlayerName());
|
||||
participant.setOrganization(detail.getOrganization());
|
||||
participant.setStatus(detail.getScheduleStatus() != null ? detail.getScheduleStatus() : "waiting");
|
||||
group.getParticipants().add(participant);
|
||||
}
|
||||
}
|
||||
|
||||
result.put("groups", new ArrayList<>(groupMap.values()));
|
||||
return R.data(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成表号: 场地(1位) + 时段(1位) + 序号(2位)
|
||||
*/
|
||||
private String generateTableNo(ScheduleGroupDetailVO detail) {
|
||||
// 场地编号(简单取第一个数字或默认1)
|
||||
int venueNo = 1;
|
||||
if (detail.getVenueName() != null) {
|
||||
String venueName = detail.getVenueName();
|
||||
for (char c : venueName.toCharArray()) {
|
||||
if (Character.isDigit(c)) {
|
||||
venueNo = Character.getNumericValue(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 时段:上午=1, 下午=2
|
||||
int period = 1;
|
||||
if (detail.getTimeSlot() != null) {
|
||||
try {
|
||||
int hour = Integer.parseInt(detail.getTimeSlot().split(":")[0]);
|
||||
period = hour < 12 ? 1 : 2;
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
// 序号:使用displayOrder或默认1
|
||||
int orderNo = detail.getDisplayOrder() != null ? detail.getDisplayOrder() : 1;
|
||||
|
||||
return String.format("%d%d%02d", venueNo, period, orderNo);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,12 +21,21 @@ import org.springblade.modules.martial.pojo.vo.MiniLoginVO;
|
||||
import org.springblade.modules.martial.pojo.vo.MiniScoreDetailVO;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import org.springblade.modules.martial.service.*;
|
||||
import org.springblade.modules.martial.pojo.dto.ChiefJudgeConfirmDTO;
|
||||
import org.springblade.modules.martial.pojo.dto.GeneralJudgeConfirmDTO;
|
||||
import org.springblade.modules.martial.pojo.entity.MtVenue;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialVenue;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialResult;
|
||||
import org.springblade.core.redis.cache.BladeRedis;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -45,9 +54,17 @@ public class MartialMiniController extends BladeController {
|
||||
private final IMartialJudgeService judgeService;
|
||||
private final IMartialCompetitionService competitionService;
|
||||
private final IMartialVenueService venueService;
|
||||
private final IMtVenueService mtVenueService;
|
||||
private final IMartialProjectService projectService;
|
||||
private final IMartialAthleteService athleteService;
|
||||
private final IMartialScoreService scoreService;
|
||||
private final BladeRedis bladeRedis;
|
||||
private final IMartialResultService resultService;
|
||||
|
||||
// Redis缓存key前缀
|
||||
private static final String MINI_LOGIN_CACHE_PREFIX = "mini:login:";
|
||||
// 登录缓存过期时间(7天)
|
||||
private static final Duration LOGIN_CACHE_EXPIRE = Duration.ofDays(7);
|
||||
|
||||
/**
|
||||
* 登录验证
|
||||
@@ -91,26 +108,55 @@ public class MartialMiniController extends BladeController {
|
||||
invite.setDeviceInfo(dto.getDeviceInfo());
|
||||
judgeInviteService.updateById(invite);
|
||||
|
||||
MartialVenue venue = null;
|
||||
// 从 martial_venue 表获取场地信息
|
||||
MartialVenue martialVenue = null;
|
||||
if (invite.getVenueId() != null) {
|
||||
venue = venueService.getById(invite.getVenueId());
|
||||
martialVenue = venueService.getById(invite.getVenueId());
|
||||
}
|
||||
|
||||
List<MiniLoginVO.ProjectInfo> projects = parseProjects(invite.getProjects());
|
||||
// 获取项目列表:总裁判看所有项目,其他裁判根据场地获取项目
|
||||
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
||||
Integer refereeTypeVal = invite.getRefereeType();
|
||||
String roleVal = invite.getRole();
|
||||
boolean isGeneralJudge = (refereeTypeVal != null && refereeTypeVal == 3)
|
||||
|| "general_judge".equals(roleVal) || "general".equals(roleVal);
|
||||
|
||||
if (isGeneralJudge) {
|
||||
// 总裁判看所有项目
|
||||
projects = getAllProjectsByCompetition(competition.getId());
|
||||
} else if (Func.isNotEmpty(invite.getProjects())) {
|
||||
projects = parseProjects(invite.getProjects());
|
||||
} else if (invite.getVenueId() != null) {
|
||||
// 未指定项目,根据场地获取项目;如果场地没有项目则返回空列表
|
||||
projects = getProjectsByVenue(invite.getVenueId());
|
||||
}
|
||||
// 如果没有场地,projects保持为空列表
|
||||
|
||||
MiniLoginVO vo = new MiniLoginVO();
|
||||
vo.setToken(token);
|
||||
vo.setUserRole("chief_judge".equals(invite.getRole()) ? "admin" : "pub");
|
||||
String role = invite.getRole();
|
||||
Integer refereeType = invite.getRefereeType();
|
||||
if ("general_judge".equals(role) || "general".equals(role) || (refereeType != null && refereeType == 3)) {
|
||||
vo.setUserRole("general");
|
||||
} else if ("chief_judge".equals(role) || (refereeType != null && refereeType == 1)) {
|
||||
vo.setUserRole("admin");
|
||||
} else {
|
||||
vo.setUserRole("pub");
|
||||
}
|
||||
vo.setMatchId(competition.getId());
|
||||
vo.setMatchName(competition.getCompetitionName());
|
||||
vo.setMatchTime(competition.getCompetitionStartTime() != null ?
|
||||
competition.getCompetitionStartTime().toString() : "");
|
||||
vo.setJudgeId(judge.getId());
|
||||
vo.setJudgeName(judge.getName());
|
||||
vo.setVenueId(venue != null ? venue.getId() : null);
|
||||
vo.setVenueName(venue != null ? venue.getVenueName() : null);
|
||||
vo.setVenueId(martialVenue != null ? martialVenue.getId() : null);
|
||||
vo.setVenueName(martialVenue != null ? martialVenue.getVenueName() : null);
|
||||
vo.setProjects(projects);
|
||||
|
||||
// 将登录信息缓存到Redis(服务重启后仍然有效)
|
||||
String cacheKey = MINI_LOGIN_CACHE_PREFIX + token;
|
||||
bladeRedis.setEx(cacheKey, vo, LOGIN_CACHE_EXPIRE);
|
||||
|
||||
return R.data(vo);
|
||||
}
|
||||
|
||||
@@ -152,8 +198,136 @@ public class MartialMiniController extends BladeController {
|
||||
}
|
||||
|
||||
boolean success = scoreService.save(score);
|
||||
|
||||
// 评分保存成功后,计算并更新选手总分
|
||||
if (success) {
|
||||
Long athleteId = parseLong(dto.getAthleteId());
|
||||
Long projectId = parseLong(dto.getProjectId());
|
||||
Long venueId = parseLong(dto.getVenueId());
|
||||
if (athleteId != null && projectId != null) {
|
||||
updateAthleteTotalScore(athleteId, projectId, venueId);
|
||||
}
|
||||
}
|
||||
|
||||
return success ? R.success("评分提交成功") : R.fail("评分提交失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算并更新选手总分
|
||||
* 总分算法:去掉一个最高分和一个最低分,取剩余分数的平均值
|
||||
* 特殊情况:裁判数量<3时,直接取平均分
|
||||
* 只有所有裁判都评分完成后才更新总分
|
||||
*/
|
||||
private void updateAthleteTotalScore(Long athleteId, Long projectId, Long venueId) {
|
||||
try {
|
||||
// 1. 查询该场地的裁判员数量
|
||||
int requiredJudgeCount = getRequiredJudgeCount(venueId);
|
||||
|
||||
// 2. 获取主裁判ID列表
|
||||
List<Long> chiefJudgeIds = getChiefJudgeIds(venueId);
|
||||
|
||||
// 3. 查询该选手在该项目的所有评分(排除主裁判的评分)
|
||||
LambdaQueryWrapper<MartialScore> scoreQuery = new LambdaQueryWrapper<>();
|
||||
scoreQuery.eq(MartialScore::getAthleteId, athleteId);
|
||||
scoreQuery.eq(MartialScore::getProjectId, projectId);
|
||||
scoreQuery.eq(MartialScore::getIsDeleted, 0);
|
||||
// 排除主裁判的所有评分(包括普通评分和修改记录)
|
||||
if (!chiefJudgeIds.isEmpty()) {
|
||||
scoreQuery.notIn(MartialScore::getJudgeId, chiefJudgeIds);
|
||||
}
|
||||
List<MartialScore> scores = scoreService.list(scoreQuery);
|
||||
|
||||
// 4. 判断是否所有裁判都已评分
|
||||
if (scores == null || scores.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果配置了裁判数量,检查是否评分完成
|
||||
if (requiredJudgeCount > 0 && scores.size() < requiredJudgeCount) {
|
||||
// 未完成评分,清空总分
|
||||
MartialAthlete athlete = athleteService.getById(athleteId);
|
||||
if (athlete != null && athlete.getTotalScore() != null) {
|
||||
athlete.setTotalScore(null);
|
||||
athleteService.updateById(athlete);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. 计算总分(去掉最高最低分取平均)
|
||||
BigDecimal totalScore = calculateTotalScore(scores);
|
||||
|
||||
// 5. 更新选手总分
|
||||
if (totalScore != null) {
|
||||
MartialAthlete athlete = athleteService.getById(athleteId);
|
||||
if (athlete != null) {
|
||||
athlete.setTotalScore(totalScore);
|
||||
athleteService.updateById(athlete);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 记录错误但不影响评分提交
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目应评分的裁判数量(裁判员,不包括主裁判)
|
||||
* 按项目过滤:检查 projects JSON 字段是否包含该项目ID
|
||||
*/
|
||||
private int getRequiredJudgeCount(Long venueId) {
|
||||
if (venueId == null || venueId <= 0) {
|
||||
return 0;
|
||||
}
|
||||
LambdaQueryWrapper<MartialJudgeInvite> judgeQuery = new LambdaQueryWrapper<>();
|
||||
judgeQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
||||
judgeQuery.eq(MartialJudgeInvite::getVenueId, venueId);
|
||||
judgeQuery.eq(MartialJudgeInvite::getRefereeType, 2); // Only count referees (type=2), exclude chief judge (type=1) and general judge (type=3)
|
||||
List<MartialJudgeInvite> judges = judgeInviteService.list(judgeQuery);
|
||||
// Use distinct judge_id to count unique judges
|
||||
return (int) judges.stream()
|
||||
.map(MartialJudgeInvite::getJudgeId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算总分
|
||||
* 算法:去掉一个最高分和一个最低分,取剩余分数的平均值
|
||||
* 特殊情况:裁判数量<3时,直接取平均分
|
||||
*/
|
||||
private BigDecimal calculateTotalScore(List<MartialScore> scores) {
|
||||
if (scores == null || scores.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 提取所有分数并排序
|
||||
List<BigDecimal> scoreValues = scores.stream()
|
||||
.map(MartialScore::getScore)
|
||||
.filter(Objects::nonNull)
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
int count = scoreValues.size();
|
||||
if (count == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (count < 3) {
|
||||
// 裁判数量<3,直接取平均分
|
||||
BigDecimal sum = scoreValues.stream()
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
return sum.divide(new BigDecimal(count), 3, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
// 去掉最高分和最低分(已排序,去掉第一个和最后一个)
|
||||
List<BigDecimal> middleScores = scoreValues.subList(1, count - 1);
|
||||
|
||||
// 计算平均分
|
||||
BigDecimal sum = middleScores.stream()
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
return sum.divide(new BigDecimal(middleScores.size()), 3, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全地将String转换为Long
|
||||
@@ -171,8 +345,8 @@ public class MartialMiniController extends BladeController {
|
||||
|
||||
/**
|
||||
* 获取选手列表(支持分页)
|
||||
* - 普通裁判:获取所有选手,标记是否已评分
|
||||
* - 裁判长:获取已有评分的选手列表
|
||||
* - 裁判员:获取所有选手,标记是否已评分
|
||||
* - 主裁判:获取所有裁判员都评分完成的选手列表
|
||||
*/
|
||||
@GetMapping("/score/athletes")
|
||||
@Operation(summary = "获取选手列表", description = "根据裁判类型获取选手列表(支持分页)")
|
||||
@@ -181,6 +355,7 @@ public class MartialMiniController extends BladeController {
|
||||
@RequestParam Integer refereeType,
|
||||
@RequestParam(required = false) Long projectId,
|
||||
@RequestParam(required = false) Long venueId,
|
||||
@RequestParam(required = false) Long competitionId,
|
||||
@RequestParam(defaultValue = "1") Integer current,
|
||||
@RequestParam(defaultValue = "10") Integer size
|
||||
) {
|
||||
@@ -188,6 +363,11 @@ public class MartialMiniController extends BladeController {
|
||||
LambdaQueryWrapper<MartialAthlete> athleteQuery = new LambdaQueryWrapper<>();
|
||||
athleteQuery.eq(MartialAthlete::getIsDeleted, 0);
|
||||
|
||||
// 按比赛ID过滤(重要:确保只显示当前比赛的选手)
|
||||
if (competitionId != null) {
|
||||
athleteQuery.eq(MartialAthlete::getCompetitionId, competitionId);
|
||||
}
|
||||
|
||||
if (projectId != null) {
|
||||
athleteQuery.eq(MartialAthlete::getProjectId, projectId);
|
||||
}
|
||||
@@ -196,35 +376,48 @@ public class MartialMiniController extends BladeController {
|
||||
|
||||
List<MartialAthlete> athletes = athleteService.list(athleteQuery);
|
||||
|
||||
// 2. 获取所有评分记录
|
||||
// 2. 获取该场地所有主裁判的judge_id列表
|
||||
List<Long> chiefJudgeIds = getChiefJudgeIds(venueId);
|
||||
|
||||
// 3. 获取所有评分记录(排除主裁判的评分)
|
||||
LambdaQueryWrapper<MartialScore> scoreQuery = new LambdaQueryWrapper<>();
|
||||
scoreQuery.eq(MartialScore::getIsDeleted, 0);
|
||||
if (projectId != null) {
|
||||
scoreQuery.eq(MartialScore::getProjectId, projectId);
|
||||
}
|
||||
// 添加场地过滤
|
||||
if (venueId != null && venueId > 0) {
|
||||
scoreQuery.eq(MartialScore::getVenueId, venueId);
|
||||
}
|
||||
// 排除主裁判的评分
|
||||
if (!chiefJudgeIds.isEmpty()) {
|
||||
scoreQuery.notIn(MartialScore::getJudgeId, chiefJudgeIds);
|
||||
}
|
||||
List<MartialScore> allScores = scoreService.list(scoreQuery);
|
||||
|
||||
// 按选手ID分组统计评分
|
||||
java.util.Map<Long, List<MartialScore>> scoresByAthlete = allScores.stream()
|
||||
.collect(java.util.stream.Collectors.groupingBy(MartialScore::getAthleteId));
|
||||
|
||||
// 3. 根据裁判类型处理选手列表
|
||||
// 4. 获取该场地的应评裁判数量
|
||||
int requiredJudgeCount = getRequiredJudgeCount(venueId);
|
||||
|
||||
// 5. 根据裁判类型处理选手列表
|
||||
List<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> filteredList;
|
||||
|
||||
if (refereeType == 1) {
|
||||
// 裁判长:返回已有评分的选手
|
||||
// 主裁判:返回所有选手,前端根据totalScore判断是否显示修改按钮
|
||||
filteredList = athletes.stream()
|
||||
.filter(athlete -> {
|
||||
List<MartialScore> scores = scoresByAthlete.get(athlete.getId());
|
||||
return scores != null && !scores.isEmpty();
|
||||
})
|
||||
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId()), judgeId))
|
||||
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId()), judgeId, requiredJudgeCount))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
} else {
|
||||
// 普通裁判:返回所有选手,标记是否已评分
|
||||
// 裁判员:返回所有选手,标记是否已评分
|
||||
filteredList = athletes.stream()
|
||||
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId()), judgeId))
|
||||
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId()), judgeId, requiredJudgeCount))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
|
||||
// 4. 手动分页
|
||||
// 6. 手动分页
|
||||
int total = filteredList.size();
|
||||
int fromIndex = (current - 1) * size;
|
||||
int toIndex = Math.min(fromIndex + size, total);
|
||||
@@ -236,13 +429,31 @@ public class MartialMiniController extends BladeController {
|
||||
pageRecords = filteredList.subList(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
// 5. 构建分页结果
|
||||
// 7. 构建分页结果
|
||||
IPage<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> page = new Page<>(current, size, total);
|
||||
page.setRecords(pageRecords);
|
||||
|
||||
return R.data(page);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取场地所有主裁判的judge_id列表
|
||||
*/
|
||||
private List<Long> getChiefJudgeIds(Long venueId) {
|
||||
if (venueId == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
LambdaQueryWrapper<MartialJudgeInvite> judgeQuery = new LambdaQueryWrapper<>();
|
||||
judgeQuery.eq(MartialJudgeInvite::getVenueId, venueId);
|
||||
judgeQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
||||
judgeQuery.eq(MartialJudgeInvite::getRole, "chief_judge");
|
||||
List<MartialJudgeInvite> chiefJudges = judgeInviteService.list(judgeQuery);
|
||||
return chiefJudges.stream()
|
||||
.map(MartialJudgeInvite::getJudgeId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取评分详情
|
||||
*/
|
||||
@@ -254,10 +465,10 @@ public class MartialMiniController extends BladeController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改评分(裁判长)
|
||||
* 修改评分(主裁判)
|
||||
*/
|
||||
@PutMapping("/score/modify")
|
||||
@Operation(summary = "修改评分", description = "裁判长修改选手总分")
|
||||
@Operation(summary = "修改评分", description = "主裁判修改选手总分")
|
||||
public R modifyScore(@RequestBody MiniScoreModifyDTO dto) {
|
||||
boolean success = scoreService.modifyScoreByAdmin(dto);
|
||||
return success ? R.success("修改成功") : R.fail("修改失败");
|
||||
@@ -268,26 +479,107 @@ public class MartialMiniController extends BladeController {
|
||||
*/
|
||||
@PostMapping("/logout")
|
||||
@Operation(summary = "退出登录", description = "清除登录状态")
|
||||
public R logout() {
|
||||
public R logout(@RequestHeader(value = "Authorization", required = false) String token) {
|
||||
// 从Redis删除登录缓存
|
||||
if (token != null && !token.isEmpty()) {
|
||||
String cacheKey = MINI_LOGIN_CACHE_PREFIX + token;
|
||||
bladeRedis.del(cacheKey);
|
||||
}
|
||||
return R.success("退出成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* Token验证
|
||||
* Token验证(从Redis恢复登录状态)
|
||||
*/
|
||||
@GetMapping("/verify")
|
||||
@Operation(summary = "Token验证", description = "验证当前token是否有效")
|
||||
public R verify() {
|
||||
return R.success("Token有效");
|
||||
@Operation(summary = "Token验证", description = "验证token并返回登录信息,支持服务重启后恢复登录状态")
|
||||
public R<MiniLoginVO> verify(@RequestHeader(value = "Authorization", required = false) String token) {
|
||||
if (token == null || token.isEmpty()) {
|
||||
return R.fail("Token不能为空");
|
||||
}
|
||||
|
||||
// 从Redis获取登录信息
|
||||
String cacheKey = MINI_LOGIN_CACHE_PREFIX + token;
|
||||
MiniLoginVO loginInfo = bladeRedis.get(cacheKey);
|
||||
|
||||
if (loginInfo != null) {
|
||||
// 刷新缓存过期时间
|
||||
bladeRedis.setEx(cacheKey, loginInfo, LOGIN_CACHE_EXPIRE);
|
||||
return R.data(loginInfo);
|
||||
}
|
||||
|
||||
// Redis中没有,尝试从数据库恢复
|
||||
LambdaQueryWrapper<MartialJudgeInvite> inviteQuery = new LambdaQueryWrapper<>();
|
||||
inviteQuery.eq(MartialJudgeInvite::getAccessToken, token);
|
||||
inviteQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
||||
MartialJudgeInvite invite = judgeInviteService.getOne(inviteQuery);
|
||||
|
||||
if (invite == null) {
|
||||
return R.fail("Token无效");
|
||||
}
|
||||
|
||||
if (invite.getTokenExpireTime() != null && invite.getTokenExpireTime().isBefore(LocalDateTime.now())) {
|
||||
return R.fail("Token已过期");
|
||||
}
|
||||
|
||||
// 重建登录信息
|
||||
MartialCompetition competition = competitionService.getById(invite.getCompetitionId());
|
||||
MartialJudge judge = judgeService.getById(invite.getJudgeId());
|
||||
MartialVenue martialVenue = invite.getVenueId() != null ? venueService.getById(invite.getVenueId()) : null;
|
||||
// 获取项目列表:总裁判看所有项目,其他裁判根据场地获取项目
|
||||
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
||||
Integer refereeTypeVal = invite.getRefereeType();
|
||||
String roleVal = invite.getRole();
|
||||
boolean isGeneralJudge = (refereeTypeVal != null && refereeTypeVal == 3)
|
||||
|| "general_judge".equals(roleVal) || "general".equals(roleVal);
|
||||
|
||||
if (isGeneralJudge) {
|
||||
// 总裁判看所有项目
|
||||
projects = getAllProjectsByCompetition(competition.getId());
|
||||
} else if (Func.isNotEmpty(invite.getProjects())) {
|
||||
projects = parseProjects(invite.getProjects());
|
||||
} else if (invite.getVenueId() != null) {
|
||||
// 未指定项目,根据场地获取项目;如果场地没有项目则返回空列表
|
||||
projects = getProjectsByVenue(invite.getVenueId());
|
||||
}
|
||||
// 如果没有场地,projects保持为空列表
|
||||
|
||||
MiniLoginVO vo = new MiniLoginVO();
|
||||
vo.setToken(token);
|
||||
String role = invite.getRole();
|
||||
Integer refereeType = invite.getRefereeType();
|
||||
if ("general_judge".equals(role) || "general".equals(role) || (refereeType != null && refereeType == 3)) {
|
||||
vo.setUserRole("general");
|
||||
} else if ("chief_judge".equals(role) || (refereeType != null && refereeType == 1)) {
|
||||
vo.setUserRole("admin");
|
||||
} else {
|
||||
vo.setUserRole("pub");
|
||||
}
|
||||
vo.setMatchId(competition != null ? competition.getId() : null);
|
||||
vo.setMatchName(competition != null ? competition.getCompetitionName() : null);
|
||||
vo.setMatchTime(competition != null && competition.getCompetitionStartTime() != null ?
|
||||
competition.getCompetitionStartTime().toString() : "");
|
||||
vo.setJudgeId(judge != null ? judge.getId() : null);
|
||||
vo.setJudgeName(judge != null ? judge.getName() : null);
|
||||
vo.setVenueId(martialVenue != null ? martialVenue.getId() : null);
|
||||
vo.setVenueName(martialVenue != null ? martialVenue.getVenueName() : null);
|
||||
vo.setProjects(projects);
|
||||
|
||||
// 重新缓存到Redis
|
||||
bladeRedis.setEx(cacheKey, vo, LOGIN_CACHE_EXPIRE);
|
||||
|
||||
return R.data(vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换选手实体为VO
|
||||
* 新增:只有评分完成时才显示总分
|
||||
*/
|
||||
private org.springblade.modules.martial.pojo.vo.MiniAthleteListVO convertToAthleteListVO(
|
||||
MartialAthlete athlete,
|
||||
List<MartialScore> scores,
|
||||
Long currentJudgeId) {
|
||||
Long currentJudgeId,
|
||||
int requiredJudgeCount) {
|
||||
org.springblade.modules.martial.pojo.vo.MiniAthleteListVO vo = new org.springblade.modules.martial.pojo.vo.MiniAthleteListVO();
|
||||
vo.setAthleteId(athlete.getId());
|
||||
vo.setName(athlete.getPlayerName());
|
||||
@@ -296,7 +588,9 @@ public class MartialMiniController extends BladeController {
|
||||
vo.setTeam(athlete.getTeamName());
|
||||
vo.setOrderNum(athlete.getOrderNum());
|
||||
vo.setCompetitionStatus(athlete.getCompetitionStatus());
|
||||
vo.setTotalScore(athlete.getTotalScore());
|
||||
|
||||
// 设置应评分裁判数量
|
||||
vo.setRequiredJudgeCount(requiredJudgeCount);
|
||||
|
||||
// 设置项目名称
|
||||
if (athlete.getProjectId() != null) {
|
||||
@@ -307,8 +601,10 @@ public class MartialMiniController extends BladeController {
|
||||
}
|
||||
|
||||
// 设置评分状态
|
||||
int scoredCount = 0;
|
||||
if (scores != null && !scores.isEmpty()) {
|
||||
vo.setScoredJudgeCount(scores.size());
|
||||
scoredCount = scores.size();
|
||||
vo.setScoredJudgeCount(scoredCount);
|
||||
|
||||
// 查找当前裁判的评分
|
||||
MartialScore myScore = scores.stream()
|
||||
@@ -326,6 +622,23 @@ public class MartialMiniController extends BladeController {
|
||||
vo.setScored(false);
|
||||
vo.setScoredJudgeCount(0);
|
||||
}
|
||||
|
||||
// 判断评分是否完成(所有裁判都已评分)
|
||||
boolean scoringComplete = false;
|
||||
if (requiredJudgeCount > 0) {
|
||||
scoringComplete = scoredCount >= requiredJudgeCount;
|
||||
} else {
|
||||
// 如果没有配置裁判数量,只要有评分就算完成
|
||||
scoringComplete = scoredCount > 0;
|
||||
}
|
||||
vo.setScoringComplete(scoringComplete);
|
||||
|
||||
// 只有评分完成时才显示总分
|
||||
if (scoringComplete) {
|
||||
vo.setTotalScore(athlete.getTotalScore());
|
||||
} else {
|
||||
vo.setTotalScore(null);
|
||||
}
|
||||
|
||||
return vo;
|
||||
}
|
||||
@@ -378,4 +691,130 @@ public class MartialMiniController extends BladeController {
|
||||
return projects;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取比赛的所有项目
|
||||
*/
|
||||
private List<MiniLoginVO.ProjectInfo> getAllProjectsByCompetition(Long competitionId) {
|
||||
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
||||
|
||||
LambdaQueryWrapper<MartialProject> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(MartialProject::getCompetitionId, competitionId);
|
||||
wrapper.eq(MartialProject::getIsDeleted, 0);
|
||||
|
||||
List<MartialProject> projectList = projectService.list(wrapper);
|
||||
|
||||
if (Func.isNotEmpty(projectList)) {
|
||||
projects = projectList.stream().map(project -> {
|
||||
MiniLoginVO.ProjectInfo info = new MiniLoginVO.ProjectInfo();
|
||||
info.setProjectId(project.getId());
|
||||
info.setProjectName(project.getProjectName());
|
||||
return info;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
return projects;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据场地获取项目列表
|
||||
*/
|
||||
private List<MiniLoginVO.ProjectInfo> getProjectsByVenue(Long venueId) {
|
||||
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
||||
|
||||
LambdaQueryWrapper<MartialProject> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(MartialProject::getVenueId, venueId);
|
||||
wrapper.eq(MartialProject::getIsDeleted, 0);
|
||||
|
||||
List<MartialProject> projectList = projectService.list(wrapper);
|
||||
|
||||
if (Func.isNotEmpty(projectList)) {
|
||||
projects = projectList.stream().map(project -> {
|
||||
MiniLoginVO.ProjectInfo info = new MiniLoginVO.ProjectInfo();
|
||||
info.setProjectId(project.getId());
|
||||
info.setProjectName(project.getProjectName());
|
||||
return info;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
return projects;
|
||||
}
|
||||
|
||||
|
||||
// ========== 三级裁判评分流程 API ==========
|
||||
|
||||
/**
|
||||
* 主裁判确认/修改分数
|
||||
*/
|
||||
@PostMapping("/chief/confirm")
|
||||
@Operation(summary = "主裁判确认分数", description = "主裁判确认或修改选手分数")
|
||||
public R confirmByChiefJudge(@RequestBody ChiefJudgeConfirmDTO dto) {
|
||||
Long resultId = parseLong(dto.getResultId());
|
||||
Long chiefJudgeId = parseLong(dto.getChiefJudgeId());
|
||||
if (resultId == null || chiefJudgeId == null) {
|
||||
return R.fail("参数错误");
|
||||
}
|
||||
boolean success = resultService.confirmByChiefJudge(resultId, chiefJudgeId, dto.getScore(), dto.getNote());
|
||||
return success ? R.success("确认成功") : R.fail("确认失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 总裁确认/修改分数
|
||||
*/
|
||||
@PostMapping("/general/confirm")
|
||||
@Operation(summary = "总裁确认分数", description = "总裁确认或修改选手分数")
|
||||
public R confirmByGeneralJudge(@RequestBody GeneralJudgeConfirmDTO dto) {
|
||||
Long resultId = parseLong(dto.getResultId());
|
||||
Long generalJudgeId = parseLong(dto.getGeneralJudgeId());
|
||||
if (resultId == null || generalJudgeId == null) {
|
||||
return R.fail("参数错误");
|
||||
}
|
||||
boolean success = resultService.confirmByGeneralJudge(resultId, generalJudgeId, dto.getScore(), dto.getNote());
|
||||
return success ? R.success("确认成功") : R.fail("确认失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取待主裁判确认的成绩列表
|
||||
*/
|
||||
@GetMapping("/chief/pending")
|
||||
@Operation(summary = "待主裁判确认列表", description = "获取待主裁判确认的成绩列表")
|
||||
public R<List<MartialResult>> getPendingChiefConfirmList(@RequestParam Long venueId) {
|
||||
List<MartialResult> list = resultService.getPendingChiefConfirmList(venueId);
|
||||
return R.data(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取待总裁确认的成绩列表
|
||||
*/
|
||||
@GetMapping("/general/pending")
|
||||
@Operation(summary = "待总裁确认列表", description = "获取待总裁确认的成绩列表(所有场地)")
|
||||
public R<List<MartialResult>> getPendingGeneralConfirmList(@RequestParam Long competitionId) {
|
||||
List<MartialResult> list = resultService.getPendingGeneralConfirmList(competitionId);
|
||||
return R.data(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有场地列表(总裁用)
|
||||
*/
|
||||
@GetMapping("/general/venues")
|
||||
@Operation(summary = "获取所有场地", description = "总裁获取比赛的所有场地列表")
|
||||
public R<List<MartialVenue>> getAllVenues(@RequestParam Long competitionId) {
|
||||
LambdaQueryWrapper<MartialVenue> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(MartialVenue::getCompetitionId, competitionId);
|
||||
wrapper.eq(MartialVenue::getIsDeleted, 0);
|
||||
wrapper.orderByAsc(MartialVenue::getVenueName);
|
||||
List<MartialVenue> venues = venueService.list(wrapper);
|
||||
return R.data(venues);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已总裁确认的成绩列表
|
||||
*/
|
||||
@GetMapping("/general/confirmed")
|
||||
@Operation(summary = "已总裁确认列表", description = "获取已总裁确认的成绩列表")
|
||||
public R<List<MartialResult>> getConfirmedGeneralList(@RequestParam Long competitionId) {
|
||||
List<MartialResult> list = resultService.getConfirmedGeneralList(competitionId);
|
||||
return R.data(list);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.springblade.modules.martial.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@@ -9,6 +11,7 @@ import org.springblade.core.mp.support.Condition;
|
||||
import org.springblade.core.mp.support.Query;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialProject;
|
||||
import org.springblade.modules.martial.service.IMartialProjectService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -42,7 +45,31 @@ public class MartialProjectController extends BladeController {
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "分页列表", description = "分页查询")
|
||||
public R<IPage<MartialProject>> list(MartialProject project, Query query) {
|
||||
IPage<MartialProject> pages = projectService.page(Condition.getPage(query), Condition.getQueryWrapper(project));
|
||||
QueryWrapper<MartialProject> queryWrapper = new QueryWrapper<>();
|
||||
// 赛事ID精确匹配
|
||||
if (project.getCompetitionId() != null) {
|
||||
queryWrapper.eq("competition_id", project.getCompetitionId());
|
||||
}
|
||||
// 项目名称模糊查询
|
||||
if (StringUtil.isNotBlank(project.getProjectName())) {
|
||||
queryWrapper.like("project_name", project.getProjectName());
|
||||
}
|
||||
// 分组类别模糊查询
|
||||
if (StringUtil.isNotBlank(project.getCategory())) {
|
||||
queryWrapper.like("category", project.getCategory());
|
||||
}
|
||||
// 项目类型精确匹配
|
||||
if (project.getEventType() != null) {
|
||||
queryWrapper.eq("event_type", project.getEventType());
|
||||
}
|
||||
// 参赛类型精确匹配
|
||||
if (project.getType() != null) {
|
||||
queryWrapper.eq("type", project.getType());
|
||||
}
|
||||
// 按排序字段和创建时间排序
|
||||
queryWrapper.orderByAsc("sort_order").orderByDesc("create_time");
|
||||
|
||||
IPage<MartialProject> pages = projectService.page(Condition.getPage(query), queryWrapper);
|
||||
return R.data(pages);
|
||||
}
|
||||
|
||||
@@ -52,9 +79,32 @@ public class MartialProjectController extends BladeController {
|
||||
@PostMapping("/submit")
|
||||
@Operation(summary = "新增或修改", description = "传入实体")
|
||||
public R submit(@RequestBody MartialProject project) {
|
||||
// Auto-generate project code for new projects
|
||||
if (project.getId() == null && StringUtil.isBlank(project.getProjectCode())) {
|
||||
String projectCode = generateProjectCode(project.getCompetitionId());
|
||||
project.setProjectCode(projectCode);
|
||||
}
|
||||
return R.status(projectService.saveOrUpdate(project));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate project code: competition prefix + sequence number
|
||||
* Format: C{competitionId}-P{sequence}, e.g., C1-P001
|
||||
*/
|
||||
private String generateProjectCode(Long competitionId) {
|
||||
if (competitionId == null) {
|
||||
return "P" + System.currentTimeMillis();
|
||||
}
|
||||
|
||||
// Count existing projects for this competition
|
||||
LambdaQueryWrapper<MartialProject> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(MartialProject::getCompetitionId, competitionId);
|
||||
long count = projectService.count(wrapper);
|
||||
|
||||
// Generate code: C{competitionId}-P{sequence}
|
||||
return String.format("C%d-P%03d", competitionId, count + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
|
||||
@@ -1,23 +1,40 @@
|
||||
package org.springblade.modules.martial.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.boot.ctrl.BladeController;
|
||||
import org.springblade.core.mp.support.Condition;
|
||||
import org.springblade.core.mp.support.Query;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialAthlete;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialCompetition;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialProject;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialRegistrationOrder;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialTeam;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialTeamMember;
|
||||
import org.springblade.modules.martial.pojo.dto.RegistrationSubmitDTO;
|
||||
import org.springblade.modules.martial.pojo.vo.MartialRegistrationOrderVO;
|
||||
import org.springblade.modules.martial.pojo.vo.OrganizationStatsVO;
|
||||
import org.springblade.modules.martial.service.IMartialAthleteService;
|
||||
import org.springblade.modules.martial.service.IMartialCompetitionService;
|
||||
import org.springblade.modules.martial.service.IMartialProjectService;
|
||||
import org.springblade.modules.martial.service.IMartialRegistrationOrderService;
|
||||
import org.springblade.modules.martial.service.IMartialTeamService;
|
||||
import org.springblade.modules.martial.mapper.MartialTeamMemberMapper;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 报名订单 控制器
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@RequestMapping("/martial/registrationOrder")
|
||||
@@ -25,39 +42,372 @@ import org.springframework.web.bind.annotation.*;
|
||||
public class MartialRegistrationOrderController extends BladeController {
|
||||
|
||||
private final IMartialRegistrationOrderService registrationOrderService;
|
||||
private final IMartialAthleteService athleteService;
|
||||
private final IMartialTeamService teamService;
|
||||
private final IMartialCompetitionService competitionService;
|
||||
private final IMartialProjectService projectService;
|
||||
private final MartialTeamMemberMapper teamMemberMapper;
|
||||
|
||||
/**
|
||||
* 详情
|
||||
*/
|
||||
@GetMapping("/detail")
|
||||
@Operation(summary = "详情", description = "传入ID")
|
||||
public R detail(@RequestParam Long id) {
|
||||
// 返回包含关联数据的完整详情
|
||||
return R.data(registrationOrderService.getDetailWithRelations(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "分页列表", description = "分页查询")
|
||||
public R<IPage<MartialRegistrationOrder>> list(MartialRegistrationOrder registrationOrder, Query query) {
|
||||
IPage<MartialRegistrationOrder> pages = registrationOrderService.page(Condition.getPage(query), Condition.getQueryWrapper(registrationOrder));
|
||||
@Operation(summary = "分页列表", description = "分页查询当前用户的报名记录")
|
||||
public R<IPage<MartialRegistrationOrderVO>> list(MartialRegistrationOrder registrationOrder, Query query) {
|
||||
Long userId = AuthUtil.getUserId();
|
||||
Integer status = registrationOrder.getStatus();
|
||||
|
||||
IPage<MartialRegistrationOrderVO> pages = registrationOrderService.getListWithRelations(
|
||||
userId, status, query.getCurrent(), query.getSize());
|
||||
|
||||
return R.data(pages);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增或修改
|
||||
*/
|
||||
@PostMapping("/submit")
|
||||
@Operation(summary = "新增或修改", description = "传入实体")
|
||||
public R submit(@RequestBody MartialRegistrationOrder registrationOrder) {
|
||||
return R.status(registrationOrderService.saveOrUpdate(registrationOrder));
|
||||
@GetMapping("/organization-stats")
|
||||
@Operation(summary = "单位统计", description = "按单位统计运动员、项目、金额")
|
||||
public R<List<OrganizationStatsVO>> getOrganizationStats(@RequestParam Long competitionId) {
|
||||
log.info("获取单位统计: competitionId={}", competitionId);
|
||||
|
||||
// 1. Get all athletes for this competition
|
||||
LambdaQueryWrapper<MartialAthlete> athleteWrapper = new LambdaQueryWrapper<>();
|
||||
athleteWrapper.eq(MartialAthlete::getCompetitionId, competitionId)
|
||||
.eq(MartialAthlete::getIsDeleted, 0);
|
||||
List<MartialAthlete> athletes = athleteService.list(athleteWrapper);
|
||||
|
||||
if (athletes.isEmpty()) {
|
||||
return R.data(new ArrayList<>());
|
||||
}
|
||||
|
||||
// 2. Get all projects for this competition
|
||||
Set<Long> projectIds = athletes.stream()
|
||||
.map(MartialAthlete::getProjectId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
final Map<Long, MartialProject> projectMap = new HashMap<>();
|
||||
if (!projectIds.isEmpty()) {
|
||||
List<MartialProject> projects = projectService.listByIds(projectIds);
|
||||
projectMap.putAll(projects.stream().collect(Collectors.toMap(MartialProject::getId, p -> p)));
|
||||
}
|
||||
|
||||
// 3. Get team members for team projects
|
||||
Set<Long> teamIds = athletes.stream()
|
||||
.filter(a -> {
|
||||
MartialProject project = projectMap.get(a.getProjectId());
|
||||
return project != null && project.getType() != null && project.getType() == 2;
|
||||
})
|
||||
.map(a -> {
|
||||
// Try to get team ID from team table by team name
|
||||
LambdaQueryWrapper<MartialTeam> teamWrapper = new LambdaQueryWrapper<>();
|
||||
teamWrapper.eq(MartialTeam::getTeamName, a.getTeamName())
|
||||
.eq(MartialTeam::getIsDeleted, 0)
|
||||
.last("LIMIT 1");
|
||||
MartialTeam team = teamService.getOne(teamWrapper, false);
|
||||
return team != null ? team.getId() : null;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// Get team members
|
||||
Map<Long, List<MartialTeamMember>> teamMembersMap = new HashMap<>();
|
||||
if (!teamIds.isEmpty()) {
|
||||
LambdaQueryWrapper<MartialTeamMember> memberWrapper = new LambdaQueryWrapper<>();
|
||||
memberWrapper.in(MartialTeamMember::getTeamId, teamIds)
|
||||
.eq(MartialTeamMember::getIsDeleted, 0);
|
||||
List<MartialTeamMember> members = teamMemberMapper.selectList(memberWrapper);
|
||||
teamMembersMap = members.stream().collect(Collectors.groupingBy(MartialTeamMember::getTeamId));
|
||||
}
|
||||
|
||||
// 4. Group by organization and calculate stats
|
||||
Map<String, OrganizationStatsVO> orgStatsMap = new LinkedHashMap<>();
|
||||
|
||||
for (MartialAthlete athlete : athletes) {
|
||||
String org = athlete.getOrganization();
|
||||
if (org == null || org.isEmpty()) {
|
||||
org = "未知单位";
|
||||
}
|
||||
|
||||
OrganizationStatsVO stats = orgStatsMap.computeIfAbsent(org, k -> {
|
||||
OrganizationStatsVO vo = new OrganizationStatsVO();
|
||||
vo.setOrganization(k);
|
||||
vo.setAthleteCount(0);
|
||||
vo.setProjectCount(0);
|
||||
vo.setSingleProjectCount(0);
|
||||
vo.setTeamProjectCount(0);
|
||||
vo.setMaleCount(0);
|
||||
vo.setFemaleCount(0);
|
||||
vo.setTotalAmount(BigDecimal.ZERO);
|
||||
vo.setProjectAmounts(new ArrayList<>());
|
||||
return vo;
|
||||
});
|
||||
|
||||
MartialProject project = projectMap.get(athlete.getProjectId());
|
||||
if (project == null) continue;
|
||||
|
||||
// Check if project already counted for this org
|
||||
boolean projectExists = stats.getProjectAmounts().stream()
|
||||
.anyMatch(pa -> pa.getProjectId().equals(athlete.getProjectId()));
|
||||
|
||||
if (!projectExists) {
|
||||
// Add project amount item
|
||||
OrganizationStatsVO.ProjectAmountItem item = new OrganizationStatsVO.ProjectAmountItem();
|
||||
item.setProjectId(project.getId());
|
||||
item.setProjectName(project.getProjectName());
|
||||
item.setProjectType(project.getType());
|
||||
item.setCount(1);
|
||||
item.setPrice(project.getPrice() != null ? project.getPrice() : BigDecimal.ZERO);
|
||||
item.setAmount(item.getPrice());
|
||||
stats.getProjectAmounts().add(item);
|
||||
|
||||
stats.setProjectCount(stats.getProjectCount() + 1);
|
||||
if (project.getType() != null && project.getType() == 2) {
|
||||
stats.setTeamProjectCount(stats.getTeamProjectCount() + 1);
|
||||
} else {
|
||||
stats.setSingleProjectCount(stats.getSingleProjectCount() + 1);
|
||||
}
|
||||
} else {
|
||||
// Update count for existing project
|
||||
stats.getProjectAmounts().stream()
|
||||
.filter(pa -> pa.getProjectId().equals(athlete.getProjectId()))
|
||||
.findFirst()
|
||||
.ifPresent(pa -> {
|
||||
pa.setCount(pa.getCount() + 1);
|
||||
pa.setAmount(pa.getPrice().multiply(BigDecimal.valueOf(pa.getCount())));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Calculate unique athletes and gender counts per organization
|
||||
for (Map.Entry<String, OrganizationStatsVO> entry : orgStatsMap.entrySet()) {
|
||||
String org = entry.getKey();
|
||||
OrganizationStatsVO stats = entry.getValue();
|
||||
|
||||
// Get all athletes for this org
|
||||
Set<String> uniqueIdCards = new HashSet<>();
|
||||
int maleCount = 0;
|
||||
int femaleCount = 0;
|
||||
|
||||
for (MartialAthlete athlete : athletes) {
|
||||
String athleteOrg = athlete.getOrganization();
|
||||
if (athleteOrg == null || athleteOrg.isEmpty()) athleteOrg = "未知单位";
|
||||
if (!athleteOrg.equals(org)) continue;
|
||||
|
||||
MartialProject project = projectMap.get(athlete.getProjectId());
|
||||
if (project == null) continue;
|
||||
|
||||
// For individual projects, count the athlete
|
||||
if (project.getType() == null || project.getType() == 1) {
|
||||
String idCard = athlete.getIdCard();
|
||||
if (idCard != null && !idCard.isEmpty() && !uniqueIdCards.contains(idCard)) {
|
||||
uniqueIdCards.add(idCard);
|
||||
if (athlete.getGender() != null && athlete.getGender() == 1) {
|
||||
maleCount++;
|
||||
} else if (athlete.getGender() != null && athlete.getGender() == 2) {
|
||||
femaleCount++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For team projects, count team members
|
||||
String teamName = athlete.getTeamName();
|
||||
if (teamName != null) {
|
||||
LambdaQueryWrapper<MartialTeam> teamWrapper = new LambdaQueryWrapper<>();
|
||||
teamWrapper.eq(MartialTeam::getTeamName, teamName)
|
||||
.eq(MartialTeam::getIsDeleted, 0)
|
||||
.last("LIMIT 1");
|
||||
MartialTeam team = teamService.getOne(teamWrapper, false);
|
||||
if (team != null && teamMembersMap.containsKey(team.getId())) {
|
||||
for (MartialTeamMember member : teamMembersMap.get(team.getId())) {
|
||||
MartialAthlete memberAthlete = athleteService.getById(member.getAthleteId());
|
||||
if (memberAthlete != null) {
|
||||
String idCard = memberAthlete.getIdCard();
|
||||
if (idCard != null && !idCard.isEmpty() && !uniqueIdCards.contains(idCard)) {
|
||||
uniqueIdCards.add(idCard);
|
||||
if (memberAthlete.getGender() != null && memberAthlete.getGender() == 1) {
|
||||
maleCount++;
|
||||
} else if (memberAthlete.getGender() != null && memberAthlete.getGender() == 2) {
|
||||
femaleCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stats.setAthleteCount(uniqueIdCards.size());
|
||||
stats.setMaleCount(maleCount);
|
||||
stats.setFemaleCount(femaleCount);
|
||||
|
||||
// Calculate total amount
|
||||
BigDecimal totalAmount = stats.getProjectAmounts().stream()
|
||||
.map(OrganizationStatsVO.ProjectAmountItem::getAmount)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
stats.setTotalAmount(totalAmount);
|
||||
}
|
||||
|
||||
return R.data(new ArrayList<>(orgStatsMap.values()));
|
||||
}
|
||||
|
||||
@PostMapping("/submit")
|
||||
@Operation(summary = "提交报名", description = "提交报名订单并关联选手或集体")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R submit(@RequestBody RegistrationSubmitDTO dto) {
|
||||
log.info("=== 提交报名订单 ===");
|
||||
log.info("订单号: {}", dto.getOrderNo());
|
||||
log.info("赛事ID: {}", dto.getCompetitionId());
|
||||
log.info("项目IDs: {}", dto.getProjectIds());
|
||||
log.info("选手IDs: {}", dto.getAthleteIds());
|
||||
log.info("集体IDs: {}", dto.getTeamIds());
|
||||
log.info("联系电话: {}", dto.getContactPhone());
|
||||
log.info("总金额: {}", dto.getTotalAmount());
|
||||
|
||||
// Validate competition exists and check registration/competition time
|
||||
MartialCompetition competition = competitionService.getById(dto.getCompetitionId());
|
||||
if (competition == null) {
|
||||
return R.fail("赛事不存在");
|
||||
}
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
// Check if registration time is valid
|
||||
if (competition.getRegistrationStartTime() != null && now.isBefore(competition.getRegistrationStartTime())) {
|
||||
return R.fail("报名尚未开始");
|
||||
}
|
||||
if (competition.getRegistrationEndTime() != null && now.isAfter(competition.getRegistrationEndTime())) {
|
||||
return R.fail("报名已结束");
|
||||
}
|
||||
|
||||
// Check if competition has ended
|
||||
if (competition.getCompetitionEndTime() != null && now.isAfter(competition.getCompetitionEndTime())) {
|
||||
return R.fail("比赛已结束,无法报名");
|
||||
}
|
||||
|
||||
// Create order entity
|
||||
MartialRegistrationOrder order = new MartialRegistrationOrder();
|
||||
order.setOrderNo(dto.getOrderNo());
|
||||
order.setCompetitionId(dto.getCompetitionId());
|
||||
order.setContactPhone(dto.getContactPhone());
|
||||
order.setTotalAmount(dto.getTotalAmount());
|
||||
order.setUserId(AuthUtil.getUserId());
|
||||
order.setUserName(AuthUtil.getUserName());
|
||||
|
||||
// Parse IDs
|
||||
List<Long> athleteIds = Func.toLongList(dto.getAthleteIds());
|
||||
List<Long> teamIds = Func.toLongList(dto.getTeamIds());
|
||||
List<Long> projectIds = Func.toLongList(dto.getProjectIds());
|
||||
|
||||
// Determine if this is a team registration
|
||||
boolean isTeamRegistration = !teamIds.isEmpty();
|
||||
|
||||
if (isTeamRegistration) {
|
||||
order.setTotalParticipants(teamIds.size());
|
||||
} else {
|
||||
order.setTotalParticipants(athleteIds.size());
|
||||
}
|
||||
|
||||
// Save order
|
||||
boolean saved = registrationOrderService.save(order);
|
||||
if (!saved) {
|
||||
return R.fail("创建订单失败");
|
||||
}
|
||||
|
||||
Long orderId = order.getId();
|
||||
log.info("订单创建成功,订单ID: {}", orderId);
|
||||
|
||||
if (isTeamRegistration) {
|
||||
// Handle team registration - create record for each team and each project
|
||||
for (Long teamId : teamIds) {
|
||||
MartialTeam team = teamService.getById(teamId);
|
||||
if (team == null) {
|
||||
log.warn("集体不存在: {}", teamId);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create a record for each project
|
||||
for (Long projectId : projectIds) {
|
||||
MartialAthlete teamAthlete = new MartialAthlete();
|
||||
teamAthlete.setOrderId(orderId);
|
||||
teamAthlete.setCompetitionId(dto.getCompetitionId());
|
||||
teamAthlete.setProjectId(projectId);
|
||||
teamAthlete.setTeamName(team.getTeamName());
|
||||
teamAthlete.setPlayerName(team.getTeamName());
|
||||
teamAthlete.setOrganization(team.getTeamName());
|
||||
teamAthlete.setRegistrationStatus(1);
|
||||
teamAthlete.setCompetitionStatus(0);
|
||||
teamAthlete.setCreateUser(AuthUtil.getUserId());
|
||||
teamAthlete.setCreateTime(new java.util.Date());
|
||||
teamAthlete.setTenantId("000000");
|
||||
teamAthlete.setIsDeleted(0);
|
||||
teamAthlete.setStatus(1);
|
||||
|
||||
athleteService.save(teamAthlete);
|
||||
log.info("创建集体参赛记录: teamName={}, projectId={}", team.getTeamName(), projectId);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Handle individual registration - create record for each athlete and each project
|
||||
for (Long athleteId : athleteIds) {
|
||||
MartialAthlete existingAthlete = athleteService.getById(athleteId);
|
||||
if (existingAthlete == null) {
|
||||
log.warn("选手不存在: {}", athleteId);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create a record for each project
|
||||
for (Long projectId : projectIds) {
|
||||
// Check if record already exists for this athlete + competition + project
|
||||
LambdaQueryWrapper<MartialAthlete> checkWrapper = new LambdaQueryWrapper<>();
|
||||
checkWrapper.eq(MartialAthlete::getCompetitionId, dto.getCompetitionId())
|
||||
.eq(MartialAthlete::getProjectId, projectId)
|
||||
.eq(MartialAthlete::getIdCard, existingAthlete.getIdCard())
|
||||
.eq(MartialAthlete::getIsDeleted, 0);
|
||||
MartialAthlete existingRecord = athleteService.getOne(checkWrapper, false);
|
||||
|
||||
if (existingRecord != null) {
|
||||
// Update existing record with order info
|
||||
existingRecord.setOrderId(orderId);
|
||||
existingRecord.setRegistrationStatus(1);
|
||||
existingRecord.setUpdateUser(AuthUtil.getUserId());
|
||||
existingRecord.setUpdateTime(new java.util.Date());
|
||||
athleteService.updateById(existingRecord);
|
||||
log.info("更新已存在的选手参赛记录: playerName={}, projectId={}", existingAthlete.getPlayerName(), projectId);
|
||||
} else {
|
||||
// Create new record
|
||||
MartialAthlete newRecord = new MartialAthlete();
|
||||
newRecord.setOrderId(orderId);
|
||||
newRecord.setCompetitionId(dto.getCompetitionId());
|
||||
newRecord.setProjectId(projectId);
|
||||
newRecord.setPlayerName(existingAthlete.getPlayerName());
|
||||
newRecord.setGender(existingAthlete.getGender());
|
||||
newRecord.setIdCard(existingAthlete.getIdCard());
|
||||
newRecord.setIdCardType(existingAthlete.getIdCardType());
|
||||
newRecord.setBirthDate(existingAthlete.getBirthDate());
|
||||
newRecord.setAge(existingAthlete.getAge());
|
||||
newRecord.setContactPhone(existingAthlete.getContactPhone());
|
||||
newRecord.setOrganization(existingAthlete.getOrganization());
|
||||
newRecord.setTeamName(existingAthlete.getTeamName());
|
||||
newRecord.setRegistrationStatus(1);
|
||||
newRecord.setCompetitionStatus(0);
|
||||
newRecord.setCreateUser(AuthUtil.getUserId());
|
||||
newRecord.setCreateTime(new java.util.Date());
|
||||
newRecord.setTenantId("000000");
|
||||
newRecord.setIsDeleted(0);
|
||||
newRecord.setStatus(1);
|
||||
|
||||
athleteService.save(newRecord);
|
||||
log.info("创建选手参赛记录: playerName={}, projectId={}", existingAthlete.getPlayerName(), projectId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return R.data(order);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
@PostMapping("/remove")
|
||||
@Operation(summary = "删除", description = "传入ID")
|
||||
public R remove(@RequestParam String ids) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.springblade.core.boot.ctrl.BladeController;
|
||||
import org.springblade.core.secure.BladeUser;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.modules.martial.config.ScheduleConfig;
|
||||
import org.springblade.modules.martial.pojo.dto.MoveScheduleGroupDTO;
|
||||
import org.springblade.modules.martial.pojo.dto.SaveScheduleDraftDTO;
|
||||
import org.springblade.modules.martial.pojo.dto.ScheduleResultDTO;
|
||||
@@ -15,6 +16,7 @@ import org.springblade.modules.martial.service.IMartialScheduleArrangeService;
|
||||
import org.springblade.modules.martial.service.IMartialScheduleService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -31,6 +33,24 @@ public class MartialScheduleArrangeController extends BladeController {
|
||||
|
||||
private final IMartialScheduleArrangeService scheduleArrangeService;
|
||||
private final IMartialScheduleService scheduleService;
|
||||
private final ScheduleConfig scheduleConfig;
|
||||
|
||||
/**
|
||||
* 获取赛程配置
|
||||
*/
|
||||
@GetMapping("/config")
|
||||
@Operation(summary = "获取赛程配置", description = "获取赛程编排的时间配置")
|
||||
public R<Map<String, Object>> getScheduleConfig() {
|
||||
Map<String, Object> config = new HashMap<>();
|
||||
config.put("morningStartTime", scheduleConfig.getMorningStartTime());
|
||||
config.put("morningEndTime", scheduleConfig.getMorningEndTime());
|
||||
config.put("afternoonStartTime", scheduleConfig.getAfternoonStartTime());
|
||||
config.put("afternoonEndTime", scheduleConfig.getAfternoonEndTime());
|
||||
config.put("maxPeoplePerGroup", scheduleConfig.getMaxPeoplePerGroup());
|
||||
config.put("targetPeoplePerGroup", scheduleConfig.getTargetPeoplePerGroup());
|
||||
config.put("defaultDurationPerPerson", scheduleConfig.getDefaultDurationPerPerson());
|
||||
return R.data(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取编排结果
|
||||
@@ -69,13 +89,11 @@ public class MartialScheduleArrangeController extends BladeController {
|
||||
@Operation(summary = "完成编排并锁定", description = "传入赛事ID")
|
||||
public R saveAndLock(@RequestBody SaveScheduleDraftDTO dto) {
|
||||
try {
|
||||
// 获取当前登录用户
|
||||
BladeUser user = AuthUtil.getUser();
|
||||
String userId = user != null ? user.getUserName() : "system";
|
||||
|
||||
boolean success = scheduleService.saveAndLockSchedule(dto.getCompetitionId());
|
||||
if (success) {
|
||||
// 调用原有的锁定逻辑
|
||||
scheduleArrangeService.saveAndLock(dto.getCompetitionId(), userId);
|
||||
return R.success("编排已完成并锁定");
|
||||
} else {
|
||||
@@ -167,4 +185,21 @@ public class MartialScheduleArrangeController extends BladeController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新参赛者签到状态
|
||||
*/
|
||||
@PostMapping("/update-check-in-status")
|
||||
@Operation(summary = "更新签到状态", description = "更新参赛者签到状态:未签到/已签到/异常")
|
||||
public R updateCheckInStatus(@RequestBody java.util.Map<String, Object> params) {
|
||||
try {
|
||||
Long participantId = Long.valueOf(String.valueOf(params.get("participantId")));
|
||||
String status = String.valueOf(params.get("status"));
|
||||
boolean success = scheduleService.updateParticipantCheckInStatus(participantId, status);
|
||||
return success ? R.success("状态更新成功") : R.fail("状态更新失败");
|
||||
} catch (Exception e) {
|
||||
log.error("更新签到状态失败", e);
|
||||
return R.fail("更新签到状态失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.springblade.core.mp.support.Query;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialScore;
|
||||
import org.springblade.modules.martial.pojo.vo.MartialScoreVO;
|
||||
import org.springblade.modules.martial.service.IMartialScoreService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@@ -43,8 +44,8 @@ public class MartialScoreController extends BladeController {
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "分页列表", description = "分页查询")
|
||||
public R<IPage<MartialScore>> list(MartialScore score, Query query) {
|
||||
IPage<MartialScore> pages = scoreService.page(Condition.getPage(query), Condition.getQueryWrapper(score));
|
||||
public R<IPage<MartialScoreVO>> list(MartialScore score, Query query) {
|
||||
IPage<MartialScoreVO> pages = scoreService.selectScoreVOPage(Condition.getPage(query), score);
|
||||
return R.data(pages);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.springblade.modules.martial.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.boot.ctrl.BladeController;
|
||||
import org.springblade.core.mp.support.Query;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.modules.martial.pojo.dto.TeamSubmitDTO;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialTeam;
|
||||
import org.springblade.modules.martial.pojo.vo.MartialTeamVO;
|
||||
import org.springblade.modules.martial.service.IMartialTeamService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@RequestMapping("/martial/team")
|
||||
@Tag(name = "集体管理", description = "集体/团队接口")
|
||||
public class MartialTeamController extends BladeController {
|
||||
|
||||
private final IMartialTeamService teamService;
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "分页列表", description = "获取当前用户的集体列表")
|
||||
public R<IPage<MartialTeamVO>> list(Query query) {
|
||||
Long userId = AuthUtil.getUserId();
|
||||
IPage<MartialTeamVO> pages = teamService.getTeamList(userId, query.getCurrent(), query.getSize());
|
||||
return R.data(pages);
|
||||
}
|
||||
|
||||
@GetMapping("/detail")
|
||||
@Operation(summary = "详情", description = "获取集体详情")
|
||||
public R<MartialTeamVO> detail(@RequestParam Long id) {
|
||||
return R.data(teamService.getTeamDetail(id));
|
||||
}
|
||||
|
||||
@PostMapping("/submit")
|
||||
@Operation(summary = "保存", description = "新增或修改集体")
|
||||
public R<Boolean> submit(@RequestBody TeamSubmitDTO dto) {
|
||||
log.info("Team submit - teamId: {}, teamName: {}, memberIds: {}", dto.getTeamId(), dto.getTeamName(), dto.getMemberIds());
|
||||
|
||||
MartialTeam team = new MartialTeam();
|
||||
team.setTeamName(dto.getTeamName());
|
||||
team.setRemark(dto.getRemark());
|
||||
|
||||
boolean result;
|
||||
if (StringUtil.isNotBlank(dto.getTeamId())) {
|
||||
Long teamId = Long.parseLong(dto.getTeamId());
|
||||
team.setId(teamId);
|
||||
log.info("Updating team with id: {}", teamId);
|
||||
result = teamService.updateTeamWithMembers(team, dto.getMemberIds());
|
||||
} else {
|
||||
log.info("Creating new team");
|
||||
result = teamService.saveTeamWithMembers(team, dto.getMemberIds());
|
||||
}
|
||||
return R.data(result);
|
||||
}
|
||||
|
||||
@PostMapping("/remove")
|
||||
@Operation(summary = "删除", description = "删除集体")
|
||||
public R<Boolean> remove(@RequestParam Long id) {
|
||||
return R.data(teamService.removeTeamWithMembers(id));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.springblade.modules.martial.excel;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
||||
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
|
||||
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Schedule export Excel - Template 2 (Competition Schedule Format)
|
||||
* Format: Sequence, Project, Participants, Groups, Time, Table Number
|
||||
*/
|
||||
@Data
|
||||
@ColumnWidth(12)
|
||||
@HeadRowHeight(25)
|
||||
@ContentRowHeight(20)
|
||||
public class ScheduleExportExcel2 implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ExcelProperty("序号")
|
||||
@ColumnWidth(8)
|
||||
private Integer sequenceNo;
|
||||
|
||||
@ExcelProperty("项目")
|
||||
@ColumnWidth(25)
|
||||
private String projectName;
|
||||
|
||||
@ExcelProperty("人数")
|
||||
@ColumnWidth(8)
|
||||
private Integer participantCount;
|
||||
|
||||
@ExcelProperty("组数")
|
||||
@ColumnWidth(8)
|
||||
private Integer groupCount;
|
||||
|
||||
@ExcelProperty("时间")
|
||||
@ColumnWidth(10)
|
||||
private Integer durationMinutes;
|
||||
|
||||
@ExcelProperty("表号")
|
||||
@ColumnWidth(10)
|
||||
private String tableNo;
|
||||
}
|
||||
@@ -22,4 +22,10 @@ public interface MartialAthleteMapper extends BaseMapper<MartialAthlete> {
|
||||
*/
|
||||
IPage<MartialAthleteVO> selectAthleteVOPage(IPage<MartialAthleteVO> page, @Param("athlete") MartialAthlete athlete);
|
||||
|
||||
/**
|
||||
* Count distinct participants by id_card for a competition
|
||||
*/
|
||||
@org.apache.ibatis.annotations.Select("SELECT COUNT(DISTINCT id_card) FROM martial_athlete WHERE competition_id = #{competitionId} AND is_deleted = 0")
|
||||
Long countDistinctParticipants(@Param("competitionId") Long competitionId);
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
LEFT JOIN martial_competition c ON a.competition_id = c.id AND c.is_deleted = 0
|
||||
LEFT JOIN martial_project p ON a.project_id = p.id AND p.is_deleted = 0
|
||||
WHERE a.is_deleted = 0
|
||||
AND (a.team_name IS NULL OR a.player_name != a.team_name)
|
||||
<if test="athlete.competitionId != null">
|
||||
AND a.competition_id = #{athlete.competitionId}
|
||||
</if>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.springblade.modules.martial.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialContact;
|
||||
|
||||
/**
|
||||
* Contact Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface MartialContactMapper extends BaseMapper<MartialContact> {
|
||||
|
||||
}
|
||||
@@ -27,6 +27,7 @@
|
||||
<result column="invite_message" property="inviteMessage"/>
|
||||
<result column="cancel_reason" property="cancelReason"/>
|
||||
<result column="competition_name" property="competitionName"/>
|
||||
<result column="venue_name" property="venueName"/>
|
||||
<result column="create_user" property="createUser"/>
|
||||
<result column="create_dept" property="createDept"/>
|
||||
<result column="create_time" property="createTime"/>
|
||||
@@ -71,17 +72,22 @@
|
||||
ji.referee_type,
|
||||
j.name AS judge_name,
|
||||
j.level AS judge_level,
|
||||
c.competition_name
|
||||
c.competition_name,
|
||||
v.venue_name
|
||||
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.is_deleted = 0
|
||||
<if test="judgeInvite.competitionId != null">
|
||||
AND ji.competition_id = #{judgeInvite.competitionId}
|
||||
</if>
|
||||
<if test="judgeInvite.inviteStatus != null">
|
||||
AND ji.invite_status = #{judgeInvite.inviteStatus}
|
||||
</if>
|
||||
<if test="judgeInvite.venueId != null">
|
||||
AND ji.venue_id = #{judgeInvite.venueId}
|
||||
</if>
|
||||
ORDER BY ji.create_time DESC
|
||||
</select>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package org.springblade.modules.martial.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialScore;
|
||||
import org.springblade.modules.martial.pojo.vo.MartialScoreVO;
|
||||
|
||||
/**
|
||||
* Score Mapper 接口
|
||||
@@ -10,4 +13,6 @@ import org.springblade.modules.martial.pojo.entity.MartialScore;
|
||||
*/
|
||||
public interface MartialScoreMapper extends BaseMapper<MartialScore> {
|
||||
|
||||
IPage<MartialScoreVO> selectScoreVOPage(IPage<MartialScoreVO> page, @Param("score") MartialScore score);
|
||||
|
||||
}
|
||||
|
||||
@@ -2,4 +2,43 @@
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.springblade.modules.martial.mapper.MartialScoreMapper">
|
||||
|
||||
<select id="selectScoreVOPage" resultType="org.springblade.modules.martial.pojo.vo.MartialScoreVO">
|
||||
SELECT
|
||||
s.*,
|
||||
a.player_name as playerName,
|
||||
a.team_name as teamName,
|
||||
a.id_card as idCard,
|
||||
a.player_no as playerNo,
|
||||
p.project_name as projectName,
|
||||
v.venue_name as venueName,
|
||||
r.chief_judge_score as chiefJudgeScore,
|
||||
r.score_status as scoreStatus,
|
||||
(SELECT GROUP_CONCAT(d.item_name SEPARATOR ', ')
|
||||
FROM martial_deduction_item d
|
||||
WHERE FIND_IN_SET(d.id, REPLACE(REPLACE(s.deduction_items, '[', ''), ']', ''))
|
||||
) as deductionItemsText
|
||||
FROM martial_score s
|
||||
LEFT JOIN martial_athlete a ON s.athlete_id = a.id AND a.is_deleted = 0
|
||||
LEFT JOIN martial_project p ON s.project_id = p.id AND p.is_deleted = 0
|
||||
LEFT JOIN martial_venue v ON s.venue_id = v.id AND v.is_deleted = 0
|
||||
LEFT JOIN martial_result r ON s.athlete_id = r.athlete_id AND s.project_id = r.project_id AND r.is_deleted = 0
|
||||
WHERE s.is_deleted = 0
|
||||
<if test="score.competitionId != null">
|
||||
AND s.competition_id = #{score.competitionId}
|
||||
</if>
|
||||
<if test="score.athleteId != null">
|
||||
AND s.athlete_id = #{score.athleteId}
|
||||
</if>
|
||||
<if test="score.projectId != null">
|
||||
AND s.project_id = #{score.projectId}
|
||||
</if>
|
||||
<if test="score.judgeId != null">
|
||||
AND s.judge_id = #{score.judgeId}
|
||||
</if>
|
||||
<if test="score.venueId != null">
|
||||
AND s.venue_id = #{score.venueId}
|
||||
</if>
|
||||
ORDER BY s.create_time DESC
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.springblade.modules.martial.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialTeam;
|
||||
|
||||
public interface MartialTeamMapper extends BaseMapper<MartialTeam> {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.springblade.modules.martial.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialTeamMember;
|
||||
|
||||
public interface MartialTeamMemberMapper extends BaseMapper<MartialTeamMember> {
|
||||
}
|
||||
@@ -20,9 +20,15 @@ public class BatchGenerateInviteDTO {
|
||||
@Schema(description = "评委ID列表", required = true)
|
||||
private List<Long> judgeIds;
|
||||
|
||||
@Schema(description = "角色:judge-普通评委,chief_judge-裁判长")
|
||||
@Schema(description = "角色:judge-普通评委,chief_judge-主裁判")
|
||||
private String role = "judge";
|
||||
|
||||
@Schema(description = "过期天数(默认30天)")
|
||||
private Integer expireDays = 30;
|
||||
|
||||
@Schema(description = "场地ID")
|
||||
private Long venueId;
|
||||
|
||||
@Schema(description = "项目ID列表JSON字符串")
|
||||
private String projects;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.springblade.modules.martial.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 主裁判确认评分DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "主裁判确认评分DTO")
|
||||
public class ChiefJudgeConfirmDTO {
|
||||
|
||||
@Schema(description = "成绩ID")
|
||||
private String resultId;
|
||||
|
||||
@Schema(description = "主裁判ID")
|
||||
private String chiefJudgeId;
|
||||
|
||||
@Schema(description = "确认/修改后的分数(null表示直接确认原分数)")
|
||||
private BigDecimal score;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String note;
|
||||
}
|
||||
@@ -23,6 +23,12 @@ public class CompetitionGroupDTO implements Serializable {
|
||||
@Schema(description = "分组ID")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 项目ID
|
||||
*/
|
||||
@Schema(description = "项目ID")
|
||||
private Long projectId;
|
||||
|
||||
/**
|
||||
* 分组标题
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.springblade.modules.martial.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 总裁确认评分DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "总裁确认评分DTO")
|
||||
public class GeneralJudgeConfirmDTO {
|
||||
|
||||
@Schema(description = "成绩ID")
|
||||
private String resultId;
|
||||
|
||||
@Schema(description = "总裁ID")
|
||||
private String generalJudgeId;
|
||||
|
||||
@Schema(description = "确认/修改后的分数(null表示直接确认原分数)")
|
||||
private BigDecimal score;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String note;
|
||||
}
|
||||
@@ -19,7 +19,7 @@ public class GenerateInviteDTO {
|
||||
@Schema(description = "评委ID", required = true)
|
||||
private Long judgeId;
|
||||
|
||||
@Schema(description = "角色:judge-普通评委,chief_judge-裁判长", required = true)
|
||||
@Schema(description = "角色:judge-普通评委,chief_judge-主裁判", required = true)
|
||||
private String role;
|
||||
|
||||
@Schema(description = "分配场地ID(普通评委必填)")
|
||||
|
||||
@@ -20,7 +20,7 @@ public class MiniScoreModifyDTO implements Serializable {
|
||||
@Schema(description = "选手ID")
|
||||
private Long athleteId;
|
||||
|
||||
@Schema(description = "修改者ID(裁判长ID)")
|
||||
@Schema(description = "修改者ID(主裁判ID)")
|
||||
private Long modifierId;
|
||||
|
||||
@Schema(description = "修改后的分数")
|
||||
|
||||
@@ -28,6 +28,12 @@ public class ParticipantDTO implements Serializable {
|
||||
@Schema(description = "学校/单位")
|
||||
private String schoolUnit;
|
||||
|
||||
/**
|
||||
* 队伍名称
|
||||
*/
|
||||
@Schema(description = "队伍名称")
|
||||
private String teamName;
|
||||
|
||||
/**
|
||||
* 状态:未签到/已签到/异常
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.springblade.modules.martial.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
@Schema(description = "报名提交数据")
|
||||
public class RegistrationSubmitDTO {
|
||||
|
||||
@Schema(description = "订单号")
|
||||
private String orderNo;
|
||||
|
||||
@Schema(description = "赛事ID")
|
||||
private Long competitionId;
|
||||
|
||||
@Schema(description = "项目ID列表")
|
||||
private String projectIds;
|
||||
|
||||
@Schema(description = "选手ID列表(个人项目)")
|
||||
private String athleteIds;
|
||||
|
||||
@Schema(description = "集体ID列表(集体项目)")
|
||||
private String teamIds;
|
||||
|
||||
@Schema(description = "联系电话")
|
||||
private String contactPhone;
|
||||
|
||||
@Schema(description = "总金额")
|
||||
private BigDecimal totalAmount;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.springblade.modules.martial.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Schema(description = "集体提交DTO")
|
||||
public class TeamSubmitDTO {
|
||||
|
||||
@Schema(description = "集体ID(更新时必填)")
|
||||
private String teamId;
|
||||
|
||||
@Schema(description = "集体名称")
|
||||
private String teamName;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "成员ID列表")
|
||||
private List<Long> memberIds;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.springblade.modules.martial.pojo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springblade.core.tenant.mp.TenantEntity;
|
||||
|
||||
/**
|
||||
* Contact entity
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("martial_contact")
|
||||
@Schema(description = "联系人")
|
||||
public class MartialContact extends TenantEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "证件类型")
|
||||
private String idType;
|
||||
|
||||
@Schema(description = "姓名")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "证件号码")
|
||||
private String idCard;
|
||||
|
||||
@Schema(description = "手机号码")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "邮箱")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "地址")
|
||||
private String address;
|
||||
|
||||
@Schema(description = "是否默认联系人")
|
||||
private Boolean isDefault;
|
||||
|
||||
}
|
||||
@@ -93,4 +93,11 @@ public class MartialDeductionItem extends TenantEntity {
|
||||
@Schema(description = "项目名称")
|
||||
private String projectName;
|
||||
|
||||
/**
|
||||
* 赛事ID
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "赛事ID")
|
||||
private Long competitionId;
|
||||
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ public class MartialJudge extends TenantEntity {
|
||||
private String idCard;
|
||||
|
||||
/**
|
||||
* 裁判类型(1-裁判长,2-普通裁判)
|
||||
* 裁判类型(1-主裁判,2-裁判员)
|
||||
*/
|
||||
@Schema(description = "裁判类型")
|
||||
private Integer refereeType;
|
||||
|
||||
@@ -37,6 +37,14 @@ public class MartialJudgeInvite extends TenantEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// ========== 角色常量 ==========
|
||||
/** 裁判员 */
|
||||
public static final String ROLE_JUDGE = "judge";
|
||||
/** 主裁判 */
|
||||
public static final String ROLE_CHIEF_JUDGE = "chief_judge";
|
||||
/** 总裁(裁判长) */
|
||||
public static final String ROLE_GENERAL_JUDGE = "general_judge";
|
||||
|
||||
/**
|
||||
* 赛事ID
|
||||
*/
|
||||
@@ -56,13 +64,13 @@ public class MartialJudgeInvite extends TenantEntity {
|
||||
private String inviteCode;
|
||||
|
||||
/**
|
||||
* 角色(judge-普通裁判,chief_judge-裁判长)
|
||||
* 角色(judge-裁判员, chief_judge-主裁判, general_judge-总裁)
|
||||
*/
|
||||
@Schema(description = "角色")
|
||||
private String role;
|
||||
|
||||
/**
|
||||
* 分配场地ID
|
||||
* 分配场地ID (总裁时为null,表示负责所有场地)
|
||||
*/
|
||||
@Schema(description = "分配场地ID")
|
||||
private Long venueId;
|
||||
@@ -169,4 +177,25 @@ public class MartialJudgeInvite extends TenantEntity {
|
||||
@Schema(description = "裁判类型")
|
||||
private Integer refereeType;
|
||||
|
||||
/**
|
||||
* 判断是否为裁判员
|
||||
*/
|
||||
public boolean isJudge() {
|
||||
return ROLE_JUDGE.equals(this.role);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为主裁判
|
||||
*/
|
||||
public boolean isChiefJudge() {
|
||||
return ROLE_CHIEF_JUDGE.equals(this.role);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为总裁
|
||||
*/
|
||||
public boolean isGeneralJudge() {
|
||||
return ROLE_GENERAL_JUDGE.equals(this.role);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,6 +45,12 @@ public class MartialProject extends TenantEntity {
|
||||
@Schema(description = "赛事ID")
|
||||
private Long competitionId;
|
||||
|
||||
/**
|
||||
* 所属场地ID
|
||||
*/
|
||||
@Schema(description = "所属场地ID")
|
||||
private Long venueId;
|
||||
|
||||
/**
|
||||
* 项目名称
|
||||
*/
|
||||
@@ -63,6 +69,13 @@ public class MartialProject extends TenantEntity {
|
||||
@Schema(description = "组别")
|
||||
private String category;
|
||||
|
||||
/**
|
||||
* 项目类型(1-套路,2-散打,3-器械,4-对练)
|
||||
*/
|
||||
@Schema(description = "项目类型")
|
||||
@com.baomidou.mybatisplus.annotation.TableField("event_type")
|
||||
private Integer eventType;
|
||||
|
||||
/**
|
||||
* 类型(1-个人,2-双人,3-集体)
|
||||
*/
|
||||
@@ -111,6 +124,18 @@ public class MartialProject extends TenantEntity {
|
||||
@Schema(description = "报名费用")
|
||||
private BigDecimal price;
|
||||
|
||||
/**
|
||||
* 报名开始时间
|
||||
*/
|
||||
@Schema(description = "报名开始时间")
|
||||
private LocalDateTime registrationStartTime;
|
||||
|
||||
/**
|
||||
* 报名结束时间
|
||||
*/
|
||||
@Schema(description = "报名结束时间")
|
||||
private LocalDateTime registrationEndTime;
|
||||
|
||||
/**
|
||||
* 难度系数(默认1.00)
|
||||
*/
|
||||
|
||||
@@ -38,6 +38,14 @@ public class MartialResult extends TenantEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// ========== 评分状态常量 ==========
|
||||
/** 评分状态:裁判员评分中 */
|
||||
public static final int SCORE_STATUS_JUDGING = 0;
|
||||
/** 评分状态:主裁判已确认 */
|
||||
public static final int SCORE_STATUS_CHIEF_CONFIRMED = 1;
|
||||
/** 评分状态:总裁已确认 */
|
||||
public static final int SCORE_STATUS_GENERAL_CONFIRMED = 2;
|
||||
|
||||
/**
|
||||
* 赛事ID
|
||||
*/
|
||||
@@ -158,4 +166,62 @@ public class MartialResult extends TenantEntity {
|
||||
@Schema(description = "发布时间")
|
||||
private LocalDateTime publishTime;
|
||||
|
||||
// ========== 主裁判确认相关字段 ==========
|
||||
|
||||
/**
|
||||
* 主裁判确认/修改后的分数
|
||||
*/
|
||||
@Schema(description = "主裁判确认/修改后的分数")
|
||||
private BigDecimal chiefJudgeScore;
|
||||
|
||||
/**
|
||||
* 主裁判ID
|
||||
*/
|
||||
@Schema(description = "主裁判ID")
|
||||
private Long chiefJudgeId;
|
||||
|
||||
/**
|
||||
* 主裁判确认时间
|
||||
*/
|
||||
@Schema(description = "主裁判确认时间")
|
||||
private LocalDateTime chiefJudgeTime;
|
||||
|
||||
/**
|
||||
* 主裁判备注
|
||||
*/
|
||||
@Schema(description = "主裁判备注")
|
||||
private String chiefJudgeNote;
|
||||
|
||||
// ========== 总裁确认相关字段 ==========
|
||||
|
||||
/**
|
||||
* 总裁确认/修改后的分数
|
||||
*/
|
||||
@Schema(description = "总裁确认/修改后的分数")
|
||||
private BigDecimal generalJudgeScore;
|
||||
|
||||
/**
|
||||
* 总裁ID
|
||||
*/
|
||||
@Schema(description = "总裁ID")
|
||||
private Long generalJudgeId;
|
||||
|
||||
/**
|
||||
* 总裁确认时间
|
||||
*/
|
||||
@Schema(description = "总裁确认时间")
|
||||
private LocalDateTime generalJudgeTime;
|
||||
|
||||
/**
|
||||
* 总裁备注
|
||||
*/
|
||||
@Schema(description = "总裁备注")
|
||||
private String generalJudgeNote;
|
||||
|
||||
/**
|
||||
* 评分状态: 0-裁判员评分中, 1-主裁判已确认, 2-总裁已确认
|
||||
*/
|
||||
@Schema(description = "评分状态: 0-裁判员评分中, 1-主裁判已确认, 2-总裁已确认")
|
||||
private Integer scoreStatus;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.springblade.modules.martial.pojo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springblade.core.tenant.mp.TenantEntity;
|
||||
|
||||
/**
|
||||
* 集体/团队实体类
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("martial_team")
|
||||
@Schema(description = "集体/团队")
|
||||
public class MartialTeam extends TenantEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "集体名称")
|
||||
private String teamName;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "成员数量")
|
||||
private Integer memberCount;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.springblade.modules.martial.pojo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 集体成员关联实体类
|
||||
*/
|
||||
@Data
|
||||
@TableName("martial_team_member")
|
||||
@Schema(description = "集体成员关联")
|
||||
public class MartialTeamMember implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "集体ID")
|
||||
private Long teamId;
|
||||
|
||||
@Schema(description = "选手ID")
|
||||
private Long athleteId;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "是否删除")
|
||||
private Integer isDeleted;
|
||||
|
||||
@Schema(description = "租户ID")
|
||||
private String tenantId;
|
||||
|
||||
}
|
||||
@@ -53,6 +53,12 @@ public class MartialVenue extends TenantEntity {
|
||||
@Schema(description = "场地编码")
|
||||
private String venueCode;
|
||||
|
||||
/**
|
||||
* 场地类型(indoor-室内,outdoor-室外)
|
||||
*/
|
||||
@Schema(description = "场地类型")
|
||||
private String venueType;
|
||||
|
||||
/**
|
||||
* 容纳人数
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.springblade.modules.martial.pojo.vo;
|
||||
|
||||
import lombok.Data;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 出场顺序分组VO
|
||||
*/
|
||||
@Data
|
||||
public class LineupGroupVO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long groupId;
|
||||
private String groupName;
|
||||
private String projectName;
|
||||
private String category;
|
||||
private String venueName;
|
||||
private String timeSlot;
|
||||
private String tableNo;
|
||||
private List<LineupParticipantVO> participants;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.springblade.modules.martial.pojo.vo;
|
||||
|
||||
import lombok.Data;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 出场顺序参赛者VO
|
||||
*/
|
||||
@Data
|
||||
public class LineupParticipantVO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long id;
|
||||
private Integer order;
|
||||
private String playerName;
|
||||
private String organization;
|
||||
private String status;
|
||||
}
|
||||
@@ -74,4 +74,21 @@ public class MartialJudgeInviteVO extends MartialJudgeInvite {
|
||||
@Schema(description = "赛事名称")
|
||||
private String competitionName;
|
||||
|
||||
/**
|
||||
* 场地名称
|
||||
*/
|
||||
@Schema(description = "场地名称")
|
||||
private String venueName;
|
||||
|
||||
/**
|
||||
* 获取场地名称
|
||||
* 总裁(referee_type=3)显示全部场地
|
||||
*/
|
||||
public String getVenueName() {
|
||||
if (this.getRefereeType() != null && this.getRefereeType() == 3) {
|
||||
return "全部场地";
|
||||
}
|
||||
return venueName;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -47,4 +47,35 @@ public class MartialScoreVO extends MartialScore {
|
||||
@Schema(description = "状态文本")
|
||||
private String statusText;
|
||||
|
||||
/**
|
||||
* 主裁判确认分数
|
||||
*/
|
||||
@Schema(description = "主裁判确认分数")
|
||||
private java.math.BigDecimal chiefJudgeScore;
|
||||
|
||||
/**
|
||||
* 评分状态 (0-待评分, 1-裁判已评分, 2-主裁判已确认)
|
||||
*/
|
||||
@Schema(description = "评分状态")
|
||||
private Integer scoreStatus;
|
||||
|
||||
/**
|
||||
* 队伍名称
|
||||
*/
|
||||
@Schema(description = "队伍名称")
|
||||
private String teamName;
|
||||
|
||||
/**
|
||||
* 身份证
|
||||
*/
|
||||
@Schema(description = "身份证")
|
||||
private String idCard;
|
||||
|
||||
/**
|
||||
* 选手编号
|
||||
*/
|
||||
@Schema(description = "选手编号")
|
||||
private String playerNo;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.springblade.modules.martial.pojo.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialTeam;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "集体视图对象")
|
||||
public class MartialTeamVO extends MartialTeam {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "成员列表")
|
||||
private List<MemberInfo> members;
|
||||
|
||||
@Data
|
||||
@Schema(description = "成员信息")
|
||||
public static class MemberInfo {
|
||||
@Schema(description = "选手ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "选手姓名")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "身份证号")
|
||||
private String idCard;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,12 +7,12 @@ import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 小程序选手评分VO(裁判长视图)
|
||||
* 小程序选手评分VO(主裁判视图)
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "小程序选手评分信息(裁判长)")
|
||||
@Schema(description = "小程序选手评分信息(主裁判)")
|
||||
public class MiniAthleteAdminVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@@ -20,7 +20,7 @@ public class MiniLoginVO implements Serializable {
|
||||
@Schema(description = "访问令牌")
|
||||
private String token;
|
||||
|
||||
@Schema(description = "用户角色:pub-普通评委, admin-裁判长")
|
||||
@Schema(description = "用户角色:pub-普通评委, admin-主裁判")
|
||||
private String userRole;
|
||||
|
||||
@Schema(description = "比赛ID")
|
||||
|
||||
@@ -25,7 +25,7 @@ public class MiniScoreDetailVO implements Serializable {
|
||||
@Schema(description = "评委评分列表")
|
||||
private List<JudgeScore> judgeScores;
|
||||
|
||||
@Schema(description = "裁判长修改信息")
|
||||
@Schema(description = "主裁判修改信息")
|
||||
private Modification modification;
|
||||
|
||||
/**
|
||||
@@ -82,10 +82,10 @@ public class MiniScoreDetailVO implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* 裁判长修改信息内部类
|
||||
* 主裁判修改信息内部类
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "裁判长修改信息")
|
||||
@Schema(description = "主裁判修改信息")
|
||||
public static class Modification implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package org.springblade.modules.martial.pojo.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Organization Statistics VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "单位统计视图对象")
|
||||
public class OrganizationStatsVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "单位名称")
|
||||
private String organization;
|
||||
|
||||
@Schema(description = "运动员人数(去重)")
|
||||
private Integer athleteCount;
|
||||
|
||||
@Schema(description = "项目数量")
|
||||
private Integer projectCount;
|
||||
|
||||
@Schema(description = "单人项目数")
|
||||
private Integer singleProjectCount;
|
||||
|
||||
@Schema(description = "集体项目数")
|
||||
private Integer teamProjectCount;
|
||||
|
||||
@Schema(description = "男运动员数")
|
||||
private Integer maleCount;
|
||||
|
||||
@Schema(description = "女运动员数")
|
||||
private Integer femaleCount;
|
||||
|
||||
@Schema(description = "总金额")
|
||||
private BigDecimal totalAmount;
|
||||
|
||||
@Schema(description = "项目金额明细")
|
||||
private List<ProjectAmountItem> projectAmounts;
|
||||
|
||||
@Data
|
||||
@Schema(description = "项目金额明细")
|
||||
public static class ProjectAmountItem implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "项目ID")
|
||||
private Long projectId;
|
||||
|
||||
@Schema(description = "项目名称")
|
||||
private String projectName;
|
||||
|
||||
@Schema(description = "项目类型(1=单人,2=集体)")
|
||||
private Integer projectType;
|
||||
|
||||
@Schema(description = "报名人数/集体数")
|
||||
private Integer count;
|
||||
|
||||
@Schema(description = "单价")
|
||||
private BigDecimal price;
|
||||
|
||||
@Schema(description = "小计金额")
|
||||
private BigDecimal amount;
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@ public interface IMartialAthleteService extends IService<MartialAthlete> {
|
||||
List<MiniAthleteScoreVO> getAthletesWithMyScore(Long judgeId, Long venueId, Long projectId);
|
||||
|
||||
/**
|
||||
* 小程序接口:获取选手列表(裁判长)
|
||||
* 小程序接口:获取选手列表(主裁判)
|
||||
*
|
||||
* @param competitionId 比赛ID
|
||||
* @param venueId 场地ID
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
package org.springblade.modules.martial.service;
|
||||
|
||||
import org.springblade.modules.martial.pojo.entity.MartialCompetitionRulesAttachment;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialCompetitionAttachment;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialCompetitionRulesChapter;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialCompetitionRulesContent;
|
||||
import org.springblade.modules.martial.pojo.vo.MartialCompetitionRulesVO;
|
||||
@@ -44,7 +44,7 @@ public interface IMartialCompetitionRulesService {
|
||||
* @param competitionId 赛事ID
|
||||
* @return 附件列表
|
||||
*/
|
||||
List<MartialCompetitionRulesAttachment> getAttachmentList(Long competitionId);
|
||||
List<MartialCompetitionAttachment> getAttachmentList(Long competitionId);
|
||||
|
||||
/**
|
||||
* 保存附件
|
||||
@@ -52,7 +52,7 @@ public interface IMartialCompetitionRulesService {
|
||||
* @param attachment 附件信息
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean saveAttachment(MartialCompetitionRulesAttachment attachment);
|
||||
boolean saveAttachment(MartialCompetitionAttachment attachment);
|
||||
|
||||
/**
|
||||
* 删除附件
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.springblade.modules.martial.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialContact;
|
||||
|
||||
/**
|
||||
* Contact Service Interface
|
||||
*/
|
||||
public interface IMartialContactService extends IService<MartialContact> {
|
||||
|
||||
/**
|
||||
* Get contact list by user
|
||||
*/
|
||||
IPage<MartialContact> getContactList(Long userId, Integer current, Integer size);
|
||||
|
||||
/**
|
||||
* Get contact detail
|
||||
*/
|
||||
MartialContact getContactDetail(Long id);
|
||||
|
||||
/**
|
||||
* Save contact with default uniqueness handling
|
||||
*/
|
||||
boolean saveContact(MartialContact contact, Long userId);
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.springblade.modules.martial.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialRegistrationOrder;
|
||||
import org.springblade.modules.martial.pojo.vo.MartialRegistrationOrderVO;
|
||||
@@ -19,4 +20,15 @@ public interface IMartialRegistrationOrderService extends IService<MartialRegist
|
||||
*/
|
||||
MartialRegistrationOrderVO getDetailWithRelations(Long id);
|
||||
|
||||
/**
|
||||
* 获取报名订单列表(包含关联数据)
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param status 状态
|
||||
* @param current 当前页
|
||||
* @param size 每页大小
|
||||
* @return 订单列表VO
|
||||
*/
|
||||
IPage<MartialRegistrationOrderVO> getListWithRelations(Long userId, Integer status, Integer current, Integer size);
|
||||
|
||||
}
|
||||
|
||||
@@ -70,4 +70,33 @@ public interface IMartialResultService extends IService<MartialResult> {
|
||||
*/
|
||||
List<CertificateVO> batchGenerateCertificates(Long projectId);
|
||||
|
||||
|
||||
// ========== 三级裁判评分流程方法 ==========
|
||||
|
||||
/**
|
||||
* 主裁判确认/修改分数
|
||||
*/
|
||||
boolean confirmByChiefJudge(Long resultId, Long chiefJudgeId, BigDecimal score, String note);
|
||||
|
||||
/**
|
||||
* 总裁确认/修改分数
|
||||
*/
|
||||
boolean confirmByGeneralJudge(Long resultId, Long generalJudgeId, BigDecimal score, String note);
|
||||
|
||||
/**
|
||||
* 获取待主裁判确认的成绩列表
|
||||
*/
|
||||
List<MartialResult> getPendingChiefConfirmList(Long venueId);
|
||||
|
||||
/**
|
||||
* 获取待总裁确认的成绩列表
|
||||
*/
|
||||
List<MartialResult> getPendingGeneralConfirmList(Long competitionId);
|
||||
|
||||
|
||||
/**
|
||||
* 获取已总裁确认的成绩列表
|
||||
*/
|
||||
List<MartialResult> getConfirmedGeneralList(Long competitionId);
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user