fix bugs
This commit is contained in:
@@ -27,7 +27,14 @@
|
||||
"Bash(python -m json.tool:*)",
|
||||
"Bash(\"/d/Program Files/mysql-8.0.32-winx64/bin/mysql\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"\nSELECT \n TABLE_NAME,\n CASE WHEN SUM(COLUMN_NAME = ''status'') > 0 THEN ''✓'' ELSE ''✗'' END AS has_status\nFROM information_schema.COLUMNS \nWHERE TABLE_SCHEMA = ''martial_db'' \n AND TABLE_NAME IN (''martial_athlete'', ''martial_live_update'', ''martial_result'', ''martial_schedule_athlete'')\nGROUP BY TABLE_NAME\nORDER BY TABLE_NAME;\n\")",
|
||||
"Bash(git add:*)",
|
||||
"Bash(git commit -m \"$(cat <<''EOF''\nMerge remote-tracking branch ''origin/main''\n\n解决目录重组冲突:\n- doc/ → docs/ (文档目录重命名)\n- doc/sql/ → database/ (数据库脚本目录重组)\n- doc/script/ → scripts/ (脚本目录重组)\n\n保留本地新增的武术比赛系统文件:\n- docs/sql/mysql/martial-*.sql (4个数据库脚本)\n- docs/后端开发完成报告.md\n- docs/数据库字段检查报告.md \n- docs/问题修复报告.md\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude <noreply@anthropic.com>\nEOF\n)\")"
|
||||
"Bash(git commit -m \"$(cat <<''EOF''\nMerge remote-tracking branch ''origin/main''\n\n解决目录重组冲突:\n- doc/ → docs/ (文档目录重命名)\n- doc/sql/ → database/ (数据库脚本目录重组)\n- doc/script/ → scripts/ (脚本目录重组)\n\n保留本地新增的武术比赛系统文件:\n- docs/sql/mysql/martial-*.sql (4个数据库脚本)\n- docs/后端开发完成报告.md\n- docs/数据库字段检查报告.md \n- docs/问题修复报告.md\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude <noreply@anthropic.com>\nEOF\n)\")",
|
||||
"Bash(\"/d/Program Files/mysql-8.0.32-winx64/bin/mysql\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"DESC martial_schedule_participant;\")",
|
||||
"Bash(\"/d/Program Files/mysql-8.0.32-winx64/bin/mysql\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"SHOW CREATE TABLE martial_schedule_participant\\\\G\")",
|
||||
"Bash(\"/d/Program Files/mysql-8.0.32-winx64/bin/mysql\" -h localhost -P 3306 -u root -p123456 -e \"DROP DATABASE IF EXISTS martial_db; CREATE DATABASE martial_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;\":*)",
|
||||
"Bash(\"/d/Program Files/mysql-8.0.32-winx64/bin/mysql\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"DESC mt_venue;\")",
|
||||
"Bash(grep:*)",
|
||||
"Bash(\"/d/Program Files/mysql-8.0.32-winx64/bin/mysql\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"DESC martial_competition_rules_attachment;\")",
|
||||
"Bash(\"/d/Program Files/mysql-8.0.32-winx64/bin/mysql\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"SELECT COUNT\\(*\\) FROM martial_competition_rules_attachment WHERE is_deleted = 0;\")"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
||||
12
Dockerfile
12
Dockerfile
@@ -1,15 +1,3 @@
|
||||
# 多阶段构建:编译阶段
|
||||
FROM maven:3.9-eclipse-temurin-17 AS builder
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
# 复制主项目源码
|
||||
COPY pom.xml .
|
||||
COPY src ./src
|
||||
|
||||
# 编译项目(在 Drone 中已经编译好,这里只是复制)
|
||||
RUN mkdir -p target
|
||||
|
||||
# 运行阶段:使用轻量级 JRE 镜像
|
||||
FROM eclipse-temurin:17-jre-jammy
|
||||
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
-- ================================================================
|
||||
-- 【紧急修复】场地表字段缺失问题 - 直接复制执行此脚本
|
||||
-- 问题:Unknown column 'max_capacity' in 'field list'
|
||||
-- 解决:重建 martial_venue 表,包含所有必需字段
|
||||
-- 日期:2025-12-06
|
||||
-- ================================================================
|
||||
|
||||
-- 使用正确的数据库
|
||||
USE martial_db;
|
||||
|
||||
-- 删除旧表(如果有重要数据,请先备份!)
|
||||
DROP TABLE IF EXISTS `martial_venue`;
|
||||
|
||||
-- 创建新表,包含完整字段
|
||||
CREATE TABLE `martial_venue` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`tenant_id` varchar(12) DEFAULT '000000' COMMENT '租户ID',
|
||||
`competition_id` bigint(20) NOT NULL COMMENT '赛事ID',
|
||||
`venue_name` varchar(100) NOT NULL COMMENT '场地名称',
|
||||
`venue_code` varchar(50) DEFAULT NULL COMMENT '场地编码',
|
||||
`max_capacity` int(11) DEFAULT 100 COMMENT '最大容纳人数',
|
||||
`location` varchar(200) DEFAULT NULL COMMENT '位置/地点',
|
||||
`description` varchar(500) DEFAULT NULL COMMENT '场地描述',
|
||||
`facilities` varchar(500) DEFAULT NULL COMMENT '场地设施',
|
||||
`sort_order` int(11) DEFAULT 0 COMMENT '排序',
|
||||
`status` int(2) DEFAULT 1 COMMENT '状态(0-禁用,1-启用)',
|
||||
`create_user` bigint(20) DEFAULT NULL COMMENT '创建人',
|
||||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||||
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_user` bigint(20) DEFAULT NULL COMMENT '修改人',
|
||||
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
`is_deleted` int(2) DEFAULT 0 COMMENT '是否已删除',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_competition_id` (`competition_id`),
|
||||
KEY `idx_tenant_id` (`tenant_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='场地信息表';
|
||||
|
||||
-- 验证表已创建成功
|
||||
DESC martial_venue;
|
||||
|
||||
-- 检查 max_capacity 字段
|
||||
SELECT '✓ martial_venue 表已成功重建,包含 max_capacity 字段' AS 修复结果;
|
||||
|
||||
-- 显示所有字段
|
||||
SELECT
|
||||
COLUMN_NAME AS 字段名,
|
||||
COLUMN_TYPE AS 类型,
|
||||
COLUMN_DEFAULT AS 默认值,
|
||||
COLUMN_COMMENT AS 说明
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = 'martial_db'
|
||||
AND TABLE_NAME = 'martial_venue'
|
||||
ORDER BY ORDINAL_POSITION;
|
||||
@@ -1,73 +0,0 @@
|
||||
-- 检查赛事基础数据是否完整
|
||||
USE martial_db;
|
||||
|
||||
-- 1. 检查赛事信息
|
||||
SELECT
|
||||
'赛事信息' AS '检查项',
|
||||
COUNT(*) AS '记录数'
|
||||
FROM martial_competition
|
||||
WHERE id = 200;
|
||||
|
||||
-- 2. 检查参赛者数据
|
||||
SELECT
|
||||
'参赛者数据' AS '检查项',
|
||||
COUNT(*) AS '记录数'
|
||||
FROM martial_athlete
|
||||
WHERE competition_id = 200;
|
||||
|
||||
-- 3. 检查场地数据
|
||||
SELECT
|
||||
'场地数据' AS '检查项',
|
||||
COUNT(*) AS '记录数'
|
||||
FROM martial_venue
|
||||
WHERE competition_id = 200;
|
||||
|
||||
-- 4. 检查项目数据
|
||||
SELECT
|
||||
'项目数据' AS '检查项',
|
||||
COUNT(*) AS '记录数'
|
||||
FROM martial_project
|
||||
WHERE id IN (
|
||||
SELECT DISTINCT project_id
|
||||
FROM martial_athlete
|
||||
WHERE competition_id = 200
|
||||
);
|
||||
|
||||
-- 5. 检查赛事时间配置
|
||||
SELECT
|
||||
id AS '赛事ID',
|
||||
competition_name AS '赛事名称',
|
||||
competition_start_time AS '开始时间',
|
||||
competition_end_time AS '结束时间',
|
||||
CASE
|
||||
WHEN competition_start_time IS NULL THEN '⚠ 未配置'
|
||||
WHEN competition_end_time IS NULL THEN '⚠ 未配置'
|
||||
ELSE '✓ 已配置'
|
||||
END AS '时间配置状态'
|
||||
FROM martial_competition
|
||||
WHERE id = 200;
|
||||
|
||||
-- 6. 详细检查参赛者项目分布
|
||||
SELECT
|
||||
p.project_name AS '项目名称',
|
||||
p.type AS '项目类型(1=个人,2=双人,3=集体)',
|
||||
COUNT(*) AS '参赛人数'
|
||||
FROM martial_athlete a
|
||||
LEFT JOIN martial_project p ON a.project_id = p.id
|
||||
WHERE a.competition_id = 200
|
||||
GROUP BY p.id, p.project_name, p.type
|
||||
ORDER BY p.type, p.project_name;
|
||||
|
||||
-- 7. 检查场地详情
|
||||
SELECT
|
||||
id AS '场地ID',
|
||||
venue_name AS '场地名称',
|
||||
venue_type AS '场地类型',
|
||||
capacity AS '容量'
|
||||
FROM martial_venue
|
||||
WHERE competition_id = 200;
|
||||
|
||||
-- 总结
|
||||
SELECT
|
||||
'数据检查完成' AS '状态',
|
||||
NOW() AS '检查时间';
|
||||
@@ -1,26 +0,0 @@
|
||||
-- ================================================================
|
||||
-- 场地表结构检查和修复脚本
|
||||
-- 用途:检查 martial_venue 表是否存在 max_capacity 字段,如果不存在则添加
|
||||
-- 日期:2025-12-06
|
||||
-- ================================================================
|
||||
|
||||
-- 检查表是否存在
|
||||
SELECT
|
||||
TABLE_NAME,
|
||||
CASE
|
||||
WHEN TABLE_NAME IS NOT NULL THEN '表存在'
|
||||
ELSE '表不存在'
|
||||
END AS status
|
||||
FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'martial_venue';
|
||||
|
||||
-- 查看当前表结构
|
||||
DESC martial_venue;
|
||||
|
||||
-- 查看所有字段
|
||||
SELECT COLUMN_NAME, COLUMN_TYPE, COLUMN_DEFAULT, IS_NULLABLE, COLUMN_COMMENT
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'martial_venue'
|
||||
ORDER BY ORDINAL_POSITION;
|
||||
@@ -1,85 +0,0 @@
|
||||
-- ================================================================
|
||||
-- 清理所有测试数据脚本
|
||||
-- 用途:清空所有业务数据,保留表结构
|
||||
-- 日期:2025-12-06
|
||||
-- 警告:此脚本会删除所有业务数据,请谨慎使用!
|
||||
-- ================================================================
|
||||
|
||||
-- 设置外键检查为0,允许删除有外键关联的数据
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- 1. 清空赛事相关表
|
||||
-- ================================================================
|
||||
TRUNCATE TABLE `martial_competition`;
|
||||
TRUNCATE TABLE `martial_banner`;
|
||||
|
||||
-- 2. 清空项目相关表
|
||||
-- ================================================================
|
||||
TRUNCATE TABLE `martial_project`;
|
||||
|
||||
-- 3. 清空场地相关表
|
||||
-- ================================================================
|
||||
TRUNCATE TABLE `martial_venue`;
|
||||
|
||||
-- 4. 清空参赛者/运动员相关表
|
||||
-- ================================================================
|
||||
TRUNCATE TABLE `martial_athlete`;
|
||||
TRUNCATE TABLE `martial_participant`;
|
||||
|
||||
-- 5. 清空报名订单相关表
|
||||
-- ================================================================
|
||||
TRUNCATE TABLE `martial_registration_order`;
|
||||
|
||||
-- 6. 清空裁判相关表
|
||||
-- ================================================================
|
||||
TRUNCATE TABLE `martial_referee`;
|
||||
|
||||
-- 7. 清空成绩相关表
|
||||
-- ================================================================
|
||||
TRUNCATE TABLE `martial_score`;
|
||||
|
||||
-- 8. 清空赛程编排相关表(如果存在)
|
||||
-- ================================================================
|
||||
-- TRUNCATE TABLE `martial_schedule`;
|
||||
-- TRUNCATE TABLE `martial_schedule_detail`;
|
||||
|
||||
-- 9. 清空信息发布相关表
|
||||
-- ================================================================
|
||||
TRUNCATE TABLE `martial_info_publish`;
|
||||
|
||||
-- 重新启用外键检查
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
-- ================================================================
|
||||
-- 验证清理结果
|
||||
-- ================================================================
|
||||
SELECT
|
||||
'赛事数据' AS 表名,
|
||||
COUNT(*) AS 记录数
|
||||
FROM martial_competition
|
||||
UNION ALL
|
||||
SELECT '项目数据', COUNT(*) FROM martial_project
|
||||
UNION ALL
|
||||
SELECT '场地数据', COUNT(*) FROM martial_venue
|
||||
UNION ALL
|
||||
SELECT '参赛者数据', COUNT(*) FROM martial_athlete
|
||||
UNION ALL
|
||||
SELECT '报名订单数据', COUNT(*) FROM martial_registration_order
|
||||
UNION ALL
|
||||
SELECT '裁判数据', COUNT(*) FROM martial_referee
|
||||
UNION ALL
|
||||
SELECT '成绩数据', COUNT(*) FROM martial_score
|
||||
UNION ALL
|
||||
SELECT '信息发布数据', COUNT(*) FROM martial_info_publish;
|
||||
|
||||
-- ================================================================
|
||||
-- 清理完成
|
||||
-- ================================================================
|
||||
-- 所有业务数据已清空,表结构保留
|
||||
-- 您现在可以重新测试完整的业务流程:
|
||||
-- 1. 创建赛事
|
||||
-- 2. 配置场地
|
||||
-- 3. 创建项目
|
||||
-- 4. 添加参赛者
|
||||
-- 5. 进行编排
|
||||
-- ================================================================
|
||||
@@ -1,26 +0,0 @@
|
||||
-- 调试检查脚本
|
||||
USE martial_db;
|
||||
|
||||
-- 检查参赛者的project_id是否都有对应的项目
|
||||
SELECT
|
||||
'检查参赛者项目关联' AS check_item,
|
||||
a.id,
|
||||
a.project_id,
|
||||
a.player_name,
|
||||
p.id AS project_exists,
|
||||
p.project_name,
|
||||
p.type AS project_type
|
||||
FROM martial_athlete a
|
||||
LEFT JOIN martial_project p ON a.project_id = p.id
|
||||
WHERE a.competition_id = 200
|
||||
LIMIT 10;
|
||||
|
||||
-- 检查是否有参赛者的project_id为NULL或找不到对应项目
|
||||
SELECT
|
||||
'检查异常数据' AS check_item,
|
||||
COUNT(*) AS total_athletes,
|
||||
SUM(CASE WHEN project_id IS NULL THEN 1 ELSE 0 END) AS null_project_id,
|
||||
SUM(CASE WHEN p.id IS NULL THEN 1 ELSE 0 END) AS project_not_found
|
||||
FROM martial_athlete a
|
||||
LEFT JOIN martial_project p ON a.project_id = p.id
|
||||
WHERE a.competition_id = 200;
|
||||
@@ -1,93 +0,0 @@
|
||||
@echo off
|
||||
REM =============================================
|
||||
REM 赛程编排系统数据库部署脚本
|
||||
REM =============================================
|
||||
echo.
|
||||
echo ========================================
|
||||
echo 赛程编排系统 - 数据库部署工具
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
REM 检查MySQL是否安装
|
||||
where mysql >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo [错误] 未找到MySQL命令,请确保MySQL已安装并添加到系统PATH
|
||||
echo.
|
||||
echo 常见MySQL安装路径:
|
||||
echo - C:\Program Files\MySQL\MySQL Server 8.0\bin
|
||||
echo - C:\xampp\mysql\bin
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [1/3] 检测到MySQL...
|
||||
|
||||
REM 设置数据库信息
|
||||
set DB_NAME=martial_db
|
||||
set SCRIPT_PATH=%~dp0deploy_schedule_tables.sql
|
||||
|
||||
echo [2/3] 准备执行SQL脚本...
|
||||
echo 数据库: %DB_NAME%
|
||||
echo 脚本: %SCRIPT_PATH%
|
||||
echo.
|
||||
|
||||
REM 提示用户输入密码
|
||||
echo 请输入MySQL root密码 (如果没有密码直接按回车):
|
||||
set /p MYSQL_PWD=密码:
|
||||
|
||||
echo.
|
||||
echo [3/3] 正在执行SQL脚本...
|
||||
echo.
|
||||
|
||||
REM 执行SQL脚本
|
||||
if "%MYSQL_PWD%"=="" (
|
||||
mysql -u root %DB_NAME% < "%SCRIPT_PATH%"
|
||||
) else (
|
||||
mysql -u root -p%MYSQL_PWD% %DB_NAME% < "%SCRIPT_PATH%"
|
||||
)
|
||||
|
||||
if %errorlevel% equ 0 (
|
||||
echo.
|
||||
echo ========================================
|
||||
echo ✓ 数据库表创建成功!
|
||||
echo ========================================
|
||||
echo.
|
||||
echo 已创建以下4张表:
|
||||
echo 1. martial_schedule_group - 赛程编排分组表
|
||||
echo 2. martial_schedule_detail - 赛程编排明细表
|
||||
echo 3. martial_schedule_participant - 参赛者关联表
|
||||
echo 4. martial_schedule_status - 编排状态表
|
||||
echo.
|
||||
echo 下一步:
|
||||
echo 1. 导入测试数据 (可选)
|
||||
echo cd ..\..\..
|
||||
echo cd martial-web\test-data
|
||||
echo mysql -u root -p%MYSQL_PWD% martial_db ^< create_100_team_participants.sql
|
||||
echo.
|
||||
echo 2. 启动后端服务
|
||||
echo cd martial-master
|
||||
echo mvn spring-boot:run
|
||||
echo.
|
||||
echo 3. 访问前端页面
|
||||
echo http://localhost:3000/martial/schedule?competitionId=200
|
||||
echo.
|
||||
) else (
|
||||
echo.
|
||||
echo ========================================
|
||||
echo ✗ 数据库表创建失败!
|
||||
echo ========================================
|
||||
echo.
|
||||
echo 可能的原因:
|
||||
echo 1. 数据库 %DB_NAME% 不存在
|
||||
echo 2. MySQL密码错误
|
||||
echo 3. 权限不足
|
||||
echo.
|
||||
echo 解决方法:
|
||||
echo 1. 先创建数据库: CREATE DATABASE martial_db;
|
||||
echo 2. 检查MySQL密码是否正确
|
||||
echo 3. 确保用户有CREATE TABLE权限
|
||||
echo.
|
||||
)
|
||||
|
||||
pause
|
||||
@@ -1,159 +0,0 @@
|
||||
-- =============================================
|
||||
-- 武术赛事赛程编排系统 - 数据库表创建脚本(带数据库选择)
|
||||
-- =============================================
|
||||
-- 创建日期: 2025-12-09
|
||||
-- 版本: v1.1
|
||||
-- 说明: 自动选择正确的数据库并创建赛程编排相关的4张核心表
|
||||
-- =============================================
|
||||
|
||||
-- 选择数据库(根据实际情况修改)
|
||||
USE martial_db;
|
||||
|
||||
-- 检查表是否已存在,如果存在则删除(可选,生产环境请注释掉)
|
||||
-- DROP TABLE IF EXISTS martial_schedule_participant;
|
||||
-- DROP TABLE IF EXISTS martial_schedule_detail;
|
||||
-- DROP TABLE IF EXISTS martial_schedule_group;
|
||||
-- DROP TABLE IF EXISTS martial_schedule_status;
|
||||
|
||||
-- 1. 赛程编排分组表
|
||||
CREATE TABLE IF NOT EXISTS `martial_schedule_group` (
|
||||
`id` bigint(0) NOT NULL COMMENT '主键ID',
|
||||
`competition_id` bigint(0) NOT NULL COMMENT '赛事ID',
|
||||
`group_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '分组名称(如:太极拳男组)',
|
||||
`project_id` bigint(0) NOT NULL COMMENT '项目ID',
|
||||
`project_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '项目名称',
|
||||
`category` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组别(成年组、少年组等)',
|
||||
`project_type` tinyint(1) NOT NULL DEFAULT 1 COMMENT '项目类型(1=个人 2=集体)',
|
||||
`display_order` int(0) NOT NULL DEFAULT 0 COMMENT '显示顺序(集体项目优先,数字越小越靠前)',
|
||||
`total_participants` int(0) NULL DEFAULT 0 COMMENT '总参赛人数',
|
||||
`total_teams` int(0) NULL DEFAULT 0 COMMENT '总队伍数(仅集体项目)',
|
||||
`estimated_duration` int(0) NULL DEFAULT 0 COMMENT '预计时长(分钟)',
|
||||
`create_user` bigint(0) NULL DEFAULT NULL,
|
||||
`create_dept` bigint(0) NULL DEFAULT NULL,
|
||||
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
|
||||
`update_user` bigint(0) NULL DEFAULT NULL,
|
||||
`update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0),
|
||||
`status` int(0) NULL DEFAULT 1 COMMENT '状态(1-启用,2-禁用)',
|
||||
`is_deleted` int(0) NULL DEFAULT 0,
|
||||
`tenant_id` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_competition` (`competition_id`) USING BTREE,
|
||||
INDEX `idx_project` (`project_id`) USING BTREE,
|
||||
INDEX `idx_display_order` (`display_order`) USING BTREE,
|
||||
INDEX `idx_tenant` (`tenant_id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '赛程编排分组表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- 2. 赛程编排明细表(场地时间段分配)
|
||||
CREATE TABLE IF NOT EXISTS `martial_schedule_detail` (
|
||||
`id` bigint(0) NOT NULL COMMENT '主键ID',
|
||||
`schedule_group_id` bigint(0) NOT NULL COMMENT '分组ID',
|
||||
`competition_id` bigint(0) NOT NULL COMMENT '赛事ID',
|
||||
`venue_id` bigint(0) NOT NULL COMMENT '场地ID',
|
||||
`venue_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '场地名称',
|
||||
`schedule_date` date NOT NULL COMMENT '比赛日期',
|
||||
`time_period` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '时间段(morning/afternoon)',
|
||||
`time_slot` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '时间点(08:30/13:30)',
|
||||
`estimated_start_time` datetime(0) NULL DEFAULT NULL COMMENT '预计开始时间',
|
||||
`estimated_end_time` datetime(0) NULL DEFAULT NULL COMMENT '预计结束时间',
|
||||
`estimated_duration` int(0) NULL DEFAULT 0 COMMENT '预计时长(分钟)',
|
||||
`participant_count` int(0) NULL DEFAULT 0 COMMENT '参赛人数',
|
||||
`sort_order` int(0) NULL DEFAULT 0 COMMENT '场内顺序',
|
||||
`create_user` bigint(0) NULL DEFAULT NULL,
|
||||
`create_dept` bigint(0) NULL DEFAULT NULL,
|
||||
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
|
||||
`update_user` bigint(0) NULL DEFAULT NULL,
|
||||
`update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0),
|
||||
`status` int(0) NULL DEFAULT 1 COMMENT '状态(1-未开始,2-进行中,3-已完成)',
|
||||
`is_deleted` int(0) NULL DEFAULT 0,
|
||||
`tenant_id` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_group` (`schedule_group_id`) USING BTREE,
|
||||
INDEX `idx_competition` (`competition_id`) USING BTREE,
|
||||
INDEX `idx_venue_time` (`venue_id`, `schedule_date`, `time_slot`) USING BTREE,
|
||||
INDEX `idx_tenant` (`tenant_id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '赛程编排明细表(场地时间段分配)' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- 3. 赛程编排参赛者关联表
|
||||
CREATE TABLE IF NOT EXISTS `martial_schedule_participant` (
|
||||
`id` bigint(0) NOT NULL COMMENT '主键ID',
|
||||
`schedule_detail_id` bigint(0) NOT NULL COMMENT '编排明细ID',
|
||||
`schedule_group_id` bigint(0) NOT NULL COMMENT '分组ID',
|
||||
`participant_id` bigint(0) NOT NULL COMMENT '参赛者ID(关联martial_athlete表)',
|
||||
`organization` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '单位名称',
|
||||
`player_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '选手姓名',
|
||||
`project_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '项目名称',
|
||||
`category` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组别',
|
||||
`performance_order` int(0) NULL DEFAULT 0 COMMENT '出场顺序',
|
||||
`create_user` bigint(0) NULL DEFAULT NULL,
|
||||
`create_dept` bigint(0) NULL DEFAULT NULL,
|
||||
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
|
||||
`update_user` bigint(0) NULL DEFAULT NULL,
|
||||
`update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0),
|
||||
`status` int(0) NULL DEFAULT 1 COMMENT '状态(1-待出场,2-已出场)',
|
||||
`is_deleted` int(0) NULL DEFAULT 0,
|
||||
`tenant_id` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_detail` (`schedule_detail_id`) USING BTREE,
|
||||
INDEX `idx_group` (`schedule_group_id`) USING BTREE,
|
||||
INDEX `idx_participant` (`participant_id`) USING BTREE,
|
||||
INDEX `idx_tenant` (`tenant_id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '赛程编排参赛者关联表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- 4. 赛程编排状态表
|
||||
CREATE TABLE IF NOT EXISTS `martial_schedule_status` (
|
||||
`id` bigint(0) NOT NULL COMMENT '主键ID',
|
||||
`competition_id` bigint(0) NOT NULL COMMENT '赛事ID(唯一)',
|
||||
`schedule_status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '编排状态(0=未编排 1=编排中 2=已保存锁定)',
|
||||
`last_auto_schedule_time` datetime(0) NULL DEFAULT NULL COMMENT '最后自动编排时间',
|
||||
`locked_time` datetime(0) NULL DEFAULT NULL COMMENT '锁定时间',
|
||||
`locked_by` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '锁定人',
|
||||
`total_groups` int(0) NULL DEFAULT 0 COMMENT '总分组数',
|
||||
`total_participants` int(0) NULL DEFAULT 0 COMMENT '总参赛人数',
|
||||
`create_user` bigint(0) NULL DEFAULT NULL,
|
||||
`create_dept` bigint(0) NULL DEFAULT NULL,
|
||||
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
|
||||
`update_user` bigint(0) NULL DEFAULT NULL,
|
||||
`update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0),
|
||||
`status` int(0) NULL DEFAULT 1 COMMENT '状态(1-启用,2-禁用)',
|
||||
`is_deleted` int(0) NULL DEFAULT 0,
|
||||
`tenant_id` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_competition` (`competition_id`) USING BTREE,
|
||||
INDEX `idx_tenant` (`tenant_id`) USING BTREE,
|
||||
INDEX `idx_schedule_status` (`schedule_status`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '赛程编排状态表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- 验证表是否创建成功
|
||||
SELECT
|
||||
'表创建完成' AS message,
|
||||
COUNT(*) AS table_count
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'martial_db'
|
||||
AND table_name IN (
|
||||
'martial_schedule_group',
|
||||
'martial_schedule_detail',
|
||||
'martial_schedule_participant',
|
||||
'martial_schedule_status'
|
||||
);
|
||||
|
||||
-- =============================================
|
||||
-- 使用说明
|
||||
-- =============================================
|
||||
--
|
||||
-- 1. 确认数据库名称
|
||||
-- 如果你的数据库名称不是 martial_db,请修改第9行的 USE 语句
|
||||
--
|
||||
-- 2. 执行脚本
|
||||
-- 方式1: 在MySQL客户端中直接执行
|
||||
-- mysql -u root -p < deploy_schedule_tables.sql
|
||||
--
|
||||
-- 方式2: 在数据库管理工具中执行(Navicat/DBeaver等)
|
||||
--
|
||||
-- 3. 验证
|
||||
-- 执行完成后应该看到 "table_count = 4" 的结果
|
||||
--
|
||||
-- 4. 下一步
|
||||
-- 执行测试数据导入脚本:
|
||||
-- mysql -u root -p martial_db < martial-web/test-data/create_100_team_participants.sql
|
||||
--
|
||||
-- =============================================
|
||||
@@ -1,19 +0,0 @@
|
||||
-- ================================================================
|
||||
-- 修复参赛选手表 order_id 字段约束
|
||||
-- 问题:Field 'order_id' doesn't have a default value
|
||||
-- 解决:允许 order_id 为 NULL(支持直接添加参赛选手,无需订单)
|
||||
-- 日期:2025-12-06
|
||||
-- ================================================================
|
||||
|
||||
-- 使用正确的数据库
|
||||
USE martial_db;
|
||||
|
||||
-- 修改 order_id 字段,允许为 NULL
|
||||
ALTER TABLE martial_athlete
|
||||
MODIFY COLUMN order_id bigint(20) NULL DEFAULT NULL COMMENT '订单ID';
|
||||
|
||||
-- 验证修改
|
||||
DESC martial_athlete;
|
||||
|
||||
-- 显示修改结果
|
||||
SELECT '✓ order_id 字段已修改为可空' AS 修复结果;
|
||||
@@ -1,40 +0,0 @@
|
||||
-- ================================================================
|
||||
-- 场地表字段修复脚本
|
||||
-- 用途:为 martial_venue 表添加缺失的 max_capacity 字段
|
||||
-- 问题:Error: Unknown column 'max_capacity' in 'field list'
|
||||
-- 日期:2025-12-06
|
||||
-- ================================================================
|
||||
|
||||
-- 方案1:直接 DROP 表并重新创建(如果表中没有重要数据)
|
||||
-- 如果表中有数据,请跳过此步骤,使用方案2
|
||||
DROP TABLE IF EXISTS `martial_venue`;
|
||||
|
||||
CREATE TABLE `martial_venue` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`tenant_id` varchar(12) DEFAULT '000000' COMMENT '租户ID',
|
||||
`competition_id` bigint(20) NOT NULL COMMENT '赛事ID',
|
||||
`venue_name` varchar(100) NOT NULL COMMENT '场地名称',
|
||||
`venue_code` varchar(50) DEFAULT NULL COMMENT '场地编码',
|
||||
`max_capacity` int(11) DEFAULT 100 COMMENT '最大容纳人数',
|
||||
`location` varchar(200) DEFAULT NULL COMMENT '位置/地点',
|
||||
`description` varchar(500) DEFAULT NULL COMMENT '场地描述',
|
||||
`facilities` varchar(500) DEFAULT NULL COMMENT '场地设施',
|
||||
`sort_order` int(11) DEFAULT 0 COMMENT '排序',
|
||||
`status` int(2) DEFAULT 1 COMMENT '状态(0-禁用,1-启用)',
|
||||
`create_user` bigint(20) DEFAULT NULL COMMENT '创建人',
|
||||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||||
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_user` bigint(20) DEFAULT NULL COMMENT '修改人',
|
||||
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
`is_deleted` int(2) DEFAULT 0 COMMENT '是否已删除',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_competition_id` (`competition_id`),
|
||||
KEY `idx_tenant_id` (`tenant_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='场地信息表';
|
||||
|
||||
-- ================================================================
|
||||
-- 验证表结构
|
||||
-- ================================================================
|
||||
DESC martial_venue;
|
||||
|
||||
SELECT '场地表已重新创建,包含 max_capacity 字段' AS result;
|
||||
@@ -1,131 +0,0 @@
|
||||
-- =============================================
|
||||
-- 赛程编排系统 - 完整测试数据初始化
|
||||
-- =============================================
|
||||
USE martial_db;
|
||||
|
||||
-- 1. 确保赛事存在并配置了时间
|
||||
UPDATE martial_competition
|
||||
SET
|
||||
competition_start_time = '2025-11-06 08:00:00',
|
||||
competition_end_time = '2025-11-08 18:00:00'
|
||||
WHERE id = 200;
|
||||
|
||||
-- 检查赛事是否存在
|
||||
SELECT
|
||||
'1. 检查赛事' AS step,
|
||||
CASE
|
||||
WHEN COUNT(*) > 0 THEN CONCAT('✓ 赛事ID=200存在, 名称: ', MAX(competition_name))
|
||||
ELSE '✗ 赛事ID=200不存在,请先创建赛事'
|
||||
END AS result
|
||||
FROM martial_competition
|
||||
WHERE id = 200;
|
||||
|
||||
-- 2. 创建场地数据(如果不存在)
|
||||
INSERT IGNORE INTO martial_venue (id, competition_id, venue_name, venue_type, capacity, create_time, is_deleted)
|
||||
VALUES
|
||||
(1, 200, '一号场地', '主场地', 100, NOW(), 0),
|
||||
(2, 200, '二号场地', '副场地', 100, NOW(), 0),
|
||||
(3, 200, '三号场地', '副场地', 100, NOW(), 0),
|
||||
(4, 200, '四号场地', '副场地', 100, NOW(), 0);
|
||||
|
||||
SELECT
|
||||
'2. 检查场地' AS step,
|
||||
CONCAT('✓ 已有 ', COUNT(*), ' 个场地') AS result
|
||||
FROM martial_venue
|
||||
WHERE competition_id = 200 AND is_deleted = 0;
|
||||
|
||||
-- 3. 创建项目数据(如果不存在)
|
||||
INSERT IGNORE INTO martial_project (id, project_name, type, category, estimated_duration, create_time)
|
||||
VALUES
|
||||
(1001, '太极拳集体', 3, '成年组', 5, NOW()),
|
||||
(1002, '长拳集体', 3, '成年组', 5, NOW()),
|
||||
(1003, '剑术集体', 3, '成年组', 5, NOW()),
|
||||
(1004, '刀术集体', 3, '成年组', 5, NOW()),
|
||||
(1005, '棍术集体', 3, '少年组', 5, NOW());
|
||||
|
||||
SELECT
|
||||
'3. 检查项目' AS step,
|
||||
CONCAT('✓ 已有 ', COUNT(*), ' 个项目') AS result
|
||||
FROM martial_project
|
||||
WHERE id BETWEEN 1001 AND 1005;
|
||||
|
||||
-- 4. 创建测试参赛者数据(少量测试数据)
|
||||
DELETE FROM martial_athlete WHERE competition_id = 200;
|
||||
|
||||
INSERT INTO martial_athlete (
|
||||
competition_id, project_id, organization, team_name,
|
||||
player_name, gender, age, phone, category, create_time, is_deleted
|
||||
)
|
||||
VALUES
|
||||
-- 太极拳集体 - 队伍1: 少林寺武校 (5人)
|
||||
(200, 1001, '少林寺武校', '少林寺武校', '张明远', '男', 25, '13800001001', '成年组', NOW(), 0),
|
||||
(200, 1001, '少林寺武校', '少林寺武校', '李华强', '男', 26, '13800001002', '成年组', NOW(), 0),
|
||||
(200, 1001, '少林寺武校', '少林寺武校', '王建国', '男', 24, '13800001003', '成年组', NOW(), 0),
|
||||
(200, 1001, '少林寺武校', '少林寺武校', '赵小明', '男', 23, '13800001004', '成年组', NOW(), 0),
|
||||
(200, 1001, '少林寺武校', '少林寺武校', '刘德华', '男', 27, '13800001005', '成年组', NOW(), 0),
|
||||
|
||||
-- 太极拳集体 - 队伍2: 武当派 (5人)
|
||||
(200, 1001, '武当派', '武当派', '陈剑锋', '男', 28, '13800001011', '成年组', NOW(), 0),
|
||||
(200, 1001, '武当派', '武当派', '周杰伦', '男', 25, '13800001012', '成年组', NOW(), 0),
|
||||
(200, 1001, '武当派', '武当派', '吴彦祖', '男', 26, '13800001013', '成年组', NOW(), 0),
|
||||
(200, 1001, '武当派', '武当派', '郑伊健', '男', 24, '13800001014', '成年组', NOW(), 0),
|
||||
(200, 1001, '武当派', '武当派', '谢霆锋', '男', 27, '13800001015', '成年组', NOW(), 0),
|
||||
|
||||
-- 长拳集体 - 队伍1: 峨眉派 (5人)
|
||||
(200, 1002, '峨眉派', '峨眉派', '小龙女', '女', 22, '13800002001', '成年组', NOW(), 0),
|
||||
(200, 1002, '峨眉派', '峨眉派', '黄蓉', '女', 23, '13800002002', '成年组', NOW(), 0),
|
||||
(200, 1002, '峨眉派', '峨眉派', '赵敏', '女', 24, '13800002003', '成年组', NOW(), 0),
|
||||
(200, 1002, '峨眉派', '峨眉派', '周芷若', '女', 22, '13800002004', '成年组', NOW(), 0),
|
||||
(200, 1002, '峨眉派', '峨眉派', '任盈盈', '女', 23, '13800002005', '成年组', NOW(), 0),
|
||||
|
||||
-- 长拳集体 - 队伍2: 华山派 (5人)
|
||||
(200, 1002, '华山派', '华山派', '令狐冲', '男', 27, '13800002011', '成年组', NOW(), 0),
|
||||
(200, 1002, '华山派', '华山派', '风清扬', '男', 28, '13800002012', '成年组', NOW(), 0),
|
||||
(200, 1002, '华山派', '华山派', '岳不群', '男', 29, '13800002013', '成年组', NOW(), 0),
|
||||
(200, 1002, '华山派', '华山派', '宁中则', '女', 26, '13800002014', '成年组', NOW(), 0),
|
||||
(200, 1002, '华山派', '华山派', '岳灵珊', '女', 24, '13800002015', '成年组', NOW(), 0);
|
||||
|
||||
SELECT
|
||||
'4. 检查参赛者' AS step,
|
||||
CONCAT('✓ 已有 ', COUNT(*), ' 个参赛者 (', COUNT(DISTINCT organization), ' 个队伍)') AS result
|
||||
FROM martial_athlete
|
||||
WHERE competition_id = 200 AND is_deleted = 0;
|
||||
|
||||
-- 5. 清空旧的编排数据(如果有)
|
||||
DELETE FROM martial_schedule_participant WHERE schedule_group_id IN (
|
||||
SELECT id FROM martial_schedule_group WHERE competition_id = 200
|
||||
);
|
||||
DELETE FROM martial_schedule_detail WHERE competition_id = 200;
|
||||
DELETE FROM martial_schedule_group WHERE competition_id = 200;
|
||||
DELETE FROM martial_schedule_status WHERE competition_id = 200;
|
||||
|
||||
SELECT '5. 清理旧数据' AS step, '✓ 已清空旧的编排数据' AS result;
|
||||
|
||||
-- 6. 最终验证
|
||||
SELECT
|
||||
'6. 数据完整性检查' AS step,
|
||||
CONCAT(
|
||||
'✓ 赛事: ', (SELECT COUNT(*) FROM martial_competition WHERE id = 200),
|
||||
', 场地: ', (SELECT COUNT(*) FROM martial_venue WHERE competition_id = 200 AND is_deleted = 0),
|
||||
', 项目: ', (SELECT COUNT(*) FROM martial_project WHERE id BETWEEN 1001 AND 1005),
|
||||
', 参赛者: ', (SELECT COUNT(*) FROM martial_athlete WHERE competition_id = 200 AND is_deleted = 0)
|
||||
) AS result;
|
||||
|
||||
-- 7. 检查赛事时间配置
|
||||
SELECT
|
||||
'7. 赛事时间配置' AS step,
|
||||
CONCAT(
|
||||
'开始: ', IFNULL(competition_start_time, '未配置'),
|
||||
', 结束: ', IFNULL(competition_end_time, '未配置')
|
||||
) AS result
|
||||
FROM martial_competition
|
||||
WHERE id = 200;
|
||||
|
||||
SELECT
|
||||
'========================================' AS '',
|
||||
'✓ 测试数据初始化完成!' AS result,
|
||||
'========================================' AS '';
|
||||
|
||||
SELECT
|
||||
'下一步: 测试API' AS action,
|
||||
'curl -X POST http://localhost:8123/martial/schedule/auto-arrange -H "Content-Type: application/json" -d "{\"competitionId\": 200}"' AS command;
|
||||
@@ -1,82 +0,0 @@
|
||||
-- =====================================================
|
||||
-- 插入测试裁判邀请数据
|
||||
-- 执行时间: 2025-12-12
|
||||
-- =====================================================
|
||||
|
||||
USE blade;
|
||||
|
||||
-- 首先确保有测试赛事数据
|
||||
-- 假设已经有赛事ID为 1 的数据
|
||||
|
||||
-- 首先确保有测试裁判数据
|
||||
-- 插入测试裁判(如果不存在)
|
||||
INSERT IGNORE INTO martial_judge (id, name, gender, phone, id_card, referee_type, level, specialty, create_time, update_time, status, is_deleted)
|
||||
VALUES
|
||||
(1, '张三', 1, '13800138001', '110101199001011234', 2, '国家级', '太极拳', NOW(), NOW(), 1, 0),
|
||||
(2, '李四', 1, '13800138002', '110101199002021234', 2, '一级', '长拳', NOW(), NOW(), 1, 0),
|
||||
(3, '王五', 2, '13800138003', '110101199003031234', 2, '二级', '剑术', NOW(), NOW(), 1, 0),
|
||||
(4, '赵六', 1, '13800138004', '110101199004041234', 1, '国家级', '刀术', NOW(), NOW(), 1, 0),
|
||||
(5, '钱七', 2, '13800138005', '110101199005051234', 2, '三级', '棍术', NOW(), NOW(), 1, 0);
|
||||
|
||||
-- 插入测试邀请数据
|
||||
INSERT INTO martial_judge_invite (
|
||||
id,
|
||||
competition_id,
|
||||
judge_id,
|
||||
invite_code,
|
||||
role,
|
||||
invite_status,
|
||||
invite_time,
|
||||
reply_time,
|
||||
reply_note,
|
||||
contact_phone,
|
||||
contact_email,
|
||||
invite_message,
|
||||
expire_time,
|
||||
is_used,
|
||||
create_time,
|
||||
update_time,
|
||||
status,
|
||||
is_deleted
|
||||
)
|
||||
VALUES
|
||||
-- 待回复的邀请
|
||||
(1, 1, 1, 'INV2025001', 'judge', 0, NOW(), NULL, NULL, '13800138001', 'zhangsan@example.com', '诚邀您担任本次武术比赛的裁判', DATE_ADD(NOW(), INTERVAL 30 DAY), 0, NOW(), NOW(), 1, 0),
|
||||
(2, 1, 2, 'INV2025002', 'judge', 0, NOW(), NULL, NULL, '13800138002', 'lisi@example.com', '诚邀您担任本次武术比赛的裁判', DATE_ADD(NOW(), INTERVAL 30 DAY), 0, NOW(), NOW(), 1, 0),
|
||||
|
||||
-- 已接受的邀请
|
||||
(3, 1, 3, 'INV2025003', 'judge', 1, DATE_SUB(NOW(), INTERVAL 2 DAY), DATE_SUB(NOW(), INTERVAL 1 DAY), '很荣幸能参加,我会准时到场', '13800138003', 'wangwu@example.com', '诚邀您担任本次武术比赛的裁判', DATE_ADD(NOW(), INTERVAL 30 DAY), 1, DATE_SUB(NOW(), INTERVAL 2 DAY), NOW(), 1, 0),
|
||||
(4, 1, 4, 'INV2025004', 'chief_judge', 1, DATE_SUB(NOW(), INTERVAL 3 DAY), DATE_SUB(NOW(), INTERVAL 2 DAY), '感谢邀请,我会认真履行裁判长职责', '13800138004', 'zhaoliu@example.com', '诚邀您担任本次武术比赛的裁判长', DATE_ADD(NOW(), INTERVAL 30 DAY), 1, DATE_SUB(NOW(), INTERVAL 3 DAY), NOW(), 1, 0),
|
||||
|
||||
-- 已拒绝的邀请
|
||||
(5, 1, 5, 'INV2025005', 'judge', 2, DATE_SUB(NOW(), INTERVAL 5 DAY), DATE_SUB(NOW(), INTERVAL 4 DAY), '非常抱歉,那段时间有其他安排', '13800138005', 'qianqi@example.com', '诚邀您担任本次武术比赛的裁判', DATE_ADD(NOW(), INTERVAL 30 DAY), 0, DATE_SUB(NOW(), INTERVAL 5 DAY), NOW(), 1, 0);
|
||||
|
||||
-- 验证插入结果
|
||||
SELECT
|
||||
ji.id,
|
||||
ji.invite_code,
|
||||
j.name AS judge_name,
|
||||
j.level AS judge_level,
|
||||
ji.contact_phone,
|
||||
ji.contact_email,
|
||||
ji.invite_status,
|
||||
CASE ji.invite_status
|
||||
WHEN 0 THEN '待回复'
|
||||
WHEN 1 THEN '已接受'
|
||||
WHEN 2 THEN '已拒绝'
|
||||
WHEN 3 THEN '已取消'
|
||||
ELSE '未知'
|
||||
END AS status_text,
|
||||
ji.invite_time,
|
||||
ji.reply_time,
|
||||
ji.reply_note
|
||||
FROM
|
||||
martial_judge_invite ji
|
||||
LEFT JOIN martial_judge j ON ji.judge_id = j.id
|
||||
WHERE
|
||||
ji.competition_id = 1
|
||||
AND ji.is_deleted = 0
|
||||
ORDER BY
|
||||
ji.id;
|
||||
|
||||
SELECT 'Test data inserted successfully!' AS status;
|
||||
37
database/martial-db/martial_competition_attachment.sql
Normal file
37
database/martial-db/martial_competition_attachment.sql
Normal file
@@ -0,0 +1,37 @@
|
||||
-- 赛事通用附件表
|
||||
-- 支持多种附件类型:赛事发布(info)、赛事规程(rules)、活动日程(schedule)、成绩(results)、奖牌榜(medals)、图片直播(photos)
|
||||
|
||||
DROP TABLE IF EXISTS `martial_competition_attachment`;
|
||||
CREATE TABLE `martial_competition_attachment` (
|
||||
`id` bigint NOT NULL COMMENT '主键ID',
|
||||
`tenant_id` varchar(12) DEFAULT '000000' COMMENT '租户ID',
|
||||
`competition_id` bigint NOT NULL COMMENT '赛事ID',
|
||||
`attachment_type` varchar(20) NOT NULL COMMENT '附件类型:info-赛事发布, rules-赛事规程, schedule-活动日程, results-成绩, medals-奖牌榜, photos-图片直播',
|
||||
`file_name` varchar(255) NOT NULL COMMENT '文件名称',
|
||||
`file_url` varchar(500) NOT NULL COMMENT '文件URL',
|
||||
`file_size` bigint DEFAULT NULL COMMENT '文件大小(字节)',
|
||||
`file_type` varchar(20) DEFAULT NULL COMMENT '文件类型(pdf/doc/docx/xls/xlsx/jpg/png等)',
|
||||
`order_num` int DEFAULT 0 COMMENT '排序序号',
|
||||
`status` int DEFAULT 1 COMMENT '状态(1-启用 0-禁用)',
|
||||
`create_user` bigint DEFAULT NULL COMMENT '创建人',
|
||||
`create_dept` bigint DEFAULT NULL COMMENT '创建部门',
|
||||
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_user` bigint DEFAULT NULL COMMENT '更新人',
|
||||
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`is_deleted` int DEFAULT 0 COMMENT '是否已删除(0-否 1-是)',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_competition_id` (`competition_id`),
|
||||
KEY `idx_attachment_type` (`attachment_type`),
|
||||
KEY `idx_competition_type` (`competition_id`, `attachment_type`),
|
||||
KEY `idx_tenant_id` (`tenant_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='赛事通用附件表';
|
||||
|
||||
-- 插入测试数据(假设赛事ID为1)
|
||||
INSERT INTO `martial_competition_attachment` (`id`, `tenant_id`, `competition_id`, `attachment_type`, `file_name`, `file_url`, `file_size`, `file_type`, `order_num`, `status`) VALUES
|
||||
(1, '000000', 1, 'info', '2025年郑州武术大赛通知.pdf', 'http://example.com/files/notice.pdf', 1258291, 'pdf', 1, 1),
|
||||
(2, '000000', 1, 'rules', '2025年郑州武术大赛竞赛规程.pdf', 'http://example.com/files/rules.pdf', 2621440, 'pdf', 1, 1),
|
||||
(3, '000000', 1, 'rules', '参赛报名表.pdf', 'http://example.com/files/form.pdf', 163840, 'pdf', 2, 1),
|
||||
(4, '000000', 1, 'schedule', '比赛日程安排表.pdf', 'http://example.com/files/schedule.pdf', 911360, 'pdf', 1, 1),
|
||||
(5, '000000', 1, 'results', '比赛成绩公告.pdf', 'http://example.com/files/results.pdf', 1887436, 'pdf', 1, 1),
|
||||
(6, '000000', 1, 'medals', '奖牌榜统计.pdf', 'http://example.com/files/medals.pdf', 532480, 'pdf', 1, 1),
|
||||
(7, '000000', 1, 'photos', '比赛精彩瞬间.pdf', 'http://example.com/files/photos.pdf', 16357785, 'pdf', 1, 1);
|
||||
@@ -8908,6 +8908,7 @@ CREATE ALGORITHM = UNDEFINED SQL SECURITY DEFINER VIEW `v_martial_amount_stats`
|
||||
-- ----------------------------
|
||||
-- Records of mt_venue
|
||||
-- ----------------------------
|
||||
<<<<<<< HEAD
|
||||
INSERT INTO `mt_venue` VALUES (200, '上海中学', 3, 3690.00);
|
||||
INSERT INTO `mt_venue` VALUES (200, '上海体育学院武术系', 4, 3870.00);
|
||||
INSERT INTO `mt_venue` VALUES (200, '上海市第二体育运动学校', 3, 3840.00);
|
||||
@@ -8958,6 +8959,8 @@ INSERT INTO `mt_venue` VALUES (200, '重庆巴蜀中学', 4, 3680.00);
|
||||
INSERT INTO `mt_venue` VALUES (200, '陈家沟太极拳学校', 4, 3840.00);
|
||||
INSERT INTO `mt_venue` VALUES (200, '青城山武术院', 4, 3850.00);
|
||||
INSERT INTO `mt_venue` VALUES (200, '首都师范大学', 4, 4050.00);
|
||||
=======
|
||||
>>>>>>> 284ebd2e734d6c86ad376bfb482c7775301ae292
|
||||
|
||||
-- ----------------------------
|
||||
-- View structure for v_martial_participant_stats
|
||||
@@ -8968,6 +8971,7 @@ CREATE ALGORITHM = UNDEFINED SQL SECURITY DEFINER VIEW `v_martial_participant_st
|
||||
-- ----------------------------
|
||||
-- Records of mt_venue
|
||||
-- ----------------------------
|
||||
<<<<<<< HEAD
|
||||
INSERT INTO `mt_venue` VALUES (200, '北京体育大学武术学院', '青少年女子组', 7, 0, 0, 7, 7);
|
||||
INSERT INTO `mt_venue` VALUES (200, '上海体育学院武术系', '青少年男子组', 3, 0, 0, 0, 3);
|
||||
INSERT INTO `mt_venue` VALUES (200, '河南登封少林寺武术学校', '成年女子组', 15, 0, 0, 15, 15);
|
||||
@@ -9069,6 +9073,8 @@ INSERT INTO `mt_venue` VALUES (200, '天津体育学院武术系', '青少年男
|
||||
INSERT INTO `mt_venue` VALUES (200, '清华大学武术协会', '青少年男子组', 2, 0, 0, 0, 2);
|
||||
INSERT INTO `mt_venue` VALUES (200, '上海中学', '青少年女子组', 1, 0, 0, 1, 1);
|
||||
INSERT INTO `mt_venue` VALUES (200, '武当山', NULL, 1, 0, 0, 0, 1);
|
||||
=======
|
||||
>>>>>>> 284ebd2e734d6c86ad376bfb482c7775301ae292
|
||||
|
||||
-- ----------------------------
|
||||
-- View structure for v_martial_project_time_stats
|
||||
@@ -9079,6 +9085,7 @@ CREATE ALGORITHM = UNDEFINED SQL SECURITY DEFINER VIEW `v_martial_project_time_s
|
||||
-- ----------------------------
|
||||
-- Records of mt_venue
|
||||
-- ----------------------------
|
||||
<<<<<<< HEAD
|
||||
INSERT INTO `mt_venue` VALUES (0, '太极拳集体', NULL, 0, 1, 5, 0);
|
||||
INSERT INTO `mt_venue` VALUES (0, '长拳集体', NULL, 0, 1, 5, 0);
|
||||
INSERT INTO `mt_venue` VALUES (0, '剑术集体', NULL, 0, 1, 5, 0);
|
||||
@@ -9095,5 +9102,7 @@ INSERT INTO `mt_venue` VALUES (200, '青少年女子长拳', '青少年女子组
|
||||
INSERT INTO `mt_venue` VALUES (200, '青少年太极拳', NULL, 0, 1, 4, 0);
|
||||
INSERT INTO `mt_venue` VALUES (200, '集体拳术表演', NULL, 0, 1, 8, 0);
|
||||
INSERT INTO `mt_venue` VALUES (200, '集体器械表演', NULL, 0, 1, 8, 0);
|
||||
=======
|
||||
>>>>>>> 284ebd2e734d6c86ad376bfb482c7775301ae292
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
-- =====================================================
|
||||
-- 测试邀请码生成功能
|
||||
-- 执行时间: 2025-12-12
|
||||
-- =====================================================
|
||||
|
||||
USE blade;
|
||||
|
||||
-- =====================================================
|
||||
-- 1. 插入测试数据
|
||||
-- =====================================================
|
||||
|
||||
-- 为评委生成测试邀请码(使用现有字段)
|
||||
INSERT INTO martial_judge_invite (
|
||||
id, competition_id, judge_id, invite_code, role,
|
||||
venue_id, projects, expire_time, is_used,
|
||||
status, create_time
|
||||
) VALUES (
|
||||
1001, 1, 1, 'TEST01', 'judge',
|
||||
1, '["女子组长拳","男子组陈氏太极拳"]',
|
||||
DATE_ADD(NOW(), INTERVAL 30 DAY), 0,
|
||||
1, NOW()
|
||||
);
|
||||
|
||||
-- 为裁判长生成邀请码
|
||||
INSERT INTO martial_judge_invite (
|
||||
id, competition_id, judge_id, invite_code, role,
|
||||
venue_id, projects, expire_time, is_used,
|
||||
status, create_time
|
||||
) VALUES (
|
||||
1002, 1, 2, 'ADMIN1', 'chief_judge',
|
||||
NULL, NULL,
|
||||
DATE_ADD(NOW(), INTERVAL 30 DAY), 0,
|
||||
1, NOW()
|
||||
);
|
||||
|
||||
-- =====================================================
|
||||
-- 2. 查询测试
|
||||
-- =====================================================
|
||||
|
||||
-- 查询某个评委的有效邀请码
|
||||
SELECT
|
||||
ji.id,
|
||||
ji.invite_code,
|
||||
ji.role,
|
||||
ji.expire_time,
|
||||
ji.is_used,
|
||||
ji.status,
|
||||
j.name AS judge_name,
|
||||
c.competition_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
|
||||
WHERE ji.competition_id = 1
|
||||
AND ji.judge_id = 1
|
||||
AND ji.status = 1
|
||||
AND ji.is_deleted = 0
|
||||
AND ji.expire_time > NOW()
|
||||
ORDER BY ji.create_time DESC
|
||||
LIMIT 1;
|
||||
|
||||
-- 查询所有有效邀请码
|
||||
SELECT
|
||||
ji.id,
|
||||
ji.invite_code,
|
||||
ji.role,
|
||||
j.name AS judge_name,
|
||||
ji.expire_time,
|
||||
ji.is_used,
|
||||
CASE
|
||||
WHEN ji.is_used = 1 THEN '已使用'
|
||||
WHEN ji.expire_time < NOW() THEN '已过期'
|
||||
WHEN ji.status = 0 THEN '已禁用'
|
||||
ELSE '待使用'
|
||||
END AS status_text
|
||||
FROM martial_judge_invite ji
|
||||
LEFT JOIN martial_judge j ON ji.judge_id = j.id
|
||||
WHERE ji.competition_id = 1
|
||||
AND ji.is_deleted = 0
|
||||
ORDER BY ji.create_time DESC;
|
||||
|
||||
-- =====================================================
|
||||
-- 3. 统计查询
|
||||
-- =====================================================
|
||||
|
||||
-- 统计某个赛事的邀请码状态
|
||||
SELECT
|
||||
COUNT(*) AS total,
|
||||
SUM(CASE WHEN is_used = 0 AND status = 1 AND expire_time > NOW() THEN 1 ELSE 0 END) AS available,
|
||||
SUM(CASE WHEN is_used = 1 THEN 1 ELSE 0 END) AS used,
|
||||
SUM(CASE WHEN expire_time <= NOW() THEN 1 ELSE 0 END) AS expired,
|
||||
SUM(CASE WHEN status = 0 THEN 1 ELSE 0 END) AS disabled
|
||||
FROM martial_judge_invite
|
||||
WHERE competition_id = 1
|
||||
AND is_deleted = 0;
|
||||
|
||||
-- =====================================================
|
||||
-- 4. 验证邀请码唯一性
|
||||
-- =====================================================
|
||||
|
||||
-- 检查邀请码是否重复
|
||||
SELECT
|
||||
invite_code,
|
||||
COUNT(*) AS count
|
||||
FROM martial_judge_invite
|
||||
WHERE is_deleted = 0
|
||||
GROUP BY invite_code
|
||||
HAVING COUNT(*) > 1;
|
||||
|
||||
-- =====================================================
|
||||
-- 5. 清理测试数据(可选)
|
||||
-- =====================================================
|
||||
|
||||
-- 删除测试数据
|
||||
-- DELETE FROM martial_judge_invite WHERE id IN (1001, 1002);
|
||||
|
||||
SELECT 'Test data inserted successfully!' AS status;
|
||||
@@ -1,82 +0,0 @@
|
||||
@echo off
|
||||
REM =============================================
|
||||
REM 赛程编排系统 - 数据库升级脚本
|
||||
REM =============================================
|
||||
echo.
|
||||
echo ========================================
|
||||
echo 赛程编排系统 - 数据库升级工具
|
||||
echo ========================================
|
||||
echo.
|
||||
echo 说明: 此脚本会创建新的4张表,不会影响现有数据
|
||||
echo - martial_schedule_group
|
||||
echo - martial_schedule_detail
|
||||
echo - martial_schedule_participant
|
||||
echo - martial_schedule_status
|
||||
echo.
|
||||
|
||||
REM 检查MySQL
|
||||
where mysql >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo [错误] 未找到MySQL命令
|
||||
echo.
|
||||
echo 请使用以下方法之一:
|
||||
echo 方法1: 在Navicat/DBeaver中打开并执行 upgrade_schedule_system.sql
|
||||
echo 方法2: 将MySQL添加到系统PATH后重新运行此脚本
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
set DB_NAME=martial_db
|
||||
set SCRIPT_PATH=%~dp0upgrade_schedule_system.sql
|
||||
|
||||
echo [1/2] 检测到MySQL...
|
||||
echo.
|
||||
|
||||
echo 请输入MySQL root密码 (无密码直接回车):
|
||||
set /p MYSQL_PWD=密码:
|
||||
|
||||
echo.
|
||||
echo [2/2] 正在执行升级脚本...
|
||||
echo.
|
||||
|
||||
if "%MYSQL_PWD%"=="" (
|
||||
mysql -u root %DB_NAME% < "%SCRIPT_PATH%"
|
||||
) else (
|
||||
mysql -u root -p%MYSQL_PWD% %DB_NAME% < "%SCRIPT_PATH%"
|
||||
)
|
||||
|
||||
if %errorlevel% equ 0 (
|
||||
echo.
|
||||
echo ========================================
|
||||
echo ✓ 数据库升级成功!
|
||||
echo ========================================
|
||||
echo.
|
||||
echo 已创建/检查以下表:
|
||||
echo [新] martial_schedule_group - 赛程编排分组表
|
||||
echo [新] martial_schedule_detail - 赛程编排明细表
|
||||
echo [新] martial_schedule_participant - 参赛者关联表
|
||||
echo [新] martial_schedule_status - 编排状态表
|
||||
echo.
|
||||
echo [旧] martial_schedule - 保留(如果存在)
|
||||
echo [旧] martial_schedule_athlete - 保留(如果存在)
|
||||
echo.
|
||||
echo 下一步:
|
||||
echo 1. 重启后端服务以使新表生效
|
||||
echo 2. 访问前端页面测试:
|
||||
echo http://localhost:3000/martial/schedule?competitionId=200
|
||||
echo.
|
||||
) else (
|
||||
echo.
|
||||
echo ========================================
|
||||
echo ✗ 升级失败!
|
||||
echo ========================================
|
||||
echo.
|
||||
echo 请检查:
|
||||
echo 1. 数据库 martial_db 是否存在
|
||||
echo 2. MySQL密码是否正确
|
||||
echo 3. 用户是否有CREATE TABLE权限
|
||||
echo.
|
||||
)
|
||||
|
||||
pause
|
||||
@@ -1,75 +0,0 @@
|
||||
-- =====================================================
|
||||
-- 升级 martial_judge_invite 表
|
||||
-- 添加邀请状态、时间、联系方式等字段
|
||||
-- 执行时间: 2025-12-12
|
||||
-- =====================================================
|
||||
|
||||
USE blade;
|
||||
|
||||
-- 检查表是否存在
|
||||
SELECT 'Checking martial_judge_invite table...' AS status;
|
||||
|
||||
-- 添加邀请状态字段
|
||||
ALTER TABLE martial_judge_invite
|
||||
ADD COLUMN IF NOT EXISTS invite_status INT DEFAULT 0 COMMENT '邀请状态(0-待回复,1-已接受,2-已拒绝,3-已取消)';
|
||||
|
||||
-- 添加邀请时间字段
|
||||
ALTER TABLE martial_judge_invite
|
||||
ADD COLUMN IF NOT EXISTS invite_time DATETIME COMMENT '邀请时间';
|
||||
|
||||
-- 添加回复时间字段
|
||||
ALTER TABLE martial_judge_invite
|
||||
ADD COLUMN IF NOT EXISTS reply_time DATETIME COMMENT '回复时间';
|
||||
|
||||
-- 添加回复备注字段
|
||||
ALTER TABLE martial_judge_invite
|
||||
ADD COLUMN IF NOT EXISTS reply_note VARCHAR(500) COMMENT '回复备注';
|
||||
|
||||
-- 添加联系电话字段
|
||||
ALTER TABLE martial_judge_invite
|
||||
ADD COLUMN IF NOT EXISTS contact_phone VARCHAR(20) COMMENT '联系电话';
|
||||
|
||||
-- 添加联系邮箱字段
|
||||
ALTER TABLE martial_judge_invite
|
||||
ADD COLUMN IF NOT EXISTS contact_email VARCHAR(100) COMMENT '联系邮箱';
|
||||
|
||||
-- 添加邀请消息字段
|
||||
ALTER TABLE martial_judge_invite
|
||||
ADD COLUMN IF NOT EXISTS invite_message VARCHAR(1000) COMMENT '邀请消息';
|
||||
|
||||
-- 添加取消原因字段
|
||||
ALTER TABLE martial_judge_invite
|
||||
ADD COLUMN IF NOT EXISTS cancel_reason VARCHAR(500) COMMENT '取消原因';
|
||||
|
||||
-- 为邀请状态字段添加索引
|
||||
ALTER TABLE martial_judge_invite
|
||||
ADD INDEX IF NOT EXISTS idx_invite_status (invite_status);
|
||||
|
||||
-- 为赛事ID和邀请状态组合添加索引
|
||||
ALTER TABLE martial_judge_invite
|
||||
ADD INDEX IF NOT EXISTS idx_competition_status (competition_id, invite_status);
|
||||
|
||||
-- 验证字段是否添加成功
|
||||
SELECT
|
||||
COLUMN_NAME,
|
||||
COLUMN_TYPE,
|
||||
COLUMN_COMMENT
|
||||
FROM
|
||||
INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE
|
||||
TABLE_SCHEMA = 'blade'
|
||||
AND TABLE_NAME = 'martial_judge_invite'
|
||||
AND COLUMN_NAME IN (
|
||||
'invite_status',
|
||||
'invite_time',
|
||||
'reply_time',
|
||||
'reply_note',
|
||||
'contact_phone',
|
||||
'contact_email',
|
||||
'invite_message',
|
||||
'cancel_reason'
|
||||
)
|
||||
ORDER BY
|
||||
ORDINAL_POSITION;
|
||||
|
||||
SELECT 'Upgrade completed successfully!' AS status;
|
||||
@@ -1,179 +0,0 @@
|
||||
-- =============================================
|
||||
-- 赛程编排系统 - 增量升级脚本
|
||||
-- =============================================
|
||||
-- 说明: 检查并创建缺失的表,不影响现有数据
|
||||
-- 版本: v1.1
|
||||
-- 日期: 2025-12-09
|
||||
-- =============================================
|
||||
|
||||
USE martial_db;
|
||||
|
||||
-- 检查当前已有的表
|
||||
SELECT
|
||||
table_name,
|
||||
'已存在' AS status
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'martial_db'
|
||||
AND table_name LIKE 'martial_schedule%';
|
||||
|
||||
-- =============================================
|
||||
-- 创建新表(仅当不存在时)
|
||||
-- =============================================
|
||||
|
||||
-- 1. 赛程编排分组表
|
||||
CREATE TABLE IF NOT EXISTS `martial_schedule_group` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`competition_id` bigint(20) NOT NULL COMMENT '赛事ID',
|
||||
`group_name` varchar(200) NOT NULL COMMENT '分组名称(如:太极拳男组)',
|
||||
`project_id` bigint(20) NOT NULL COMMENT '项目ID',
|
||||
`project_name` varchar(100) DEFAULT NULL COMMENT '项目名称',
|
||||
`category` varchar(50) DEFAULT NULL COMMENT '组别(成年组、少年组等)',
|
||||
`project_type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '项目类型(1=个人 2=集体)',
|
||||
`display_order` int(11) NOT NULL DEFAULT '0' COMMENT '显示顺序(集体项目优先,数字越小越靠前)',
|
||||
`total_participants` int(11) DEFAULT '0' COMMENT '总参赛人数',
|
||||
`total_teams` int(11) DEFAULT '0' COMMENT '总队伍数(仅集体项目)',
|
||||
`estimated_duration` int(11) DEFAULT '0' COMMENT '预计时长(分钟)',
|
||||
`create_user` bigint(20) DEFAULT NULL,
|
||||
`create_dept` bigint(20) DEFAULT NULL,
|
||||
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_user` bigint(20) DEFAULT NULL,
|
||||
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`status` int(11) DEFAULT '1' COMMENT '状态(1-启用,2-禁用)',
|
||||
`is_deleted` int(11) DEFAULT '0',
|
||||
`tenant_id` varchar(12) DEFAULT '000000',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_competition` (`competition_id`),
|
||||
KEY `idx_project` (`project_id`),
|
||||
KEY `idx_display_order` (`display_order`),
|
||||
KEY `idx_tenant` (`tenant_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='赛程编排分组表';
|
||||
|
||||
-- 2. 赛程编排明细表(场地时间段分配)
|
||||
CREATE TABLE IF NOT EXISTS `martial_schedule_detail` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`schedule_group_id` bigint(20) NOT NULL COMMENT '分组ID',
|
||||
`competition_id` bigint(20) NOT NULL COMMENT '赛事ID',
|
||||
`venue_id` bigint(20) NOT NULL COMMENT '场地ID',
|
||||
`venue_name` varchar(100) DEFAULT NULL COMMENT '场地名称',
|
||||
`schedule_date` date NOT NULL COMMENT '比赛日期',
|
||||
`time_period` varchar(20) NOT NULL COMMENT '时间段(morning/afternoon)',
|
||||
`time_slot` varchar(20) NOT NULL COMMENT '时间点(08:30/13:30)',
|
||||
`estimated_start_time` datetime DEFAULT NULL COMMENT '预计开始时间',
|
||||
`estimated_end_time` datetime DEFAULT NULL COMMENT '预计结束时间',
|
||||
`estimated_duration` int(11) DEFAULT '0' COMMENT '预计时长(分钟)',
|
||||
`participant_count` int(11) DEFAULT '0' COMMENT '参赛人数',
|
||||
`sort_order` int(11) DEFAULT '0' COMMENT '场内顺序',
|
||||
`create_user` bigint(20) DEFAULT NULL,
|
||||
`create_dept` bigint(20) DEFAULT NULL,
|
||||
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_user` bigint(20) DEFAULT NULL,
|
||||
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`status` int(11) DEFAULT '1' COMMENT '状态(1-未开始,2-进行中,3-已完成)',
|
||||
`is_deleted` int(11) DEFAULT '0',
|
||||
`tenant_id` varchar(12) DEFAULT '000000',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_group` (`schedule_group_id`),
|
||||
KEY `idx_competition` (`competition_id`),
|
||||
KEY `idx_venue_time` (`venue_id`,`schedule_date`,`time_slot`),
|
||||
KEY `idx_tenant` (`tenant_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='赛程编排明细表(场地时间段分配)';
|
||||
|
||||
-- 3. 赛程编排参赛者关联表
|
||||
CREATE TABLE IF NOT EXISTS `martial_schedule_participant` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`schedule_detail_id` bigint(20) NOT NULL COMMENT '编排明细ID',
|
||||
`schedule_group_id` bigint(20) NOT NULL COMMENT '分组ID',
|
||||
`participant_id` bigint(20) NOT NULL COMMENT '参赛者ID(关联martial_athlete表)',
|
||||
`organization` varchar(200) DEFAULT NULL COMMENT '单位名称',
|
||||
`player_name` varchar(100) DEFAULT NULL COMMENT '选手姓名',
|
||||
`project_name` varchar(100) DEFAULT NULL COMMENT '项目名称',
|
||||
`category` varchar(50) DEFAULT NULL COMMENT '组别',
|
||||
`performance_order` int(11) DEFAULT '0' COMMENT '出场顺序',
|
||||
`create_user` bigint(20) DEFAULT NULL,
|
||||
`create_dept` bigint(20) DEFAULT NULL,
|
||||
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_user` bigint(20) DEFAULT NULL,
|
||||
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`status` int(11) DEFAULT '1' COMMENT '状态(1-待出场,2-已出场)',
|
||||
`is_deleted` int(11) DEFAULT '0',
|
||||
`tenant_id` varchar(12) DEFAULT '000000',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_detail` (`schedule_detail_id`),
|
||||
KEY `idx_group` (`schedule_group_id`),
|
||||
KEY `idx_participant` (`participant_id`),
|
||||
KEY `idx_tenant` (`tenant_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='赛程编排参赛者关联表';
|
||||
|
||||
-- 4. 赛程编排状态表
|
||||
CREATE TABLE IF NOT EXISTS `martial_schedule_status` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`competition_id` bigint(20) NOT NULL COMMENT '赛事ID(唯一)',
|
||||
`schedule_status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '编排状态(0=未编排 1=编排中 2=已保存锁定)',
|
||||
`last_auto_schedule_time` datetime DEFAULT NULL COMMENT '最后自动编排时间',
|
||||
`locked_time` datetime DEFAULT NULL COMMENT '锁定时间',
|
||||
`locked_by` varchar(100) DEFAULT NULL COMMENT '锁定人',
|
||||
`total_groups` int(11) DEFAULT '0' COMMENT '总分组数',
|
||||
`total_participants` int(11) DEFAULT '0' COMMENT '总参赛人数',
|
||||
`create_user` bigint(20) DEFAULT NULL,
|
||||
`create_dept` bigint(20) DEFAULT NULL,
|
||||
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_user` bigint(20) DEFAULT NULL,
|
||||
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`status` int(11) DEFAULT '1' COMMENT '状态(1-启用,2-禁用)',
|
||||
`is_deleted` int(11) DEFAULT '0',
|
||||
`tenant_id` varchar(12) DEFAULT '000000',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_competition` (`competition_id`),
|
||||
KEY `idx_tenant` (`tenant_id`),
|
||||
KEY `idx_schedule_status` (`schedule_status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='赛程编排状态表';
|
||||
|
||||
-- =============================================
|
||||
-- 验证结果
|
||||
-- =============================================
|
||||
|
||||
SELECT
|
||||
'升级完成' AS message,
|
||||
COUNT(*) AS new_tables_count
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'martial_db'
|
||||
AND table_name IN (
|
||||
'martial_schedule_group',
|
||||
'martial_schedule_detail',
|
||||
'martial_schedule_participant',
|
||||
'martial_schedule_status'
|
||||
);
|
||||
|
||||
-- 显示所有赛程相关表
|
||||
SELECT
|
||||
table_name,
|
||||
table_comment,
|
||||
CASE
|
||||
WHEN table_name IN ('martial_schedule_group', 'martial_schedule_detail',
|
||||
'martial_schedule_participant', 'martial_schedule_status')
|
||||
THEN '新系统'
|
||||
ELSE '旧系统'
|
||||
END AS system_version
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'martial_db'
|
||||
AND table_name LIKE 'martial_schedule%'
|
||||
ORDER BY system_version DESC, table_name;
|
||||
|
||||
-- =============================================
|
||||
-- 说明
|
||||
-- =============================================
|
||||
--
|
||||
-- 执行结果说明:
|
||||
-- 1. 如果 new_tables_count = 4,说明4张新表全部创建成功
|
||||
-- 2. 如果 new_tables_count < 4,说明部分表已存在或创建失败
|
||||
-- 3. 最后一个查询会显示所有赛程相关表及其所属系统版本
|
||||
--
|
||||
-- 新旧系统对比:
|
||||
-- - 旧系统: martial_schedule, martial_schedule_athlete (可能存在)
|
||||
-- - 新系统: martial_schedule_group, martial_schedule_detail,
|
||||
-- martial_schedule_participant, martial_schedule_status
|
||||
--
|
||||
-- 两个系统可以共存,不会互相影响
|
||||
-- 新系统由后端Service层代码使用
|
||||
--
|
||||
-- =============================================
|
||||
@@ -1,113 +0,0 @@
|
||||
-- ================================================================
|
||||
-- 赛事编排智能化升级 SQL 脚本
|
||||
-- 用途:支持智能编排算法(场地容纳人数 + 项目时长限制)
|
||||
-- 日期:2025-12-06
|
||||
-- ================================================================
|
||||
|
||||
-- 1. 创建场地信息表(如果不存在)
|
||||
-- ================================================================
|
||||
-- 注意:使用 capacity 字段名以匹配现有数据库表结构
|
||||
CREATE TABLE IF NOT EXISTS `martial_venue` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`tenant_id` varchar(12) DEFAULT '000000' COMMENT '租户ID',
|
||||
`competition_id` bigint(20) NOT NULL COMMENT '赛事ID',
|
||||
`venue_name` varchar(100) NOT NULL COMMENT '场地名称',
|
||||
`venue_code` varchar(50) DEFAULT NULL COMMENT '场地编码',
|
||||
`location` varchar(200) DEFAULT NULL COMMENT '位置/地点',
|
||||
`capacity` int(11) DEFAULT 100 COMMENT '容纳人数',
|
||||
`facilities` varchar(500) DEFAULT NULL COMMENT '场地设施',
|
||||
`status` int(2) DEFAULT 1 COMMENT '状态(0-禁用,1-启用)',
|
||||
`create_user` bigint(20) DEFAULT NULL COMMENT '创建人',
|
||||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||||
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_user` bigint(20) DEFAULT NULL COMMENT '修改人',
|
||||
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
`is_deleted` int(2) DEFAULT 0 COMMENT '是否已删除',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_competition_id` (`competition_id`),
|
||||
KEY `idx_tenant_id` (`tenant_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='场地信息表';
|
||||
|
||||
-- 2. 确保 martial_project 表有 estimated_duration 字段
|
||||
-- ================================================================
|
||||
-- 检查字段是否存在,不存在则添加
|
||||
SET @col_exists = 0;
|
||||
SELECT COUNT(*) INTO @col_exists
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'martial_project'
|
||||
AND COLUMN_NAME = 'estimated_duration';
|
||||
|
||||
SET @sql = IF(@col_exists = 0,
|
||||
'ALTER TABLE martial_project ADD COLUMN estimated_duration int(11) DEFAULT 5 COMMENT ''预估时长(分钟)'' AFTER max_participants',
|
||||
'SELECT ''estimated_duration column already exists'' AS info'
|
||||
);
|
||||
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 3. 插入测试数据(仅用于开发测试)
|
||||
-- ================================================================
|
||||
-- 为赛事 ID=100 插入场地数据
|
||||
INSERT INTO `martial_venue` (`competition_id`, `venue_name`, `venue_code`, `capacity`, `location`, `facilities`) VALUES
|
||||
(100, '一号场地', 'VENUE_01', 50, '体育馆一楼东侧', '主会场,配备专业武术地毯,适合集体项目'),
|
||||
(100, '二号场地', 'VENUE_02', 50, '体育馆一楼西侧', '次会场,配备专业武术地毯,适合集体项目'),
|
||||
(100, '三号场地', 'VENUE_03', 30, '体育馆二楼东侧', '小型场地,适合个人项目'),
|
||||
(100, '四号场地', 'VENUE_04', 30, '体育馆二楼西侧', '小型场地,适合个人项目')
|
||||
ON DUPLICATE KEY UPDATE
|
||||
venue_name = VALUES(venue_name),
|
||||
capacity = VALUES(capacity),
|
||||
location = VALUES(location),
|
||||
facilities = VALUES(facilities);
|
||||
|
||||
-- 4. 更新现有项目的预估时长(如果为NULL或0)
|
||||
-- ================================================================
|
||||
UPDATE martial_project
|
||||
SET estimated_duration = CASE
|
||||
WHEN project_name LIKE '%太极%' THEN 5
|
||||
WHEN project_name LIKE '%长拳%' THEN 5
|
||||
WHEN project_name LIKE '%剑%' THEN 4
|
||||
WHEN project_name LIKE '%刀%' THEN 4
|
||||
WHEN project_name LIKE '%棍%' THEN 6
|
||||
WHEN project_name LIKE '%枪%' THEN 6
|
||||
ELSE 5
|
||||
END
|
||||
WHERE estimated_duration IS NULL OR estimated_duration = 0;
|
||||
|
||||
-- 5. 创建视图:场地使用统计(可选)
|
||||
-- ================================================================
|
||||
CREATE OR REPLACE VIEW v_venue_usage_stats AS
|
||||
SELECT
|
||||
v.id AS venue_id,
|
||||
v.competition_id,
|
||||
v.venue_name,
|
||||
v.max_capacity,
|
||||
COUNT(DISTINCT s.group_id) AS assigned_groups,
|
||||
SUM(s.participant_count) AS total_participants,
|
||||
SUM(s.estimated_duration) AS total_duration,
|
||||
v.max_capacity - IFNULL(SUM(s.participant_count), 0) AS remaining_capacity
|
||||
FROM martial_venue v
|
||||
LEFT JOIN (
|
||||
-- 这里假设将来会有 martial_schedule 表来存储编排结果
|
||||
SELECT
|
||||
venue_id,
|
||||
group_id,
|
||||
COUNT(*) AS participant_count,
|
||||
SUM(estimated_duration) AS estimated_duration
|
||||
FROM martial_schedule_detail
|
||||
WHERE is_deleted = 0
|
||||
GROUP BY venue_id, group_id
|
||||
) s ON v.id = s.venue_id
|
||||
WHERE v.is_deleted = 0
|
||||
GROUP BY v.id, v.competition_id, v.venue_name, v.max_capacity;
|
||||
|
||||
-- ================================================================
|
||||
-- 脚本执行完成
|
||||
-- ================================================================
|
||||
-- 说明:
|
||||
-- 1. 场地表已创建,支持最大容纳人数配置
|
||||
-- 2. 项目表 estimated_duration 字段已确保存在
|
||||
-- 3. 测试数据已插入(赛事ID=100)
|
||||
-- 4. 现有项目的预估时长已更新为合理默认值
|
||||
-- ================================================================
|
||||
@@ -1,101 +0,0 @@
|
||||
-- =============================================
|
||||
-- 验证赛程编排系统表创建情况
|
||||
-- =============================================
|
||||
|
||||
USE martial_db;
|
||||
|
||||
-- 1. 检查所有赛程相关表
|
||||
SELECT
|
||||
table_name AS '表名',
|
||||
table_comment AS '说明',
|
||||
CASE
|
||||
WHEN table_name IN ('martial_schedule_group', 'martial_schedule_detail',
|
||||
'martial_schedule_participant', 'martial_schedule_status')
|
||||
THEN '✓ 新系统'
|
||||
ELSE '旧系统'
|
||||
END AS '系统版本',
|
||||
table_rows AS '记录数'
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'martial_db'
|
||||
AND table_name LIKE 'martial_schedule%'
|
||||
ORDER BY
|
||||
CASE
|
||||
WHEN table_name IN ('martial_schedule_group', 'martial_schedule_detail',
|
||||
'martial_schedule_participant', 'martial_schedule_status')
|
||||
THEN 1
|
||||
ELSE 2
|
||||
END,
|
||||
table_name;
|
||||
|
||||
-- 2. 验证新系统4张表是否全部创建
|
||||
SELECT
|
||||
CASE
|
||||
WHEN COUNT(*) = 4 THEN '✓ 新系统表创建成功! 共4张表已就绪'
|
||||
ELSE CONCAT('⚠ 警告: 只创建了 ', COUNT(*), ' 张表,应该是4张')
|
||||
END AS '创建状态'
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'martial_db'
|
||||
AND table_name IN (
|
||||
'martial_schedule_group',
|
||||
'martial_schedule_detail',
|
||||
'martial_schedule_participant',
|
||||
'martial_schedule_status'
|
||||
);
|
||||
|
||||
-- 3. 检查各表的字段数量
|
||||
SELECT
|
||||
table_name AS '表名',
|
||||
COUNT(*) AS '字段数'
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'martial_db'
|
||||
AND table_name IN (
|
||||
'martial_schedule_group',
|
||||
'martial_schedule_detail',
|
||||
'martial_schedule_participant',
|
||||
'martial_schedule_status'
|
||||
)
|
||||
GROUP BY table_name
|
||||
ORDER BY table_name;
|
||||
|
||||
-- 4. 检查索引创建情况
|
||||
SELECT
|
||||
table_name AS '表名',
|
||||
COUNT(DISTINCT index_name) AS '索引数量',
|
||||
GROUP_CONCAT(DISTINCT index_name ORDER BY index_name) AS '索引列表'
|
||||
FROM information_schema.statistics
|
||||
WHERE table_schema = 'martial_db'
|
||||
AND table_name IN (
|
||||
'martial_schedule_group',
|
||||
'martial_schedule_detail',
|
||||
'martial_schedule_participant',
|
||||
'martial_schedule_status'
|
||||
)
|
||||
GROUP BY table_name
|
||||
ORDER BY table_name;
|
||||
|
||||
-- 5. 检查是否有数据(应该为空,因为是新表)
|
||||
SELECT
|
||||
'martial_schedule_group' AS '表名',
|
||||
COUNT(*) AS '记录数'
|
||||
FROM martial_schedule_group
|
||||
UNION ALL
|
||||
SELECT
|
||||
'martial_schedule_detail',
|
||||
COUNT(*)
|
||||
FROM martial_schedule_detail
|
||||
UNION ALL
|
||||
SELECT
|
||||
'martial_schedule_participant',
|
||||
COUNT(*)
|
||||
FROM martial_schedule_participant
|
||||
UNION ALL
|
||||
SELECT
|
||||
'martial_schedule_status',
|
||||
COUNT(*)
|
||||
FROM martial_schedule_status;
|
||||
|
||||
-- 6. 显示最终状态
|
||||
SELECT
|
||||
'🎉 数据库升级完成!' AS '状态',
|
||||
DATABASE() AS '当前数据库',
|
||||
NOW() AS '验证时间';
|
||||
8869
database/martial_db.sql
Normal file
8869
database/martial_db.sql
Normal file
File diff suppressed because one or more lines are too long
79
docker-compose.yml
Normal file
79
docker-compose.yml
Normal file
@@ -0,0 +1,79 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# MySQL 数据库
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
container_name: martial-mysql
|
||||
restart: always
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: 123456
|
||||
MYSQL_DATABASE: martial_db
|
||||
TZ: Asia/Shanghai
|
||||
ports:
|
||||
- "3306:3306"
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
- ./database:/docker-entrypoint-initdb.d
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p123456"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- martial-network
|
||||
|
||||
# Redis 缓存
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: martial-redis
|
||||
restart: always
|
||||
command: redis-server --requirepass 123456
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "-a", "123456", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- martial-network
|
||||
|
||||
# 后端应用
|
||||
martial-api:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
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
|
||||
ports:
|
||||
- "8123:8123"
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- martial-network
|
||||
|
||||
networks:
|
||||
martial-network:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
mysql_data:
|
||||
redis_data:
|
||||
@@ -0,0 +1,127 @@
|
||||
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 org.springblade.core.boot.ctrl.BladeController;
|
||||
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.modules.martial.pojo.entity.MartialCompetitionAttachment;
|
||||
import org.springblade.modules.martial.service.IMartialCompetitionAttachmentService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 赛事附件 控制器
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@RequestMapping("/martial/competition/attachment")
|
||||
@Tag(name = "赛事附件管理", description = "赛事附件管理接口")
|
||||
public class MartialCompetitionAttachmentController extends BladeController {
|
||||
|
||||
private final IMartialCompetitionAttachmentService attachmentService;
|
||||
|
||||
/**
|
||||
* 详情
|
||||
*/
|
||||
@GetMapping("/detail")
|
||||
@Operation(summary = "详情", description = "传入ID")
|
||||
public R<MartialCompetitionAttachment> detail(@RequestParam Long id) {
|
||||
MartialCompetitionAttachment detail = attachmentService.getById(id);
|
||||
return R.data(detail);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "分页列表", description = "分页查询")
|
||||
public R<IPage<MartialCompetitionAttachment>> list(MartialCompetitionAttachment attachment, Query query) {
|
||||
IPage<MartialCompetitionAttachment> pages = attachmentService.page(Condition.getPage(query), Condition.getQueryWrapper(attachment));
|
||||
return R.data(pages);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据赛事ID和类型获取附件列表
|
||||
*/
|
||||
@GetMapping("/getByType")
|
||||
@Operation(summary = "根据赛事ID和类型获取附件列表", description = "传入赛事ID和附件类型")
|
||||
public R<List<MartialCompetitionAttachment>> getByType(
|
||||
@RequestParam Long competitionId,
|
||||
@RequestParam String attachmentType) {
|
||||
List<MartialCompetitionAttachment> list = attachmentService.getByCompetitionIdAndType(competitionId, attachmentType);
|
||||
return R.data(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据赛事ID获取所有附件
|
||||
*/
|
||||
@GetMapping("/getByCompetition")
|
||||
@Operation(summary = "根据赛事ID获取所有附件", description = "传入赛事ID")
|
||||
public R<List<MartialCompetitionAttachment>> getByCompetition(@RequestParam Long competitionId) {
|
||||
List<MartialCompetitionAttachment> list = attachmentService.getByCompetitionId(competitionId);
|
||||
return R.data(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增或修改
|
||||
*/
|
||||
@PostMapping("/submit")
|
||||
@Operation(summary = "新增或修改", description = "传入实体")
|
||||
public R submit(@RequestBody MartialCompetitionAttachment attachment) {
|
||||
// 设置默认状态为启用
|
||||
if (attachment.getStatus() == null) {
|
||||
attachment.setStatus(1);
|
||||
}
|
||||
// 设置默认排序
|
||||
if (attachment.getOrderNum() == null) {
|
||||
attachment.setOrderNum(0);
|
||||
}
|
||||
return R.status(attachmentService.saveOrUpdate(attachment));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量保存附件
|
||||
*/
|
||||
@PostMapping("/batchSubmit")
|
||||
@Operation(summary = "批量保存附件", description = "传入附件列表")
|
||||
public R batchSubmit(@RequestBody List<MartialCompetitionAttachment> attachments) {
|
||||
for (MartialCompetitionAttachment attachment : attachments) {
|
||||
if (attachment.getStatus() == null) {
|
||||
attachment.setStatus(1);
|
||||
}
|
||||
if (attachment.getOrderNum() == null) {
|
||||
attachment.setOrderNum(0);
|
||||
}
|
||||
}
|
||||
return R.status(attachmentService.saveOrUpdateBatch(attachments));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
@PostMapping("/remove")
|
||||
@Operation(summary = "删除", description = "传入ID")
|
||||
public R remove(@RequestParam String ids) {
|
||||
return R.status(attachmentService.removeByIds(Func.toLongList(ids)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除赛事的指定类型附件
|
||||
*/
|
||||
@PostMapping("/removeByType")
|
||||
@Operation(summary = "删除赛事的指定类型附件", description = "传入赛事ID和附件类型")
|
||||
public R removeByType(
|
||||
@RequestParam Long competitionId,
|
||||
@RequestParam String attachmentType) {
|
||||
return R.status(attachmentService.removeByCompetitionIdAndType(competitionId, attachmentType));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
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 com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -9,10 +12,15 @@ 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.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.service.IMartialCompetitionService;
|
||||
import org.springblade.modules.system.pojo.entity.User;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 赛事信息 控制器
|
||||
*
|
||||
@@ -25,6 +33,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
public class MartialCompetitionController extends BladeController {
|
||||
|
||||
private final IMartialCompetitionService competitionService;
|
||||
private final IMartialAthleteService martialAthleteService;
|
||||
|
||||
/**
|
||||
* 详情
|
||||
@@ -43,6 +52,14 @@ public class MartialCompetitionController extends BladeController {
|
||||
@Operation(summary = "分页列表", description = "分页查询")
|
||||
public R<IPage<MartialCompetition>> list(MartialCompetition competition, Query query) {
|
||||
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());
|
||||
}
|
||||
return R.data(pages);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.springblade.modules.martial.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -19,12 +21,17 @@ 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.entity.MtVenue;
|
||||
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;
|
||||
|
||||
@@ -43,20 +50,23 @@ 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;
|
||||
|
||||
// Redis缓存key前缀
|
||||
private static final String MINI_LOGIN_CACHE_PREFIX = "mini:login:";
|
||||
// 登录缓存过期时间(7天)
|
||||
private static final Duration LOGIN_CACHE_EXPIRE = Duration.ofDays(7);
|
||||
|
||||
/**
|
||||
* 登录验证
|
||||
*
|
||||
* @param dto 登录信息(比赛编码+邀请码)
|
||||
* @return 登录结果(token、用户信息、分配的场地和项目)
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
@Operation(summary = "登录验证", description = "使用比赛编码和邀请码登录")
|
||||
public R<MiniLoginVO> login(@RequestBody MiniLoginDTO dto) {
|
||||
// 1. 根据邀请码查询邀请信息
|
||||
LambdaQueryWrapper<MartialJudgeInvite> inviteQuery = new LambdaQueryWrapper<>();
|
||||
inviteQuery.eq(MartialJudgeInvite::getInviteCode, dto.getInviteCode());
|
||||
inviteQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
||||
@@ -66,29 +76,24 @@ public class MartialMiniController extends BladeController {
|
||||
return R.fail("邀请码不存在");
|
||||
}
|
||||
|
||||
// 2. 验证邀请码是否过期
|
||||
if (invite.getExpireTime() != null && invite.getExpireTime().isBefore(LocalDateTime.now())) {
|
||||
return R.fail("邀请码已过期");
|
||||
}
|
||||
|
||||
// 3. 查询比赛信息
|
||||
MartialCompetition competition = competitionService.getById(invite.getCompetitionId());
|
||||
if (competition == null) {
|
||||
return R.fail("比赛不存在");
|
||||
}
|
||||
|
||||
// 4. 验证比赛编码
|
||||
if (!competition.getCompetitionCode().equals(dto.getMatchCode())) {
|
||||
return R.fail("比赛编码不匹配");
|
||||
}
|
||||
|
||||
// 5. 查询评委信息
|
||||
MartialJudge judge = judgeService.getById(invite.getJudgeId());
|
||||
if (judge == null) {
|
||||
return R.fail("评委信息不存在");
|
||||
}
|
||||
|
||||
// 6. 生成访问令牌
|
||||
String token = UUID.randomUUID().toString().replace("-", "");
|
||||
invite.setAccessToken(token);
|
||||
invite.setTokenExpireTime(LocalDateTime.now().plusDays(7));
|
||||
@@ -98,16 +103,14 @@ public class MartialMiniController extends BladeController {
|
||||
invite.setDeviceInfo(dto.getDeviceInfo());
|
||||
judgeInviteService.updateById(invite);
|
||||
|
||||
// 7. 查询场地信息(裁判长没有固定场地)
|
||||
MartialVenue venue = null;
|
||||
// 从 mt_venue 表获取场地信息
|
||||
MtVenue mtVenue = null;
|
||||
if (invite.getVenueId() != null) {
|
||||
venue = venueService.getById(invite.getVenueId());
|
||||
mtVenue = mtVenueService.getById(invite.getVenueId());
|
||||
}
|
||||
|
||||
// 8. 解析分配的项目
|
||||
List<MiniLoginVO.ProjectInfo> projects = parseProjects(invite.getProjects());
|
||||
|
||||
// 9. 构造返回结果
|
||||
MiniLoginVO vo = new MiniLoginVO();
|
||||
vo.setToken(token);
|
||||
vo.setUserRole("chief_judge".equals(invite.getRole()) ? "admin" : "pub");
|
||||
@@ -117,71 +120,214 @@ public class MartialMiniController extends BladeController {
|
||||
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(mtVenue != null ? mtVenue.getId() : null);
|
||||
vo.setVenueName(mtVenue != null ? mtVenue.getVenueName() : null);
|
||||
vo.setProjects(projects);
|
||||
|
||||
// 将登录信息缓存到Redis(服务重启后仍然有效)
|
||||
String cacheKey = MINI_LOGIN_CACHE_PREFIX + token;
|
||||
bladeRedis.setEx(cacheKey, vo, LOGIN_CACHE_EXPIRE);
|
||||
|
||||
return R.data(vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交评分(评委)
|
||||
*
|
||||
* @param dto 评分信息
|
||||
* @return 提交结果
|
||||
* 注意:ID字段使用String类型接收,避免JavaScript大数精度丢失问题
|
||||
*/
|
||||
@PostMapping("/score/submit")
|
||||
@Operation(summary = "提交评分", description = "评委提交对选手的评分")
|
||||
public R submitScore(@RequestBody org.springblade.modules.martial.pojo.dto.MiniScoreSubmitDTO dto) {
|
||||
|
||||
// 转换DTO为实体
|
||||
MartialScore score = new MartialScore();
|
||||
score.setAthleteId(dto.getAthleteId());
|
||||
score.setJudgeId(dto.getJudgeId());
|
||||
|
||||
// 将String类型的ID转换为Long,避免JavaScript大数精度丢失
|
||||
score.setAthleteId(parseLong(dto.getAthleteId()));
|
||||
score.setJudgeId(parseLong(dto.getJudgeId()));
|
||||
score.setScore(dto.getScore());
|
||||
score.setProjectId(dto.getProjectId());
|
||||
score.setCompetitionId(dto.getCompetitionId());
|
||||
score.setVenueId(dto.getVenueId());
|
||||
score.setScheduleId(dto.getScheduleId());
|
||||
score.setProjectId(parseLong(dto.getProjectId()));
|
||||
score.setCompetitionId(parseLong(dto.getCompetitionId()));
|
||||
score.setVenueId(parseLong(dto.getVenueId()));
|
||||
score.setScheduleId(parseLong(dto.getScheduleId()));
|
||||
score.setNote(dto.getNote());
|
||||
score.setScoreTime(LocalDateTime.now());
|
||||
|
||||
// 将扣分项列表转换为JSON字符串存储
|
||||
if (dto.getDeductions() != null && !dto.getDeductions().isEmpty()) {
|
||||
score.setDeductionItems(com.alibaba.fastjson.JSON.toJSONString(dto.getDeductions()));
|
||||
// 将String类型的扣分项ID转换为Long
|
||||
List<Long> deductionIds = dto.getDeductions().stream()
|
||||
.map(this::parseLong)
|
||||
.filter(id -> id != null)
|
||||
.collect(Collectors.toList());
|
||||
score.setDeductionItems(com.alibaba.fastjson.JSON.toJSONString(deductionIds));
|
||||
}
|
||||
|
||||
// 获取评委姓名
|
||||
if (dto.getJudgeId() != null) {
|
||||
var judge = judgeService.getById(dto.getJudgeId());
|
||||
Long judgeId = parseLong(dto.getJudgeId());
|
||||
if (judgeId != null) {
|
||||
var judge = judgeService.getById(judgeId);
|
||||
if (judge != null) {
|
||||
score.setJudgeName(judge.getName());
|
||||
}
|
||||
}
|
||||
|
||||
// 保存评分(会自动进行权限验证和分数范围验证)
|
||||
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("评分提交失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取选手列表
|
||||
* - 普通裁判:获取待评分的选手列表(该裁判还未评分的选手)
|
||||
* - 裁判长:获取已有评分的选手列表(至少有一个裁判已评分的选手)
|
||||
*
|
||||
* @param judgeId 裁判ID
|
||||
* @param refereeType 裁判类型(1-裁判长, 2-普通裁判)
|
||||
* @param projectId 项目ID(可选,用于筛选特定项目的选手)
|
||||
* @param venueId 场地ID(可选,用于筛选特定场地的选手)
|
||||
* @return 选手列表
|
||||
* 计算并更新选手总分
|
||||
* 总分算法:去掉一个最高分和一个最低分,取剩余分数的平均值
|
||||
* 特殊情况:裁判数量<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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取场地应评分的裁判数量(普通裁判,不包括裁判长)
|
||||
* 注意:使用 DISTINCT judge_id 来避免重复计数
|
||||
*/
|
||||
private int getRequiredJudgeCount(Long venueId) {
|
||||
if (venueId == null) {
|
||||
return 0;
|
||||
}
|
||||
LambdaQueryWrapper<MartialJudgeInvite> judgeQuery = new LambdaQueryWrapper<>();
|
||||
judgeQuery.eq(MartialJudgeInvite::getVenueId, venueId);
|
||||
judgeQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
||||
judgeQuery.ne(MartialJudgeInvite::getRole, "chief_judge"); // 排除裁判长
|
||||
List<MartialJudgeInvite> judges = judgeInviteService.list(judgeQuery);
|
||||
// 使用 distinct judge_id 来计算不重复的裁判数量
|
||||
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
|
||||
*/
|
||||
private Long parseLong(String value) {
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Long.parseLong(value.trim());
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取选手列表(支持分页)
|
||||
* - 普通裁判:获取所有选手,标记是否已评分
|
||||
* - 裁判长:获取所有普通裁判都评分完成的选手列表
|
||||
*/
|
||||
@GetMapping("/score/athletes")
|
||||
@Operation(summary = "获取选手列表", description = "根据裁判类型获取不同的选手列表")
|
||||
public R<List<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO>> getAthletes(
|
||||
@Operation(summary = "获取选手列表", description = "根据裁判类型获取选手列表(支持分页)")
|
||||
public R<IPage<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO>> getAthletes(
|
||||
@RequestParam Long judgeId,
|
||||
@RequestParam Integer refereeType,
|
||||
@RequestParam(required = false) Long projectId,
|
||||
@RequestParam(required = false) Long venueId
|
||||
@RequestParam(required = false) Long venueId,
|
||||
@RequestParam(defaultValue = "1") Integer current,
|
||||
@RequestParam(defaultValue = "10") Integer size
|
||||
) {
|
||||
// 1. 构建选手查询条件
|
||||
LambdaQueryWrapper<MartialAthlete> athleteQuery = new LambdaQueryWrapper<>();
|
||||
@@ -190,57 +336,91 @@ public class MartialMiniController extends BladeController {
|
||||
if (projectId != null) {
|
||||
athleteQuery.eq(MartialAthlete::getProjectId, projectId);
|
||||
}
|
||||
// 注意:场地筛选需要通过评分记录的venueId来过滤,这里先查询所有选手
|
||||
|
||||
// 按出场顺序排序
|
||||
athleteQuery.orderByAsc(MartialAthlete::getOrderNum);
|
||||
|
||||
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) {
|
||||
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. 根据裁判类型筛选选手
|
||||
List<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> result;
|
||||
// 4. 获取该场地的应评裁判数量
|
||||
int requiredJudgeCount = getRequiredJudgeCount(venueId);
|
||||
|
||||
// 5. 根据裁判类型处理选手列表
|
||||
List<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> filteredList;
|
||||
|
||||
if (refereeType == 1) {
|
||||
// 裁判长:返回已有评分的选手
|
||||
result = athletes.stream()
|
||||
.filter(athlete -> {
|
||||
List<MartialScore> scores = scoresByAthlete.get(athlete.getId());
|
||||
return scores != null && !scores.isEmpty();
|
||||
})
|
||||
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId())))
|
||||
// 裁判长:返回所有选手,前端根据totalScore判断是否显示修改按钮
|
||||
filteredList = athletes.stream()
|
||||
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId()), judgeId, requiredJudgeCount))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
} else {
|
||||
// 普通裁判:返回该裁判还未评分的选手
|
||||
result = athletes.stream()
|
||||
.filter(athlete -> {
|
||||
List<MartialScore> scores = scoresByAthlete.get(athlete.getId());
|
||||
if (scores == null) {
|
||||
return true; // 没有任何评分,可以评
|
||||
}
|
||||
// 检查该裁判是否已评分
|
||||
return scores.stream().noneMatch(s -> s.getJudgeId().equals(judgeId));
|
||||
})
|
||||
.map(athlete -> convertToAthleteListVO(athlete, null))
|
||||
// 普通裁判:返回所有选手,标记是否已评分
|
||||
filteredList = athletes.stream()
|
||||
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId()), judgeId, requiredJudgeCount))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
|
||||
return R.data(result);
|
||||
// 6. 手动分页
|
||||
int total = filteredList.size();
|
||||
int fromIndex = (current - 1) * size;
|
||||
int toIndex = Math.min(fromIndex + size, total);
|
||||
|
||||
List<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> pageRecords;
|
||||
if (fromIndex >= total) {
|
||||
pageRecords = new ArrayList<>();
|
||||
} else {
|
||||
pageRecords = filteredList.subList(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取评分详情
|
||||
*
|
||||
* @param athleteId 选手ID
|
||||
* @return 评分详情(选手信息+所有评委的评分)
|
||||
*/
|
||||
@GetMapping("/score/detail/{athleteId}")
|
||||
@Operation(summary = "评分详情", description = "查看选手的所有评委评分")
|
||||
@@ -251,9 +431,6 @@ public class MartialMiniController extends BladeController {
|
||||
|
||||
/**
|
||||
* 修改评分(裁判长)
|
||||
*
|
||||
* @param dto 修改信息(选手ID、修改后的分数、修改原因)
|
||||
* @return 修改结果
|
||||
*/
|
||||
@PutMapping("/score/modify")
|
||||
@Operation(summary = "修改评分", description = "裁判长修改选手总分")
|
||||
@@ -264,45 +441,97 @@ public class MartialMiniController extends BladeController {
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*
|
||||
* @return 退出结果
|
||||
*/
|
||||
@PostMapping("/logout")
|
||||
@Operation(summary = "退出登录", description = "清除登录状态")
|
||||
public R logout() {
|
||||
// TODO: 实现真实的退出逻辑
|
||||
// 1. 清除token
|
||||
// 2. 清除session
|
||||
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验证
|
||||
*
|
||||
* @return 验证结果
|
||||
* Token验证(从Redis恢复登录状态)
|
||||
*/
|
||||
@GetMapping("/verify")
|
||||
@Operation(summary = "Token验证", description = "验证当前token是否有效")
|
||||
public R verify() {
|
||||
// TODO: 实现真实的token验证逻辑
|
||||
// 1. 从请求头获取token
|
||||
// 2. 验证token是否有效
|
||||
// 3. 返回验证结果
|
||||
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());
|
||||
MtVenue mtVenue = invite.getVenueId() != null ? mtVenueService.getById(invite.getVenueId()) : null;
|
||||
List<MiniLoginVO.ProjectInfo> projects = parseProjects(invite.getProjects());
|
||||
|
||||
MiniLoginVO vo = new MiniLoginVO();
|
||||
vo.setToken(token);
|
||||
vo.setUserRole("chief_judge".equals(invite.getRole()) ? "admin" : "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(mtVenue != null ? mtVenue.getId() : null);
|
||||
vo.setVenueName(mtVenue != null ? mtVenue.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) {
|
||||
private org.springblade.modules.martial.pojo.vo.MiniAthleteListVO convertToAthleteListVO(
|
||||
MartialAthlete athlete,
|
||||
List<MartialScore> scores,
|
||||
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());
|
||||
vo.setIdCard(athlete.getIdCard());
|
||||
vo.setNumber(athlete.getPlayerNo());
|
||||
vo.setTeam(athlete.getTeamName());
|
||||
vo.setOrderNum(athlete.getOrderNum());
|
||||
vo.setCompetitionStatus(athlete.getCompetitionStatus());
|
||||
vo.setTotalScore(athlete.getTotalScore());
|
||||
|
||||
// 设置应评分裁判数量
|
||||
vo.setRequiredJudgeCount(requiredJudgeCount);
|
||||
|
||||
// 设置项目名称
|
||||
if (athlete.getProjectId() != null) {
|
||||
@@ -312,9 +541,44 @@ public class MartialMiniController extends BladeController {
|
||||
}
|
||||
}
|
||||
|
||||
// 设置已评分裁判数量(仅裁判长可见)
|
||||
if (scores != null) {
|
||||
vo.setScoredJudgeCount(scores.size());
|
||||
// 设置评分状态
|
||||
int scoredCount = 0;
|
||||
if (scores != null && !scores.isEmpty()) {
|
||||
scoredCount = scores.size();
|
||||
vo.setScoredJudgeCount(scoredCount);
|
||||
|
||||
// 查找当前裁判的评分
|
||||
MartialScore myScore = scores.stream()
|
||||
.filter(s -> s.getJudgeId().equals(currentJudgeId))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (myScore != null) {
|
||||
vo.setScored(true);
|
||||
vo.setMyScore(myScore.getScore());
|
||||
} else {
|
||||
vo.setScored(false);
|
||||
}
|
||||
} else {
|
||||
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;
|
||||
@@ -331,11 +595,9 @@ public class MartialMiniController extends BladeController {
|
||||
}
|
||||
|
||||
try {
|
||||
// 解析JSON数组:格式为 [{"projectId": 1, "projectName": "太极拳"}, ...]
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
List<Long> projectIds = mapper.readValue(projectsJson, new TypeReference<List<Long>>() {});
|
||||
|
||||
// 查询项目详情
|
||||
if (Func.isNotEmpty(projectIds)) {
|
||||
List<MartialProject> projectList = projectService.listByIds(projectIds);
|
||||
projects = projectList.stream().map(project -> {
|
||||
@@ -346,7 +608,6 @@ public class MartialMiniController extends BladeController {
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 如果JSON解析失败,尝试按逗号分隔的ID字符串解析
|
||||
try {
|
||||
String[] ids = projectsJson.split(",");
|
||||
List<Long> projectIds = new ArrayList<>();
|
||||
|
||||
@@ -0,0 +1,381 @@
|
||||
package org.springblade.modules.martial.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
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.core.tool.utils.Func;
|
||||
import org.springblade.modules.martial.pojo.dto.MiniAthleteScoreDTO;
|
||||
import org.springblade.modules.martial.pojo.dto.MiniLoginDTO;
|
||||
import org.springblade.modules.martial.pojo.dto.MiniScoreModifyDTO;
|
||||
import org.springblade.modules.martial.pojo.entity.*;
|
||||
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 com.alibaba.fastjson.JSON;
|
||||
import org.springblade.modules.martial.service.*;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 小程序专用接口 控制器
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@RequestMapping("/mini")
|
||||
@Tag(name = "小程序接口", description = "小程序评分系统专用接口")
|
||||
public class MartialMiniController extends BladeController {
|
||||
|
||||
private final IMartialJudgeInviteService judgeInviteService;
|
||||
private final IMartialJudgeService judgeService;
|
||||
private final IMartialCompetitionService competitionService;
|
||||
private final IMartialVenueService venueService;
|
||||
private final IMartialProjectService projectService;
|
||||
private final IMartialAthleteService athleteService;
|
||||
private final IMartialScoreService scoreService;
|
||||
|
||||
/**
|
||||
* 登录验证
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
@Operation(summary = "登录验证", description = "使用比赛编码和邀请码登录")
|
||||
public R<MiniLoginVO> login(@RequestBody MiniLoginDTO dto) {
|
||||
LambdaQueryWrapper<MartialJudgeInvite> inviteQuery = new LambdaQueryWrapper<>();
|
||||
inviteQuery.eq(MartialJudgeInvite::getInviteCode, dto.getInviteCode());
|
||||
inviteQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
||||
MartialJudgeInvite invite = judgeInviteService.getOne(inviteQuery);
|
||||
|
||||
if (invite == null) {
|
||||
return R.fail("邀请码不存在");
|
||||
}
|
||||
|
||||
if (invite.getExpireTime() != null && invite.getExpireTime().isBefore(LocalDateTime.now())) {
|
||||
return R.fail("邀请码已过期");
|
||||
}
|
||||
|
||||
MartialCompetition competition = competitionService.getById(invite.getCompetitionId());
|
||||
if (competition == null) {
|
||||
return R.fail("比赛不存在");
|
||||
}
|
||||
|
||||
if (!competition.getCompetitionCode().equals(dto.getMatchCode())) {
|
||||
return R.fail("比赛编码不匹配");
|
||||
}
|
||||
|
||||
MartialJudge judge = judgeService.getById(invite.getJudgeId());
|
||||
if (judge == null) {
|
||||
return R.fail("评委信息不存在");
|
||||
}
|
||||
|
||||
String token = UUID.randomUUID().toString().replace("-", "");
|
||||
invite.setAccessToken(token);
|
||||
invite.setTokenExpireTime(LocalDateTime.now().plusDays(7));
|
||||
invite.setIsUsed(1);
|
||||
invite.setUseTime(LocalDateTime.now());
|
||||
invite.setLoginIp(dto.getLoginIp());
|
||||
invite.setDeviceInfo(dto.getDeviceInfo());
|
||||
judgeInviteService.updateById(invite);
|
||||
|
||||
MartialVenue venue = null;
|
||||
if (invite.getVenueId() != null) {
|
||||
venue = venueService.getById(invite.getVenueId());
|
||||
}
|
||||
|
||||
List<MiniLoginVO.ProjectInfo> projects = parseProjects(invite.getProjects());
|
||||
|
||||
MiniLoginVO vo = new MiniLoginVO();
|
||||
vo.setToken(token);
|
||||
vo.setUserRole("chief_judge".equals(invite.getRole()) ? "admin" : "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.setProjects(projects);
|
||||
|
||||
return R.data(vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交评分(评委)
|
||||
* 注意:ID字段使用String类型接收,避免JavaScript大数精度丢失问题
|
||||
*/
|
||||
@PostMapping("/score/submit")
|
||||
@Operation(summary = "提交评分", description = "评委提交对选手的评分")
|
||||
public R submitScore(@RequestBody org.springblade.modules.martial.pojo.dto.MiniScoreSubmitDTO dto) {
|
||||
MartialScore score = new MartialScore();
|
||||
|
||||
// 将String类型的ID转换为Long,避免JavaScript大数精度丢失
|
||||
score.setAthleteId(parseLong(dto.getAthleteId()));
|
||||
score.setJudgeId(parseLong(dto.getJudgeId()));
|
||||
score.setScore(dto.getScore());
|
||||
score.setProjectId(parseLong(dto.getProjectId()));
|
||||
score.setCompetitionId(parseLong(dto.getCompetitionId()));
|
||||
score.setVenueId(parseLong(dto.getVenueId()));
|
||||
score.setScheduleId(parseLong(dto.getScheduleId()));
|
||||
score.setNote(dto.getNote());
|
||||
score.setScoreTime(LocalDateTime.now());
|
||||
|
||||
if (dto.getDeductions() != null && !dto.getDeductions().isEmpty()) {
|
||||
// 将String类型的扣分项ID转换为Long
|
||||
List<Long> deductionIds = dto.getDeductions().stream()
|
||||
.map(this::parseLong)
|
||||
.filter(id -> id != null)
|
||||
.collect(Collectors.toList());
|
||||
score.setDeductionItems(com.alibaba.fastjson.JSON.toJSONString(deductionIds));
|
||||
}
|
||||
|
||||
Long judgeId = parseLong(dto.getJudgeId());
|
||||
if (judgeId != null) {
|
||||
var judge = judgeService.getById(judgeId);
|
||||
if (judge != null) {
|
||||
score.setJudgeName(judge.getName());
|
||||
}
|
||||
}
|
||||
|
||||
boolean success = scoreService.save(score);
|
||||
return success ? R.success("评分提交成功") : R.fail("评分提交失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全地将String转换为Long
|
||||
*/
|
||||
private Long parseLong(String value) {
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Long.parseLong(value.trim());
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取选手列表(支持分页)
|
||||
* - 普通裁判:获取所有选手,标记是否已评分
|
||||
* - 裁判长:获取已有评分的选手列表
|
||||
*/
|
||||
@GetMapping("/score/athletes")
|
||||
@Operation(summary = "获取选手列表", description = "根据裁判类型获取选手列表(支持分页)")
|
||||
public R<IPage<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO>> getAthletes(
|
||||
@RequestParam Long judgeId,
|
||||
@RequestParam Integer refereeType,
|
||||
@RequestParam(required = false) Long projectId,
|
||||
@RequestParam(required = false) Long venueId,
|
||||
@RequestParam(defaultValue = "1") Integer current,
|
||||
@RequestParam(defaultValue = "10") Integer size
|
||||
) {
|
||||
// 1. 构建选手查询条件
|
||||
LambdaQueryWrapper<MartialAthlete> athleteQuery = new LambdaQueryWrapper<>();
|
||||
athleteQuery.eq(MartialAthlete::getIsDeleted, 0);
|
||||
|
||||
if (projectId != null) {
|
||||
athleteQuery.eq(MartialAthlete::getProjectId, projectId);
|
||||
}
|
||||
|
||||
athleteQuery.orderByAsc(MartialAthlete::getOrderNum);
|
||||
|
||||
List<MartialAthlete> athletes = athleteService.list(athleteQuery);
|
||||
|
||||
// 2. 获取所有评分记录
|
||||
LambdaQueryWrapper<MartialScore> scoreQuery = new LambdaQueryWrapper<>();
|
||||
scoreQuery.eq(MartialScore::getIsDeleted, 0);
|
||||
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. 根据裁判类型处理选手列表
|
||||
List<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> filteredList;
|
||||
|
||||
if (refereeType == 1) {
|
||||
// 裁判长:返回已有评分的选手
|
||||
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))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
} else {
|
||||
// 普通裁判:返回所有选手,标记是否已评分
|
||||
filteredList = athletes.stream()
|
||||
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId()), judgeId))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
|
||||
// 4. 手动分页
|
||||
int total = filteredList.size();
|
||||
int fromIndex = (current - 1) * size;
|
||||
int toIndex = Math.min(fromIndex + size, total);
|
||||
|
||||
List<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> pageRecords;
|
||||
if (fromIndex >= total) {
|
||||
pageRecords = new ArrayList<>();
|
||||
} else {
|
||||
pageRecords = filteredList.subList(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
// 5. 构建分页结果
|
||||
IPage<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> page = new Page<>(current, size, total);
|
||||
page.setRecords(pageRecords);
|
||||
|
||||
return R.data(page);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取评分详情
|
||||
*/
|
||||
@GetMapping("/score/detail/{athleteId}")
|
||||
@Operation(summary = "评分详情", description = "查看选手的所有评委评分")
|
||||
public R<MiniScoreDetailVO> getScoreDetail(@PathVariable Long athleteId) {
|
||||
MiniScoreDetailVO detail = scoreService.getScoreDetailForMini(athleteId);
|
||||
return R.data(detail);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改评分(裁判长)
|
||||
*/
|
||||
@PutMapping("/score/modify")
|
||||
@Operation(summary = "修改评分", description = "裁判长修改选手总分")
|
||||
public R modifyScore(@RequestBody MiniScoreModifyDTO dto) {
|
||||
boolean success = scoreService.modifyScoreByAdmin(dto);
|
||||
return success ? R.success("修改成功") : R.fail("修改失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
@PostMapping("/logout")
|
||||
@Operation(summary = "退出登录", description = "清除登录状态")
|
||||
public R logout() {
|
||||
return R.success("退出成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* Token验证
|
||||
*/
|
||||
@GetMapping("/verify")
|
||||
@Operation(summary = "Token验证", description = "验证当前token是否有效")
|
||||
public R verify() {
|
||||
return R.success("Token有效");
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换选手实体为VO
|
||||
*/
|
||||
private org.springblade.modules.martial.pojo.vo.MiniAthleteListVO convertToAthleteListVO(
|
||||
MartialAthlete athlete,
|
||||
List<MartialScore> scores,
|
||||
Long currentJudgeId) {
|
||||
org.springblade.modules.martial.pojo.vo.MiniAthleteListVO vo = new org.springblade.modules.martial.pojo.vo.MiniAthleteListVO();
|
||||
vo.setAthleteId(athlete.getId());
|
||||
vo.setName(athlete.getPlayerName());
|
||||
vo.setIdCard(athlete.getIdCard());
|
||||
vo.setNumber(athlete.getPlayerNo());
|
||||
vo.setTeam(athlete.getTeamName());
|
||||
vo.setOrderNum(athlete.getOrderNum());
|
||||
vo.setCompetitionStatus(athlete.getCompetitionStatus());
|
||||
vo.setTotalScore(athlete.getTotalScore());
|
||||
|
||||
// 设置项目名称
|
||||
if (athlete.getProjectId() != null) {
|
||||
MartialProject project = projectService.getById(athlete.getProjectId());
|
||||
if (project != null) {
|
||||
vo.setProjectName(project.getProjectName());
|
||||
}
|
||||
}
|
||||
|
||||
// 设置评分状态
|
||||
if (scores != null && !scores.isEmpty()) {
|
||||
vo.setScoredJudgeCount(scores.size());
|
||||
|
||||
// 查找当前裁判的评分
|
||||
MartialScore myScore = scores.stream()
|
||||
.filter(s -> s.getJudgeId().equals(currentJudgeId))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (myScore != null) {
|
||||
vo.setScored(true);
|
||||
vo.setMyScore(myScore.getScore());
|
||||
} else {
|
||||
vo.setScored(false);
|
||||
}
|
||||
} else {
|
||||
vo.setScored(false);
|
||||
vo.setScoredJudgeCount(0);
|
||||
}
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析项目JSON字符串
|
||||
*/
|
||||
private List<MiniLoginVO.ProjectInfo> parseProjects(String projectsJson) {
|
||||
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
||||
|
||||
if (Func.isEmpty(projectsJson)) {
|
||||
return projects;
|
||||
}
|
||||
|
||||
try {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
List<Long> projectIds = mapper.readValue(projectsJson, new TypeReference<List<Long>>() {});
|
||||
|
||||
if (Func.isNotEmpty(projectIds)) {
|
||||
List<MartialProject> projectList = projectService.listByIds(projectIds);
|
||||
projects = projectList.stream().map(project -> {
|
||||
MiniLoginVO.ProjectInfo info = new MiniLoginVO.ProjectInfo();
|
||||
info.setProjectId(project.getId());
|
||||
info.setProjectName(project.getProjectName());
|
||||
return info;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
String[] ids = projectsJson.split(",");
|
||||
List<Long> projectIds = new ArrayList<>();
|
||||
for (String id : ids) {
|
||||
projectIds.add(Long.parseLong(id.trim()));
|
||||
}
|
||||
|
||||
if (Func.isNotEmpty(projectIds)) {
|
||||
List<MartialProject> projectList = projectService.listByIds(projectIds);
|
||||
projects = projectList.stream().map(project -> {
|
||||
MiniLoginVO.ProjectInfo info = new MiniLoginVO.ProjectInfo();
|
||||
info.setProjectId(project.getId());
|
||||
info.setProjectName(project.getProjectName());
|
||||
return info;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
// 解析失败,返回空列表
|
||||
}
|
||||
}
|
||||
|
||||
return projects;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the dreamlu.net developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: Chill 庄骞 (smallchill@163.com)
|
||||
*/
|
||||
package org.springblade.modules.martial.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialCompetitionAttachment;
|
||||
|
||||
/**
|
||||
* 赛事附件 Mapper 接口
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public interface MartialCompetitionAttachmentMapper extends BaseMapper<MartialCompetitionAttachment> {
|
||||
|
||||
}
|
||||
@@ -17,11 +17,13 @@
|
||||
d.venue_name AS venueName,
|
||||
d.time_slot AS timeSlot,
|
||||
d.time_slot_index AS timeSlotIndex,
|
||||
d.schedule_date AS scheduleDate,
|
||||
p.id AS participantId,
|
||||
p.organization AS organization,
|
||||
p.check_in_status AS checkInStatus,
|
||||
p.schedule_status AS scheduleStatus,
|
||||
p.performance_order AS performanceOrder
|
||||
p.performance_order AS performanceOrder,
|
||||
p.player_name AS playerName
|
||||
FROM
|
||||
martial_schedule_group g
|
||||
LEFT JOIN
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
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.MtVenue;
|
||||
|
||||
/**
|
||||
* 场地 Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface MtVenueMapper extends BaseMapper<MtVenue> {
|
||||
}
|
||||
@@ -28,4 +28,7 @@ public class MiniScoreModifyDTO implements Serializable {
|
||||
|
||||
@Schema(description = "修改原因/备注")
|
||||
private String note;
|
||||
|
||||
@Schema(description = "场地ID")
|
||||
private Long venueId;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import java.util.List;
|
||||
/**
|
||||
* 小程序提交评分请求DTO
|
||||
*
|
||||
* 注意:所有ID字段使用String类型,避免JavaScript大数精度丢失问题
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Data
|
||||
@@ -19,29 +21,29 @@ public class MiniScoreSubmitDTO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "选手ID")
|
||||
private Long athleteId;
|
||||
private String athleteId;
|
||||
|
||||
@Schema(description = "评委ID")
|
||||
private Long judgeId;
|
||||
private String judgeId;
|
||||
|
||||
@Schema(description = "评分")
|
||||
private BigDecimal score;
|
||||
|
||||
@Schema(description = "扣分项ID列表")
|
||||
private List<Long> deductions;
|
||||
private List<String> deductions;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String note;
|
||||
|
||||
@Schema(description = "项目ID")
|
||||
private Long projectId;
|
||||
private String projectId;
|
||||
|
||||
@Schema(description = "赛事ID")
|
||||
private Long competitionId;
|
||||
private String competitionId;
|
||||
|
||||
@Schema(description = "场地ID")
|
||||
private Long venueId;
|
||||
private String venueId;
|
||||
|
||||
@Schema(description = "赛程ID")
|
||||
private Long scheduleId;
|
||||
private String scheduleId;
|
||||
}
|
||||
|
||||
@@ -40,4 +40,10 @@ public class ParticipantDTO implements Serializable {
|
||||
@Schema(description = "排序")
|
||||
private Integer sortOrder;
|
||||
|
||||
/**
|
||||
* 选手姓名
|
||||
*/
|
||||
@Schema(description = "选手姓名")
|
||||
private String playerName;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the dreamlu.net developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: Chill 庄骞 (smallchill@163.com)
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* 赛事附件实体类(通用)
|
||||
* 支持多种附件类型:赛事发布、赛事规程、活动日程、成绩、奖牌榜、图片直播
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("martial_competition_attachment")
|
||||
@Schema(description = "赛事附件")
|
||||
public class MartialCompetitionAttachment extends TenantEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 赛事ID
|
||||
*/
|
||||
@Schema(description = "赛事ID")
|
||||
private Long competitionId;
|
||||
|
||||
/**
|
||||
* 附件类型
|
||||
* info-赛事发布, rules-赛事规程, schedule-活动日程,
|
||||
* results-成绩, medals-奖牌榜, photos-图片直播
|
||||
*/
|
||||
@Schema(description = "附件类型:info-赛事发布, rules-赛事规程, schedule-活动日程, results-成绩, medals-奖牌榜, photos-图片直播")
|
||||
private String attachmentType;
|
||||
|
||||
/**
|
||||
* 文件名称
|
||||
*/
|
||||
@Schema(description = "文件名称")
|
||||
private String fileName;
|
||||
|
||||
/**
|
||||
* 文件URL
|
||||
*/
|
||||
@Schema(description = "文件URL")
|
||||
private String fileUrl;
|
||||
|
||||
/**
|
||||
* 文件大小(字节)
|
||||
*/
|
||||
@Schema(description = "文件大小(字节)")
|
||||
private Long fileSize;
|
||||
|
||||
/**
|
||||
* 文件类型(扩展名)
|
||||
*/
|
||||
@Schema(description = "文件类型(pdf/doc/docx/xls/xlsx/jpg/png等)")
|
||||
private String fileType;
|
||||
|
||||
/**
|
||||
* 排序序号
|
||||
*/
|
||||
@Schema(description = "排序序号")
|
||||
private Integer orderNum;
|
||||
|
||||
/**
|
||||
* 状态(1-启用 0-禁用)
|
||||
*/
|
||||
@Schema(description = "状态(1-启用 0-禁用)")
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* 场地信息实体类(mt_venue 表)
|
||||
*/
|
||||
@Data
|
||||
@TableName("mt_venue")
|
||||
@Schema(description = "场地信息")
|
||||
public class MtVenue implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "赛事ID")
|
||||
private Long competitionId;
|
||||
|
||||
@Schema(description = "场地名称")
|
||||
private String venueName;
|
||||
|
||||
@Schema(description = "场地编号")
|
||||
private Integer venueNo;
|
||||
|
||||
@Schema(description = "状态")
|
||||
private Integer status;
|
||||
|
||||
private LocalDateTime createTime;
|
||||
private LocalDateTime updateTime;
|
||||
private Integer isDeleted;
|
||||
private String tenantId;
|
||||
private Long createUser;
|
||||
private Long createDept;
|
||||
private Long updateUser;
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
package org.springblade.modules.martial.pojo.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -9,38 +13,72 @@ import java.math.BigDecimal;
|
||||
/**
|
||||
* 小程序选手列表VO
|
||||
*
|
||||
* 注意:Long类型的ID字段使用ToStringSerializer序列化为字符串,
|
||||
* 避免JavaScript大数精度丢失问题
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "小程序选手列表")
|
||||
@JsonInclude(JsonInclude.Include.ALWAYS)
|
||||
public class MiniAthleteListVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "选手ID")
|
||||
@JsonProperty("athleteId")
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long athleteId;
|
||||
|
||||
@Schema(description = "选手姓名")
|
||||
@JsonProperty("name")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "身份证号")
|
||||
@JsonProperty("idCard")
|
||||
private String idCard = "";
|
||||
|
||||
@Schema(description = "参赛编号")
|
||||
@JsonProperty("number")
|
||||
private String number;
|
||||
|
||||
@Schema(description = "队伍名称")
|
||||
@JsonProperty("team")
|
||||
private String team;
|
||||
|
||||
@Schema(description = "项目名称")
|
||||
@JsonProperty("projectName")
|
||||
private String projectName;
|
||||
|
||||
@Schema(description = "出场顺序")
|
||||
@JsonProperty("orderNum")
|
||||
private Integer orderNum;
|
||||
|
||||
@Schema(description = "总分(裁判长可见)")
|
||||
@Schema(description = "是否已评分(当前裁判)")
|
||||
@JsonProperty("scored")
|
||||
private Boolean scored = false;
|
||||
|
||||
@Schema(description = "我的评分(当前裁判的评分)")
|
||||
@JsonProperty("myScore")
|
||||
private BigDecimal myScore;
|
||||
|
||||
@Schema(description = "总分(只有所有裁判评分完成后才显示)")
|
||||
@JsonProperty("totalScore")
|
||||
private BigDecimal totalScore;
|
||||
|
||||
@Schema(description = "已评分裁判数量(裁判长可见)")
|
||||
@Schema(description = "已评分裁判数量")
|
||||
@JsonProperty("scoredJudgeCount")
|
||||
private Integer scoredJudgeCount;
|
||||
|
||||
@Schema(description = "应评分裁判总数")
|
||||
@JsonProperty("requiredJudgeCount")
|
||||
private Integer requiredJudgeCount;
|
||||
|
||||
@Schema(description = "评分是否完成(所有裁判都已评分)")
|
||||
@JsonProperty("scoringComplete")
|
||||
private Boolean scoringComplete = false;
|
||||
|
||||
@Schema(description = "比赛状态(0-待出场,1-进行中,2-已完成)")
|
||||
@JsonProperty("competitionStatus")
|
||||
private Integer competitionStatus;
|
||||
}
|
||||
|
||||
@@ -29,10 +29,16 @@ public class ScheduleGroupDetailVO implements Serializable {
|
||||
private String timeSlot;
|
||||
private Integer timeSlotIndex; // 时间段索引(0=第1天上午,1=第1天下午,2=第2天上午,...)
|
||||
|
||||
// === 比赛日期 ===
|
||||
private String scheduleDate;
|
||||
|
||||
// === 参赛者信息 ===
|
||||
private Long participantId;
|
||||
private String organization;
|
||||
private String checkInStatus;
|
||||
private String scheduleStatus;
|
||||
private Integer performanceOrder;
|
||||
|
||||
// === 选手姓名 ===
|
||||
private String playerName;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.springblade.modules.martial.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialCompetitionAttachment;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 赛事附件 服务类
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public interface IMartialCompetitionAttachmentService extends IService<MartialCompetitionAttachment> {
|
||||
|
||||
/**
|
||||
* 根据赛事ID和附件类型获取附件列表
|
||||
*
|
||||
* @param competitionId 赛事ID
|
||||
* @param attachmentType 附件类型
|
||||
* @return 附件列表
|
||||
*/
|
||||
List<MartialCompetitionAttachment> getByCompetitionIdAndType(Long competitionId, String attachmentType);
|
||||
|
||||
/**
|
||||
* 根据赛事ID获取所有附件
|
||||
*
|
||||
* @param competitionId 赛事ID
|
||||
* @return 附件列表
|
||||
*/
|
||||
List<MartialCompetitionAttachment> getByCompetitionId(Long competitionId);
|
||||
|
||||
/**
|
||||
* 删除赛事的指定类型附件
|
||||
*
|
||||
* @param competitionId 赛事ID
|
||||
* @param attachmentType 附件类型
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean removeByCompetitionIdAndType(Long competitionId, String attachmentType);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.springblade.modules.martial.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.springblade.modules.martial.pojo.entity.MtVenue;
|
||||
|
||||
/**
|
||||
* 场地 Service 接口
|
||||
*/
|
||||
public interface IMtVenueService extends IService<MtVenue> {
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.springblade.modules.martial.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springblade.modules.martial.mapper.MartialCompetitionAttachmentMapper;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialCompetitionAttachment;
|
||||
import org.springblade.modules.martial.service.IMartialCompetitionAttachmentService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 赛事附件 服务实现类
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Service
|
||||
public class MartialCompetitionAttachmentServiceImpl extends ServiceImpl<MartialCompetitionAttachmentMapper, MartialCompetitionAttachment> implements IMartialCompetitionAttachmentService {
|
||||
|
||||
@Override
|
||||
public List<MartialCompetitionAttachment> getByCompetitionIdAndType(Long competitionId, String attachmentType) {
|
||||
LambdaQueryWrapper<MartialCompetitionAttachment> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(MartialCompetitionAttachment::getCompetitionId, competitionId)
|
||||
.eq(MartialCompetitionAttachment::getAttachmentType, attachmentType)
|
||||
.eq(MartialCompetitionAttachment::getStatus, 1)
|
||||
.orderByAsc(MartialCompetitionAttachment::getOrderNum);
|
||||
return this.list(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MartialCompetitionAttachment> getByCompetitionId(Long competitionId) {
|
||||
LambdaQueryWrapper<MartialCompetitionAttachment> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(MartialCompetitionAttachment::getCompetitionId, competitionId)
|
||||
.eq(MartialCompetitionAttachment::getStatus, 1)
|
||||
.orderByAsc(MartialCompetitionAttachment::getAttachmentType)
|
||||
.orderByAsc(MartialCompetitionAttachment::getOrderNum);
|
||||
return this.list(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeByCompetitionIdAndType(Long competitionId, String attachmentType) {
|
||||
LambdaQueryWrapper<MartialCompetitionAttachment> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(MartialCompetitionAttachment::getCompetitionId, competitionId)
|
||||
.eq(MartialCompetitionAttachment::getAttachmentType, attachmentType);
|
||||
return this.remove(wrapper);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -744,7 +744,7 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang
|
||||
participant.setScheduleDetailId(detailId);
|
||||
participant.setScheduleGroupId(groupId);
|
||||
participant.setParticipantId(athlete.getId());
|
||||
participant.setOrganization(athlete.getOrganization());
|
||||
participant.setOrganization(athlete.getTeamName() != null && !athlete.getTeamName().isEmpty() ? athlete.getTeamName() : athlete.getOrganization());
|
||||
participant.setPlayerName(athlete.getPlayerName());
|
||||
participant.setProjectName(groupData.getGroupName());
|
||||
participant.setCategory(athlete.getCategory());
|
||||
|
||||
@@ -60,77 +60,69 @@ public class MartialScheduleServiceImpl extends ServiceImpl<MartialScheduleMappe
|
||||
*/
|
||||
@Override
|
||||
public List<ScheduleExportExcel> exportSchedule(Long competitionId) {
|
||||
// 1. 查询该赛事的所有赛程
|
||||
List<MartialSchedule> schedules = this.list(
|
||||
new QueryWrapper<MartialSchedule>()
|
||||
.eq("competition_id", competitionId)
|
||||
.eq("is_deleted", 0)
|
||||
.orderByAsc("schedule_date", "start_time")
|
||||
);
|
||||
|
||||
List<ScheduleExportExcel> exportList = new ArrayList<>();
|
||||
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
|
||||
// 2. 遍历每个赛程
|
||||
for (MartialSchedule schedule : schedules) {
|
||||
// 3. 获取该赛程的所有运动员
|
||||
List<MartialScheduleAthlete> scheduleAthletes = scheduleAthleteService.list(
|
||||
new QueryWrapper<MartialScheduleAthlete>()
|
||||
.eq("schedule_id", schedule.getId())
|
||||
.eq("is_deleted", 0)
|
||||
.orderByAsc("order_num")
|
||||
);
|
||||
// 使用与 getScheduleResult 相同的数据源
|
||||
List<ScheduleGroupDetailVO> details = scheduleGroupMapper.selectScheduleGroupDetails(competitionId);
|
||||
|
||||
// 4. 获取项目和场地信息(一次查询,避免重复)
|
||||
MartialProject project = schedule.getProjectId() != null
|
||||
? projectService.getById(schedule.getProjectId())
|
||||
: null;
|
||||
MartialVenue venue = schedule.getVenueId() != null
|
||||
? venueService.getById(schedule.getVenueId())
|
||||
: null;
|
||||
if (details.isEmpty()) {
|
||||
return exportList;
|
||||
}
|
||||
|
||||
// 5. 如果没有运动员,创建一条基础记录
|
||||
if (scheduleAthletes.isEmpty()) {
|
||||
// 按分组ID分组,然后按 displayOrder 排序
|
||||
Map<Long, List<ScheduleGroupDetailVO>> groupMap = details.stream()
|
||||
.collect(Collectors.groupingBy(ScheduleGroupDetailVO::getGroupId));
|
||||
|
||||
// 获取分组的排序顺序
|
||||
List<Long> sortedGroupIds = details.stream()
|
||||
.collect(Collectors.toMap(
|
||||
ScheduleGroupDetailVO::getGroupId,
|
||||
d -> d.getDisplayOrder() != null ? d.getDisplayOrder() : 999,
|
||||
(a, b) -> a
|
||||
))
|
||||
.entrySet().stream()
|
||||
.sorted(Map.Entry.comparingByValue())
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 按排序顺序遍历分组
|
||||
for (Long groupId : sortedGroupIds) {
|
||||
List<ScheduleGroupDetailVO> groupDetails = groupMap.get(groupId);
|
||||
if (groupDetails == null || groupDetails.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ScheduleGroupDetailVO firstDetail = groupDetails.get(0);
|
||||
|
||||
// 过滤出有参赛者的记录,并按出场顺序排序
|
||||
List<ScheduleGroupDetailVO> participants = groupDetails.stream()
|
||||
.filter(d -> d.getParticipantId() != null)
|
||||
.sorted(Comparator.comparing(d -> d.getPerformanceOrder() != null ? d.getPerformanceOrder() : 999))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (participants.isEmpty()) {
|
||||
// 没有参赛者,创建一条基础记录
|
||||
ScheduleExportExcel excel = new ScheduleExportExcel();
|
||||
excel.setScheduleDate(schedule.getScheduleDate() != null
|
||||
? schedule.getScheduleDate().format(dateFormatter)
|
||||
: "");
|
||||
excel.setTimeSlot(schedule.getTimeSlot());
|
||||
excel.setVenueName(venue != null ? venue.getVenueName() : "");
|
||||
excel.setProjectName(project != null ? project.getProjectName() : "");
|
||||
excel.setCategory(schedule.getGroupTitle());
|
||||
excel.setStatus(schedule.getIsConfirmed() != null && schedule.getIsConfirmed() == 1
|
||||
? "已确认" : "未确认");
|
||||
excel.setScheduleDate(firstDetail.getScheduleDate() != null ? firstDetail.getScheduleDate() : "");
|
||||
excel.setTimeSlot(firstDetail.getTimeSlot());
|
||||
excel.setVenueName(firstDetail.getVenueName());
|
||||
excel.setProjectName(firstDetail.getGroupName());
|
||||
excel.setCategory(firstDetail.getCategory());
|
||||
excel.setStatus("completed".equals(firstDetail.getScheduleStatus()) ? "已完成" : "草稿");
|
||||
exportList.add(excel);
|
||||
} else {
|
||||
// 6. 为每个运动员创建导出记录
|
||||
for (MartialScheduleAthlete scheduleAthlete : scheduleAthletes) {
|
||||
MartialAthlete athlete = athleteService.getById(scheduleAthlete.getAthleteId());
|
||||
if (athlete == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 为每个参赛者创建导出记录
|
||||
for (ScheduleGroupDetailVO detail : participants) {
|
||||
ScheduleExportExcel excel = new ScheduleExportExcel();
|
||||
excel.setScheduleDate(schedule.getScheduleDate() != null
|
||||
? schedule.getScheduleDate().format(dateFormatter)
|
||||
: "");
|
||||
excel.setTimeSlot(schedule.getTimeSlot());
|
||||
excel.setVenueName(venue != null ? venue.getVenueName() : "");
|
||||
excel.setProjectName(project != null ? project.getProjectName() : "");
|
||||
excel.setCategory(schedule.getGroupTitle());
|
||||
excel.setAthleteName(athlete.getPlayerName());
|
||||
excel.setTeamName(athlete.getTeamName());
|
||||
excel.setSortOrder(scheduleAthlete.getOrderNum());
|
||||
|
||||
// 状态转换
|
||||
if (scheduleAthlete.getIsCompleted() != null && scheduleAthlete.getIsCompleted() == 1) {
|
||||
excel.setStatus("已完赛");
|
||||
} else if (schedule.getIsConfirmed() != null && schedule.getIsConfirmed() == 1) {
|
||||
excel.setStatus("已确认");
|
||||
} else {
|
||||
excel.setStatus("待确认");
|
||||
}
|
||||
|
||||
excel.setScheduleDate(detail.getScheduleDate() != null ? detail.getScheduleDate() : "");
|
||||
excel.setTimeSlot(detail.getTimeSlot());
|
||||
excel.setVenueName(detail.getVenueName());
|
||||
excel.setProjectName(detail.getGroupName());
|
||||
excel.setCategory(detail.getCategory());
|
||||
excel.setAthleteName(detail.getPlayerName());
|
||||
excel.setTeamName(detail.getOrganization());
|
||||
excel.setSortOrder(detail.getPerformanceOrder());
|
||||
excel.setStatus(detail.getCheckInStatus() != null ? detail.getCheckInStatus() : "未签到");
|
||||
exportList.add(excel);
|
||||
}
|
||||
}
|
||||
@@ -224,6 +216,7 @@ public class MartialScheduleServiceImpl extends ServiceImpl<MartialScheduleMappe
|
||||
dto.setSchoolUnit(d.getOrganization());
|
||||
dto.setStatus(d.getCheckInStatus() != null ? d.getCheckInStatus() : "未签到");
|
||||
dto.setSortOrder(d.getPerformanceOrder());
|
||||
dto.setPlayerName(d.getPlayerName());
|
||||
return dto;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
@@ -285,7 +285,13 @@ public class MartialScoreServiceImpl extends ServiceImpl<MartialScoreMapper, Mar
|
||||
// 注意:这里假设修改记录存储在选手的 totalScore 和一个额外的字段中
|
||||
// 由于 MartialAthlete 实体没有 originalScore 字段,我们查找修改过的评分记录
|
||||
MartialScore modifiedScore = scores.stream()
|
||||
.filter(s -> s.getOriginalScore() != null && s.getModifyReason() != null)
|
||||
.filter(s -> s.getOriginalScore() != null)
|
||||
.sorted((a, b) -> {
|
||||
if (a.getModifyTime() == null && b.getModifyTime() == null) return 0;
|
||||
if (a.getModifyTime() == null) return 1;
|
||||
if (b.getModifyTime() == null) return -1;
|
||||
return b.getModifyTime().compareTo(a.getModifyTime());
|
||||
})
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
@@ -326,42 +332,77 @@ public class MartialScoreServiceImpl extends ServiceImpl<MartialScoreMapper, Mar
|
||||
throw new ServiceException("选手不存在");
|
||||
}
|
||||
|
||||
// 2. 验证分数范围
|
||||
// 2. 验证分数基本范围
|
||||
if (!validateScore(dto.getModifiedScore())) {
|
||||
throw new ServiceException("修改后的分数必须在5.000-10.000之间");
|
||||
}
|
||||
|
||||
// 3. 保存原始总分(如果是第一次修改)
|
||||
BigDecimal originalTotalScore = athlete.getTotalScore();
|
||||
// 3. 查找是否已存在裁判长的修改记录
|
||||
LambdaQueryWrapper<MartialScore> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(MartialScore::getAthleteId, dto.getAthleteId())
|
||||
.like(MartialScore::getJudgeName, "裁判长修改");
|
||||
MartialScore existingRecord = this.getOne(queryWrapper);
|
||||
|
||||
// 4. 更新选手总分
|
||||
// 4. 确定原始计算总分(用于范围验证)
|
||||
// 如果已有修改记录,使用记录中保存的原始分数;否则使用当前总分
|
||||
BigDecimal originalCalculatedScore;
|
||||
if (existingRecord != null && existingRecord.getOriginalScore() != null) {
|
||||
// 已有修改记录,使用记录中的原始计算总分
|
||||
originalCalculatedScore = existingRecord.getOriginalScore();
|
||||
} else {
|
||||
// 首次修改,当前总分就是原始计算总分
|
||||
originalCalculatedScore = athlete.getTotalScore();
|
||||
}
|
||||
|
||||
// 5. 验证修改范围(基于原始计算总分±0.050)
|
||||
if (originalCalculatedScore != null) {
|
||||
BigDecimal minAllowed = originalCalculatedScore.subtract(new BigDecimal("0.050"));
|
||||
BigDecimal maxAllowed = originalCalculatedScore.add(new BigDecimal("0.050"));
|
||||
if (dto.getModifiedScore().compareTo(minAllowed) < 0 || dto.getModifiedScore().compareTo(maxAllowed) > 0) {
|
||||
throw new ServiceException("修改分数只能在原始计算总分(" + originalCalculatedScore + ")±0.050范围内");
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 更新选手总分
|
||||
athlete.setTotalScore(dto.getModifiedScore());
|
||||
boolean athleteUpdated = athleteService.updateById(athlete);
|
||||
|
||||
// 5. 记录修改日志(可以新增一条特殊的评分记录,或更新现有记录)
|
||||
// 这里选择创建一条新的修改记录
|
||||
MartialScore modificationRecord = new MartialScore();
|
||||
modificationRecord.setCompetitionId(athlete.getCompetitionId());
|
||||
modificationRecord.setAthleteId(athlete.getId());
|
||||
modificationRecord.setProjectId(athlete.getProjectId());
|
||||
modificationRecord.setJudgeId(dto.getModifierId());
|
||||
modificationRecord.setScore(dto.getModifiedScore());
|
||||
modificationRecord.setOriginalScore(originalTotalScore);
|
||||
modificationRecord.setModifyReason(dto.getNote());
|
||||
modificationRecord.setModifyTime(LocalDateTime.now());
|
||||
modificationRecord.setScoreTime(LocalDateTime.now());
|
||||
boolean recordSaved;
|
||||
if (existingRecord != null) {
|
||||
// 7a. 更新现有的裁判长修改记录(保持原始计算总分不变)
|
||||
existingRecord.setScore(dto.getModifiedScore());
|
||||
// originalScore 保持不变,始终是系统计算的原始总分
|
||||
existingRecord.setModifyReason(dto.getNote());
|
||||
existingRecord.setModifyTime(LocalDateTime.now());
|
||||
// 直接使用 baseMapper.updateById 绕过 Service 层的状态检查,裁判长可以无限次修改
|
||||
recordSaved = this.baseMapper.updateById(existingRecord) > 0;
|
||||
log.info("裁判长更新评分记录 - 选手ID:{}, 姓名:{}, 原始计算总分:{}, 修改后总分:{}, 修改原因:{}",
|
||||
athlete.getId(), athlete.getPlayerName(), originalCalculatedScore, dto.getModifiedScore(), dto.getNote());
|
||||
} else {
|
||||
// 7b. 创建新的裁判长修改记录
|
||||
MartialScore modificationRecord = new MartialScore();
|
||||
modificationRecord.setCompetitionId(athlete.getCompetitionId());
|
||||
modificationRecord.setAthleteId(athlete.getId());
|
||||
modificationRecord.setProjectId(athlete.getProjectId());
|
||||
modificationRecord.setVenueId(dto.getVenueId()); // 设置场地ID
|
||||
modificationRecord.setJudgeId(dto.getModifierId());
|
||||
modificationRecord.setScore(dto.getModifiedScore());
|
||||
modificationRecord.setOriginalScore(originalCalculatedScore); // 保存原始计算总分
|
||||
modificationRecord.setModifyReason(dto.getNote());
|
||||
modificationRecord.setModifyTime(LocalDateTime.now());
|
||||
modificationRecord.setScoreTime(LocalDateTime.now());
|
||||
|
||||
// 查询修改者信息
|
||||
MartialJudge modifier = judgeService.getById(dto.getModifierId());
|
||||
if (modifier != null) {
|
||||
modificationRecord.setJudgeName(modifier.getName() + "(裁判长修改)");
|
||||
// 查询修改者信息
|
||||
MartialJudge modifier = judgeService.getById(dto.getModifierId());
|
||||
if (modifier != null) {
|
||||
modificationRecord.setJudgeName(modifier.getName() + "(裁判长修改)");
|
||||
}
|
||||
|
||||
recordSaved = this.save(modificationRecord);
|
||||
log.info("裁判长新增评分记录 - 选手ID:{}, 姓名:{}, 原始计算总分:{}, 修改后总分:{}, 修改原因:{}",
|
||||
athlete.getId(), athlete.getPlayerName(), originalCalculatedScore, dto.getModifiedScore(), dto.getNote());
|
||||
}
|
||||
|
||||
boolean recordSaved = this.save(modificationRecord);
|
||||
|
||||
log.info("裁判长修改评分 - 选手ID:{}, 姓名:{}, 原始总分:{}, 修改后总分:{}, 修改原因:{}",
|
||||
athlete.getId(), athlete.getPlayerName(), originalTotalScore, dto.getModifiedScore(), dto.getNote());
|
||||
|
||||
return athleteUpdated && recordSaved;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,374 @@
|
||||
package org.springblade.modules.martial.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.modules.martial.pojo.dto.MiniScoreModifyDTO;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialAthlete;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialJudge;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialScore;
|
||||
import org.springblade.modules.martial.mapper.MartialScoreMapper;
|
||||
import org.springblade.modules.martial.pojo.vo.MiniScoreDetailVO;
|
||||
import org.springblade.modules.martial.service.IMartialAthleteService;
|
||||
import org.springblade.modules.martial.service.IMartialJudgeProjectService;
|
||||
import org.springblade.modules.martial.service.IMartialJudgeService;
|
||||
import org.springblade.modules.martial.service.IMartialScoreService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Score 服务实现类
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class MartialScoreServiceImpl extends ServiceImpl<MartialScoreMapper, MartialScore> implements IMartialScoreService {
|
||||
|
||||
@Autowired
|
||||
private IMartialJudgeProjectService judgeProjectService;
|
||||
|
||||
@Autowired
|
||||
private IMartialAthleteService athleteService;
|
||||
|
||||
@Autowired
|
||||
private IMartialJudgeService judgeService;
|
||||
|
||||
/** 最低分 */
|
||||
private static final BigDecimal MIN_SCORE = new BigDecimal("5.000");
|
||||
/** 最高分 */
|
||||
private static final BigDecimal MAX_SCORE = new BigDecimal("10.000");
|
||||
/** 异常分数偏差阈值(偏离平均分超过此值报警) */
|
||||
private static final BigDecimal ANOMALY_THRESHOLD = new BigDecimal("1.000");
|
||||
|
||||
/**
|
||||
* Task 2.2: 验证分数范围
|
||||
*
|
||||
* @param score 分数
|
||||
* @return 是否有效
|
||||
*/
|
||||
public boolean validateScore(BigDecimal score) {
|
||||
if (score == null) {
|
||||
return false;
|
||||
}
|
||||
return score.compareTo(MIN_SCORE) >= 0 && score.compareTo(MAX_SCORE) <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Task 2.2 & 2.5: 保存评分(带验证和权限检查)
|
||||
*
|
||||
* @param score 评分记录
|
||||
* @return 是否成功
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean save(MartialScore score) {
|
||||
// Task 2.5: 权限验证 - 裁判只能给被分配的项目打分
|
||||
// 注意:如果 martial_judge_project 表中没有数据,可以临时注释掉权限验证
|
||||
boolean hasPermission = judgeProjectService.hasPermission(score.getJudgeId(), score.getProjectId());
|
||||
if (!hasPermission) {
|
||||
log.warn("⚠️ 权限验证失败 - 裁判ID:{}, 项目ID:{} (如果是测试环境,请在 martial_judge_project 表中添加关联记录)",
|
||||
score.getJudgeId(), score.getProjectId());
|
||||
// 临时允许通过,但记录警告日志
|
||||
// 生产环境请取消注释下面这行
|
||||
// throw new ServiceException("您没有权限给该项目打分");
|
||||
}
|
||||
|
||||
// Task 2.2: 验证分数范围
|
||||
if (!validateScore(score.getScore())) {
|
||||
throw new ServiceException("分数必须在5.000-10.000之间");
|
||||
}
|
||||
|
||||
// Task 2.3: 检查异常分数
|
||||
checkAnomalyScore(score);
|
||||
|
||||
return super.save(score);
|
||||
}
|
||||
|
||||
/**
|
||||
* Task 2.5: 更新评分(禁止修改已提交的成绩)
|
||||
*
|
||||
* @param score 评分记录
|
||||
* @return 是否成功
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean updateById(MartialScore score) {
|
||||
// 检查原记录状态
|
||||
MartialScore existing = this.getById(score.getId());
|
||||
if (existing == null) {
|
||||
throw new ServiceException("评分记录不存在");
|
||||
}
|
||||
|
||||
// Task 2.5: 已提交的成绩不能修改(status=1表示正常已提交)
|
||||
if (existing.getStatus() != null && existing.getStatus() == 1) {
|
||||
log.error("❌ 禁止修改 - 评分ID:{}, 裁判:{}, 状态:已提交",
|
||||
score.getId(), existing.getJudgeName());
|
||||
throw new ServiceException("已提交的评分不能修改");
|
||||
}
|
||||
|
||||
// Task 2.5: 权限验证
|
||||
if (!judgeProjectService.hasPermission(score.getJudgeId(), score.getProjectId())) {
|
||||
throw new ServiceException("您没有权限修改该项目的评分");
|
||||
}
|
||||
|
||||
// 验证分数范围
|
||||
if (!validateScore(score.getScore())) {
|
||||
throw new ServiceException("分数必须在5.000-10.000之间");
|
||||
}
|
||||
|
||||
// 标记为已修改
|
||||
score.setStatus(2);
|
||||
|
||||
return super.updateById(score);
|
||||
}
|
||||
|
||||
/**
|
||||
* Task 2.3: 检测异常分数
|
||||
*
|
||||
* @param newScore 新评分
|
||||
*/
|
||||
public void checkAnomalyScore(MartialScore newScore) {
|
||||
// 获取同一运动员的其他裁判评分
|
||||
List<MartialScore> scores = this.list(
|
||||
new QueryWrapper<MartialScore>()
|
||||
.eq("athlete_id", newScore.getAthleteId())
|
||||
.eq("project_id", newScore.getProjectId())
|
||||
.ne("judge_id", newScore.getJudgeId())
|
||||
.eq("is_deleted", 0)
|
||||
);
|
||||
|
||||
if (scores.size() < 2) {
|
||||
return; // 评分数量不足,无法判断
|
||||
}
|
||||
|
||||
// 计算其他裁判的平均分
|
||||
BigDecimal avgScore = scores.stream()
|
||||
.map(MartialScore::getScore)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add)
|
||||
.divide(new BigDecimal(scores.size()), 3, RoundingMode.HALF_UP);
|
||||
|
||||
// 判断偏差
|
||||
BigDecimal diff = newScore.getScore().subtract(avgScore).abs();
|
||||
if (diff.compareTo(ANOMALY_THRESHOLD) > 0) {
|
||||
// 偏差超过阈值,记录警告
|
||||
log.warn("⚠️ 异常评分检测 - 裁判:{}(ID:{}), 运动员ID:{}, 评分:{}, 其他裁判平均分:{}, 偏差:{}",
|
||||
newScore.getJudgeName(),
|
||||
newScore.getJudgeId(),
|
||||
newScore.getAthleteId(),
|
||||
newScore.getScore(),
|
||||
avgScore,
|
||||
diff);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Task 2.3: 获取异常评分列表
|
||||
*
|
||||
* @param athleteId 运动员ID
|
||||
* @param projectId 项目ID
|
||||
* @return 异常评分列表
|
||||
*/
|
||||
public List<MartialScore> getAnomalyScores(Long athleteId, Long projectId) {
|
||||
// 获取该运动员的所有评分
|
||||
List<MartialScore> scores = this.list(
|
||||
new QueryWrapper<MartialScore>()
|
||||
.eq("athlete_id", athleteId)
|
||||
.eq("project_id", projectId)
|
||||
.eq("is_deleted", 0)
|
||||
.orderByDesc("score")
|
||||
);
|
||||
|
||||
if (scores.size() < 3) {
|
||||
return List.of(); // 评分数量不足,无异常
|
||||
}
|
||||
|
||||
// 计算平均分
|
||||
BigDecimal avgScore = scores.stream()
|
||||
.map(MartialScore::getScore)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add)
|
||||
.divide(new BigDecimal(scores.size()), 3, RoundingMode.HALF_UP);
|
||||
|
||||
// 筛选偏差大于阈值的评分
|
||||
return scores.stream()
|
||||
.filter(score -> {
|
||||
BigDecimal diff = score.getScore().subtract(avgScore).abs();
|
||||
return diff.compareTo(ANOMALY_THRESHOLD) > 0;
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Task 2.2: 批量验证评分
|
||||
*
|
||||
* @param athleteId 运动员ID
|
||||
* @param projectId 项目ID
|
||||
* @return 验证结果
|
||||
*/
|
||||
public boolean validateScores(Long athleteId, Long projectId) {
|
||||
List<MartialScore> scores = this.list(
|
||||
new QueryWrapper<MartialScore>()
|
||||
.eq("athlete_id", athleteId)
|
||||
.eq("project_id", projectId)
|
||||
.eq("is_deleted", 0)
|
||||
);
|
||||
|
||||
if (scores.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (MartialScore score : scores) {
|
||||
if (!validateScore(score.getScore())) {
|
||||
log.error("分数验证失败 - 裁判:{}, 分数:{}", score.getJudgeName(), score.getScore());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 小程序接口:获取评分详情
|
||||
*
|
||||
* @param athleteId 选手ID
|
||||
* @return 评分详情(选手信息+所有评委评分+修改记录)
|
||||
*/
|
||||
@Override
|
||||
public MiniScoreDetailVO getScoreDetailForMini(Long athleteId) {
|
||||
MiniScoreDetailVO vo = new MiniScoreDetailVO();
|
||||
|
||||
// 1. 查询选手信息
|
||||
MartialAthlete athlete = athleteService.getById(athleteId);
|
||||
if (athlete == null) {
|
||||
throw new ServiceException("选手不存在");
|
||||
}
|
||||
|
||||
MiniScoreDetailVO.AthleteInfo athleteInfo = new MiniScoreDetailVO.AthleteInfo();
|
||||
athleteInfo.setAthleteId(athlete.getId());
|
||||
athleteInfo.setName(athlete.getPlayerName());
|
||||
athleteInfo.setIdCard(athlete.getIdCard());
|
||||
athleteInfo.setTeam(athlete.getTeamName());
|
||||
athleteInfo.setNumber(athlete.getPlayerNo());
|
||||
athleteInfo.setTotalScore(athlete.getTotalScore());
|
||||
vo.setAthleteInfo(athleteInfo);
|
||||
|
||||
// 2. 查询所有评委的评分
|
||||
LambdaQueryWrapper<MartialScore> scoreQuery = new LambdaQueryWrapper<>();
|
||||
scoreQuery.eq(MartialScore::getAthleteId, athleteId);
|
||||
scoreQuery.eq(MartialScore::getIsDeleted, 0);
|
||||
scoreQuery.orderByAsc(MartialScore::getScoreTime);
|
||||
|
||||
List<MartialScore> scores = this.list(scoreQuery);
|
||||
|
||||
List<MiniScoreDetailVO.JudgeScore> judgeScores = scores.stream().map(score -> {
|
||||
MiniScoreDetailVO.JudgeScore judgeScore = new MiniScoreDetailVO.JudgeScore();
|
||||
judgeScore.setJudgeId(score.getJudgeId());
|
||||
judgeScore.setJudgeName(score.getJudgeName());
|
||||
judgeScore.setScore(score.getScore());
|
||||
judgeScore.setScoreTime(score.getScoreTime());
|
||||
judgeScore.setNote(score.getNote());
|
||||
return judgeScore;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
vo.setJudgeScores(judgeScores);
|
||||
|
||||
// 3. 查询裁判长修改记录(检查选手的 originalScore 字段)
|
||||
// 注意:这里假设修改记录存储在选手的 totalScore 和一个额外的字段中
|
||||
// 由于 MartialAthlete 实体没有 originalScore 字段,我们查找修改过的评分记录
|
||||
MartialScore modifiedScore = scores.stream()
|
||||
.filter(s -> s.getOriginalScore() != null)
|
||||
.sorted((a, b) -> {
|
||||
if (a.getModifyTime() == null && b.getModifyTime() == null) return 0;
|
||||
if (a.getModifyTime() == null) return 1;
|
||||
if (b.getModifyTime() == null) return -1;
|
||||
return b.getModifyTime().compareTo(a.getModifyTime());
|
||||
})
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (modifiedScore != null) {
|
||||
MiniScoreDetailVO.Modification modification = new MiniScoreDetailVO.Modification();
|
||||
modification.setOriginalScore(modifiedScore.getOriginalScore());
|
||||
modification.setModifiedScore(modifiedScore.getScore());
|
||||
modification.setModifierId(modifiedScore.getUpdateUser());
|
||||
modification.setModifyReason(modifiedScore.getModifyReason());
|
||||
modification.setModifyTime(modifiedScore.getModifyTime());
|
||||
|
||||
// 查询修改者姓名
|
||||
if (modifiedScore.getUpdateUser() != null) {
|
||||
MartialJudge modifier = judgeService.getById(modifiedScore.getUpdateUser());
|
||||
if (modifier != null) {
|
||||
modification.setModifierName(modifier.getName());
|
||||
}
|
||||
}
|
||||
|
||||
vo.setModification(modification);
|
||||
}
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 小程序接口:修改评分(裁判长)
|
||||
*
|
||||
* @param dto 修改信息
|
||||
* @return 修改成功/失败
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean modifyScoreByAdmin(MiniScoreModifyDTO dto) {
|
||||
// 1. 查询选手信息
|
||||
MartialAthlete athlete = athleteService.getById(dto.getAthleteId());
|
||||
if (athlete == null) {
|
||||
throw new ServiceException("选手不存在");
|
||||
}
|
||||
|
||||
// 2. 验证分数范围
|
||||
if (!validateScore(dto.getModifiedScore())) {
|
||||
throw new ServiceException("修改后的分数必须在5.000-10.000之间");
|
||||
}
|
||||
|
||||
// 3. 保存原始总分(如果是第一次修改)
|
||||
BigDecimal originalTotalScore = athlete.getTotalScore();
|
||||
|
||||
// 4. 更新选手总分
|
||||
athlete.setTotalScore(dto.getModifiedScore());
|
||||
boolean athleteUpdated = athleteService.updateById(athlete);
|
||||
|
||||
// 5. 记录修改日志(可以新增一条特殊的评分记录,或更新现有记录)
|
||||
// 这里选择创建一条新的修改记录
|
||||
MartialScore modificationRecord = new MartialScore();
|
||||
modificationRecord.setCompetitionId(athlete.getCompetitionId());
|
||||
modificationRecord.setAthleteId(athlete.getId());
|
||||
modificationRecord.setProjectId(athlete.getProjectId());
|
||||
modificationRecord.setJudgeId(dto.getModifierId());
|
||||
modificationRecord.setScore(dto.getModifiedScore());
|
||||
modificationRecord.setOriginalScore(originalTotalScore);
|
||||
modificationRecord.setModifyReason(dto.getNote());
|
||||
modificationRecord.setModifyTime(LocalDateTime.now());
|
||||
modificationRecord.setScoreTime(LocalDateTime.now());
|
||||
|
||||
// 查询修改者信息
|
||||
MartialJudge modifier = judgeService.getById(dto.getModifierId());
|
||||
if (modifier != null) {
|
||||
modificationRecord.setJudgeName(modifier.getName() + "(裁判长修改)");
|
||||
}
|
||||
|
||||
boolean recordSaved = this.save(modificationRecord);
|
||||
|
||||
log.info("裁判长修改评分 - 选手ID:{}, 姓名:{}, 原始总分:{}, 修改后总分:{}, 修改原因:{}",
|
||||
athlete.getId(), athlete.getPlayerName(), originalTotalScore, dto.getModifiedScore(), dto.getNote());
|
||||
|
||||
return athleteUpdated && recordSaved;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.springblade.modules.martial.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springblade.modules.martial.mapper.MtVenueMapper;
|
||||
import org.springblade.modules.martial.pojo.entity.MtVenue;
|
||||
import org.springblade.modules.martial.service.IMtVenueService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 场地 Service 实现类
|
||||
*/
|
||||
@Service
|
||||
public class MtVenueServiceImpl extends ServiceImpl<MtVenueMapper, MtVenue> implements IMtVenueService {
|
||||
}
|
||||
Reference in New Issue
Block a user