# 实战业务培训 — 技术方案 > 依据:同目录 `实战业务培训功能需求.md`。培训主档 **CRUD** + **发布/下架** + **`training_status` 持久化** + **小程序报名/取消**(`biz_practical_training_member`);封面复用若依 `/common/upload`。 --- ## 1. 技术架构 | 项 | 说明 | | --- | --- | | **后端** | RuoYi **v3.9.2**(**springboot2** 分支):JDK 8、Spring Boot 2.x、Spring MVC、MyBatis、Druid | | **数据库** | MySQL **5.7.39**,InnoDB,`utf8mb4` | | **前端** | 若依 Vue2;时间 `yyyy-MM-dd HH:mm:ss`;封面上传后提交 URL、存储路径 | | **代码包(建议)** | `com.ruoyi.web.modules.techservice` | **分层**:`Controller` → `Service`(`PracticalTrainingValidation`、**报名状态计算**、发布状态机、替换封面删旧文件)→ `Mapper`/XML → `domain`。 **业务摘要** | 场景 | 行为 | | --- | --- | | 新增「仅保存」 | `publish_status=0`,`publish_time=NULL` | | 新增「保存并发布」 | Body `saveAndPublish=true` 或调发布接口;`publish_status=1`,`publish_time=now()` | | 修改 / 删除 / 发布 | **仅** `publish_status=0` 且 `del_flag=0` | | 下架 | **仅** `publish_status=1` 且 `now() < registration_start_time` → `publish_status=0`,写 `offline_time`;**保留** `publish_time` | | 报名状态 | 列表/详情由 `PracticalTrainingEnrollStatusResolver` 按 **§3.4** 计算,**不入库** | | 实际报名人数 | 字段 `actual_enrolled_count`,默认 `0`;后续报名模块 `UPDATE` 汇总 | | 列表排序 | `training_time DESC, id DESC` | | 列表筛选 | `trainingTopic` **精确**;`startDate`/`endDate` 对 `DATE(training_time)` 闭区间 | --- ## 2. 数据库设计 ### 2.1 表 `biz_practical_training`(实战业务培训) | 字段 | 类型 | 非空 | 默认值 | 说明 | | --- | --- | --- | --- | --- | | `id` | `bigint(20)` | Y | 自增 | 主键 | | `training_topic` | `varchar(64)` | Y | — | 培训主题(**1~20** 字) | | `training_time` | `datetime` | Y | — | 培训时间 | | `training_intro` | `varchar(500)` | Y | — | 培训简介 | | `planned_head_count` | `int(11)` | Y | — | 计划人数(正整数) | | `training_location` | `varchar(200)` | Y | — | 培训地点 | | `expected_outcome` | `text` | N | NULL | 预期成果 | | `registration_start_time` | `datetime` | Y | — | 报名开始时间 | | `registration_end_time` | `datetime` | Y | — | 报名截止时间 | | `cover_file_url` | `varchar(512)` | N | NULL | 封面 URL | | `cover_file_path` | `varchar(512)` | N | NULL | 封面存储路径 | | `actual_enrolled_count` | `int(11)` | Y | `0` | 实际报名人数(成员表汇总) | | `training_status` | `varchar(32)` | Y | `006001` | 培训状态字典 `006` | | `publish_status` | `tinyint(4)` | Y | `0` | `0` 未发布 `1` 已发布 | | `publish_time` | `datetime` | N | NULL | 发布时间(列表「发布日期」取日期部分) | | `offline_time` | `datetime` | N | NULL | 下架时间 | | `del_flag` | `char(1)` | Y | `'0'` | `0` 存在 `2` 删除 | | `create_by` / `create_time` / `update_by` / `update_time` | 若依惯例 | — | — | 审计 | | `remark` | `varchar(500)` | N | NULL | 备注 | **索引**:`PRIMARY KEY (id)`;`KEY idx_training_time (training_time)`;`KEY idx_training_topic (training_topic)`;`KEY idx_publish (publish_status)`;`KEY idx_reg_start (registration_start_time)`;`KEY idx_del_flag (del_flag)`。 ### 2.2 DDL(MySQL 5.7) ```sql CREATE TABLE `biz_practical_training` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `training_topic` varchar(64) NOT NULL COMMENT '培训主题', `training_time` datetime NOT NULL COMMENT '培训时间', `training_intro` varchar(500) NOT NULL COMMENT '培训简介', `planned_head_count` int(11) NOT NULL COMMENT '计划人数', `training_location` varchar(200) NOT NULL COMMENT '培训地点', `expected_outcome` text COMMENT '预期成果', `registration_start_time` datetime NOT NULL COMMENT '报名开始时间', `registration_end_time` datetime NOT NULL COMMENT '报名截止时间', `cover_file_url` varchar(512) DEFAULT NULL COMMENT '封面URL', `cover_file_path` varchar(512) DEFAULT NULL COMMENT '封面存储路径', `actual_enrolled_count` int(11) NOT NULL DEFAULT '0' COMMENT '实际报名人数', `publish_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0未发布 1已发布', `publish_time` datetime DEFAULT NULL COMMENT '发布时间', `offline_time` datetime DEFAULT NULL COMMENT '下架时间', `del_flag` char(1) NOT NULL DEFAULT '0' COMMENT '0存在 2删除', `create_by` varchar(64) DEFAULT '' COMMENT '创建者', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_by` varchar(64) DEFAULT '' COMMENT '更新者', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `remark` varchar(500) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`), KEY `idx_training_time` (`training_time`), KEY `idx_training_topic` (`training_topic`), KEY `idx_publish` (`publish_status`), KEY `idx_reg_start` (`registration_start_time`), KEY `idx_del_flag` (`del_flag`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='实战业务培训'; ``` ### 2.3 枚举 **发布状态 `publish_status`** | 值 | 标签 | | --- | --- | | 0 | 未发布 | | 1 | 已发布 | **培训状态 `training_status`** | 编码 | 标签 | | --- | --- | | `006001` | 未开始 | | `006002` | 报名中 | | `006003` | 已报满 | | `006004` | 已截止 | 管理端列表仍返回 `enrollStatus`(1–4)与 `enrollStatusName`,由 `training_status` 映射,兼容原前端。 ### 2.4 表 `biz_practical_training_member` | 字段 | 说明 | | --- | --- | | `training_id` | 关联主表 | | `member_name` | 报名人姓名 | | `sys_user_id` | 小程序用户 | | `enroll_status` | `0` 已报名 `1` 已取消 | | `enroll_time` / `cancel_time` | 报名/取消时间 | 唯一约束:`uk_training_user (training_id, sys_user_id)`。 ### 2.5 移动端接口 见 `doc/app/实战业务培训/实战业务培训接口说明.md`:`GET /myEnroll/list`、`GET /myEnroll/{id}`、`POST /{id}/enroll`、`POST /{id}/cancelEnroll`。 --- ## 3. 接口设计 **统一响应**:`AjaxResult` / `TableDataInfo`。 **权限标识**:`techService:practicalTraining:list|query|add|edit|remove|publish|offline` **Base Path**:`/techService/practicalTraining` | # | 说明 | Method | URI | 权限 | | --- | --- | --- | --- | --- | | 3.1 | 分页列表 | GET | `/techService/practicalTraining/list` | `list` | | 3.2 | 详情 | GET | `/techService/practicalTraining/{id}` | `query` | | 3.3 | 新增 | POST | `/techService/practicalTraining` | `add` | | 3.4 | 修改 | PUT | `/techService/practicalTraining` | `edit` | | 3.5 | 删除 | DELETE | `/techService/practicalTraining/{ids}` | `remove` | | 3.6 | 发布 | POST | `/techService/practicalTraining/publish/{id}` | `publish` | | 3.7 | 下架 | POST | `/techService/practicalTraining/offline/{id}` | `offline` | #### 3.1.1 列表 Query | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `pageNum` / `pageSize` | int | N | 默认 `1` / `20` | | `trainingTopic` | string | N | 培训主题**精确**匹配 | | `startDate` / `endDate` | string | N | `yyyy-MM-dd`;`DATE(training_time)` 闭区间;起 > 止抛业务异常 | **列表行扩展(驼峰)**:除表字段外含 `enrollStatus`、`enrollStatusName`(**§3.4**);`publishTime` 格式化为日期供「发布日期」列。 #### 3.2.1 详情 `data` 全业务字段 + `enrollStatus` / `enrollStatusName` + 审计字段;`canOffline`(boolean,是否允许下架,供前端控制按钮)。 #### 3.3.1 新增 / 修改 Body(驼峰) | 字段 | 新增 | 修改 | 说明 | | --- | --- | --- | --- | | `id` | — | Y | 主键 | | `trainingTopic` | Y | Y | 1~20 字 | | `trainingTime` | Y | Y | `yyyy-MM-dd HH:mm:ss` | | `trainingIntro` | Y | Y | trim 后非空;≤500 字 | | `plannedHeadCount` | Y | Y | 整数 ≥1 | | `trainingLocation` | Y | Y | 1~200 字 | | `expectedOutcome` | N | N | 选填 | | `registrationStartTime` | Y | Y | 须满足 **§3.5** 时间链 | | `registrationEndTime` | Y | Y | 同上 | | `coverFileUrl` / `coverFilePath` | N | N | 同时有或同时无 | | `saveAndPublish` | N | — | 仅新增;`true` 时保存后发布 | | `remark` | N | N | ≤500 字 | #### 3.4 培训状态计算(服务端) ``` IF publish_status = 0 → 006001 ELSE IF now >= registration_end_time → 006004 ELSE IF now < registration_start_time → 006001 ELSE IF actual_enrolled_count >= planned_head_count → 006003 ELSE IF registration_start <= now < registration_end → 006002 ``` 实现类:`PracticalTrainingEnrollStatusResolver.resolveTrainingStatus(row, now)`;持久化:`PracticalTrainingStatusSupport.refreshAndPersist`。 #### 3.5 服务端校验(`PracticalTrainingValidation`) | 项 | 规则 | | --- | --- | | 时间链 | `registrationStartTime < registrationEndTime < trainingTime` | | 计划人数 | `plannedHeadCount >= 1` | | 封面 | 可空;有则 `jpg,jpeg,png`,≤10MB(与上传接口一致) | | 发布/修改/删除 | 目标记录 `publish_status=0` 且 `del_flag=0` | | 下架 | `publish_status=1` 且 `now() < registration_start_time`;否则 `ServiceException`「报名已开始,不允许下架」 | | 删除 | 若 `actual_enrolled_count > 0` 宜禁止(可选,评审) | | 列表日期 | `startDate ≤ endDate`(均填时) | #### 3.6 发布 / 下架要点 | 操作 | 结果 | | --- | --- | | 发布 | `publish_status=1`,`publish_time=now()`;再次校验 **§3.5** | | 下架 | `publish_status=0`,`offline_time=now()`;**不删**封面;**不清** `publish_time` | #### 3.7 Mapper 约定 - CRUD 列表/详情带 `del_flag='0'`。 - 逻辑删除:`del_flag='2'`。 - 修改/发布/删除条件附加 `publish_status=0`(下架除外)。 --- ## 4. 菜单与权限(示例) | 类型 | 名称 | 权限标识 | | --- | --- | --- | | 菜单 | 实战业务培训 | `techService:practicalTraining:list` | | 按钮 | 查询 | `techService:practicalTraining:query` | | 按钮 | 新增 | `techService:practicalTraining:add` | | 按钮 | 修改 | `techService:practicalTraining:edit` | | 按钮 | 删除 | `techService:practicalTraining:remove` | | 按钮 | 发布 | `techService:practicalTraining:publish` | | 按钮 | 下架 | `techService:practicalTraining:offline` | 组件路径:`techService/practicalTraining/index`(「科技平台与服务」目录下)。 --- ## 5. 交付清单 - [ ] `sql/biz_practical_training.sql`、菜单权限 SQL - [ ] `BizPracticalTraining`、Mapper/XML、Service、Controller - [ ] `PracticalTrainingValidation`、`PracticalTrainingEnrollStatusResolver`、`PracticalTrainingRules` - [ ] 单元测试(时间链、报名状态四档、下架前置) - [ ] MockMvc(list 筛选、CRUD、发布/下架、已发布不可改) --- ## 6. 修订记录 | 版本 | 说明 | | --- | --- | | 1.0 | 初稿:`biz_practical_training`;CRUD+发布下架;报名状态计算;下架须早于报名开始;封面复用通用上传 |