# 项目与补贴 — 项目 — 技术方案 > 依据:同目录 `项目与补贴-项目功能需求.md`。台账 CRUD + **执行状态自动推算** + **发布/下架**。 --- ## 1. 技术架构 | 项 | 说明 | | --- | --- | | **后端** | RuoYi **v3.9.2**(**springboot2** 分支):JDK 8、Spring Boot 2.x、Spring MVC、MyBatis、Druid | | **数据库** | MySQL **5.7.39**,InnoDB,`utf8mb4` | | **前端** | 若依 Vue2;封面上传复用 `/common/upload` | **分层**:`Controller` → `Service`(校验、**状态推算**、删封面文件)→ `Mapper`/XML → `domain`。 **状态推算(Service 摘要)** | 场景 | 行为 | | --- | --- | | 新增/修改保存 | 校验通过后调用 `resolveExecStatus` 写库 | | 列表/详情查询 | 对 `manual_complete_flag=0` 的记录按当前日期**计算展示态**(推荐同步写回 `exec_status`,避免列表过期) | | `manual_complete_flag=1` | 不自动改 `exec_status`(已完成锁定,见 **§2.1**) | **推算规则(T=今天,S=开始日,E=预计完成日,优先级序)**: 1. `T < S` → `exec_status=1`(未开始) 2. `T = E` → `exec_status=3`(已完成);若 `actual_end_date` 为空则默认 `T` 3. `T < E` 且 `T ≥ S` → `exec_status=2`(进行中) 4. `T > E` → `exec_status=2`(进行中,超期不自动完成) 用户**手工**提交 `execStatus=3` 时须带 `actualEndDate`,并置 `manual_complete_flag=1`。 **发布状态(摘要)** | 操作 | 前置 `publish_status` | 结果 | | --- | --- | --- | | 新增/修改/删除/发布 | `0`(未发布) | 发布 → `1`,写 `publish_time` | | 下架 | `1`(已发布) | `0`,写 `offline_time`;**不删**封面 | | 修改/删除/发布 | `1` | **拒绝** | --- ## 2. 数据库设计 ### 2.1 表 `biz_subsidy_project`(补贴类投资项目) | 字段 | 类型 | 非空 | 默认值 | 说明 | | --- | --- | --- | --- | --- | | `id` | `bigint(20)` | Y | 自增 | 主键 | | `project_name` | `varchar(64)` | Y | — | 项目名称(**1~20** 字) | | `introduction` | `varchar(200)` | N | NULL | 项目简介,选填 | | `investment_amount` | `decimal(14,2)` | Y | — | 投资金额(> 0) | | `project_start_date` | `date` | Y | — | 项目开始日期 | | `planned_end_date` | `date` | Y | — | 预计完成日期(≥ 开始日) | | `cover_file_url` | `varchar(512)` | N | NULL | 封面 URL | | `cover_file_path` | `varchar(512)` | N | NULL | 封面存储路径 | | `exec_status` | `tinyint(4)` | Y | `1` | `1` 未开始 `2` 进行中 `3` 已完成 | | `actual_end_date` | `date` | N | NULL | 实际完成日期(已完成时) | | `manual_complete_flag` | `tinyint(4)` | Y | `0` | `0` 可自动推算;`1` 手工已完成锁定 | | `publish_status` | `tinyint(4)` | Y | `0` | `0` 未发布 `1` 已发布 | | `publish_time` | `datetime` | N | NULL | 发布时间 | | `offline_time` | `datetime` | N | NULL | 下架时间 | | `create_by` / `create_time` / `update_by` / `update_time` | 若依惯例 | — | — | 审计 | | `remark` | `varchar(500)` | N | NULL | 备注 | **索引**:`PRIMARY KEY (id)`;`KEY idx_start_date (project_start_date)`;`KEY idx_exec_status (exec_status)`;`KEY idx_planned_end (planned_end_date)`;`KEY idx_publish (publish_status)`。 **DDL(MySQL 5.7)** ```sql CREATE TABLE `biz_subsidy_project` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `project_name` varchar(64) NOT NULL COMMENT '项目名称', `introduction` varchar(200) DEFAULT NULL COMMENT '项目简介', `investment_amount` decimal(14,2) NOT NULL COMMENT '投资金额', `project_start_date` date NOT NULL COMMENT '项目开始日期', `planned_end_date` date NOT NULL COMMENT '预计完成日期', `cover_file_url` varchar(512) DEFAULT NULL COMMENT '封面URL', `cover_file_path` varchar(512) DEFAULT NULL COMMENT '封面存储路径', `exec_status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1未开始 2进行中 3已完成', `actual_end_date` date DEFAULT NULL COMMENT '实际完成日期', `manual_complete_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0可自动推算 1手工已完成锁定', `publish_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0未发布 1已发布', `publish_time` datetime DEFAULT NULL COMMENT '发布时间', `offline_time` datetime DEFAULT NULL COMMENT '下架时间', `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_start_date` (`project_start_date`), KEY `idx_exec_status` (`exec_status`), KEY `idx_planned_end` (`planned_end_date`), KEY `idx_publish` (`publish_status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='项目与补贴-项目'; ``` ### 2.2 枚举常量 | 字段 | 值 | 含义 | | --- | --- | --- | | `exec_status` | `1` | 未开始 | | | `2` | 进行中 | | | `3` | 已完成 | | `manual_complete_flag` | `0` | 允许自动推算 | | | `1` | 手工已完成,不自动降级 | | `publish_status` | `0` | 未发布 | | | `1` | 已发布 | --- ## 3. 接口设计 **统一响应**:`AjaxResult` / 分页 `TableDataInfo`。 **权限标识(示例)**:`industryPolicy:subsidyProject:list|query|add|edit|remove|publish|offline|export` **Base Path(示例)**:`/industryPolicy/subsidyProject` | # | 说明 | Method | URI | 权限 | 要点 | | --- | --- | --- | --- | --- | --- | | 3.1 | 分页列表 | GET | `/industryPolicy/subsidyProject/list` | `industryPolicy:subsidyProject:list` | Query **3.1.1**;排序 `project_start_date DESC, create_time DESC, id DESC`;返回前执行状态刷新(**§1**) | | 3.2 | 详情 | GET | `/industryPolicy/subsidyProject/{id}` | `industryPolicy:subsidyProject:query` | 全字段;状态刷新后返回 | | 3.3 | 新增 | POST | `/industryPolicy/subsidyProject` | `industryPolicy:subsidyProject:add` | Body **3.3.1**;`publish_status=0`;保存前 `resolveExecStatus` | | 3.4 | 修改 | PUT | `/industryPolicy/subsidyProject` | `industryPolicy:subsidyProject:edit` | **仅** `publish_status=0`;替换封面删旧 | | 3.5 | 删除 | DELETE | `/industryPolicy/subsidyProject/{ids}` | `industryPolicy:subsidyProject:remove` | **仅** `publish_status=0`;删封面文件 | | 3.6 | 发布 | POST | `/industryPolicy/subsidyProject/publish/{id}` | `industryPolicy:subsidyProject:publish` | 前置 `0`;`publish_status=1`,`publish_time=now()` | | 3.7 | 下架 | POST | `/industryPolicy/subsidyProject/offline/{id}` | `industryPolicy:subsidyProject:offline` | 前置 `1`;`publish_status=0`,`offline_time=now()` | #### 3.1.1 列表 Query | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `pageNum` / `pageSize` | int | N | 默认 `1` / `20` | | `keyword` | string | N | 项目名称 / 简介 **OR** 模糊 | | `execStatus` | int | N | `1`~`3` 精确 | | `publishStatus` | int | N | `0` / `1` 精确 | | `startDate` / `endDate` | string | N | 针对 **项目开始日期** 闭区间;`startDate`≤`endDate` | #### 3.3.1 新增 / 修改 Body(驼峰) | 字段 | 必填 | 说明 | | --- | --- | --- | | `id` | 修改 Y | 主键 | | `projectName` | Y | 1~20 字符 | | `introduction` | N | ≤50 字 | | `investmentAmount` | Y | > 0,≤2 位小数 | | `projectStartDate` | Y | `yyyy-MM-dd` | | `plannedEndDate` | Y | ≥ `projectStartDate` | | `coverFileUrl` / `coverFilePath` | N | 封面对 | | `execStatus` | Y | 用户选择值;保存时与推算协同 | | `actualEndDate` | 条件 | `execStatus=3` 时必填;≤ 当天 | | `remark` | N | 备注 | **保存逻辑(后端)** 1. 校验字段;`plannedEndDate` ≥ `projectStartDate`。 2. 若请求 `execStatus=3`:校验 `actualEndDate`,设 `manual_complete_flag=1`。 3. 若 `manual_complete_flag=0`:按 **§1** 优先级覆盖 `exec_status`;仅当 `T=E` 时为 `3`;自动为 `3` 且未传 `actualEndDate` 时默认 `actualEndDate=T`。 4. 若用户将 `execStatus` 从 `3` 改为非 `3`:清 `manual_complete_flag=0`、`actual_end_date=null`(可选规则)。 ### 3.4 服务端校验(摘要) | 项 | 规则 | | --- | --- | | 项目名称 | 非空,1~20 | | 投资金额 | > 0 | | 日期 | `plannedEndDate` ≥ `projectStartDate` | | 封面 | 可空;jpg/jpeg/png,≤10MB | | 已完成 | `actualEndDate` 必填 | | 列表区间 | `startDate` ≤ `endDate` | --- ## 4. 菜单与权限 SQL(示例) | 类型 | 名称 | 权限标识 | | --- | --- | --- | | 菜单 | 项目与补贴-项目 | `industryPolicy:subsidyProject:list` | | 按钮 | 查询/新增/修改/删除/发布/下架/导出 | `…:query` `…:add` `…:edit` `…:remove` `…:publish` `…:offline` `…:export` | 组件路径建议:`policy/subsidyProject/index`。 --- ## 5. 交付清单 - [ ] `biz_subsidy_project` DDL、菜单权限 SQL - [ ] `BizSubsidyProject` + `SubsidyProjectStatusResolver`(状态推算) - [ ] 前端:关键字/状态/开始日期筛选、封面上传、已完成必填实际完成日 - [ ] 用例:自动推算、手工锁定、改期、删封面 --- ## 6. 修订记录 | 日期 | 说明 | | --- | --- | | — | 初稿:对齐功能需求;`exec_status` 1/2/3;`manual_complete_flag` 支持已完成不降级 | | 1.1 | 同步草稿 §1.1:已完成仅 `T=E`;`T>E` 仍为进行中;自动完成默认 `actual_end_date=T` | | 1.2 | 草稿 §6:新增 `publish_status`/`publish_time`/`offline_time`;发布/下架接口 |