# 背景 文件名: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 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 convertToDetails(JsonNode dataNode) { List 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` (与菜单配置一致)