# 牦牛疾病预警 — 技术方案

> 依据：同目录 `牦牛疾病预警功能需求.md`。本期 **列表 + 详情只读** + **第三方同步写入**；无手工增删改接口。

---

## 1. 技术架构

| 项 | 说明 |
| --- | --- |
| **后端** | RuoYi **v3.9.2**（**springboot2** 分支）：JDK 8、Spring Boot 2.x、Spring MVC、MyBatis、Druid |
| **数据库** | MySQL **5.7.39**，InnoDB，`utf8mb4` |
| **前端** | 若依 Vue2（本方案不展开） |
| **第三方** | HTTP 客户端（与牦牛资产同源或独立告警接口）；`application.yml` 配置地址、鉴权、超时 |
| **关联表** | `biz_pasture`（牧场映射 `pasture_id`）；`biz_yak_asset`（`yak_no` 关联，非强外键） |

**分层**：`Controller` → `YakDiseaseWarningSyncService`（拉取、按外部 ID 合并）→ `IBizYakDiseaseWarningService`（查询、数据权限）→ `Mapper`/XML → `domain`。

**代码位置（建议）**：`baqing-admin` → `com.ruoyi.web.modules.industryservice`（与牧场、牦牛资产同包）。

| 场景 | 行为 |
| --- | --- |
| 同步 | 以第三方 `external_alert_id` **唯一**；有则更新，无则插入 |
| 列表/详情 | 无 `del_flag` 删除需求；全量展示有效同步记录；排序 `alert_time DESC, id DESC` |
| 日期筛选 | `startDate`/`endDate`（`yyyy-MM-dd`）转当日 00:00:00～23:59:59（`Asia/Shanghai`） |
| 牧场权限 | 列表/详情/移动端按用户可见 `pasture_id` 过滤（`@DataScope` 或 Service 注入牧场 ID 集合） |
| 并发同步 | 接口加锁（`synchronized` / Redis 锁），防重复点击 |

---

## 2. 数据库设计

### 2.1 表 `biz_yak_disease_warning`（牦牛疾病预警）

| 字段 | 类型 | 非空 | 说明 |
| --- | --- | --- | --- |
| `id` | `bigint(20)` | Y | 主键 |
| `external_alert_id` | `varchar(64)` | Y | 第三方预警唯一 ID，**去重键** |
| `yak_no` | `varchar(64)` | Y | 牦牛编号 |
| `pasture_id` | `bigint(20)` | N | 映射 `biz_pasture.id` 成功时写入 |
| `pasture_name` | `varchar(128)` | N | 所属牧场名称（冗余展示） |
| `alert_time` | `datetime` | Y | 预警时间 |
| `alert_message` | `varchar(1024)` | Y | 预警信息（自然语言全文） |
| `warning_type` | `tinyint(4)` | N | 预警类型 **§2.2**；列表可不展示 |
| `measured_value` | `varchar(128)` | N | 实测值描述，如 `39.8℃` |
| `reference_range` | `varchar(128)` | N | 参考范围描述 |
| `device_no` | `varchar(64)` | N | 设备/耳标编号（第三方有则存） |
| `collect_time` | `datetime` | N | 指标采集时间 |
| `yak_avatar_url` | `varchar(512)` | N | 牦牛头像 URL（移动端展示） |
| `data_source` | `varchar(32)` | N | 数据来源，默认 `third_party` |
| `last_sync_time` | `datetime` | N | 本条最近同步时间 |
| `create_time` / `update_time` | `datetime` | — | 审计 |

**索引**

| 索引 | 字段 | 用途 |
| --- | --- | --- |
| `PRIMARY` | `id` | 主键 |
| `UNIQUE uk_external_alert` | `external_alert_id` | 同步幂等 |
| `KEY idx_alert_time` | `alert_time` | 列表排序、日期范围 |
| `KEY idx_pasture_time` | `pasture_id`, `alert_time` | 牧场 + 时间筛选 |
| `KEY idx_yak_no` | `yak_no` | 关联档案、产业统计去重 |
| `KEY idx_yak_alert_time` | `yak_no`, `alert_time` | 按牛统计周期内预警 |

### 2.2 枚举 `warning_type`（可选入库）

| 值 | 含义 |
| --- | --- |
| `1` | 体温异常 |
| `2` | 运动量异常 |
| `9` | 其他 |

第三方无类型码时，仅写 `alert_message`，`warning_type` 可空或由文案解析填充。

### 2.3 DDL（MySQL 5.7）

