Ver Fonte

feat(game): 新增客户端赛事项目保存功能

- 添加 ClientProjectSaveBo 业务对象用于客户端数据传输
- 修改 GameEventProject 实体类将 groups 字段改为 rgName 并新增 rgId 字段
- 更新 GameEventProjectBo 和 GameEventProjectVo 对应字段变更
- 优化 GameEventProjectMapper.xml 中的查询逻辑和条件筛选
- 新增 IToClientService 接口和服务实现类
- 添加 ToClientController 控制器提供赛事数据同步接口
- 实现客户端同步赛事及项目信息的完整业务逻辑
- 支持赛事、项目、组别、配置信息的批量维护和关联处理
zhou há 2 semanas atrás
pai
commit
ea4ed2f0b9

+ 38 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/controller/app_client/ToClientController.java

@@ -0,0 +1,38 @@
+package org.dromara.system.controller.app_client;
+
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.system.domain.bo.ClientProjectSaveBo;
+import org.dromara.system.service.app.IToClientService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 面向app客户端
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/app/toClient")
+public class ToClientController {
+
+    private final IToClientService toClientService;
+
+    /**
+     * 赛事列表项添加/修改接口 (接口4)
+     */
+    @Log(title = "app客户端添加/修改赛事项", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PostMapping("/eventSaveOrUpdate")
+    public R<Void> eventSaveOrUpdate(@Validated @RequestBody ClientProjectSaveBo bo) {
+        toClientService.saveOrUpdateEventFromClient(bo);
+        return R.ok();
+    }
+
+}

+ 6 - 1
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/GameEventProject.java

@@ -162,7 +162,12 @@ public class GameEventProject extends TenantEntity {
     /**
      * 参赛组别
      */
-    private String groups;
+    private String rgName;
+
+    /**
+     * 排名分组ID
+     */
+    private Long rgId;
 
     /**
      * 限报男生人数 (个人项目为总人数 / 团体项目为每队人数)

+ 111 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/bo/ClientProjectSaveBo.java

@@ -0,0 +1,111 @@
+package org.dromara.system.domain.bo;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 客户端赛事项目保存业务对象
+ *
+ * @author zlt
+ * @date 2025-08-18
+ */
+@Data
+public class ClientProjectSaveBo {
+
+    /**
+     * 赛事名称
+     */
+    @NotBlank(message = "赛事名称不能为空")
+    private String eventName;
+
+    /**
+     * 赛事编号
+     */
+    @NotBlank(message = "赛事编号不能为空")
+    private String eventCode;
+
+    /**
+     * 机器编号
+     */
+    private String machineCode;
+
+    /**
+     * 项目类型--参考字典game_project_type,一般不修改
+     * (如1-径赛,2-田赛,3-趣味个人,4-趣味集体,5-体侧。6;领导男子,7-领导女子,8-赛前拔河,9-径赛集体)
+     * 如新增的项目类型,值顺延,如10-xxx1,11-xxx2,...
+     */
+    private String projectType;
+
+    /**
+     * 归类(0个人项目/1团体项目)
+     */
+    @NotBlank(message = "归类不能为空")
+    private String classification;
+
+    /**
+     * 项目内容(项目名称列表)
+     */
+    @NotEmpty(message = "项目内容不能为空")
+    private List<String> projectNames;
+
+    /**
+     * 成绩类型--字典game_score_type
+     * 1-计时类;2-距离类;3-单次计数类;4-多次计数类;5-排名类;6-远度距离类;7-高度距离类
+     */
+    private String scoreRule;
+
+    /**
+     * 性别---字典sys_group_sex
+     * 1-男;2-女;3-混合;4-其他
+     */
+    private String gender;
+
+    /**
+     * 参赛组别(队伍排名分组)
+     * 没有就新增
+     */
+    private String rgName;
+
+    /**
+     * 组别ID
+     */
+    private Long rgId;
+
+    /**
+     * 比赛阶段--字典game_stage
+     * 1-预赛;2-决赛;3-复赛
+     */
+    private String gameStage;
+
+    /**
+     * 轮次--字典game_round
+     * 1-一轮;2-二轮
+     */
+    private String gameRound;
+
+    /**
+     * 排名方式---字典game_order_type
+     * 0-升序(1...9);1-降序(9...1);2-失误次数A(1...9);3-失误次数B(9...1);4-求和 ;5-最大值;6-最小值;7-平均值
+     * 多个用英文逗号分隔
+     */
+    private String orderType;
+
+    /**
+     * 录取名次(以英文逗号分隔)
+     */
+    private String roundType;
+
+    /**
+     * 赛事提示
+     */
+    private String eventTip;
+
+    /**
+     * 上传成绩路径
+     */
+    private String uploadPath;
+
+}

+ 6 - 1
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/bo/GameEventProjectBo.java

@@ -179,7 +179,12 @@ public class GameEventProjectBo extends BaseEntity {
     /**
      * 参赛组别
      */
-    private String groups;
+    private String rgName;
+
+    /**
+     * 排名分组ID
+     */
+    private Long rgId;
 
     /**
      * 限报男生人数 (个人项目为总人数 / 团体项目为每队人数)

+ 23 - 17
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/GameEventProjectVo.java

@@ -15,7 +15,6 @@ import java.io.Serial;
 import java.io.Serializable;
 import java.util.List;
 
-
 /**
  * 赛事项目视图对象 game_event_project
  *
@@ -39,13 +38,13 @@ public class GameEventProjectVo implements Serializable {
     /**
      * 赛事ID
      */
-//    @ExcelProperty(value = "赛事ID")
+    // @ExcelProperty(value = "赛事ID")
     private Long eventId;
 
     /**
      * 赛事ID
      */
-//    @ExcelProperty(value = "赛事名称")
+    // @ExcelProperty(value = "赛事名称")
     private String eventName;
 
     /**
@@ -55,7 +54,9 @@ public class GameEventProjectVo implements Serializable {
     private String projectName;
 
     /**
-     * 项目类型(个人/团体)
+     * 项目类型--参考字典game_project_type,一般不修改
+     * (如1-径赛,2-田赛,3-趣味个人,4-趣味集体,5-体侧。6;领导男子,7-领导女子,8-赛前拔河,9-径赛集体)
+     * 如新增的项目类型,值顺延,如10-xxx1,11-xxx2,...
      */
     @ExcelProperty(value = "项目类型", converter = ExcelDictConvert.class)
     @ExcelDictFormat(dictType = "game_project_type")
@@ -94,8 +95,13 @@ public class GameEventProjectVo implements Serializable {
     /**
      * 参赛组别
      */
-//    @ExcelProperty(value = "参赛组别")
-    private String groups;
+    @ExcelProperty(value = "参赛组别")
+    private String rgName;
+
+    /**
+     * 排名分组ID
+     */
+    private Long rgId;
 
     /**
      * 比赛阶段
@@ -172,69 +178,69 @@ public class GameEventProjectVo implements Serializable {
     /**
      * 更新时间
      */
-//    @ExcelProperty(value = "更新时间")
+    // @ExcelProperty(value = "更新时间")
     @DateTimeFormat("yyyy-MM-dd HH:mm:ss")
     private Date updateTime;
 
     /**
      * 项目限报人数
      */
-//    @ExcelProperty(value = "项目限报人数")
+    // @ExcelProperty(value = "项目限报人数")
     private Integer limitPerson;
 
     /**
      * 参赛组数
      */
-//    @ExcelProperty(value = "参赛组数")
+    // @ExcelProperty(value = "参赛组数")
     private Long groupNum;
 
     /**
      * 参赛人数
      */
-//    @ExcelProperty(value = "参赛人数")
+    // @ExcelProperty(value = "参赛人数")
     private Long participateNum;
 
     /**
      * 计时格式
      */
-//    @ExcelProperty(value = "计时格式")
+    // @ExcelProperty(value = "计时格式")
     private String timingFormat;
 
     /**
      * 距离模式
      */
-//    @ExcelProperty(value = "距离模式")
+    // @ExcelProperty(value = "距离模式")
     private String distanceMode;
 
     /**
      * 计数单位
      */
-//    @ExcelProperty(value = "计数单位")
+    // @ExcelProperty(value = "计数单位")
     private String countUnit;
 
     /**
      * 成绩数量
      */
-//    @ExcelProperty(value = "成绩数量")
+    // @ExcelProperty(value = "成绩数量")
     private Integer scoreCount;
 
     /**
      * 奖项
      */
-//    @ExcelProperty(value = "奖项")
+    // @ExcelProperty(value = "奖项")
     private String award;
 
     /**
      * 状态(0正常 1停用)
      */
-//    @ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
+    // @ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
     @ExcelDictFormat(dictType = "game_event_status")
     private String status;
 
     /**
      * 备注
      */
-//    @ExcelProperty(value = "备注")
+    // @ExcelProperty(value = "备注")
     private String remark;
 
     /**

+ 13 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/app/IToClientService.java

@@ -0,0 +1,13 @@
+package org.dromara.system.service.app;
+
+import org.dromara.system.domain.bo.ClientProjectSaveBo;
+
+public interface IToClientService {
+
+    /**
+     * 客户端同步赛事及项目信息 (接口4)
+     *
+     * @param bo 客户端保存对象
+     */
+    void saveOrUpdateEventFromClient(ClientProjectSaveBo bo);
+}

+ 169 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/app/ToClientServiceImpl.java

@@ -0,0 +1,169 @@
+package org.dromara.system.service.impl.app;
+
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.toolkit.Db;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.system.domain.GameEvent;
+import org.dromara.system.domain.GameEventConfig;
+import org.dromara.system.domain.GameEventProject;
+import org.dromara.system.domain.GameRankGroup;
+import org.dromara.system.domain.bo.ClientProjectSaveBo;
+import org.dromara.system.domain.bo.GameRankGroupBo;
+import org.dromara.system.mapper.GameEventMapper;
+import org.dromara.system.mapper.GameEventProjectMapper;
+import org.dromara.system.service.IGameRankGroupService;
+import org.dromara.system.service.app.IToClientService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class ToClientServiceImpl implements IToClientService {
+
+    private final GameEventProjectMapper projectMapper;
+    private final GameEventMapper gameEventMapper;
+    private final IGameRankGroupService gameRankGroupService;
+
+    /**
+     * 客户端同步赛事及项目信息 (接口4)
+     *
+     * @param bo 客户端保存对象
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void saveOrUpdateEventFromClient(ClientProjectSaveBo bo) {
+        // 1. 获取或创建赛事
+        GameEvent event = gameEventMapper.selectOne(Wrappers.lambdaQuery(GameEvent.class)
+                .eq(GameEvent::getEventCode, bo.getEventCode()));
+        if (event == null) {
+            event = new GameEvent();
+            event.setEventCode(bo.getEventCode());
+            event.setEventName(bo.getEventName());
+            event.setStatus("0");
+            gameEventMapper.insert(event);
+        } else if (!event.getEventName().equals(bo.getEventName())) {
+            throw new RuntimeException("赛事编号" + bo.getEventCode() + "已被赛事【" + event.getEventName() + "】占用");
+        }
+        Long eventId = event.getEventId();
+
+        // 2. 批量维护配置信息 (机器编号、赛事提示、上传成绩路径)
+        batchSaveConfigs(eventId, bo);
+
+        // 3. 维护组别信息 (参赛组别)
+        Long rgId = bo.getRgId();
+        String rgName = bo.getRgName();
+        if (rgId == null && StringUtils.isNotBlank(rgName)) {
+            GameRankGroup group = Db.getOne(Wrappers.lambdaQuery(GameRankGroup.class)
+                    .select(GameRankGroup::getRgId)
+                    .eq(GameRankGroup::getEventId, eventId)
+                    .eq(GameRankGroup::getRgName, rgName)
+                    .last("LIMIT 1"));
+
+            if (group == null) {
+                GameRankGroupBo addGroup = new GameRankGroupBo();
+                addGroup.setEventId(eventId);
+                addGroup.setRgName(rgName);
+                addGroup.setParentId(0L);
+                addGroup.setStatus("0");
+                gameRankGroupService.insertByBo(addGroup);
+                rgId = addGroup.getRgId();
+            } else {
+                rgId = group.getRgId();
+            }
+        }
+
+        // 4. 维护项目信息 (多填项目内容) - 使用批量操作优化性能
+        List<String> projectNames = bo.getProjectNames();
+        if (CollectionUtils.isNotEmpty(projectNames)) {
+            // 批量查询现有项目
+            List<GameEventProject> existingProjects = projectMapper
+                    .selectList(Wrappers.lambdaQuery(GameEventProject.class)
+                            .eq(GameEventProject::getEventId, eventId)
+                            .in(GameEventProject::getProjectName, projectNames)
+                            .eq(StringUtils.isNotBlank(rgName), GameEventProject::getRgName, rgName)
+                            .eq(rgId != null, GameEventProject::getRgId, rgId));
+
+            // 转换为 Map 方便查找
+            Map<String, GameEventProject> projectMap = existingProjects.stream()
+                    .collect(Collectors.toMap(GameEventProject::getProjectName, p -> p, (p1, p2) -> p1));
+
+            List<GameEventProject> projectsToSave = new ArrayList<>();
+            for (String projectName : projectNames) {
+                GameEventProject project = projectMap.get(projectName);
+                if (project == null) {
+                    project = new GameEventProject();
+                    project.setEventId(eventId);
+                    project.setProjectName(projectName);
+                }
+                project.setProjectType(bo.getProjectType());
+                project.setClassification(bo.getClassification());
+                project.setScoreRule(bo.getScoreRule());
+                project.setGender(bo.getGender());
+                project.setRgName(rgName);
+                project.setRgId(rgId);
+                project.setGameStage(bo.getGameStage());
+                project.setGameRound(bo.getGameRound());
+                project.setOrderType(bo.getOrderType());
+                project.setRoundType(bo.getRoundType());
+                projectsToSave.add(project);
+            }
+            // 批量保存或更新
+            Db.saveOrUpdateBatch(projectsToSave);
+        }
+    }
+
+    /**
+     * 批量保存配置信息
+     */
+    private void batchSaveConfigs(Long eventId, ClientProjectSaveBo bo) {
+        Map<String, String> configValues = new HashMap<>();
+        if (StringUtils.isNotBlank(bo.getMachineCode())) configValues.put("machine_code", bo.getMachineCode());
+        if (StringUtils.isNotBlank(bo.getEventTip())) configValues.put("event_tip", bo.getEventTip());
+        if (StringUtils.isNotBlank(bo.getUploadPath())) configValues.put("upload_path", bo.getUploadPath());
+
+        if (configValues.isEmpty()) return;
+
+        Map<String, String> configDescs = Map.of(
+            "machine_code", "机器编号",
+            "event_tip", "赛事提示",
+            "upload_path", "上传成绩路径"
+        );
+
+        // 批量查询现有配置
+        List<GameEventConfig> existingConfigs = Db.list(Wrappers.lambdaQuery(GameEventConfig.class)
+            .eq(GameEventConfig::getEventId, eventId)
+            .in(GameEventConfig::getConfigKey, configValues.keySet()));
+
+        Map<String, GameEventConfig> configMap = existingConfigs.stream()
+            .collect(Collectors.toMap(GameEventConfig::getConfigKey, c -> c));
+
+        List<GameEventConfig> toSave = new ArrayList<>();
+        for (Map.Entry<String, String> entry : configValues.entrySet()) {
+            String key = entry.getKey();
+            String value = entry.getValue();
+            GameEventConfig config = configMap.get(key);
+            if (config == null) {
+                config = new GameEventConfig();
+                config.setEventId(eventId);
+                config.setConfigType("4"); // 系统配置
+                config.setConfigKey(key);
+                config.setConfigDesc(configDescs.get(key));
+                config.setIsEnabled("0");
+                config.setStatus("0");
+            }
+            config.setConfigValue(value);
+            toSave.add(config);
+        }
+        Db.saveOrUpdateBatch(toSave);
+    }
+}

+ 9 - 6
ruoyi-modules/ruoyi-game-event/src/main/resources/mapper/system/GameEventProjectMapper.xml

@@ -12,11 +12,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
     <select id="selectPageWithStats" resultMap="GameEventProjectVoWithStatsResult">
         SELECT p.*,
-            IFNULL((SELECT COUNT(*) FROM game_athlete a WHERE a.event_id = p.event_id AND a.del_flag = '0' 
-            AND (JSON_CONTAINS(a.project_value, JSON_ARRAY(p.project_id)) 
+            IFNULL((SELECT COUNT(*) FROM game_athlete a WHERE a.event_id = p.event_id AND a.del_flag = '0'
+            AND (JSON_CONTAINS(a.project_value, JSON_ARRAY(p.project_id))
             OR JSON_CONTAINS(a.project_value, JSON_ARRAY(CAST(p.project_id AS CHAR))))), 0) as athlete_count,
-            IFNULL((SELECT COUNT(DISTINCT team_id) FROM game_athlete a WHERE a.event_id = p.event_id AND a.del_flag = '0' 
-            AND (JSON_CONTAINS(a.project_value, JSON_ARRAY(p.project_id)) 
+            IFNULL((SELECT COUNT(DISTINCT team_id) FROM game_athlete a WHERE a.event_id = p.event_id AND a.del_flag = '0'
+            AND (JSON_CONTAINS(a.project_value, JSON_ARRAY(p.project_id))
             OR JSON_CONTAINS(a.project_value, JSON_ARRAY(CAST(p.project_id AS CHAR))))), 0) as team_count,
             IFNULL((SELECT COUNT(*) FROM game_event_group g WHERE g.project_id = p.project_id AND g.del_flag = '0'), 0) as group_count
         FROM game_event_project p
@@ -34,8 +34,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="query.gender != null and query.gender != ''">
                 AND p.gender = #{query.gender}
             </if>
-            <if test="query.groups != null and query.groups != ''">
-                AND FIND_IN_SET(#{query.groups}, p.groups)
+            <if test="query.rgName != null and query.rgName != ''">
+                AND p.rg_name like #{query.rgName}
+            </if>
+            <if test="query.rgId != null and query.rgId != ''">
+                AND p.rg_id = #{query.rgId}
             </if>
             <!-- 数据权限 -->
             ${query.params.dataScope}