# 养殖产业数据 — 技术方案 > 依据:同目录 `养殖产业数据功能需求.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;见 `养殖产业数据前端技术方案.md` | | **时区** | `Asia/Shanghai`(自然年、统计日、预警时间范围) | **分层**:`Controller` → `IBreedingIndustryDashboardService`(口径编排、数据权限)→ `BreedingIndustryStatsMapper`(聚合 SQL)→ 既有 `biz_*` 表。 **代码位置(建议)**:`baqing-admin` → `com.ruoyi.web.modules.industryservice`(`BreedingIndustryDashboardController`、`BreedingIndustryStatsMapper`、VO)。 | 场景 | 行为 | | --- | --- | | 看板加载/刷新 | 一次请求返回四块数据;`statDate` 取服务器当前日期 | | 数据权限 | Service 注入用户可见 `pastureId` 列表;牦牛、预警按 **§2.2** 过滤;草场全县台账不按牧场过滤 | | 月末存栏趋势(档案) | 历史能力保留于 `BreedingIndustryCalculator`;**看板趋势已改填报表**,见 **§2.11** | | 存栏变动/年龄/趋势 | 复用 `HomeScreenStatsMapper` + `HomeScreenSupport` 滚动 12 月逻辑,与大屏一致 | | 性能 | 数据量大时启用 **§2.3** 可选快照表 + 短 TTL 缓存;本期可先实时聚合;月末 12 点目标 **≤3s**(1 万级档案,评审可调) | **依赖模块(只读)** | 表 | 模块 | | --- | --- | | `biz_pasture` | 牧场管理 | | `biz_grassland` | 草场管理 | | `biz_yak_asset` | 牦牛资产档案 | | `biz_yak_disease_warning` | 牦牛疾病预警 | | `biz_yak_herd_inventory` | 牦牛存栏数据填报(存栏变动、年龄结构、总览存栏) | | `biz_yak_outbound_report` | 牦牛出栏数据填报(总览出栏、农牧户) | --- ## 2. 数据库设计 ### 2.1 设计原则 本模块**不新增业务台账表**;指标均通过对上表 **SELECT 聚合** 得到,口径以功能需求 **§2** 为准。 ### 2.2 数据源与字段映射 #### `biz_pasture`(牧场数量) | 指标 | SQL 条件要点 | | --- | --- | | 牧场数量 | `del_flag='0'` AND `biz_status=1` AND `id IN (可见牧场ID集合)` | #### `biz_grassland`(草场数量、面积、可用/使用中、退化占比) | 指标 | SQL 条件要点 | | --- | --- | | 草场数量 | `del_flag='0'` | | 草场面积 | `SUM(area_mu)`,`area_mu IS NOT NULL` | | 可用草场 | `del_flag='0'` AND `degradation_level IN (1,2)`(1未退化 2轻度) | | 使用中草场 | `del_flag='0'` AND (`utilization_type IN (2,3)` OR (`utilization_type=1` AND 统计日 NOT BETWEEN `grazing_ban_start_date` AND `grazing_ban_end_date`)) | | 退化分组 | `GROUP BY degradation_level`(1~5) | **枚举(与草场管理一致)** | 字段 | 值 | 含义 | | --- | --- | --- | | `degradation_level` | 1~5 | 未退化、轻度、中度、重度、极重度 | | `utilization_type` | 1/2/3 | 禁牧、轮牧、全年放牧 | #### `biz_yak_asset`(存栏、出栏、性别、年龄、月趋势) | 指标 | SQL 条件要点 | | --- | --- | | 当前存栏 | `del_flag='0'` AND `pasture_id IN (...)` AND `pasture_id IS NOT NULL` AND `asset_status=1` | | 年出栏 | 同上牧场范围 AND `asset_status=4` AND `status_change_date` 在当年且 ≤ 统计日 AND `status_change_date IS NOT NULL` | | 公牛/母牛 | 在存栏集合上 `gender='公'` / `gender='母'` | | 未标注性别 | 存栏总量 − 公牛 − 母牛 | | 年龄分档 | 在存栏集合上按 `age_months`(或 `birth_date` 计算月龄)分 0~6、7~12、≥13 | | 月末存栏 | 对每条档案用 **§2.2 月末存栏判定**(见下),非仅 `asset_status=1` | **`asset_status`**:`1` 正常 `2` 死淘 `3` 丢失 `4` 出栏。 **月末时点计入存栏(单头判定,记月末日为 `M`)** ```text 计入 ⇔ del_flag='0' AND pasture_id IN (可见牧场) AND pasture_id IS NOT NULL AND NOT (asset_status IN (2,3,4) AND status_change_date IS NOT NULL AND status_change_date <= M) AND asset_status 可识别(1 或 2/3/4 且 change_date > M) ``` 当前月:`M` 替换为 **统计日**。 #### `biz_yak_disease_warning`(预警牦牛数量) ```sql -- 口径示意:当年 1/1 0:00 ~ 统计日 23:59:59,去重 yak_no,牦牛须在纳入集合内 SELECT COUNT(DISTINCT w.yak_no) FROM biz_yak_disease_warning w INNER JOIN biz_yak_asset y ON y.yak_no = w.yak_no AND y.del_flag = '0' WHERE w.alert_time >= ? AND w.alert_time <= ? AND y.pasture_id IN (...) AND y.pasture_id IS NOT NULL; ``` ### 2.3 可选表 `biz_breeding_dashboard_cache`(性能优化) 数据量较大或刷新慢时启用;**非本期必建**。 | 字段 | 类型 | 说明 | | --- | --- | --- | | `id` | `bigint(20)` | 主键 | | `scope_key` | `varchar(64)` | 权限范围键,如 `all` 或 `user:{userId}` | | `stat_date` | `date` | 统计日 | | `payload_json` | `mediumtext` | 看板完整 JSON(与 **§3.2** `data` 结构一致) | | `expire_time` | `datetime` | 过期时间 | | `create_time` | `datetime` | 写入时间 | **索引**:`UNIQUE uk_scope_stat (scope_key, stat_date)`。 刷新时:先查未过期缓存命中则直接返回;否则实时计算后 UPSERT。TTL 建议 5~15 分钟(可配置)。 ### 2.4 索引建议(既有表) 若统计偏慢,确认已存在(`sql/biz_yak_asset.sql` 等已含大部分): | 表 | 建议索引 | | --- | --- | | `biz_yak_asset` | `(pasture_id, del_flag, asset_status)`、`status_change_date` | | `biz_yak_disease_warning` | `(alert_time)`、`(yak_no, alert_time)` | | `biz_grassland` | `(del_flag, degradation_level)`、`(del_flag, utilization_type)` | | `biz_pasture` | `(del_flag, biz_status)` | ### 2.5 可选缓存表 DDL ```sql CREATE TABLE IF NOT EXISTS `biz_breeding_dashboard_cache` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `scope_key` varchar(64) NOT NULL COMMENT '权限范围键', `stat_date` date NOT NULL COMMENT '统计日', `payload_json` mediumtext NOT NULL COMMENT '看板JSON', `expire_time` datetime NOT NULL COMMENT '过期时间', `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_scope_stat` (`scope_key`,`stat_date`), KEY `idx_expire_time` (`expire_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='养殖产业看板缓存(可选)'; ``` ### 2.6 数据权限实现 与「牦牛疾病预警」列表一致:聚合前解析**可见牧场 ID 集合** `allowedPastureIds`,传入 `BreedingIndustryStatsMapper` 的 `IN (...)` 条件(牦牛、预警、牧场数量);**草场**统计不加牧场条件。 **建议类**:`com.ruoyi.web.modules.industryservice.support.BreedingIndustryPastureScope`(或复用后续统一的 `IndustryPastureScopeHelper`)。 | 步骤 | 说明 | | --- | --- | | 1 | 查询 `biz_pasture`:`del_flag='0'` AND `biz_status=1`,得到候选 ID 列表 | | 2 | 按当前登录用户应用项目**数据权限**(部门/角色自定义范围,与预警 `allowedPastureIds` 同源);县级全量角色得到**全部**候选 ID | | 3 | 将结果写入统计上下文 `BreedingIndustryStatsContext.allowedPastureIds` | **三种语义(实现须区分)** | `allowedPastureIds` | 牦牛 / 预警 / 牧场数 | 草场 | | --- | --- | --- | | `null`(未限制,全量管理员) | 候选列表 = 步骤 1 全部正常牧场 ID | 全县 `del_flag=0` | | 非空列表 | `pasture_id IN (...)` 且 `pasture_id IS NOT NULL` | 不变 | | **空列表** `[]`(有权限框架但无可见牧场) | 牦牛类、预警、牧场数 **均为 0** | 仍全县统计 | **Controller 示例** ```java List allowed = pastureScope.resolveAllowedPastureIds(); return success(dashboardService.loadDashboard(allowed, forceRefresh)); ``` **Mapper 约定**:与 `BizYakDiseaseWarningMapper.xml` 相同写法——仅当 `allowedPastureIds != null && size > 0` 时追加 `pasture_id in (...)`;全量管理员传**完整 ID 列表**(勿传 `null` 跳过牦牛过滤,以免误统计 `pasture_id` 为空的档案)。 **预警**:`biz_yak_disease_warning` 与 `biz_yak_asset` 按 `yak_no` 关联后,再限制 `y.pasture_id IN (...)`(见 **§2.2** SQL)。 --- ## 3. 接口设计 **统一响应**:`AjaxResult`(`code` / `msg` / `data`)。 **权限**:`dataModel:breedingIndustry:query`(查看与刷新共用)。 **Base Path**:`/dataModel/breedingIndustry` 本期**仅 1 个**业务接口;**不提供** POST/PUT/DELETE 维护接口。 | # | 说明 | Method | URI | 权限 | | --- | --- | --- | --- | --- | | 3.1 | 看板全量数据 | GET | `/dashboard` | `query` | 前端进入页与点击「刷新」均调用 **3.1**。 #### 3.1.1 Query | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `forceRefresh` | boolean | N | 默认 `false`;`true` 时跳过 **§2.3** 缓存 | #### 3.1.2 响应 `data` 结构 | 节点 | 类型 | 说明 | | --- | --- | --- | | `statDate` | string | 统计日 `yyyy-MM-dd` | | `statYear` | int | 当前自然年,如 `2026` | | `overview` | object | 产业总览 **§3.1.3** | | `inventoryChange` | object | 牦牛存栏变动 **§3.1.4** | | `ageStructure` | array | 年龄结构 **§3.1.5** | | `grassland` | object | 草场类型占比 **§3.1.6** | #### 3.1.3 `overview` | 字段 | 类型 | 说明 | | --- | --- | --- | | `inventoryTotal` | int | 牦牛存栏总量(头);`biz_yak_herd_inventory` **最近填报年月**全县 `yak_total` 合计 | | `annualOutbound` | int | 牦牛年出栏量(头);`biz_yak_outbound_report` **最近填报年月**全县 `yak_outbound_count` 合计 | | `warningYakCount` | int | 预警牦牛数量(头);**§2.5** 档案口径 | | `pastureCount` | int | 农牧户(户);`biz_yak_outbound_report` **最近填报年月**全县 `farmer_household_count` 合计 | | `grasslandCount` | int | 草场数量(个) | | `grasslandAreaMu` | decimal | 草场面积(亩) | #### 3.1.4 `inventoryChange` | 字段 | 类型 | 说明 | | --- | --- | --- | | `bullCount` | int | 公牛数量(头);最近填报年月全县 `bull_total` 合计 | | `cowCount` | int | 母牛数量(头);最近填报年月全县 `cow_total` 合计 | | `unknownGenderCount` | int | 固定 **0**(不展示未标注性别) | | `monthlyTrend` | array | 滚动最近 **12** 自然月;固定 **12** 条 | `monthlyTrend[]` 元素: | 字段 | 类型 | 说明 | | --- | --- | --- | | `statYear` | int | 所属自然年 | | `month` | int | 所属月份 1~12 | | `bullCount` | int | 该月全县公牛合计;无数据为 **0** | | `cowCount` | int | 该月全县母牛合计;无数据为 **0** | #### 3.1.5 `ageStructure[]` | 字段 | 类型 | 说明 | | --- | --- | --- | | `bandCode` | string | `COW_YOUNG` / `COW_ADULT` | | `bandLabel` | string | `0~1.5岁幼畜` / `1.5岁以上成畜` | | `count` | int | 头数 | | `ratio` | decimal | 占比 0~100 数值,保留 1 位小数 | 两档固定返回(count 为 0 也返回);与大屏年龄结构一致;数据源为存栏填报 `cow_young_count` / `cow_adult_count`。 #### 3.1.6 `grassland` | 字段 | 类型 | 说明 | | --- | --- | --- | | `availableCount` | int | 可用草场(个) | | `inUseCount` | int | 使用中草场(个) | | `degradationStats` | array | 退化等级统计 | `degradationStats[]` 元素: | 字段 | 类型 | 说明 | | --- | --- | --- | | `degradationLevel` | int | 1~5 | | `degradationLabel` | string | 未退化、轻度… | | `count` | int | 个数 | | `ratio` | decimal | 占比 %(同 `ageStructure`) | 五档固定返回;`count` 之和应等于 `overview.grasslandCount`。 #### 3.1.7 响应示例(节选) ```json { "code": 200, "msg": "操作成功", "data": { "statDate": "2026-05-20", "statYear": 2026, "overview": { "inventoryTotal": 120, "annualOutbound": 8, "warningYakCount": 5, "pastureCount": 12, "grasslandCount": 80, "grasslandAreaMu": 12500.50 }, "inventoryChange": { "bullCount": 45, "cowCount": 60, "unknownGenderCount": 0, "monthlyTrend": [ { "statYear": 2025, "month": 8, "bullCount": 40, "cowCount": 55 }, { "statYear": 2026, "month": 5, "bullCount": 45, "cowCount": 60 } ] }, "ageStructure": [ { "bandCode": "COW_U1", "bandLabel": "1岁以下", "count": 30, "ratio": 25.0 } ], "grassland": { "availableCount": 50, "inUseCount": 40, "degradationStats": [ { "degradationLevel": 1, "degradationLabel": "未退化", "count": 30, "ratio": 37.5 } ] } } } ``` #### 3.1.8 异常与校验 | 场景 | 处理 | | --- | --- | | 无查询权限 | 403 或 `AjaxResult.error` 无权限 | | 聚合异常 | `AjaxResult.error("加载失败,请稍后重试")`,`data` 为空 | | 可见牧场为空 | 牦牛类指标均为 0;草场类仍按全县统计 | | 刷新中重复点击 | 前端防重;后端可选短锁(与资产同步类似) | --- ## 4. 实现要点(摘要) | 项 | 说明 | | --- | --- | | **月龄** | `COALESCE(age_months, TIMESTAMPDIFF(MONTH, birth_date, statDate))`,`birth_date` 空则 0 | | **性别** | 入库值 `trim` 后精确匹配 `公`/`母`;第三方若写「公牛」「母牛」等,Service 层归一后再统计 | | **存栏变动/年龄/趋势** | `HomeScreenStatsMapper` + `HomeScreenSupport.buildRolling12MonthBullCowTrend` / `buildCowAgeStructureFromHerdReport`;`BreedingIndustryDashboardSupport` 做 VO 转换 | | **总览填报三项** | `overview.inventoryTotal` / `annualOutbound` / `pastureCount` 取各表**最近填报年月**全县合计 | | **预警** | 仍用 `biz_yak_asset` + `biz_yak_disease_warning`,按 **§2.9** 可见牧场过滤 | | **比率** | `ratio = count * 100.0 / NULLIF(total, 0)`,分母为 0 时 ratio=0 | | **月末性能** | 优先 1 条 SQL + `CASE WHEN` 按月末分组,或 Java 循环 12 次;超过 3s 启用 **§2.3** 缓存 | | **日志** | 看板接口可 `@Log(title="养殖产业数据", businessType=OTHER)` 或仅记录慢查询 | **前端**:见同目录 `养殖产业数据前端技术方案.md`。 --- ## 5. 菜单与权限(示例) | 类型 | 名称 | 权限标识 | | --- | --- | --- | | 目录/菜单 | 养殖产业数据 | `dataModel:breedingIndustry:query` | | 按钮 | 刷新(与查看共用) | `dataModel:breedingIndustry:query` | 组件路径:`livestockIndustry/breedingIndustry/index`(挂载「产业数据模型」或同级目录,与菜单配置一致)。 **`sys_menu` 示例(`menu_id` 按环境递增)** ```sql -- 父菜单 parent_id 替换为「产业数据模型」实际 ID INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark) VALUES ('养殖产业数据', {parentId}, 10, 'breedingIndustry', 'livestockIndustry/breedingIndustry/index', 1, 0, 'C', '0', '0', 'dataModel:breedingIndustry:query', 'chart', 'admin', sysdate(), '养殖产业看板'); ``` 本期**无** `add`/`edit`/`remove`/`sync` 按钮权限。 --- ## 6. 需求追溯 | 功能需求 | 技术落点 | | --- | --- | | §5.2 产业总览 | `overview` + **§2.2** SQL | | §5.3 存栏变动 | `inventoryChange` + 填报表滚动 12 月(与大屏一致) | | §5.4 年龄结构 | `ageStructure` 母牛两档(填报最近年月) | | §5.5 草场占比 | `grassland` | | §5.1 刷新 | **3.1** `forceRefresh` | | §2.9 数据权限 | **§2.6** `BreedingIndustryPastureScope`(仅预警等档案类指标) | | §2.5 预警 | **§2.2** warning SQL | --- ## 7. 修订记录 | 版本 | 日期 | 说明 | | --- | --- | --- | | 1.3 | 2026-07-17 | 年龄结构两档 `COW_YOUNG`/`COW_ADULT` | | 1.2 | 2026-07-04 | 存栏变动/年龄/趋势改 `biz_yak_herd_inventory` 填报表;滚动 12 月;与大屏字段对齐 | | 1.1 | (既有) | 总览三项改填报合计 |