# 畜牧科技资源管理 — 技术方案

> 依据：`畜牧科技资源管理功能需求.md`（同目录）

---

## 1. 技术架构

| 层级 | 说明 |
| --- | --- |
| **整体** | RuoYi **v3.9.2**（**springboot2** 分支）单体后端 + 若依 **Vue2** 前端（与分支配套） |
| **运行时** | JDK 8、Spring Boot 2.x、Spring MVC、MyBatis、Druid |
| **数据库** | MySQL **5.7.39**，存储引擎 InnoDB，字符集 **utf8mb4** |
| **文件** | 复用 RuoYi **`/common/upload`**；业务表存 **URL + 存储路径**（`photo_*` / `cover_*` / `video_*`） |
| **外部系统** | 平台 **用户/角色**（分配账号）；**移动端** 查询 `publish_status=1` 且 `del_flag=0` 展示（接口由移动端或网关实现） |

**后端分层**

| 层 | 职责 |
| --- | --- |
| `Controller` | 参数接收、`@PreAuthorize`、返回 `AjaxResult` / `TableDataInfo` |
| `Service` | 按 `resource_type` 校验、发布状态机、分配账号、替换附件删旧文件 |
| `Mapper` + XML | 持久化、列表三态筛选 |
| `domain` + `support` | 实体；`TechResourceRules`、`TechResourceValidation`、`TechResourceAccountAssigner` 等 |

**代码包（建议）**：`com.ruoyi.web.modules.technology`  
**初始化脚本**：`sql/biz_tech_resource.sql`

**一致性策略（与需求对齐）**

| 规则 | 实现要点 |
| --- | --- |
| 单表四类 | `resource_type` 区分；保存后**不可改类型** |
| 改删 / 发布 | 仅 `publish_status=0` 且 `del_flag=0`（已下架 = `publish_status=0` 且 `offline_time` 非空） |
| 下架 | 仅 `publish_status=1` → `publish_status=0`、`offline_time=now()`；**保留** `publish_time` |
| 再次发布 | `publish_status=1`、`publish_time=now()`、**清空** `offline_time` |
| 分配账号 | 仅 `resource_type=004005`、`account_assigned=0`、`publish_status=0`；成功一次；登录名=姓名拼音**首字母**+`id`；角色 `role_key=exp`；登录名冲突**不**置已分配 |
| 移动端可见 | `publish_status=1` 且 `del_flag=0` |

---

## 2. 数据库设计

### 2.1 表：`biz_tech_resource`（畜牧科技资源主表）

单表承载四类共用字段 + 差异化可空列；逻辑删除 `del_flag`。

