# 市场行情登记 — 技术方案

> 依据：同目录 `市场行情登记功能需求.md`。日度行情 **CRUD** + **逻辑删除**；**渠道**关联 `biz_trade_market`；**均价**服务端按 `(最低价+最高价)/2` 计算；概率合计 **100%**。

---

## 1. 技术架构

| 项 | 说明 |
| --- | --- |
| **后端** | RuoYi **v3.9.2**（**springboot2** 分支）：JDK 8、Spring Boot 2.x、Spring MVC、MyBatis、Druid |
| **数据库** | MySQL **5.7.39**，InnoDB，`utf8mb4` |
| **前端** | 若依 Vue2；产地 `RegionCascader`；渠道下拉；字段 **小驼峰**（本方案不展开页面） |

**分层**：`Controller` → `Service`（`MarketDailyQuoteValidation`、**均价计算**、维度唯一校验）→ `Mapper`/XML → `domain`。

**代码包（建议）**：`com.ruoyi.web.modules.trademarket`

**关联主数据**：`biz_trade_market`（畜牧流通 · 交易市场管理）；仅 `del_flag=0` 且 `operate_status=1` 的市场可作为渠道。

**业务摘要**

| 场景 | 行为 |
| --- | --- |
| 新增/修改 | 校验维度与概率；**重算** `avg_price`；可选字段空存 `NULL` |
| 列表/详情 | 仅 `del_flag=0`；关联市场名称；排序 `quote_date DESC, create_time DESC, id DESC` |
| 列表筛选 | 日期区间、`trade_market_id` 精确、`county_code` 精确（产地至县） |
| 删除 | `del_flag=2`；有关联时 `ServiceException` |
| 唯一性 | 同 **§2.4** 维度组合在 `del_flag=0` 内唯一（Service 校验） |

---

## 2. 数据库设计

### 2.1 表 `biz_market_daily_quote`（日度行情）

| 字段 | 类型 | 非空 | 默认值 | 说明 |
| --- | --- | --- | --- | --- |
| `id` | `bigint(20)` | Y | 自增 | 主键 |
| `quote_date` | `date` | Y | — | 行情日期 |
| `province_code` | `varchar(12)` | Y | — | 省编码 |
| `province_name` | `varchar(64)` | Y | — | 省名称 |
| `city_code` | `varchar(12)` | Y | — | 市编码 |
| `city_name` | `varchar(64)` | Y | — | 市名称 |
| `county_code` | `varchar(12)` | Y | — | 县编码 |
| `county_name` | `varchar(64)` | Y | — | 县名称 |
| `trade_market_id` | `bigint(20)` | Y | — | 渠道（交易市场 id） |
| `cattle_gender` | `tinyint(4)` | Y | `1` | 公母 **§2.3** |
| `weight_grade` | `tinyint(4)` | N | NULL | 重量档 |
| `growth_stage` | `tinyint(4)` | N | NULL | 生长阶段 |
| `age_month_grade` | `tinyint(4)` | N | NULL | 月龄档 |
| `fat_grade` | `tinyint(4)` | N | NULL | 膘情 |
| `price_min` | `decimal(12,2)` | Y | — | 最低价 |
| `price_max` | `decimal(12,2)` | Y | — | 最高价 |
| `price_unit` | `tinyint(4)` | Y | `2` | 单位：1 元/头 2 元/斤 |
| `avg_price` | `decimal(12,2)` | Y | — | 均价（服务端计算落库） |
| `supply_status` | `tinyint(4)` | Y | — | 出栏情况 |
| `change_factors` | `varchar(128)` | Y | — | 变化因素，逗号分隔枚举码，如 `1,3,5` |
| `prob_rise` | `decimal(5,2)` | Y | — | 看涨概率 % |
| `prob_flat` | `decimal(5,2)` | Y | — | 看平概率 % |
| `prob_fall` | `decimal(5,2)` | Y | — | 看跌概率 % |
| `outbound_advice` | `varchar(500)` | Y | — | 出栏建议 |
| `del_flag` | `char(1)` | Y | `'0'` | `0` 存在 `2` 删除 |
| `create_by` / `create_time` / `update_by` / `update_time` | 若依惯例 | — | — | 审计 |