```sql
CREATE TABLE `biz_yak_disease_warning` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `external_alert_id` varchar(64) NOT NULL COMMENT '第三方预警ID',
  `yak_no` varchar(64) NOT NULL COMMENT '牦牛编号',
  `pasture_id` bigint(20) DEFAULT NULL COMMENT '牧场ID',
  `pasture_name` varchar(128) DEFAULT NULL COMMENT '所属牧场名称',
  `alert_time` datetime NOT NULL COMMENT '预警时间',
  `alert_message` varchar(1024) NOT NULL COMMENT '预警信息',
  `warning_type` tinyint(4) DEFAULT NULL COMMENT '1体温 2运动量 9其他',
  `measured_value` varchar(128) DEFAULT NULL COMMENT '实测值',
  `reference_range` varchar(128) DEFAULT NULL COMMENT '参考范围',
  `device_no` varchar(64) DEFAULT NULL COMMENT '设备编号',
  `collect_time` datetime DEFAULT NULL COMMENT '采集时间',
  `yak_avatar_url` varchar(512) DEFAULT NULL COMMENT '头像URL',
  `data_source` varchar(32) DEFAULT 'third_party' COMMENT '数据来源',
  `last_sync_time` datetime DEFAULT NULL COMMENT '最近同步时间',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_external_alert` (`external_alert_id`),
  KEY `idx_alert_time` (`alert_time`),
  KEY `idx_pasture_time` (`pasture_id`,`alert_time`),
  KEY `idx_yak_no` (`yak_no`),
  KEY `idx_yak_alert_time` (`yak_no`,`alert_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='牦牛疾病预警';
```

**可选**：`biz_yak_warning_sync_log`（同步批次日志：操作人、起止时间、新增/更新/失败条数 JSON），与资产档案同步日志模式一致。

### 2.4 产业统计引用（只读）

「养殖产业数据」预警牦牛头数：

```sql
-- 示例：周期内去重头数（周期参数由统计模块传入）
SELECT COUNT(DISTINCT yak_no)
FROM biz_yak_disease_warning
WHERE alert_time >= ? AND alert_time < ?
  AND (pasture_id IN (?) OR ? IS NULL);
```

---

## 3. 接口设计

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

**权限标识**：`dataModel:yakDiseaseWarning:list|query|sync`

**后台 Base Path**：`/dataModel/yakDiseaseWarning`

| # | 说明 | Method | URI | 权限 | 要点 |
| --- | --- | --- | --- | --- | --- |
| 3.1 | 分页列表 | GET | `/list` | `list` | Query **§3.1.1**；牧场数据权限 |
| 3.2 | 预警详情 | GET | `/{id}` | `query` | 全字段；越权牧场返回业务失败 |
| 3.3 | 第三方同步 | POST | `/sync` | `sync` | 返回 **§3.3.1**；`@Log` |
| 3.4 | 牧场下拉（复用） | GET | `/dataModel/pasture/list` | `dataModel:pasture:list` | 前端筛选用；`pageSize` 放大或专用精简接口 |

本期**不提供** `POST/PUT/DELETE` 预警维护接口。

#### 3.1.1 列表 Query

| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `pageNum` / `pageSize` | int | N | 默认 `1` / `20` |
| `startDate` | string | N | 预警开始日 `yyyy-MM-dd`；空不过滤 |
| `endDate` | string | N | 预警结束日 `yyyy-MM-dd`；空不过滤 |
| `pastureId` | long | N | 所属牧场 **精确**匹配 `pasture_id` |

**服务端校验**

| 规则 | 处理 |
| --- | --- |
| `startDate` > `endDate` | `AjaxResult.error("开始日期不能晚于结束日期")` |
| 仅 `startDate` | `alert_time >= startDate 00:00:00` 且 `<= 当日 23:59:59`（结束缺省为今天） |
| 仅 `endDate` | `alert_time <= endDate 23:59:59` |
| 均为空 | 不按日期过滤 |

**列表 `rows[]` 字段（驼峰）**

| 字段 | 对应列 |
| --- | --- |
| `id` | 主键 |
| `alertTime` | 预警时间 |
| `yakNo` | 牦牛编号 |
| `pastureName` | 所属牧场 |
| `alertMessage` | 预警信息 |

排序：`alert_time DESC, id DESC`。

#### 3.2.1 详情响应 `data`

| 字段 | 说明 |
| --- | --- |
| 列表字段 | `id`、`yakNo`、`pastureId`、`pastureName`、`alertTime`、`alertMessage` |
| 扩展字段 | `warningType`、`measuredValue`、`referenceRange`、`deviceNo`、`collectTime`、`yakAvatarUrl` |
| 元数据 | `dataSource`、`lastSyncTime`、`createTime`、`updateTime` |
| 可选 | `yakAssetId`：按 `yak_no` 查 `biz_yak_asset` 有则返回，供前端跳转档案 |

记录不存在或牧场越权：`msg` 如「预警记录不存在」/「无权查看该预警」。

#### 3.3.1 同步响应 `data`

| 字段 | 说明 |
| --- | --- |
| `insertCount` | 新增条数 |
| `updateCount` | 更新条数 |
| `failCount` | 失败条数 |
| `failMessages` | 失败摘要（可选，最多 N 条） |
| `syncTime` | 完成时间 |

同步中重复请求：返回「正在同步，请稍候」。

---

### 3.5 移动端接口

**Base Path**：`/app/healthAlert`（与界面「健康预警」一致）

| # | 说明 | Method | URI | 鉴权 | 要点 |
| --- | --- | --- | --- | --- | --- |
| M1 | 预警列表 | GET | `/list` | 登录 Token | Query 仅 `pageNum`/`pageSize`；**同 §3.1 牧场数据权限**；排序同后台 |
| M2 | 预警详情 | GET | `/{id}` | 登录 Token | **本期可选**；原型列表直显全文时可不做 |

**M1 `rows[]` 建议字段**：`id`、`yakNo`、`alertTime`、`alertMessage`、`yakAvatarUrl`（无则前端占位图）。

本期移动端**不提供** `startDate`/`pastureId` 筛选（见功能需求 §5.5 扩展项）。

---

## 4. 第三方同步（实现要点）

| 项 | 说明 |
| --- | --- |
| 配置键 | `third-party.yak-alert.base-url`、`api-key`、`connect-timeout`、`read-timeout`（可与资产共用 `third-party.yak` 节点） |
| 客户端 | `ThirdPartyYakAlertClient.fetchAlerts()` → `List<YakDiseaseWarningDTO>` |
| 映射 | 第三方牧场编码 → `biz_pasture.id`；失败仅写 `pasture_name` |
| 合并 | `INSERT ... ON DUPLICATE KEY UPDATE` 或先 `selectByExternalId` 再 insert/update |
| 文案 | 第三方无完整 `alert_message` 时，由 `measured_value` + `reference_range` + `warning_type` 拼接 |
| 失败策略 | 单条失败记 `failCount`，不 rollback 已成功条（批处理事务按产品选择：默认**逐条提交**或**整批事务**二选一，推荐逐条） |
| 与资产同步 | `POST /sync` **仅同步预警**；若需连带资产，前端顺序调用 `/dataModel/yakAsset/sync`（不在本接口内强制） |

---

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

| 类型 | 名称 | 权限标识 |
| --- | --- | --- |
| 菜单 | 牦牛疾病预警 | `dataModel:yakDiseaseWarning:list` |
| 按钮 | 查询 | `dataModel:yakDiseaseWarning:query` |
| 按钮 | 同步 | `dataModel:yakDiseaseWarning:sync` |

组件路径建议：`dataModel/yakDiseaseWarning/index`（挂载「产业数据模型及体系」目录）。

移动端菜单/路由由小程序或 H5 工程配置，**不依赖**上述后台菜单权限；接口走登录态 + 牧场数据范围。

---

## 6. 类清单与交付

| 交付项 | 说明 |
| --- | --- |
| `sql/biz_yak_disease_warning.sql` | 建表脚本 |
| `BizYakDiseaseWarning` | Domain |
| `BizYakDiseaseWarningMapper` + XML | 列表、详情、按 `external_alert_id` 查询 |
| `ThirdPartyYakAlertClient`、`YakDiseaseWarningSyncService` | 第三方拉取与入库 |
| `IBizYakDiseaseWarningService`、`BizYakDiseaseWarningServiceImpl` | 查询、日期校验、数据权限 |
| `BizYakDiseaseWarningController` | 后台 3.1～3.3 |
| `BizAppHealthAlertController` | 移动端 M1（可选 M2） |
| 单元测试 / MockMvc | 列表日期边界、同步幂等、越权详情 |

---

## 7. 修订记录

| 版本 | 日期 | 说明 |
| --- | --- | --- |
| 1.0 | 2026-05-21 | 初版：单表 + 后台/移动端接口 + 第三方同步 |