| 字段名 | 类型 | 非空 | 默认 | 说明 |
| --- | --- | --- | --- | --- |
| `id` | `bigint(20)` | Y | 自增 | 主键 |
| `resource_type` | `varchar(32)` | Y | — | 资讯类别编码（`parentCode=004`、`categoryType=7`），见 **2.2** |
| `resource_name` | `varchar(64)` | Y | — | 展示名称（专家姓名/成果名称/仪器名称/课程标题） |
| `photo_file_url` | `varchar(512)` | N | NULL | 主图 URL（专家照片、仪器照片） |
| `photo_file_path` | `varchar(512)` | N | NULL | 主图存储路径（`/profile/...`） |
| `cover_file_url` | `varchar(512)` | N | NULL | 封面 URL（技术成果、视频课程，选填） |
| `cover_file_path` | `varchar(512)` | N | NULL | 封面存储路径 |
| `video_file_url` | `varchar(512)` | N | NULL | 视频 URL（视频课程） |
| `video_file_path` | `varchar(512)` | N | NULL | 视频存储路径 |
| `introduction` | `varchar(500)` | N | NULL | 介绍/简介（专家、仪器、课程） |
| `achievement_summary` | `varchar(500)` | N | NULL | 成果摘要（技术成果） |
| `key_tech_principle` | `varchar(500)` | N | NULL | 关键技术原理 |
| `tech_advantage` | `varchar(500)` | N | NULL | 技术优势 |
| `research_direction` | `varchar(32)` | N | NULL | 研究方向（可输入下拉落库文本） |
| `completion_unit` | `varchar(64)` | N | NULL | 完成单位 |
| `contact_phone` | `varchar(20)` | N | NULL | 联系电话 |
| `affiliated_unit` | `varchar(64)` | N | NULL | 隶属单位 |
| `detail_address` | `varchar(200)` | N | NULL | 详细地址（专家） |
| `service_area` | `varchar(200)` | N | NULL | 服务区域（专家） |
| `fee_standard` | `decimal(10,2)` | N | NULL | 收费标准，元/小时（专家） |
| `expert_rating` | `decimal(2,1)` | N | NULL | 专家评分 1.0～5.0；**仅专家**新增时默认 `5.0`，其它类型为 NULL；管理端不展示 |
| `borrow_fee` | `decimal(10,2)` | N | NULL | 借用金额，元/小时（共享仪器） |
| `service_start_time` | `varchar(8)` | N | NULL | 服务开始 `HH:mm` |
| `service_end_time` | `varchar(8)` | N | NULL | 服务结束 `HH:mm` |
| `service_weekdays` | `varchar(32)` | N | NULL | 服务周日，逗号分隔 `1`～`7` |
| `instrument_model` | `varchar(32)` | N | NULL | 仪器型号 |
| `storage_location` | `varchar(200)` | N | NULL | 存放地点 |
| `reservation_process` | `varchar(500)` | N | NULL | 预约流程 |
| `person_in_charge` | `varchar(32)` | N | NULL | 负责人 |
| `course_topic` | `varchar(32)` | N | NULL | 课程主题（资讯类别编码 parentCode=005，见 **2.5**） |
| `longitude` | `decimal(10,6)` | N | NULL | 经度（地图标点预留） |
| `latitude` | `decimal(10,6)` | N | NULL | 纬度（预留） |
| `publish_status` | `tinyint(4)` | Y | `0` | 发布状态，见 **2.3** |
| `publish_time` | `datetime` | N | NULL | 最近发布时间 |
| `offline_time` | `datetime` | N | NULL | 最近下架时间 |
| `account_assigned` | `tinyint(4)` | Y | `0` | 是否已分配账号 |
| `sys_user_id` | `bigint(20)` | N | NULL | 关联 `sys_user.user_id` |
| `assigned_login_name` | `varchar(64)` | N | NULL | 已分配登录名 |
| `del_flag` | `char(1)` | Y | `'0'` | `0` 正常 / `2` 删除 |
| `create_by` / `create_time` / `update_by` / `update_time` / `remark` | — | — | — | 若依惯例 |

**索引**：`PRIMARY KEY (id)`；`idx_type (resource_type)`；`idx_name (resource_name)`；`idx_publish (publish_status)`；`idx_del (del_flag)`。

**DDL（MySQL 5.7）**

```sql
CREATE TABLE `biz_tech_resource` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `resource_type` varchar(32) NOT NULL COMMENT '资讯类别编码(parentCode=004,categoryType=7)',
  `resource_name` varchar(64) NOT NULL COMMENT '资源名称',
  `photo_file_url` varchar(512) DEFAULT NULL COMMENT '主图URL',
  `photo_file_path` varchar(512) DEFAULT NULL COMMENT '主图路径',
  `cover_file_url` varchar(512) DEFAULT NULL COMMENT '封面URL',
  `cover_file_path` varchar(512) DEFAULT NULL COMMENT '封面路径',
  `video_file_url` varchar(512) DEFAULT NULL COMMENT '视频URL',
  `video_file_path` varchar(512) DEFAULT NULL COMMENT '视频路径',
  `introduction` varchar(500) DEFAULT NULL COMMENT '介绍/课程简介',
  `achievement_summary` varchar(500) DEFAULT NULL COMMENT '成果摘要',
  `key_tech_principle` varchar(500) DEFAULT NULL COMMENT '关键技术原理',
  `tech_advantage` varchar(500) DEFAULT NULL COMMENT '技术优势',
  `research_direction` varchar(32) DEFAULT NULL COMMENT '研究方向',
  `completion_unit` varchar(64) DEFAULT NULL COMMENT '完成单位',
  `contact_phone` varchar(20) DEFAULT NULL COMMENT '联系电话',
  `affiliated_unit` varchar(64) DEFAULT NULL COMMENT '隶属单位',
  `detail_address` varchar(200) DEFAULT NULL COMMENT '详细地址',
  `service_area` varchar(200) DEFAULT NULL COMMENT '服务区域',
  `fee_standard` decimal(10,2) DEFAULT NULL COMMENT '收费标准元/小时',
  `expert_rating` decimal(2,1) DEFAULT NULL COMMENT '专家评分1.0-5.0(仅专家)',
  `borrow_fee` decimal(10,2) DEFAULT NULL COMMENT '借用金额元/小时',
  `service_start_time` varchar(8) DEFAULT NULL COMMENT '服务开始HH:mm',
  `service_end_time` varchar(8) DEFAULT NULL COMMENT '服务结束HH:mm',
  `service_weekdays` varchar(32) DEFAULT NULL COMMENT '服务周日1-7',
  `instrument_model` varchar(32) DEFAULT NULL COMMENT '仪器型号',
  `storage_location` varchar(200) DEFAULT NULL COMMENT '存放地点',
  `reservation_process` varchar(500) DEFAULT NULL COMMENT '预约流程',
  `person_in_charge` varchar(32) DEFAULT NULL COMMENT '负责人',
  `course_topic` varchar(32) DEFAULT NULL COMMENT '课程主题（资讯类别编码，parentCode=005,categoryType=8）',
  `longitude` decimal(10,6) DEFAULT NULL COMMENT '经度预留',
  `latitude` decimal(10,6) DEFAULT NULL COMMENT '纬度预留',
  `publish_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0非发布态1已发布',
  `publish_time` datetime DEFAULT NULL COMMENT '发布时间',
  `offline_time` datetime DEFAULT NULL COMMENT '下架时间',
  `account_assigned` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0未分配1已分配',
  `sys_user_id` bigint(20) DEFAULT NULL COMMENT '关联用户ID',
  `assigned_login_name` varchar(64) 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_type` (`resource_type`),
  KEY `idx_name` (`resource_name`),
  KEY `idx_publish` (`publish_status`),
  KEY `idx_del` (`del_flag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='畜牧科技资源';