**索引**：`PRIMARY KEY (id)`；`KEY idx_quote_date (quote_date)`；`KEY idx_trade_market_id (trade_market_id)`；`KEY idx_county_code (county_code)`；`KEY idx_del_flag (del_flag)`；`KEY idx_quote_dim (quote_date, county_code, trade_market_id, cattle_gender)`（辅助查重，唯一性以 Service 为准，因可空维度需归一化）。

### 2.2 DDL（MySQL 5.7）

```sql
CREATE TABLE `biz_market_daily_quote` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `quote_date` date NOT NULL COMMENT '行情日期',
  `province_code` varchar(12) NOT NULL COMMENT '省编码',
  `province_name` varchar(64) NOT NULL COMMENT '省名称',
  `city_code` varchar(12) NOT NULL COMMENT '市编码',
  `city_name` varchar(64) NOT NULL COMMENT '市名称',
  `county_code` varchar(12) NOT NULL COMMENT '县编码',
  `county_name` varchar(64) NOT NULL COMMENT '县名称',
  `trade_market_id` bigint(20) NOT NULL COMMENT '交易市场ID',
  `cattle_gender` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1公牛2母牛3混合',
  `weight_grade` tinyint(4) DEFAULT NULL COMMENT '重量档',
  `growth_stage` tinyint(4) DEFAULT NULL COMMENT '生长阶段',
  `age_month_grade` tinyint(4) DEFAULT NULL COMMENT '月龄档',
  `fat_grade` tinyint(4) DEFAULT NULL COMMENT '膘情',
  `price_min` decimal(12,2) NOT NULL COMMENT '最低价',
  `price_max` decimal(12,2) NOT NULL COMMENT '最高价',
  `price_unit` tinyint(4) NOT NULL DEFAULT '2' COMMENT '1元/头2元/斤',
  `avg_price` decimal(12,2) NOT NULL COMMENT '均价',
  `supply_status` tinyint(4) NOT NULL COMMENT '出栏情况',
  `change_factors` varchar(128) NOT NULL COMMENT '变化因素枚举码逗号分隔',
  `prob_rise` decimal(5,2) NOT NULL COMMENT '看涨%',
  `prob_flat` decimal(5,2) NOT NULL COMMENT '看平%',
  `prob_fall` decimal(5,2) NOT NULL COMMENT '看跌%',
  `outbound_advice` varchar(500) NOT 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 '更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_quote_date` (`quote_date`),
  KEY `idx_trade_market_id` (`trade_market_id`),
  KEY `idx_county_code` (`county_code`),
  KEY `idx_del_flag` (`del_flag`),
  KEY `idx_quote_dim` (`quote_date`,`county_code`,`trade_market_id`,`cattle_gender`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='日度市场行情';
```

### 2.3 枚举（存库 tinyint / 码串）

**公母 `cattle_gender`**

| 值 | 标签 |
| --- | --- |
| 1 | 公牛 |
| 2 | 母牛 |
| 3 | 公母混合 |

**重量 `weight_grade`**

| 值 | 标签 |
| --- | --- |
| 1 | 50斤以下 |
| 2 | 50-100斤 |
| 3 | 100-200斤 |
| 4 | 200-300斤 |
| 5 | 300-500斤 |
| 6 | 500-800斤 |
| 7 | 800斤以上 |

**生长阶段 `growth_stage`**

| 值 | 标签 |
| --- | --- |
| 1 | 犊牛 |
| 2 | 架子牛 |
| 3 | 屠宰牛 |

**月龄 `age_month_grade`**

| 值 | 标签 |
| --- | --- |
| 1 | 1月龄 |
| 2 | 2-3月龄 |
| 3 | 4-5月龄 |
| 4 | 6-7月龄 |
| 5 | 8-9月龄 |
| 6 | 10-11月龄 |
| 7 | 12月龄 |

**膘情 `fat_grade`**

| 值 | 标签 |
| --- | --- |
| 1 | 43% |
| 2 | 45% |
| 3 | 50% |
| 4 | 53% |

**单位 `price_unit`**

| 值 | 标签 |
| --- | --- |
| 1 | 元/头 |
| 2 | 元/斤 |

**出栏情况 `supply_status`**

| 值 | 标签 |
| --- | --- |
| 1 | 货源充足 |
| 2 | 货源减少 |
| 3 | 货源增多 |

**变化因素 `change_factors`（多选，库存逗号分隔码）**

| 码 | 标签 |
| --- | --- |
| 1 | 天气 |
| 2 | 节日 |
| 3 | 政策 |
| 4 | 供大于求 |
| 5 | 供不应求 |
| 6 | 其他产地冲击 |
| 7 | 产地抬价 |
| 8 | 其他 |

**删除标记 `del_flag`**

| 值 | 含义 |
| --- | --- |
| 0 | 正常 |
| 2 | 已逻辑删除 |

### 2.4 维度唯一（Service）

查重键：`quote_date` + `county_code` + `trade_market_id` + `cattle_gender` + `IFNULL(weight_grade,0)` + `IFNULL(growth_stage,0)` + `IFNULL(age_month_grade,0)` + `IFNULL(fat_grade,0)`，在 `del_flag=0` 且排除当前 `id`（修改时）下 `count>0` 则拒绝。

---

## 3. 接口设计

**统一响应**：`AjaxResult` / `TableDataInfo`。

**权限标识**：`tradeMarket:marketQuote:list|query|add|edit|remove`

**Base Path**：`/tradeMarket/marketQuote`

| # | 说明 | Method | URI | 权限 |
| --- | --- | --- | --- | --- |
| 3.1 | 分页列表 | GET | `/tradeMarket/marketQuote/list` | `list` |
| 3.2 | 详情 | GET | `/tradeMarket/marketQuote/{id}` | `query` |
| 3.3 | 新增 | POST | `/tradeMarket/marketQuote` | `add` |
| 3.4 | 修改 | PUT | `/tradeMarket/marketQuote` | `edit` |
| 3.5 | 删除 | DELETE | `/tradeMarket/marketQuote/{ids}` | `remove` |
| 3.6 | 渠道下拉 | GET | `/tradeMarket/marketQuote/tradeMarketOptions` | `list` 或 `add` |

#### 3.1.1 列表 Query

| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `pageNum` / `pageSize` | int | N | 默认 `1` / `20` |
| `quoteDate` | date | N | 单日精确 `quote_date = ?` |
| `beginQuoteDate` / `endQuoteDate` | date | N | 区间（与 `quoteDate` 二选一或区间优先） |
| `tradeMarketId` | long | N | 渠道精确 |
| `countyCode` | string | N | 县编码精确（产地筛选） |

**列表行扩展（驼峰）**：`tradeMarketName`、`cattleGenderName`、`weightGradeName` 等标签字段由 Service/字典填充；`changeFactorNames`（码转中文列表）。

**排序**：`quote_date DESC, create_time DESC, id DESC`。

#### 3.2.1 详情 `data`

含 **§2.1** 全部业务字段 + 上述 `*Name` 扩展 + 审计字段。

#### 3.3.1 新增 / 修改 Body（驼峰）

| 字段 | 新增 | 修改 | 说明 |
| --- | --- | --- | --- |
| `id` | — | Y | 主键 |
| `quoteDate` | Y | Y | `yyyy-MM-dd`；≤ 今天 |
| `provinceCode` / `provinceName` | Y | Y | 省 |
| `cityCode` / `cityName` | Y | Y | 市 |
| `countyCode` / `countyName` | Y | Y | 县 |
| `tradeMarketId` | Y | Y | 须存在且市场 `operate_status=1` |
| `cattleGender` | Y | Y | 1/2/3；缺省 1 |
| `weightGrade` | N | N | 1～7 或 null |
| `growthStage` | N | N | 1～3 或 null |
| `ageMonthGrade` | N | N | 1～7 或 null |
| `fatGrade` | N | N | 1～4 或 null |
| `priceMin` / `priceMax` | Y | Y | >0；min≤max |
| `priceUnit` | Y | Y | 1/2；缺省 2 |
| `avgPrice` | N | N | **可不传**；以服务端计算为准 |
| `supplyStatus` | Y | Y | 1～3 |
| `changeFactors` | Y | Y | 整型码数组 `[1,3,5]` 或逗号串，至少 1 项 |
| `probRise` / `probFlat` / `probFall` | Y | Y | 合计 100（±0.01） |
| `outboundAdvice` | Y | Y | 1～500 字 |

#### 3.4 渠道下拉 `tradeMarketOptions`

- 返回 `{ id, marketName }[]`：`biz_trade_market` 且 `del_flag=0`、`operate_status=1`，按 `market_name` 排序。  
- 亦可前端直调 `/livestock/tradeMarket/list?operateStatus=1&pageSize=500`（需跨模块权限时由菜单统一授权）。

#### 3.5 服务端校验（`MarketDailyQuoteValidation`）

| 项 | 规则 |
| --- | --- |
| 行情日期 | 非空；≤ 当前日期 |
| 区划 | 省/市/县码、名齐全 |
| 渠道 | `trade_market_id` 有效且正常 |
| 枚举 | 各字段 ∈ **§2.3** |
| 价格 | `price_min`≤`price_max`；`avg_price = (min+max)/2` 四舍五入 2 位 |
| 变化因素 | 至少 1 个合法码；去重排序后入库逗号串 |
| 概率 | `prob_rise+prob_flat+prob_fall` ∈ [99.99, 100.01] 或整数 100 |
| 出栏建议 | trim 后 1～500 字 |
| 维度唯一 | **§2.4** |
| 删除 | `MarketDailyQuoteReferenceChecker.hasReference(id)` 为 false（本期可占位 `false`） |

#### 3.6 Mapper 约定

- 条件均带 `del_flag='0'`。  
- 日期：`quote_date BETWEEN #{beginQuoteDate} AND #{endQuoteDate}` 或 `quote_date = #{quoteDate}`。  
- 列表 `LEFT JOIN biz_trade_market` 取 `market_name`（`del_flag=0`）。  
- 逻辑删除：`UPDATE … SET del_flag='2' WHERE id=? AND del_flag='0'`。

---

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

| 类型 | 名称 | 权限标识 |
| --- | --- | --- |
| 菜单 | 市场行情登记 | `tradeMarket:marketQuote:list` |
| 按钮 | 查询 | `tradeMarket:marketQuote:query` |
| 按钮 | 新增 | `tradeMarket:marketQuote:add` |
| 按钮 | 修改 | `tradeMarket:marketQuote:edit` |
| 按钮 | 删除 | `tradeMarket:marketQuote:remove` |

组件路径：`tradeMarket/marketQuote/index`（「交易市场平台」目录下）。

---

## 5. 模块对照

| 项 | 交易市场管理 | 市场行情登记（本模块） |
| --- | --- | --- |
| 表 | `biz_trade_market` | `biz_market_daily_quote` |
| Base Path | `/livestock/tradeMarket` | `/tradeMarket/marketQuote` |
| 关系 | 主档 | 外键 `trade_market_id` 引用主档 |

---

## 6. 交付清单

- [ ] `sql/biz_market_daily_quote.sql`、菜单权限 SQL  
- [ ] `BizMarketDailyQuote`、Mapper/XML、Service、Controller  
- [ ] `MarketDailyQuoteValidation`、均价与概率、维度唯一  
- [ ] `tradeMarketOptions` 或文档化复用 livestock 列表  
- [ ] 单元测试（均价、概率合计、查重、日期 SQL）  
- [ ] MockMvc（list 筛选、CRUD）

---

## 7. 修订记录

| 版本 | 说明 |
| --- | --- |
| 1.0 | 初稿：`biz_market_daily_quote`；`/tradeMarket/marketQuote`；关联 `biz_trade_market`；枚举与均价/概率规则 |
