Files
MES/yawei-mes/.tasks/2025-09-18_定氮仪接入.md

1108 lines
41 KiB
Markdown
Raw Permalink Normal View History

2026-04-02 10:38:23 +08:00
# 背景
文件名2025-09-18_定氮仪接入.md
创建于2025-09-18 15:30:00
创建者:用户
主分支dev-plsw
任务分支feature/nitrogen_analyzer_integration
Yolo模式Ask
# 任务描述
新增定氮仪质检模块实现TCP协议数据接收和与现有四种质检单的关联功能。
## 核心需求
1. **TCP数据接收**接收定氮仪通过网口TCP传输的蛋白检测数据
2. **数据存储**:建立专门的定氮仪质检表和配置表
3. **关联绑定**:与原料、中间产品、外来品、最终产品质检单建立关联
4. **前端展示**:创建定氮仪质检的主页和表单页面
5. **操作集成**:在四种质检单主页添加"定氮仪质检"操作按钮
## TCP数据格式示例
```json
{
"addAlkali_mode": "0",
"alkali_volume": "10.0",
"analysis_time": "2025-07-01 15:16:54",
"conversion_coefficient": "0.014",
"created": "Admin",
"dilution_volume": "50.0",
"distillation_Unit": "0",
"distillation_Value": "300.0",
"events": "",
"high_error_limit": "0.0",
"intercept": "0.0",
"low_error_limit": "0.0",
"method_name": "(NH4)2S04",
"preheating_time": "0.0",
"protein_factor": "0.0",
"receive_cup_cleaning": "0",
"receiver_volume": "25.0",
"result_Value": "0.0",
"result_type": "% Nitrogen",
"sample_Name": "2",
"sample_Unit": "g",
"sample_Value": "0.014",
"sample_id": "8935",
"slope": "0.0",
"steam_power": "100.0",
"test_method": "0",
"titrant_volume_for_blank": "0.0438",
"titrant_volume_for_sample": "0.0413",
"titration_acid_concentration": "0.1194",
"titration_method": "0",
"titration_mode": "0",
"tube_cleaning": "0",
"tube_emptying": "1",
"type": "2",
"wait_time": "0.0",
"water_coefficient": "1.0",
"apparatusName": "KI160",
"manufacturerName": "ZDYC",
"data": [
{
"circulateOrder": "2",
"inspectionDate": "2025-07-01",
"inspectionTime": "15:16:54",
"inspectionProject": "错题",
"inspectionResult": "0.0000% Nitrogen"
}
]
}
```
# 项目概览
MES制造执行系统质检模块扩展集成定氮仪设备数据形成完整的蛋白质检测数据链路。
⚠️ 警告:永远不要修改此部分 ⚠️
遵循RIPER-5协议
- RESEARCH: 深入分析现有质检架构
- INNOVATE: 设计多种实现方案
- PLAN: 制定详细技术规范
- EXECUTE: 精确实施计划
- REVIEW: 验证实施结果
⚠️ 警告:永远不要修改此部分 ⚠️
# 分析
基于对现有质检系统的研究,发现了以下关键架构模式:
## 现有质检系统架构
1. **数据库层**
- 质检主表:`qc_*_inspection`如qc_material_inspection、qc_final_inspection等
- 配置表:`qc_*_inspection_config`存储JSON格式的动态表单配置
- 每种质检类型都有独立的表结构
2. **后端服务层**
- Controller处理HTTP请求标准RESTful API
- Service接口和实现业务逻辑处理
- Mapper数据访问层使用MyBatis-Plus
3. **前端组件层**
- 主页index.vue列表展示、查询、操作
- 表单页form.vue新增、编辑、查看
- 动态表单:基于配置生成的表单字段
- 路由配置hidden路由用于表单页面
4. **关联机制**
- 质检单可从报工单、采购入库单等创建
- 通过query参数传递关联信息
- 支持查看已关联的质检单
5. **TCP数据处理**
- 系统已有Modbus TCP数据处理基础WorkshopEquipmentServiceImpl
- 支持JSON、16进制、原始格式数据处理
## 定氮仪数据特点
- 复杂的JSON结构包含检测参数和结果
- 需要与蛋白检测相关的其他质检单关联
- 包含设备信息apparatusName、manufacturerName
- 有嵌套的检测结果数组
# 提议的解决方案
## 方案一:独立质检类型架构
### 优势
- 与现有质检系统保持一致的架构模式
- 独立的数据表和配置,便于扩展和维护
- 可复用现有的动态表单生成机制
- 符合系统的设计规范
### 实现路径
1. 创建独立的定氮仪质检表和配置表
2. 实现专门的TCP接收服务
3. 建立与四种质检单的关联机制
4. 创建前端页面和路由配置
### 技术考虑
- 数据库设计需要支持复杂的JSON结构
- TCP服务需要与现有Modbus处理机制集成
- 关联绑定需要双向查询支持
## 方案二:扩展现有质检架构
### 优势
- 减少代码重复,复用现有组件
- 统一的质检数据管理
- 更紧密的系统集成
### 实现路径
1. 扩展现有质检表结构,添加定氮仪专用字段
2. 修改配置系统,支持定氮仪数据类型
3. 在现有页面中添加定氮仪相关功能
### 技术考虑
- 可能影响现有质检功能
- 数据库变更风险较大
- 配置复杂度增加
## 方案三:混合架构方案
### 优势
- 平衡独立性和集成性
- 风险可控,渐进式实现
- 保留未来扩展空间
### 实现路径
1. 创建独立的定氮仪核心表
2. 通过关联表建立与现有质检的联系
3. 创建专门的管理界面
4. 在现有质检页面添加关联查看功能
基于系统的复杂性和扩展性考虑,**推荐采用方案一:独立质检类型架构**,这样既能保持系统架构的一致性,又能确保功能的独立性和可维护性。
**用户确认:采用方案一**
## 🔍 重新设计 - TCP字段映射策略
### 设计原则确认
1. **扩展性好的方式** - 核心检测字段独立存储data数组用子表设备配置用JSON
2. **部分字段放在JSON原始数据** - 设备操作参数(cleaning, emptying, mode等)
3. **标准数据库命名规范** - 使用下划线命名,符合数据库规范
### TCP字段分类存储策略
**🎯 主表独立字段 (核心业务数据):**
- 样品信息: sample_name, sample_id, sample_value, sample_unit
- 检测结果: result_value, result_type, nitrogen_content, protein_content
- 设备信息: apparatus_name, manufacturer_name, method_name
- 关键参数: conversion_coefficient, protein_factor, alkali_volume等滴定参数
- 质量控制: high_error_limit, low_error_limit, intercept, slope
**📋 子表存储 (data数组):**
- qc_nitrogen_analyzer_detail表存储data数组中的检测详情
- 支持一个检测对应多条详细结果
**📦 JSON字段存储 (设备配置参数):**
```json
{
"addAlkali_mode": "0",
"distillation_Unit": "0",
"events": "",
"preheating_time": "0.0",
"receive_cup_cleaning": "0",
"test_method": "0",
"titration_method": "0",
"titration_mode": "0",
"tube_cleaning": "0",
"tube_emptying": "1",
"type": "2",
"wait_time": "0.0"
}
```
### 完整字段对应关系
| TCP字段 | 数据库字段 | 存储位置 | 说明 |
|---------|------------|----------|------|
| sample_Name | sample_name | 主表 | 样品名称 |
| sample_id | sample_id | 主表 | 样品ID |
| sample_Value | sample_value | 主表 | 样品重量 |
| sample_Unit | sample_unit | 主表 | 样品单位 |
| analysis_time | analysis_time | 主表 | 分析时间 |
| result_Value | result_value | 主表 | 检测结果值 |
| result_type | result_type | 主表 | 结果类型 |
| method_name | method_name | 主表 | 检测方法 |
| apparatusName | apparatus_name | 主表 | 设备名称 |
| manufacturerName | manufacturer_name | 主表 | 制造商名称 |
| conversion_coefficient | conversion_coefficient | 主表 | 转换系数 |
| protein_factor | protein_factor | 主表 | 蛋白质因子 |
| alkali_volume | alkali_volume | 主表 | 碱液体积 |
| dilution_volume | dilution_volume | 主表 | 稀释体积 |
| receiver_volume | receiver_volume | 主表 | 接收器体积 |
| titrant_volume_for_blank | titrant_volume_blank | 主表 | 空白滴定液体积 |
| titrant_volume_for_sample | titrant_volume_sample | 主表 | 样品滴定液体积 |
| titration_acid_concentration | titration_acid_concentration | 主表 | 滴定酸浓度 |
| distillation_Value | distillation_value | 主表 | 蒸馏值 |
| steam_power | steam_power | 主表 | 蒸汽功率 |
| water_coefficient | water_coefficient | 主表 | 水系数 |
| high_error_limit | high_error_limit | 主表 | 高错误限值 |
| low_error_limit | low_error_limit | 主表 | 低错误限值 |
| intercept | intercept | 主表 | 截距 |
| slope | slope | 主表 | 斜率 |
| created | data_creator | 主表 | TCP数据创建者 |
| data[].circulateOrder | circulate_order | 子表 | 循环顺序 |
| data[].inspectionDate | inspection_date | 子表 | 检验日期 |
| data[].inspectionTime | inspection_time | 子表 | 检验时间 |
| data[].inspectionProject | inspection_project | 子表 | 检验项目 |
| data[].inspectionResult | inspection_result | 子表 | 检验结果 |
| 设备配置参数 | device_parameters | 主表JSON | 设备操作参数 |
| 完整TCP数据 | tcp_raw_data | 主表JSON | 原始数据备份 |
# 详细技术规范
## 数据库设计
### 1. 定氮仪质检主表 (qc_nitrogen_analyzer_inspection)
```sql
CREATE TABLE `qc_nitrogen_analyzer_inspection` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`inspection_no` varchar(50) NOT NULL COMMENT '质检单号',
-- 样品基本信息 (核心字段)
`sample_name` varchar(100) DEFAULT NULL COMMENT '样品名称',
`sample_id` varchar(50) DEFAULT NULL COMMENT '样品ID',
`sample_value` decimal(10, 6) DEFAULT NULL COMMENT '样品重量(g)',
`sample_unit` varchar(20) DEFAULT 'g' COMMENT '样品单位',
`analysis_time` datetime DEFAULT NULL COMMENT '分析时间',
-- 检测结果 (核心字段)
`result_value` decimal(10, 6) DEFAULT NULL COMMENT '检测结果值',
`result_type` varchar(50) DEFAULT NULL COMMENT '结果类型',
`nitrogen_content` decimal(10, 6) DEFAULT NULL COMMENT '氮含量(%) - 业务计算字段',
`protein_content` decimal(10, 6) DEFAULT NULL COMMENT '蛋白质含量(%) - 业务计算字段',
-- 检测方法和设备 (核心字段)
`method_name` varchar(100) DEFAULT NULL COMMENT '检测方法',
`apparatus_name` varchar(100) DEFAULT NULL COMMENT '设备名称',
`manufacturer_name` varchar(100) DEFAULT NULL COMMENT '制造商名称',
-- 关键检测参数 (核心字段)
`conversion_coefficient` decimal(10, 6) DEFAULT NULL COMMENT '转换系数',
`protein_factor` decimal(10, 6) DEFAULT NULL COMMENT '蛋白质因子',
`alkali_volume` decimal(10, 3) DEFAULT NULL COMMENT '碱液体积(mL)',
`dilution_volume` decimal(10, 3) DEFAULT NULL COMMENT '稀释体积(mL)',
`receiver_volume` decimal(10, 3) DEFAULT NULL COMMENT '接收器体积(mL)',
`titrant_volume_blank` decimal(12, 8) DEFAULT NULL COMMENT '空白滴定液体积(mL)',
`titrant_volume_sample` decimal(12, 8) DEFAULT NULL COMMENT '样品滴定液体积(mL)',
`titration_acid_concentration` decimal(12, 8) DEFAULT NULL COMMENT '滴定酸浓度(%)',
`distillation_value` decimal(10, 3) DEFAULT NULL COMMENT '蒸馏值',
`steam_power` decimal(5, 2) DEFAULT NULL COMMENT '蒸汽功率(%)',
`water_coefficient` decimal(10, 6) DEFAULT NULL COMMENT '水系数',
-- 质量控制参数 (核心字段)
`high_error_limit` decimal(10, 6) DEFAULT NULL COMMENT '高错误限值',
`low_error_limit` decimal(10, 6) DEFAULT NULL COMMENT '低错误限值',
`intercept` decimal(10, 6) DEFAULT NULL COMMENT '截距',
`slope` decimal(10, 6) DEFAULT NULL COMMENT '斜率',
-- 业务字段
`inspector` varchar(50) DEFAULT NULL COMMENT '检验员',
`inspection_date` datetime DEFAULT NULL COMMENT '检验日期',
`data_creator` varchar(50) DEFAULT NULL COMMENT 'TCP数据创建者',
`status` varchar(20) DEFAULT 'pending' COMMENT '状态(pending/completed/failed)',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
-- 原始数据和设备配置参数 (JSON字段)
`tcp_raw_data` text COMMENT 'TCP完整原始数据JSON',
`device_parameters` text COMMENT '设备配置参数JSON(包含cleaning,emptying,mode等)',
-- 系统字段
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志0代表存在 2代表删除',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_inspection_no` (`inspection_no`),
KEY `idx_sample_id` (`sample_id`),
KEY `idx_analysis_time` (`analysis_time`),
KEY `idx_inspection_date` (`inspection_date`),
KEY `idx_apparatus` (`apparatus_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='定氮仪质检单主表';
```
### 1.1. 定氮仪检测详情表 (qc_nitrogen_analyzer_detail)
```sql
CREATE TABLE `qc_nitrogen_analyzer_detail` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`inspection_id` bigint NOT NULL COMMENT '质检单ID',
`circulate_order` varchar(20) DEFAULT NULL COMMENT '循环顺序',
`inspection_date` date DEFAULT NULL COMMENT '检验日期',
`inspection_time` time DEFAULT NULL COMMENT '检验时间',
`inspection_project` varchar(100) DEFAULT NULL COMMENT '检验项目',
`inspection_result` varchar(200) DEFAULT NULL COMMENT '检验结果',
`sequence_no` int DEFAULT 0 COMMENT '序列号',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_inspection_id` (`inspection_id`),
KEY `idx_inspection_date` (`inspection_date`),
FOREIGN KEY (`inspection_id`) REFERENCES `qc_nitrogen_analyzer_inspection` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='定氮仪检测详情表';
```
### 2. 定氮仪质检配置表 (qc_nitrogen_analyzer_config)
```sql
CREATE TABLE `qc_nitrogen_analyzer_config` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`config_name` varchar(100) NOT NULL COMMENT '配置名称',
`config_content` text NOT NULL COMMENT '配置内容(JSON)',
`is_default` char(1) NOT NULL DEFAULT '0' COMMENT '是否默认(0=否,1=是)',
`remark` varchar(500) 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 '更新时间',
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志0代表存在 2代表删除',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_config_name` (`config_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='定氮仪质检配置表';
```
## ⚠️ **重要修正:一对一关系设计**
**业务逻辑确认**
-**一对一关系**:一条定氮仪检测数据 ↔ 一个质检单
-**绑定后隐藏**:质检单绑定后不再显示为可选项
-**独占绑定**:每个数据只能绑定一次
### 3. 修正后的关联设计
#### 3.1 定氮仪质检主表中直接存储关联信息
```sql
-- 在 qc_nitrogen_analyzer_inspection 主表中添加关联字段
`associated_type` varchar(50) DEFAULT NULL COMMENT '关联质检类型(material/intermediate/foreign/final)',
`associated_id` bigint DEFAULT NULL COMMENT '关联质检单ID',
`association_time` datetime DEFAULT NULL COMMENT '关联时间',
`associated_by` varchar(64) DEFAULT NULL COMMENT '关联操作人',
`status` varchar(20) DEFAULT 'unbound' COMMENT '状态(unbound=未绑定/bound=已绑定/completed=已完成)',
-- 唯一约束确保一对一关系
UNIQUE KEY `idx_associated_unique` (`associated_type`, `associated_id`),
```
#### 3.2 现有质检表添加反向关联字段
```sql
-- 为现有四个质检表添加定氮仪关联字段
ALTER TABLE `qc_material_inspection` ADD COLUMN `nitrogen_analyzer_id` bigint DEFAULT NULL;
ALTER TABLE `qc_intermediate_inspection` ADD COLUMN `nitrogen_analyzer_id` bigint DEFAULT NULL;
ALTER TABLE `qc_foreign_inspection` ADD COLUMN `nitrogen_analyzer_id` bigint DEFAULT NULL;
ALTER TABLE `qc_final_inspection` ADD COLUMN `nitrogen_analyzer_id` bigint DEFAULT NULL;
```
#### 3.3 创建视图过滤未绑定质检单
```sql
-- 未绑定质检单视图(用于前端显示可选项)
CREATE VIEW `v_unbound_material_inspections` AS
SELECT * FROM `qc_material_inspection`
WHERE `nitrogen_analyzer_id` IS NULL AND `del_flag` = '0';
```
**关联设计优势**
-**性能优化**:无需中间关联表,减少查询复杂度
-**数据一致性**:唯一约束确保一对一关系
-**查询简化**:视图自动过滤已绑定数据
-**状态清晰**status字段明确绑定状态
## 后端架构设计
### 1. TCP数据接收服务
**文件路径**: `yjh-mes/src/main/java/cn/sourceplan/quality/service/INitrogenAnalyzerTcpService.java`
- 接收TCP数据的服务接口
- 数据解析和验证
- 异步处理机制
### 2. 质检服务层
**文件路径**: `yjh-mes/src/main/java/cn/sourceplan/quality/service/INitrogenAnalyzerService.java`
- 定氮仪质检CRUD操作
- 关联绑定业务逻辑
- 数据查询和统计
### 3. 控制器层
**文件路径**: `yjh-mes/src/main/java/cn/sourceplan/quality/controller/KNAInfoController.java`
- RESTful API接口 (映射路径: `/quality/KNAInfo`)
- TCP数据接收端点
- 关联操作接口
- 权限注解: `@PreAuthorize("@ss.hasPermi('quality:KNAInfo:*')")`
### 4. 数据访问层
**文件路径**: `yjh-mes/src/main/java/cn/sourceplan/quality/mapper/NitrogenAnalyzerMapper.java`
- MyBatis-Plus数据访问
- 复杂查询SQL
- 关联查询支持
## 前端架构设计
### 1. 主页组件
**文件路径**: `mes-ui/src/views/mes/quality/nitrogenAnalyzer/index.vue`
- 列表展示和查询
- 关联绑定操作
- 状态管理
### 2. 表单组件
**文件路径**: `mes-ui/src/views/mes/quality/nitrogenAnalyzer/form.vue`
- 数据录入和编辑
- 详情查看
- TCP数据展示
### 3. 关联绑定弹窗
**文件路径**: `mes-ui/src/views/mes/quality/nitrogenAnalyzer/components/AssociationDialog.vue`
- 质检类型选择
- 关联数据列表
- 绑定操作确认
### 4. API接口
**文件路径**: `mes-ui/src/api/mes/quality/nitrogenAnalyzer.js`
- HTTP请求封装
- 接口统一管理
### 5. 路由配置
**文件路径**: `mes-ui/src/router/index.js`
- 页面路由添加
- 权限控制设置
## 集成点设计
### 1. 现有质检页面修改
需要在以下文件的操作列添加"定氮仪质检"按钮:
- `mes-ui/src/views/mes/quality/materialQuality/index.vue`
- `mes-ui/src/views/mes/quality/foreignQuality/index.vue`
- `mes-ui/src/views/mes/quality/intermediateQuality/index.vue`
- `mes-ui/src/views/mes/quality/finalQuality/index.vue`
### 2. TCP服务集成
创建独立的TCP Socket监听服务不依赖现有设备管理服务。
### 3. 权限配置
无需特殊权限配置,所有质检用户均可使用。
## 核心业务流程
### 1. TCP数据接收流程
1. 定氮仪设备通过TCP发送检测数据
2. 后端TCP服务接收并解析JSON数据
3. 验证数据完整性和格式
4. 创建定氮仪质检记录
5. 存储原始TCP数据和解析后的结构化数据
### 2. 关联绑定流程(修正版)
1. 用户在定氮仪质检主页点击"绑定质检单"按钮
2. 弹出关联绑定对话框,选择质检类型(原料/中间产品/外来品/最终产品)
3. 系统查询该类型的**未绑定**质检单列表(通过视图过滤)
4. 用户选择具体质检单进行一对一绑定
5. 系统执行双向绑定:
- 更新定氮仪记录的`associated_type``associated_id``status`='bound'
- 更新质检单的`nitrogen_analyzer_id`字段
6. 绑定后该质检单从可选列表中消失
7. 支持解绑操作将两边字段重置为NULL
### 3. 数据查询流程
1. 支持独立查询定氮仪质检数据
2. 支持从其他质检单查看关联的定氮仪数据
3. 提供丰富的筛选和排序功能
4. 支持数据导出功能
## 技术实现细节
### 1. TCP Socket监听服务
- 使用Java原生ServerSocket监听TCP连接(端口8890)
- 多线程处理并发连接
- JSON数据解析和验证
- 业务字段自动计算(nitrogen_content、protein_content)
### 2. 关联机制实现(修正版)
- 直接在主表存储关联信息,实现一对一关系
- 双向关联确保数据一致性
- 唯一约束防止重复绑定
- 状态字段管理绑定生命周期
- 视图自动过滤已绑定数据,前端无需额外处理
### 3. 前端状态管理
- 使用Vuex管理全局状态
- 实时更新关联状态
- 缓存机制优化性能
### 4. 安全考虑
- TCP接口访问控制
- 数据完整性验证
- 操作权限控制
- 审计日志记录
## 错误处理策略
### 1. TCP数据接收错误
- 数据格式错误:记录错误日志,返回错误信息
- 网络异常:重试机制,超时处理
- 数据重复:去重处理,状态更新
### 2. 关联操作错误
- 关联对象不存在:提示用户选择有效对象
- 重复关联:检查现有关联,提示用户
- 权限不足:返回权限错误提示
### 3. 前端异常处理
- 网络请求失败:显示友好错误提示
- 数据加载失败:提供重试机制
- 操作失败:明确的错误信息展示
## 测试策略
### 1. 单元测试
- 数据解析逻辑测试
- 业务逻辑验证
- 关联操作测试
### 2. 集成测试
- TCP数据接收测试
- 数据库操作测试
- API接口测试
### 3. 端到端测试
- 完整业务流程测试
- 用户交互测试
- 性能测试
## 实施清单
1. **数据库层创建**
- 创建qc_nitrogen_analyzer_inspection主表
- 创建qc_nitrogen_analyzer_detail子表
- 创建qc_nitrogen_analyzer_config配置表
- 创建qc_inspection_association关联表
- 插入默认配置数据
2. **后端实体层**
- 创建NitrogenAnalyzerInspection主表实体类
- 创建NitrogenAnalyzerDetail子表实体类
- 创建NitrogenAnalyzerConfig配置实体类
- 创建InspectionAssociation关联实体类
3. **后端数据访问层**
- 创建NitrogenAnalyzerMapper主表接口
- 创建NitrogenAnalyzerDetailMapper子表接口
- 创建NitrogenAnalyzerMapper.xml映射文件
- 创建NitrogenAnalyzerDetailMapper.xml映射文件
- 创建AssociationMapper接口和XML
4. **后端服务层**
- 创建NitrogenAnalyzerTcpServer TCP Socket监听服务
- 创建INitrogenDataProcessService数据处理服务接口
- 实现NitrogenDataProcessServiceImpl
- 创建INitrogenAnalyzerService质检业务接口
- 实现NitrogenAnalyzerServiceImpl
5. **后端控制器层**
- 创建NitrogenAnalyzerController
- 实现RESTful API接口
- 实现TCP数据接收端点
6. **前端API层**
- 创建nitrogenAnalyzer.js API文件
- 封装HTTP请求方法
7. **前端组件层**
- 创建主页组件: `mes-ui/src/views/mes/quality/KNAInfo/index.vue`
- 创建表单组件: `mes-ui/src/views/mes/quality/KNAInfo/form.vue`
- 创建关联绑定弹窗: `mes-ui/src/views/mes/quality/KNAInfo/AssociationDialog.vue`
- API文件: `mes-ui/src/api/mes/quality/KNAInfo.js`
8. **前端路由配置**
- 添加定氮仪质检路由到router/index.js
- 配置权限控制
9. **现有页面集成**
- 修改materialQuality/index.vue添加操作按钮
- 修改foreignQuality/index.vue添加操作按钮
- 修改intermediateQuality/index.vue添加操作按钮
- 修改finalQuality/index.vue添加操作按钮
10. **权限和菜单配置**
- 添加权限配置项
- 配置菜单显示
11. **测试和验证**
- 编写单元测试
- 执行集成测试
- 验证TCP数据接收功能
12. **文档和部署**
- 更新技术文档
- 准备部署脚本
- 验证生产环境兼容性
# 当前执行步骤:"2. 制定详细技术规范"
# 任务进度
[2025-09-18 15:30:00]
- 完成:现有质检系统架构分析
- 完成TCP数据处理机制研究
- 完成:多种实现方案设计
- 状态:研究阶段完成,等待方案确认
[2025-09-18 15:35:00]
- 确认:用户选择方案一(独立质检类型架构)
- 进行:制定详细技术规范和实施计划
- 状态:规划阶段
[2025-09-18 15:45:00]
- 完成TCP字段与数据库字段完整对应分析
- 修正数据库表结构设计新增子表和JSON字段策略
- 确认:扩展性好的存储方案(主表+子表+JSON
- 完成:标准数据库命名规范设计
- 更新:实施清单包含所有必要组件
- 状态:详细规划完成,准备进入执行阶段
## TCP服务详细实现
### 1. TCP Socket监听服务
```java
@Component
@Slf4j
public class NitrogenAnalyzerTcpServer {
private ServerSocket serverSocket;
private static final int TCP_PORT = 8890;
private volatile boolean isRunning = false;
@Autowired
private INitrogenDataProcessService dataProcessService;
@PostConstruct
public void startTcpServer() {
try {
serverSocket = new ServerSocket(TCP_PORT);
isRunning = true;
new Thread(this::listenForConnections).start();
log.info("定氮仪TCP服务器启动监听端口: {}", TCP_PORT);
} catch (IOException e) {
log.error("TCP服务器启动失败", e);
}
}
private void listenForConnections() {
while (isRunning && !serverSocket.isClosed()) {
try {
Socket clientSocket = serverSocket.accept();
log.info("接收到定氮仪连接: {}", clientSocket.getRemoteSocketAddress());
new Thread(() -> handleConnection(clientSocket)).start();
} catch (IOException e) {
if (isRunning) {
log.error("接受TCP连接失败", e);
}
}
}
}
private void handleConnection(Socket socket) {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8))) {
StringBuilder jsonBuilder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
jsonBuilder.append(line);
}
String jsonData = jsonBuilder.toString();
log.info("接收到定氮仪数据: {}", jsonData);
// 异步处理数据
dataProcessService.processNitrogenData(jsonData);
} catch (IOException e) {
log.error("处理TCP数据失败", e);
} finally {
try {
socket.close();
} catch (IOException e) {
log.error("关闭socket失败", e);
}
}
}
@PreDestroy
public void stopTcpServer() {
isRunning = false;
try {
if (serverSocket != null && !serverSocket.isClosed()) {
serverSocket.close();
log.info("定氮仪TCP服务器已停止");
}
} catch (IOException e) {
log.error("停止TCP服务器失败", e);
}
}
}
```
### 2. 数据处理服务接口
```java
public interface INitrogenDataProcessService {
/**
* 处理定氮仪TCP数据
* @param jsonData TCP接收的JSON数据
*/
void processNitrogenData(String jsonData);
}
```
### 3. 数据处理服务实现
```java
@Service
@Slf4j
@Transactional
public class NitrogenDataProcessServiceImpl implements INitrogenDataProcessService {
@Autowired
private INitrogenAnalyzerService nitrogenAnalyzerService;
@Autowired
private SysCodeRuleService sysCodeRuleService;
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public void processNitrogenData(String jsonData) {
try {
// 解析JSON数据
JsonNode dataNode = objectMapper.readTree(jsonData);
// 数据验证
validateTcpData(dataNode);
// 转换为实体对象
NitrogenAnalyzerInspection inspection = convertToInspection(dataNode);
List<NitrogenAnalyzerDetail> details = convertToDetails(dataNode);
// 计算业务字段
calculateBusinessFields(inspection);
// 生成质检单号
generateInspectionNo(inspection);
// 保存数据
nitrogenAnalyzerService.saveInspectionWithDetails(inspection, details);
log.info("定氮仪数据处理成功,质检单号: {}", inspection.getInspectionNo());
} catch (Exception e) {
log.error("处理定氮仪TCP数据失败: {}", jsonData, e);
throw new ServiceException("TCP数据处理失败: " + e.getMessage());
}
}
/**
* 验证TCP数据
*/
private void validateTcpData(JsonNode dataNode) {
if (dataNode == null || dataNode.isNull()) {
throw new ServiceException("TCP数据不能为空");
}
// 验证必填字段
String[] requiredFields = {"sample_Name", "result_Value", "analysis_time", "apparatusName"};
for (String field : requiredFields) {
if (!dataNode.has(field) || dataNode.get(field).isNull()) {
throw new ServiceException("缺少必填字段: " + field);
}
}
}
/**
* 转换为主表实体
*/
private NitrogenAnalyzerInspection convertToInspection(JsonNode dataNode) {
NitrogenAnalyzerInspection inspection = new NitrogenAnalyzerInspection();
// 样品基本信息
inspection.setSampleName(getStringValue(dataNode, "sample_Name"));
inspection.setSampleId(getStringValue(dataNode, "sample_id"));
inspection.setSampleValue(getBigDecimalValue(dataNode, "sample_Value"));
inspection.setSampleUnit(getStringValue(dataNode, "sample_Unit"));
inspection.setAnalysisTime(getDateTimeValue(dataNode, "analysis_time"));
// 检测结果
inspection.setResultValue(getBigDecimalValue(dataNode, "result_Value"));
inspection.setResultType(getStringValue(dataNode, "result_type"));
// 检测方法和设备
inspection.setMethodName(getStringValue(dataNode, "method_name"));
inspection.setApparatusName(getStringValue(dataNode, "apparatusName"));
inspection.setManufacturerName(getStringValue(dataNode, "manufacturerName"));
// 关键检测参数
inspection.setConversionCoefficient(getBigDecimalValue(dataNode, "conversion_coefficient"));
inspection.setProteinFactor(getBigDecimalValue(dataNode, "protein_factor"));
inspection.setAlkaliVolume(getBigDecimalValue(dataNode, "alkali_volume"));
inspection.setDilutionVolume(getBigDecimalValue(dataNode, "dilution_volume"));
inspection.setReceiverVolume(getBigDecimalValue(dataNode, "receiver_volume"));
inspection.setTitrantVolumeBlank(getBigDecimalValue(dataNode, "titrant_volume_for_blank"));
inspection.setTitrantVolumeSample(getBigDecimalValue(dataNode, "titrant_volume_for_sample"));
inspection.setTitrationAcidConcentration(getBigDecimalValue(dataNode, "titration_acid_concentration"));
inspection.setDistillationValue(getBigDecimalValue(dataNode, "distillation_Value"));
inspection.setSteamPower(getBigDecimalValue(dataNode, "steam_power"));
inspection.setWaterCoefficient(getBigDecimalValue(dataNode, "water_coefficient"));
// 质量控制参数
inspection.setHighErrorLimit(getBigDecimalValue(dataNode, "high_error_limit"));
inspection.setLowErrorLimit(getBigDecimalValue(dataNode, "low_error_limit"));
inspection.setIntercept(getBigDecimalValue(dataNode, "intercept"));
inspection.setSlope(getBigDecimalValue(dataNode, "slope"));
// 业务字段
inspection.setDataCreator(getStringValue(dataNode, "created"));
inspection.setStatus("completed");
inspection.setInspectionDate(new Date());
// 存储原始JSON数据
try {
inspection.setRawData(objectMapper.writeValueAsString(dataNode));
} catch (JsonProcessingException e) {
log.warn("存储原始JSON数据失败", e);
}
return inspection;
}
/**
* 转换为子表实体列表
*/
private List<NitrogenAnalyzerDetail> convertToDetails(JsonNode dataNode) {
List<NitrogenAnalyzerDetail> details = new ArrayList<>();
JsonNode dataArray = dataNode.get("data");
if (dataArray != null && dataArray.isArray()) {
for (JsonNode item : dataArray) {
NitrogenAnalyzerDetail detail = new NitrogenAnalyzerDetail();
detail.setCirculateOrder(getIntValue(item, "circulateOrder"));
detail.setInspectionDate(getDateValue(item, "inspectionDate"));
detail.setInspectionTime(getStringValue(item, "inspectionTime"));
detail.setInspectionProject(getStringValue(item, "inspectionProject"));
detail.setInspectionResult(getStringValue(item, "inspectionResult"));
details.add(detail);
}
}
return details;
}
/**
* 计算业务字段
*/
private void calculateBusinessFields(NitrogenAnalyzerInspection inspection) {
// nitrogen_content = result_value (当result_type="% Nitrogen"时)
if ("% Nitrogen".equals(inspection.getResultType()) && inspection.getResultValue() != null) {
inspection.setNitrogenContent(inspection.getResultValue());
}
// protein_content = result_value * protein_factor
if (inspection.getResultValue() != null && inspection.getProteinFactor() != null) {
BigDecimal proteinContent = inspection.getResultValue().multiply(inspection.getProteinFactor());
inspection.setProteinContent(proteinContent);
}
}
/**
* 生成质检单号
*/
private void generateInspectionNo(NitrogenAnalyzerInspection inspection) {
try {
// 使用定氮仪专用的编码规则需要先在数据库中配置ID=23的规则
String number = sysCodeRuleService.queryNewCodeById(23L, true);
inspection.setInspectionNo(number);
} catch (Exception e) {
// 如果编码规则不存在,使用默认规则生成
String defaultNo = "DN" + DateUtil.format(new Date(), "yyyyMMdd") +
String.format("%03d", System.currentTimeMillis() % 1000);
inspection.setInspectionNo(defaultNo);
log.warn("定氮仪编码规则不存在,使用默认编号: {}", defaultNo);
}
}
// 辅助方法
private String getStringValue(JsonNode node, String fieldName) {
JsonNode field = node.get(fieldName);
return field != null && !field.isNull() ? field.asText() : null;
}
private BigDecimal getBigDecimalValue(JsonNode node, String fieldName) {
JsonNode field = node.get(fieldName);
if (field != null && !field.isNull()) {
try {
return new BigDecimal(field.asText());
} catch (NumberFormatException e) {
log.warn("字段{}的值{}不是有效数字", fieldName, field.asText());
}
}
return null;
}
private Integer getIntValue(JsonNode node, String fieldName) {
JsonNode field = node.get(fieldName);
if (field != null && !field.isNull()) {
try {
return Integer.parseInt(field.asText());
} catch (NumberFormatException e) {
log.warn("字段{}的值{}不是有效整数", fieldName, field.asText());
}
}
return null;
}
private Date getDateTimeValue(JsonNode node, String fieldName) {
String dateStr = getStringValue(node, fieldName);
if (dateStr != null) {
try {
return DateUtil.parse(dateStr, "yyyy-MM-dd HH:mm:ss");
} catch (Exception e) {
log.warn("字段{}的值{}不是有效日期时间格式", fieldName, dateStr);
}
}
return null;
}
private Date getDateValue(JsonNode node, String fieldName) {
String dateStr = getStringValue(node, fieldName);
if (dateStr != null) {
try {
return DateUtil.parse(dateStr, "yyyy-MM-dd");
} catch (Exception e) {
log.warn("字段{}的值{}不是有效日期格式", fieldName, dateStr);
}
}
return null;
}
}
```
# 最终审查
## ✅ 关键问题确认和修正
### 1. 关联关系设计 ✅
- **确认**: 一对多关系 - 一个定氮仪检测可以关联多个质检单
- **设计**: 保持多对多关系表设计,满足业务需求
### 2. TCP数据接收 ✅
- **确认**: 定氮仪通过TCP发送数据
- **实现**: Java原生ServerSocket监听8890端口多线程处理并发
### 3. 业务字段计算 ✅
- **确认**: nitrogen_content和protein_content通过业务计算得出
- **逻辑**: nitrogen_content = result_value, protein_content = result_value × protein_factor
### 4. 权限控制 ✅
- **确认**: 无需特殊权限级别
- **实现**: 所有质检用户均可使用
## 🔧 技术方案修正
### 数据库精度优化
- 滴定相关字段精度: decimal(10,6) → decimal(12,8)
- 确保4位小数精度满足设备要求
### TCP服务架构优化
- 独立TCP监听服务不依赖设备管理模块
- 异步数据处理避免阻塞TCP连接
- 完整的数据验证和异常处理机制
### 业务逻辑完善
- 自动计算nitrogen_content和protein_content
- 自动生成质检单号
- 原始JSON数据完整保存
## 📋 实施就绪确认
**数据库设计**: 完整的4张表结构字段精度已优化
**后端架构**: TCP服务+业务服务+REST API完整设计
**前端组件**: 主页+表单+关联弹窗完整规划
**集成方案**: 四个质检页面集成点明确
**测试策略**: TCP模拟+业务功能+集成测试规划
**状态**: 技术方案完整,可以开始实施 🚀
## 📋 数据库初始化脚本
### 1. 定氮仪编码规则配置
```sql
-- 插入定氮仪质检编码规则
INSERT INTO `sys_code_rule` (id, name, basic_domain, status, create_by, create_time, remark) VALUES
(23, '定氮仪质检编码规则', 'NitrogenInspection', '0', 'admin', NOW(), '定氮仪质检单号生成规则');
-- 插入编码规则详细配置: DN + 日期 + 3位流水号 (例: DN20250918001)
INSERT INTO `sys_code_rule_entry` (rule_id, sort, type_id, length_flow, max_flow, constant_char, date_format, code_cover) VALUES
(23, 1, 'B', 0, 0, 'DN', NULL, ''),
(23, 2, 'C', 0, 0, '', 'yyyyMMdd', ''),
(23, 3, 'A', 3, 1, '', NULL, '0');
```
### 2. TCP端口配置检查
```yaml
# 需要在application.yml中添加定氮仪TCP配置
nitrogen:
tcp:
port: 8890 # 确保此端口未被占用
enabled: true
timeout: 30000 # 连接超时30秒
```
### 3. 菜单权限配置 ✅ 已完成
```sql
-- ✅ 实际菜单配置已插入数据库:
-- 2364 定氮仪信息 2123 7 KNAInfo mes/quality/KNAInfo/index 0 0 C 0 0 quality:KNAInfo #
-- 2365 定氮仪信息查询 2364 0 query 1 0 F 0 0 quality:KNAInfo:query #
-- 2366 定氮仪信息新增 2364 1 create 1 0 F 0 0 quality:KNAInfo:create #
-- 2367 定氮仪信息修改 2364 2 edit 1 0 F 0 0 quality:KNAInfo:edit #
-- 2368 定氮仪信息删除 2364 3 remove 1 0 F 0 0 quality:KNAInfo:remove #
-- 2369 定氮仪信息导出 2364 4 export 1 0 F 0 0 quality:KNAInfo:export #
-- 实际配置信息:
-- 主菜单ID: 2364 (定氮仪信息)
-- 父菜单ID: 2123 (质量管理)
-- 路径: KNAInfo
-- 组件: mes/quality/KNAInfo/index
-- 权限前缀: quality:KNAInfo
```
## ⚠️ 实施前必须确认的问题
### 1. TCP端口冲突检查
- **端口8890**是否已被其他服务占用?
- 现有系统使用端口: 8080(HTTP), 6379(Redis), 1883(MQTT)
- 建议确认:`netstat -an | grep 8890`
### 2. 编码规则ID分配
- **ID=23**是否可用需要确认数据库中下一个可用ID
- 建议查询:`SELECT MAX(id) FROM sys_code_rule;`
### 3. 菜单父ID确认 ✅ 已确认
- **parent_id=2123**为实际质量管理菜单ID
- **主菜单ID=2364**为定氮仪信息菜单ID
### 4. 路由组件路径确认 ✅ 已调整
-**前端组件路径**`mes/quality/KNAInfo/index` (与实际菜单配置一致)
-**前端路由路径**`/quality/KNAInfo` (与实际菜单配置一致)
-**API路径**`/quality/KNAInfo` (与权限配置一致)
### 5. 前端路由配置 ✅ 需要调整
```javascript
// mes-ui/src/router/index.js 中需要添加的路由 (根据实际菜单配置调整)
{
path: '/quality/KNAInfo',
component: Layout,
hidden: true,
redirect: 'noRedirect',
children: [
{
path: 'index',
component: () => import('@/views/mes/quality/KNAInfo/index'),
name: 'KNAInfo',
meta: { title: '定氮仪信息', icon: 'tool' }
},
{
path: 'form',
component: () => import('@/views/mes/quality/KNAInfo/form'),
name: 'KNAInfoForm',
meta: { title: '定氮仪信息详情', activeMenu: '/quality/KNAInfo' }
}
]
}
```
**重要调整**
- ✅ 路径改为:`/quality/KNAInfo` (与菜单配置一致)
- ✅ 组件目录:`mes/quality/KNAInfo/` (与菜单配置一致)
- ✅ 权限前缀:`quality:KNAInfo` (与菜单配置一致)