# 养殖标准管理 — 技术方案

> 依据：`养殖标准管理功能需求.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 既有上传能力（本地/MinIO 等以项目现有配置为准）；业务表存访问 URL、**存储路径**、**正文附件名称**（见 **2.1**，不落库文件大小） |
| **外部系统** | **知识库后台管理服务**（HTTP）：养殖标准在同步、发布、下架等环节调用其接口完成 **知识库变更通知**；用户侧问答另走其 **提示词查询接口**（见 **第 4 章**）。URL、鉴权、报文另附《知识库接口对接说明》。 |

**后端分层（与若依一致）**

- `Controller`：参数校验、权限注解、返回 `AjaxResult` / `TableDataInfo`  
- `Service`：状态机校验、事务、知识库编排、删文件  
- `Mapper` + XML：持久化  
- `domain`：实体与枚举常量  

**一致性策略（与需求对齐，`养殖标准管理功能需求.md` §4.7～4.8）**

- 修改：**仅** `publish_status=0` 且 `kb_sync_status=0`；**不**在 `kb_sync_status=1` 时自动推库（与需求「已同步不可改」一致，改内容须先移出）。  
- **下架（与「移出」区分）：** 业务侧 **`publish_status` 置回 `0`（未发布）**，**`kb_sync_status` 保持 `1`（已同步）**；知识库侧 **不删除**文档条目，仅完成「结束对外发布/对外检索可见」等（具体 HTTP 语义见 **§4.2（5）**，可为可见性变更、`DELETE` 的对端否定操作由对接文档约定）。**禁止**将下架实现为与「移出」相同的库条目物理删除并再把本地 `kb_sync` 置 0（除非产品另行变更需求）。  
- **移出知识库：** 通知知识库删除/下线条目后，本地 **`kb_sync_status=0`**，清空 `kb_doc_id`（与下架不同）。  
- **删除：** **`kb_sync_status=0`** 允许删（删库 + 删附件 + 通知知识库删除，见 **3.5**）；**未发布且已同步**须先移出或接口内原子联动删库条目再删本地；**已发布**禁止删除。

---

## 2. 数据库设计

### 2.1 表：`biz_breeding_standard`（养殖标准主表）

单表承载业务字段 + 双维状态 + 知识库侧关联键（若有）。

| 字段名 | 类型 | 非空 | 默认值 | 说明 |
| --- | --- | --- | --- | --- |
| `id` | `bigint(20)` | Y | 自增 | 主键 |
| `standard_name` | `varchar(64)` | Y | — | 标准名称，需求 1～20 字符，库宽略放大 |
| `standard_type` | `varchar(32)` | Y | — | 资讯类别编码，见 **2.3** |
| `introduction` | `varchar(200)` | N | NULL | 简介，选填，≤50 字 |
| `content_file_url` | `varchar(512)` | Y | — | 正文附件访问 URL（与若依上传返回 `url` 等对齐，用于下载/预览） |
| `content_file_path` | `varchar(512)` | Y | — | 正文文件在存储侧的**存储路径**（与若依上传返回 `fileName` 等对齐） |
| `content_file_name` | `varchar(256)` | Y | — | **正文附件名称**（用户上传时的原始文件名，用于列表/详情展示与下载展示名） |
| `cover_file_url` | `varchar(512)` | N | NULL | 封面访问 URL，选填 |
| `cover_file_path` | `varchar(512)` | N | NULL | 封面在存储侧的**存储路径**，选填 |
| `publish_status` | `tinyint(4)` | Y | `0` | 上架状态，见 **2.4** |
| `kb_sync_status` | `tinyint(4)` | Y | `0` | 知识库同步状态，见 **2.5** |
| `kb_doc_id` | `varchar(128)` | N | NULL | 知识库文档/条目 ID，未同步可空 |
| `publish_time` | `datetime` | N | NULL | 发布时间 |
| `offline_time` | `datetime` | N | NULL | 下架时间 |
| `create_by` | `varchar(64)` | N | — | 若依惯例 |
| `create_time` | `datetime` | N | — | 若依惯例 |
| `update_by` | `varchar(64)` | N | — | 若依惯例 |
| `update_time` | `datetime` | N | — | 若依惯例 |
| `remark` | `varchar(500)` | N | NULL | 备注（可选） |

**索引**

- `PRIMARY KEY (id)`  
- `KEY idx_create_time (create_time)` — 列表 `ORDER BY create_time DESC`  
- `KEY idx_type (standard_type)`  
- `KEY idx_pub_kb (publish_status, kb_sync_status)` — 后台筛选  

**DDL 示例（MySQL 5.7）**

```sql
CREATE TABLE `biz_breeding_standard` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `standard_name` varchar(64) NOT NULL COMMENT '标准名称（业务1～20字符）',
  `standard_type` varchar(32) NOT NULL COMMENT '标准类型编码，对应资讯类别 parentCode=001&categoryType=1',
  `introduction` varchar(200) DEFAULT NULL COMMENT '简介',
  `content_file_url` varchar(512) NOT NULL COMMENT '正文附件访问URL',
  `content_file_path` varchar(512) NOT NULL COMMENT '正文文件存储路径',
  `content_file_name` varchar(256) NOT NULL COMMENT '正文附件名称',
  `cover_file_url` varchar(512) DEFAULT NULL COMMENT '封面访问URL',
  `cover_file_path` varchar(512) DEFAULT NULL COMMENT '封面文件存储路径',
  `publish_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '上架 0未发布 1已发布（下架回流写回0，不用2作稳态）',
  `kb_sync_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '知识库 0未同步 1已同步',
  `kb_doc_id` varchar(128) DEFAULT NULL COMMENT '知识库文档ID',
  `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_create_time` (`create_time`),
  KEY `idx_type` (`standard_type`),
  KEY `idx_pub_kb` (`publish_status`,`kb_sync_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='养殖标准';
```

**已有表升级（示例）**：先增加可空列并回填，再改为非空。

```sql
ALTER TABLE `biz_breeding_standard`
  ADD COLUMN `content_file_name` varchar(256) DEFAULT NULL COMMENT '正文附件名称' AFTER `content_file_path`;
-- 按 `content_file_path` 或业务规则 UPDATE 回填后：
ALTER TABLE `biz_breeding_standard`
  MODIFY COLUMN `content_file_name` varchar(256) NOT NULL COMMENT '正文附件名称';
```

**合法组合（应用层强校验，与需求 §2.2.3 一致；禁止非法落库）**

| publish_status | kb_sync_status | 说明 |
| --- | --- | --- |
| 0 未发布 | 0 未同步 | ✓（含新建、移出后） |
| 0 未发布 | 1 已同步 | ✓（含手动同步、**已发布下架回流**，库内保留副本） |
| 1 已发布 | 1 已同步 | ✓ |
| 其余（如 **1+0** 等） | — | ✗ |

应用层校验 **仅允许上表三行** 合法组合落库与流转。

### 2.3 标准类型与资讯类别

- **数据来源**：`GET /category/informationCategory/children?parentCode=001&categoryType=1`；响应字段 `code`（编码）、`name`（展示名）。`GET /category/informationCategory/tree` 树节点字段同为 `code`、`name`、`categoryType`、`children`。
- **落库值**：`standard_type` 存编码（如 `001001`）。
- **校验**：`BreedingStandardTypeValidator`；非法编码返回「标准类型无效，请选择资讯类别中的类型」。

| 编码（示例） | 名称 |
| --- | --- |
| `001001` | 繁育作业 |
| `001002` | 饲养工作 |
| `001003` | 免疫程序 |
| `001004` | 环境调控 |

**存量库迁移**：执行 `sql/biz_breeding_standard_alter_standard_type.sql`（`1`→`001001` … `4`→`001004`）。

### 2.4 上架状态 `publish_status`

| 值 | 含义 |
| --- | --- |
| `0` | 未发布（含草稿、下架回流） |
| `1` | 已发布 |

**下架：** 操作完成后须将本字段置 **`0`**，与需求「未发布 + 已同步」一致。

### 2.5 知识库同步 `kb_sync_status`

| 值 | 含义 |
| --- | --- |
| `0` | 未同步 |
| `1` | 已同步 |

---

## 3. 接口设计

统一响应：**RuoYi 惯例** `AjaxResult`（`code` / `msg` / `data`）或分页 `TableDataInfo`；错误码与文案区分参数校验、状态非法、文件非法、知识库失败等。

**权限标识建议（示例）**：`breeding:standard:list|query|add|edit|remove|syncKb|removeKb|publish|offline`

**Base Path（示例）**：`/breeding/standard`（实际前缀与若依 `context-path`、模块路由对齐）

### 3.1 分页列表

| 项 | 说明 |
| --- | --- |
| **Method / URI** | `GET /breeding/standard/list` |
| **权限** | `breeding:standard:list` |
| **Query** | `pageNum`（默认 1）、`pageSize`（默认 **20**）、`keyword`（名称/简介 OR 模糊）、`standardType`（精确，可空） |
| **排序** | 默认 `create_time DESC`, `id DESC` |
| **响应** | `TableDataInfo` 行内字段：id、standardName、standardType、introduction、`contentFileName`（可选展示）、publishStatus、kbSyncStatus、createTime 等 |

### 3.2 详情

| 项 | 说明 |
| --- | --- |
| **Method / URI** | `GET /breeding/standard/{id}` |
| **权限** | `breeding:standard:query` |
| **响应** | 含 `standardName`、`introduction`、`contentFileUrl`、`contentFilePath`、`contentFileName`、`coverFileUrl`、`coverFilePath`、双维状态、时间等；附件下载可走若依 `/common/download` 或直链（需鉴权） |

### 3.3 新增

| 项 | 说明 |
| --- | --- |
| **Method / URI** | `POST /breeding/standard` |
| **权限** | `breeding:standard:add` |
| **Body** | JSON：`standardName`、`standardType`、`introduction`（可空）、`contentFileUrl`、`contentFilePath`、`contentFileName`（正文附件名称，与上传返回 `originalFilename` 对齐）、`coverFileUrl`、`coverFilePath`（封面可空）；正文须先上传 |
| **校验** | 名称长度 **1～20**；`standard_type` 为资讯类别有效编码（**2.3**）；简介字数、正文后缀白名单 **`pdf`/`md`/`doc`/`docx`/`xls`/`xlsx`/`pptx`** 与 ≤30MB、封面后缀 **`jpg`/`jpeg`/`png`** 与 ≤10MB（`BreedingStandardRules.isContentExtensionAllowed`） |
| **落库** | `publish_status=0`，`kb_sync_status=0`，时间字段由若依填充 |

### 3.4 修改

| 项 | 说明 |
| --- | --- |
| **Method / URI** | `PUT /breeding/standard` |
| **权限** | `breeding:standard:edit` |
| **前置** | 仅 `publish_status=0` 且 `kb_sync_status=0`；否则返回业务错误（如「知识库已同步状态不允许修改」） |
| **Body** | 含 `id`；可替换正文/封面的 `Url`、`Path` 及正文 `contentFileName` |
| **逻辑** | 更新库表；**不**自动调用知识库（改内容须先移出后再手动同步） |

### 3.5 删除

| 项 | 说明 |
| --- | --- |
| **Method / URI** | `DELETE /breeding/standard/{ids}` |
| **权限** | `breeding:standard:remove` |
| **规则** | **`publish_status=1` 禁止**；**`kb_sync_status=0`** 允许删除；**`publish_status=0` 且 `kb_sync_status=1`** 须先移出（**3.7**）或本接口内 **原子联动删知识库条目再删本地** |
| **副作用** | 物理删记录 + 删除 OSS/磁盘上正文与封面文件 + **通知知识库删除**对应条目（与需求 §4.4） |

### 3.6 同步知识库（手动）

| 项 | 说明 |
| --- | --- |
| **Method / URI** | `POST /breeding/standard/syncKb/{id}` |
| **权限** | `breeding:standard:syncKb` |
| **前置** | `publish_status=0` 且 `kb_sync_status=0` |
| **结果** | 调知识库入库，`kb_sync_status=1`，写入 `kb_doc_id` |

### 3.7 移出知识库

| 项 | 说明 |
| --- | --- |
| **Method / URI** | `POST /breeding/standard/removeKb/{id}` |
| **权限** | `breeding:standard:removeKb` |
| **前置** | `publish_status=0` 且 `kb_sync_status=1` |
| **结果** | 调知识库删除，`kb_sync_status=0`，`kb_doc_id` 清空 |

### 3.8 发布

| 项 | 说明 |
| --- | --- |
| **Method / URI** | `POST /breeding/standard/publish/{id}` |
| **权限** | `breeding:standard:publish` |
| **前置** | `publish_status=0` 且 `kb_sync_status=1` |
| **结果** | `publish_status=1`，`publish_time=now()` |

### 3.9 下架

| 项 | 说明 |
| --- | --- |
| **Method / URI** | `POST /breeding/standard/offline/{id}` |
| **权限** | `breeding:standard:offline` |
| **前置** | `publish_status=1` |
| **结果** | 调知识库完成「对外下架」语义（**非**移出删条目，见 **§4.2（5）**）；本地 **`publish_status=0`**，**`kb_sync_status=1`**（保留 `kb_doc_id`）；**`offline_time=now()`**（审计下架时刻）；**可选**清空 `publish_time` 或由产品约定是否保留上次发布时间 |

---

## 4. 与知识库后台管理服务交互（流程）

本章约定：**养殖标准业务服务**（RuoYi 后端）与 **知识库后台管理服务** 之间通过 **HTTP** 交互；具体路径、Header（如 `Authorization`/`X-Api-Key`）、请求体字段以《知识库后台管理服务接口文档》为准，此处描述 **调用时机、顺序职责与失败策略**。

### 4.1 交互角色

| 角色 | 说明 |
| --- | --- |
| **养殖标准业务服务** | 在本模块内完成鉴权、状态机、落库后，**按需调用**知识库后台管理服务接口，驱动知识库侧文档/索引/可见性变更 |
| **知识库后台管理服务** | 接收变更通知，执行入库、更新、发布可见、下架移除等；可提供 **提示词查询** 能力供终端提问 |
| **终端用户（提问）** | 一般 **不经过** 养殖标准 CRUD 接口；通过前端或统一网关 **直接调用** 知识库后台管理服务的 **提示词查询接口** 完成问答（与养殖标准数据是否已发布、是否仍在库内等业务规则由知识库侧检索策略决定） |

### 4.2 养殖标准触发的 HTTP 调用（知识库变更通知）

下列步骤中「调用知识库接口」均指 **知识库后台管理服务** 暴露的管理类 HTTP API（命名示例：`/kb/admin/doc/...`，以对接文档为准）。

#### 变更通知统一必填语义（报文体）

每次调用变更通知接口时，须告知以下字段（字段名可与对接文档对齐，语义不变）：

| 字段语义 | 说明 |
| --- | --- |
| **操作类型** | **`添加`**：首次同步入库、已同步后的保存更新（内容替换）、发布等对「库内需保留/更新该标准正文」的通知；**`删除`**：**移出知识库**、业务**删除**等对「库内需移除该标准条目」的通知。**下架**不等于删除条目：下架走「可见性/发布态」类接口（可能仍归类为扩展字段或单独 API，见 **§4.2（5）**），不应默认使用本表的 **`删除`** 语义清掉副本。（若对端要求枚举值为英文，可约定 `ADD` / `DELETE`） |
| **标准名称** | 当前养殖标准 **`standard_name`**，与业务侧一致 |
| **标准 HTTP 下载地址** | 正文附件 **`content_file_url`**（可被浏览器或服务端拉取的 **HTTP(S)** 下载地址；对内下载网关则需对外亦为可达或可再由知识库回源拉取） |

**说明：**  
- **`添加`** 类：`content_file_url` 指向 **最新** 可下载的正文；若同一业务反复推送更新，均为 **`添加`** 语义下的版本刷新（对端实现幂等或覆盖策略）。  
- **`删除`** 类：仍携带 **标准名称** 与 **当时的 `content_file_url`（库内留存值）**，便于对端定位；若记录已删仅存快照，以对端约定为准。  
- 其余扩展字段（业务主键 `id`、`kb_doc_id`、类型、简介等）可按《知识库接口对接说明》选填，**不得弱化**上述三项必填语义。

#### （1）同步至知识库（手动首次）

```text
用户点击「同步」→ Service 校验 publish_status=0 且 kb_sync_status=0
  → HTTP：变更通知（操作类型=添加，标准名称，标准 HTTP 下载地址=content_file_url）
  ← 成功返回知识库文档 ID（写入 kb_doc_id）
  → 本地 kb_sync_status=1；失败则不打勾成功态，提示重试
```

#### （2）修改保存（仅未同步）

```text
用户保存修改 → Service 校验 publish_status=0 且 kb_sync_status=0
  → 仅更新本地库表；不调用知识库
  ← 成功则提交；kb_sync_status=1 时接口拒绝
```

**说明：** 已同步记录须先 **移出知识库**（（3））后再走本流程，改完须再 **手动同步**（（1））。

#### （3）移出知识库（仅未发布且已同步）

```text
用户点击「移出」→ HTTP：变更通知（操作类型=删除，标准名称，content_file_url）
  ← 成功 → 本地 kb_sync_status=0，清空 kb_doc_id
```

#### （4）发布

```text
用户点击「发布」→ Service 校验未发布且已同步
  → HTTP：变更通知（操作类型=添加，标准名称，content_file_url；语义上可附带「发布可见」，以对端扩展字段为准）
  ← 成功 → 本地 publish_status=1，publish_time 落库
```

#### （5）下架（与「移出」区分）

```text
用户点击「下架」→ Service 校验 publish_status=1
  → HTTP：按对接文档通知知识库「结束对外发布/检索可见」（语义可为 UPDATE 可见性、撤销发布位等；需求明确库内保留可编辑副本，不得等同于「移出」的 DELETE 整条）
  ← 成功 → 本地 publish_status=0，kb_sync_status=1，offline_time=now()；kb_doc_id 保留
```

若对端 **仅支持**「删除类」接口表达下架，须在《对接说明》中约定 **二次入库/副本保留** 策略，否则与需求「下架后仍为已同步」冲突，须产品书面确认。

#### （6）删除（未发布且已同步时的联动）

若删除流程内需先清库：在删本地记录前 **HTTP：变更通知（操作类型=删除，标准名称，content_file_url）**，成功后再删库与删附件（与 **3.5** 规则一致）。

### 4.3 提示词查询接口（用户提问）

| 项 | 说明 |
| --- | --- |
| **调用方** | 业务前端、移动端或统一 API 网关（**非** 养殖标准 Module 内必选依赖） |
| **被调服务** | 知识库后台管理服务（或其对外的查询子域，以部署为准）提供的 **提示词查询 / 对话检索** 类 **HTTP** 接口 |
| **请求必填语义** | **`用户标识`**：调用方传入当前提问用户的唯一标识（如用户 ID、统一账号 `sub`、企微 `openid` 等，格式与对端约定一致）；**`问题内容`**：用户输入的自然语言问题正文 |
| **用途** | 携带上述字段发起提问，返回基于知识库检索与模型编排的答案；可检索范围是否包含「仅已发布的养殖标准」等 **由知识库侧策略与标签过滤** 实现 |
| **与养殖标准的关系** | 养殖标准通过 **4.2** 保证入库、发布、下架时知识库内容与状态一致；问答链路 **独立调用** 查询接口，无需养殖标准 Controller 再转发（若产品要求经网关统一鉴权，可在网关层聚合） |

### 4.4 实现要点（研发）

- **客户端封装**：`KbAdminClient`（或同名 Bean），封装变更通知（**操作类型 / 标准名称 / content_file_url**）与查询请求（**用户标识 / 问题内容**）；超时、重试策略（慎用）、幂等键（若对端要求）、结构化日志（bizId、kbDocId、接口名、耗时）。  
- **配置**：`kb.admin.base-url`、`kb.admin.token`（或 OAuth 客户端配置）等写入 `application-*.yml`，区分环境。  
- **安全**：管理接口仅限服务端调用，**禁止** 把管理密钥下发浏览器；提示词查询接口按产品要求做用户鉴权与限流。  
- **联调**：以对接文档中的错误码映射到前端 `msg`，与 **3.x** 业务接口返回风格一致。

---

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

- 前端：先调若依现有 **`/common/upload`**（或项目封装接口），得到 **`url`**（写入 `content_file_url` / `cover_file_url`）、**存储路径**（写入 `content_file_path` / `cover_file_path`，一般为返回的 `fileName` 或项目约定字段）、**原始文件名**（写入 `content_file_name`，对应返回 `originalFilename`）；**大小**仅在提交前用于校验 **≤30MB / ≤10MB**，**不入库**。  
- 服务端：对白名单后缀与大小 **二次校验**（大小可由上传接口或临时读取流获取）；替换附件时按 **path** 删除旧物理文件避免残留。

---

## 6. 交付清单（研发勾选）

- [ ] 表 `biz_breeding_standard` DDL 与初始化脚本  
- [ ] Domain / Mapper / Service / Controller  
- [ ] 菜单与按钮权限 SQL  
- [ ] **知识库后台管理服务** Client（变更通知类接口）+ 配置项（**§4**）；对接文档中单列「提示词查询」接口供前端/网关引用  
- [ ] 核心场景集成测试：同步、发布、下架、移出、删除与非法状态拦截；**已同步修改被拒绝**；与知识库后台 **HTTP** 联调记录  

---

## 7. 修订记录

| 日期 | 说明 |
| --- | --- |
| — | 初稿，对齐 `养殖标准管理功能需求.md` |
| — | 库表：删除 `content_file_size`、`cover_file_size`；正文侧增加 `content_file_path`（存储路径）；`cover_file_name`→`cover_file_path`（封面存储路径）；接口 Body 同步调整 |
| — | 库表：新增 `content_file_name`（正文附件名称）；接口增加 `contentFileName` |
| — | 新增 **第 4 章**：与 **知识库后台管理服务** HTTP 交互流程（同步、移出、发布、下架、删除联动）；**已同步禁止修改**；**提示词查询接口** 供用户独立调用说明；原 §4～§6 顺延为 §5～§7 |
| — | **§4.2** 变更通知统一携带：**操作类型（添加/删除）**、**标准名称**、**标准 HTTP 下载地址（content_file_url）**；**§4.3** 提问携带：**用户标识**、**问题内容** |
| — | 对齐需求修订：**下架**回流为 `publish_status=0` + `kb_sync_status=1`，库内保留副本；**下架 ≠ 移出**；合法组合仅 **(0,0)(0,1)(1,1)**；删除规则与 **3.5 / 3.9** 同步更新 |
| — | 正文附件白名单：`pdf/md/doc/docx/xls/xlsx/pptx`（不含 `ppt`）；`md` 经 `/common/upload` 全局白名单放行 |