```

**界面三态与查询映射**

| 界面状态 | `publish_status` | `offline_time` | 列表 `publishStatus` 参数 |
| --- | --- | --- | --- |
| 未发布 | `0` | `NULL` | `0` |
| 已发布 | `1` | 任意 | `1` |
| 已下架 | `0` | **非** `NULL` | `2` |

**类型与字段落库矩阵（应用层强校验）**

| 字段 | 专家(004005) | 技术成果(004006) | 共享仪器(004007) | 视频课程(004008) |
| --- | --- | --- | --- | --- |
| `resource_name` | ✓ | ✓ | ✓ | ✓ |
| `photo_*` | ○ | — | ○ | — |
| `cover_*` | — | ○ | — | ○ |
| `video_*` | — | — | — | ✓ |
| `introduction` | ○ | — | ○ | ○ |
| `achievement_summary` / `key_tech_principle` / `tech_advantage` | — | ○ | — | — |
| `research_direction` / `completion_unit` | — | ✓/○ | — | — |
| `contact_phone` | ○ | — | ○ | — |
| `affiliated_unit` | ○ | — | ○ | — |
| `detail_address` / `service_area` / `fee_standard` | ○ | — | — | — |
| `service_start_time` / `service_end_time` / `service_weekdays` | ○ | — | ○ | — |
| `borrow_fee` / `instrument_model` / `storage_location` / `reservation_process` / `person_in_charge` | — | — | ○ | — |
| `course_topic` | — | — | — | ✓ |

> **○** 选填；**✓** 该类型必填（应用层）。

### 2.2 资源类型 `resource_type`

数据来自 `GET /category/informationCategory/children?parentCode=004&categoryType=7`（响应 `code`、`name`）。落库与接口传 **叶子 `code`**。

| `code` | 含义 |
| --- | --- |
| `004005` | 专家 |
| `004006` | 技术成果 |
| `004007` | 共享仪器 |
| `004008` | 视频课程 |

保存校验：`TechResourceTypeValidator` 以上述 children 返回的 `code` 集合为准。历史 `tinyint` 迁移见 `sql/biz_tech_resource_alter_resource_type.sql`（`1`→`004005` … `4`→`004008`）。

### 2.5 课程主题 `course_topic`（仅 `resource_type=004008`）

数据来自 `GET /category/informationCategory/children?parentCode=005&categoryType=8`。落库与接口传 **叶子 `code`**。

| `code` | 含义 |
| --- | --- |
| `005001` | 基础养殖 |
| `005002` | 疫病防治 |
| `005003` | 繁育技术 |
| `005004` | 政策解读 |

保存校验：`TechResourceCourseTopicValidator`（`resource_type=004008` 时调用）。历史中文主题迁移见 `sql/biz_tech_resource_alter_course_topic.sql`。

### 2.3 发布状态 `publish_status`

| 值 | 含义 |
| --- | --- |
| `0` | 非发布态（未发布或已下架，由 `offline_time` 区分） |
| `1` | 已发布 |

- **发布**：`publish_status=1`，`publish_time=now()`，`offline_time=NULL`。  
- **下架**：`publish_status=0`，`offline_time=now()`；**不改** `publish_time`。

### 2.4 其他枚举

| 项 | 约定 |
| --- | --- |
| `service_weekdays` | 多选入库，逗号分隔 `1`～`7`（周一=1） |
| `account_assigned` | `0` 未分配 / `1` 已分配 |
| `del_flag` | `0` 正常 / `2` 逻辑删除 |
| 分配账号角色 | 专家（`resource_type=004005`）按 `sys_role.role_key=**exp**` 解析 `role_id` 并绑定（库中常见 `role_id=101`，以配置为准） |
| 登录名 | 专家姓名汉语拼音**首字母**（小写）+ `id`；实现类 `TechResourceLoginNameGenerator`（委托 `TradingPartnerLoginNameGenerator`） |

**研究方向**：库内存**文本**；前端可输入下拉，预设项由前端常量维护。**课程主题**见 **§2.5**，存资讯类别 `code`。

### 2.6 分配账号（`TechResourceAccountAssigner`）

| 项 | 说明 |
| --- | --- |
| 范围 | 仅 `resource_type=004005`（专家） |
| 前置 | `account_assigned=0`；`publish_status=0`（未发布或已下架） |
| 登录名 | 对 `resource_name` 逐字取汉语拼音**首字母**（小写）；ASCII 字母数字原样小写拼接；再拼接 `id`（如「张专家」、id=12 → `zzj12`） |
| 角色 | `SysRoleMapper.checkRoleKeyUnique("exp")`；未配置则「未配置权限字符为「exp」的角色，无法分配账号」 |
| 落库 | `account_assigned=1`、`sys_user_id`、`assigned_login_name`、`update_by` |
| 次数 | 每条仅成功一次；重复调用返回「该资源已分配账号，不可重复分配」 |
| 删除联动 | 逻辑删除专家且已分配时 `deleteUserById(sys_user_id)` |

---

## 3. 接口设计

**统一响应**：`AjaxResult`（`code` / `msg` / `data`）或分页 `TableDataInfo`（`rows` / `total`）。

**权限标识**：`techService:techResource:list|query|add|edit|remove|publish|offline|assignAccount`

**Base Path**：`/techService/techResource`

### 3.1 分页列表

| 项 | 说明 |
| --- | --- |
| **Method / URI** | `GET /techService/techResource/list` |
| **权限** | `techService:techResource:list` |
| **Query** | `pageNum`（默认 1）、`pageSize`（默认 **20**）、`resourceType`（**必填**，精确）、`resourceName`（模糊，可空）、`publishStatus`（`0`/`1`/`2`，可空，映射见 **§2.1**） |
| **排序** | `create_time DESC`, `id DESC` |
| **条件** | `del_flag='0'` |
| **响应** | 行字段含 `id`、`resourceType`、`resourceName`、`introduction`（可截断）、`displayPublishStatus`、`displayPublishStatusName`、`publishStatus`、`accountAssigned`、`createTime` 及类型相关列；`serviceWeekdays` 列表场景可不展开 |

### 3.2 详情

| 项 | 说明 |
| --- | --- |
| **Method / URI** | `GET /techService/techResource/{id}` |
| **权限** | `techService:techResource:query` |
| **响应** | 全业务字段（驼峰）；`serviceWeekdays` 以 **数组** 返回；含展示用发布三态、`canEdit` / `canPublish` / `canOffline` / `canAssignAccount` 等（可选）；仅 `del_flag=0` |

### 3.3 新增

| 项 | 说明 |
| --- | --- |
| **Method / URI** | `POST /techService/techResource` |
| **权限** | `techService:techResource:add` |
| **Body** | JSON：`resourceType`、`resourceName` 及按类型字段；`serviceWeekdays` 为数组；图片/视频须先 `/common/upload` |
| **校验** | `TechResourceValidation` 按 **§2.1** 矩阵；手机号、时段、正数金额；图片 jpg/jpeg/png；视频 mp4 |
| **落库** | `publish_status=0`，`offline_time=NULL`，`account_assigned=0`，`del_flag=0` |

### 3.4 修改

| 项 | 说明 |
| --- | --- |
| **Method / URI** | `PUT /techService/techResource` |
| **权限** | `techService:techResource:edit` |
| **前置** | `publish_status=0` 且 `del_flag=0`；否则「已发布资源请先下架后再编辑」 |
| **Body** | 含 `id`；可改各业务字段及附件路径；**不可改** `resourceType` |
| **逻辑** | 替换 `photo_*` / `cover_*` / `video_*` 时删除旧物理文件；不自动发布 |

### 3.5 删除

| 项 | 说明 |
| --- | --- |
| **Method / URI** | `DELETE /techService/techResource/{ids}` |
| **权限** | `techService:techResource:remove` |
| **规则** | `publish_status=1` 禁止；仅 `publish_status=0` 可逻辑删除；存在下游引用时 `TechResourceReferenceChecker` 拒绝（若实现） |
| **结果** | `del_flag='2'`；删除 `photo_*`/`cover_*`/`video_*` 物理文件；**若 `account_assigned=1` 且 `sys_user_id` 非空**，调用 `ISysUserService.deleteUserById` 删除平台账号 |
| **顺序** | 校验 → 引用检查 → `TechResourceAccountAssigner.deleteLinkedAccount` → 删附件 → 逻辑删除 |

### 3.6 发布

| 项 | 说明 |
| --- | --- |
| **Method / URI** | `POST /techService/techResource/publish/{id}` |
| **权限** | `techService:techResource:publish` |
| **前置** | `publish_status=0`；全量校验通过 |
| **结果** | `publish_status=1`，`publish_time=now()`，`offline_time=NULL` |

### 3.7 下架

| 项 | 说明 |
| --- | --- |
| **Method / URI** | `POST /techService/techResource/offline/{id}` |
| **权限** | `techService:techResource:offline` |
| **前置** | `publish_status=1` |
| **结果** | `publish_status=0`，`offline_time=now()`；保留 `publish_time` |

### 3.8 分配账号

| 项 | 说明 |
| --- | --- |
| **Method / URI** | `POST /techService/techResource/assignAccount/{id}` |
| **权限** | `techService:techResource:assignAccount` |
| **前置** | `resource_type=004005`；`account_assigned=0`；`publish_status=0` |
| **结果** | 创建 `sys_user`、绑定 `role_key=**exp**` 对应角色；写 `sys_user_id`、`assigned_login_name`、`account_assigned=1` |
| **失败** | 登录名冲突等返回明确 `msg`，**不**置 `account_assigned=1` |

> **地图标点**：本期无接口。

---

## 4. 附件上传（实现提示）

| 类型 | 前端上传 | 落库字段 | 服务端校验 |
| --- | --- | --- | --- |
| 图片 | `/common/upload` → `url`、`fileName` | `photo_*` 或 `cover_*` | jpg/jpeg/png；路径 `/profile` 前缀；≤10MB（前端+可选后端） |
| 视频 | 同上（或扩展视频上传配置） | `video_*` | mp4；≤30MB |

---

## 5. 菜单与权限（示例）

| 类型 | 名称 | 权限标识 |
| --- | --- | --- |
| 菜单 | 畜牧科技资源管理 | `techService:techResource:list` |
| 按钮 | 查询 | `techService:techResource:query` |
| 按钮 | 新增 | `techService:techResource:add` |
| 按钮 | 修改 | `techService:techResource:edit` |
| 按钮 | 删除 | `techService:techResource:remove` |
| 按钮 | 发布 | `techService:techResource:publish` |
| 按钮 | 下架 | `techService:techResource:offline` |
| 按钮 | 分配账号 | `techService:techResource:assignAccount` |

组件路径：`techService/techResource/index`（挂载「牧业养殖科技服务」目录下）。

---

## 6. 交付清单

- [ ] `sql/biz_tech_resource.sql`  
- [ ] Domain / Mapper / Service / Controller（`technology` 包）  
- [ ] `TechResourceValidation`、`TechResourceRules`、分配账号组件  
- [ ] 菜单与按钮权限 SQL  
- [ ] 单元测试 / MockMvc：四类校验、发布三态、专家分配账号、已发布不可改删  

---

## 7. 修订记录

| 日期 | 说明 |
| --- | --- |
| — | 初稿，对齐 `畜牧科技资源管理功能需求.md` v2.0 |
| — | 单表 `biz_tech_resource`；`/techService/techResource`；专家角色 101；技术成果/视频分字段与封面、视频列 |
| — | 删除接口：专家已分配账号时联动 `deleteUserById`（对齐草稿 §10） |
| 1.2 | 专家分配账号：登录名拼音首字母+`id`；角色按 `role_key=exp` 解析；仅一次；§2.6、`§3.8` |
