diff --git a/database/sqls/03-collect-analysis-tables.sql b/database/sqls/03-collect-analysis-tables.sql new file mode 100644 index 0000000..430652e --- /dev/null +++ b/database/sqls/03-collect-analysis-tables.sql @@ -0,0 +1,93 @@ +-- ============================================================ +-- 采集分析日志表 + 采集周期汇总表(幂等迁移脚本) +-- 创建时间:2026-05-05 +-- 说明:在 cnc_log 库中新增两张按月分区表 +-- log_collect_analysis: 每次采集、每台机床的分析记录 +-- log_collect_cycle: 每次采集周期的汇总信息 +-- 执行前提:USE cnc_log; 已执行 01-init-schema.sql +-- ============================================================ + +USE cnc_log; + +-- ----------------------------------------------------------- +-- 1. 采集分析日志表 log_collect_analysis(按月分区) +-- 记录每次采集后对每台机床的数据变化分析 +-- ----------------------------------------------------------- +DROP TABLE IF EXISTS log_collect_analysis; +CREATE TABLE log_collect_analysis ( + id BIGINT AUTO_INCREMENT, + analysis_time DATETIME NOT NULL COMMENT '分析时间(分区键)', + raw_log_id BIGINT NOT NULL COMMENT '关联原始日志ID(log_collect_raw.id)', + collect_address_id INT NOT NULL COMMENT '采集地址ID(关联cnc_collect_address)', + machine_id INT NOT NULL COMMENT '机床ID(关联cnc_machine)', + analysis_type VARCHAR(30) NOT NULL COMMENT '分析类型:NORMAL_UNCHANGED/PART_COUNT_INCREASE/PROGRAM_SWITCH/MANUAL_RESET/DEVICE_ONLINE/DEVICE_OFFLINE/NEW_DEVICE_FOUND/DATA_ANOMALY/COLLECTION_FAILED', + previous_program VARCHAR(200) NULL COMMENT '上一次NC程序名', + current_program VARCHAR(200) NULL COMMENT '本次NC程序名', + previous_part_count DECIMAL(15,5) NULL COMMENT '上一次零件计数', + current_part_count DECIMAL(15,5) NULL COMMENT '本次零件计数', + part_count_delta DECIMAL(15,5) NULL COMMENT '零件计数变化量(正=增加,负=减少)', + previous_status VARCHAR(20) NULL COMMENT '上一次设备状态', + current_status VARCHAR(20) NULL COMMENT '本次设备状态', + analysis_summary VARCHAR(500) NOT NULL COMMENT '人类可读的分析摘要', + analysis_detail JSON NULL COMMENT '完整的字段级对比数据(JSON)', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id, analysis_time), + INDEX idx_address_time (collect_address_id, analysis_time), + INDEX idx_machine_time (machine_id, analysis_time), + INDEX idx_type_time (analysis_type, analysis_time), + INDEX idx_raw_log (raw_log_id), + INDEX idx_program_time (current_program, analysis_time) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + COMMENT='采集分析日志表(按月分区,记录每次采集对每台机床的数据变化分析)' + PARTITION BY RANGE (TO_DAYS(analysis_time)) ( + PARTITION p202605 VALUES LESS THAN (TO_DAYS('2026-06-01')), + PARTITION p202606 VALUES LESS THAN (TO_DAYS('2026-07-01')), + PARTITION p202607 VALUES LESS THAN (TO_DAYS('2026-08-01')), + PARTITION p_future VALUES LESS THAN MAXVALUE + ); + +-- ----------------------------------------------------------- +-- 2. 采集周期汇总表 log_collect_cycle(按月分区) +-- 记录每次采集周期(一个地址的一次完整采集)的汇总信息 +-- ----------------------------------------------------------- +DROP TABLE IF EXISTS log_collect_cycle; +CREATE TABLE log_collect_cycle ( + id BIGINT AUTO_INCREMENT, + cycle_time DATETIME NOT NULL COMMENT '周期开始时间(分区键)', + collect_address_id INT NOT NULL COMMENT '采集地址ID(关联cnc_collect_address)', + raw_log_id BIGINT NOT NULL COMMENT '关联原始日志ID(log_collect_raw.id)', + end_time DATETIME NULL COMMENT '周期结束时间', + duration_ms INT NULL COMMENT '本次采集总耗时(毫秒)', + total_machines INT NOT NULL DEFAULT 0 COMMENT '本周期采集的机床总数', + success_count INT NOT NULL DEFAULT 0 COMMENT '成功采集的机床数', + fail_count INT NOT NULL DEFAULT 0 COMMENT '失败采集的机床数', + change_distribution JSON NULL COMMENT '变化类型分布(如 {"PROGRAM_SWITCH":2,"PART_COUNT_INCREASE":5,"NORMAL_UNCHANGED":3})', + has_anomaly TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否存在异常(1=有异常:DATA_ANOMALY/COLLECTION_FAILED/DEVICE_OFFLINE)', + cycle_summary VARCHAR(500) NULL COMMENT '人类可读的周期汇总', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id, cycle_time), + INDEX idx_address_time (collect_address_id, cycle_time), + INDEX idx_time (cycle_time), + INDEX idx_anomaly_time (has_anomaly, cycle_time) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + COMMENT='采集周期汇总表(按月分区,每次采集周期的汇总信息)' + PARTITION BY RANGE (TO_DAYS(cycle_time)) ( + PARTITION p202605 VALUES LESS THAN (TO_DAYS('2026-06-01')), + PARTITION p202606 VALUES LESS THAN (TO_DAYS('2026-07-01')), + PARTITION p202607 VALUES LESS THAN (TO_DAYS('2026-08-01')), + PARTITION p_future VALUES LESS THAN MAXVALUE + ); + +-- ----------------------------------------------------------- +-- 3. 为现有 log_collect_raw 表增加补充索引 +-- 支持按采集成功/失败筛选,以及按响应时长分析 +-- ----------------------------------------------------------- +-- 检查索引是否已存在,若不存在则添加(幂等) +SET @exist := (SELECT COUNT(*) FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = 'cnc_log' AND TABLE_NAME = 'log_collect_raw' AND INDEX_NAME = 'idx_success_time'); +SET @sqlstmt := IF(@exist = 0, + 'ALTER TABLE cnc_log.log_collect_raw ADD INDEX idx_success_time (is_success, request_time)', + 'SELECT ''索引 idx_success_time 已存在,跳过'''); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; diff --git a/docs/01-数据库设计.md b/docs/01-数据库设计.md index 01fe219..d15a363 100644 --- a/docs/01-数据库设计.md +++ b/docs/01-数据库设计.md @@ -511,7 +511,7 @@ CREATE TABLE cnc_screen_filter ( --- -## 三、日志库 cnc_log(3张表) +## 三、日志库 cnc_log(5张表) ### 3.1 原始采集JSON表 log_collect_raw(按月分区) @@ -594,6 +594,99 @@ CREATE TABLE log_collector_heartbeat ( --- +### 3.4 采集分析日志表 log_collect_analysis(按月分区) + +记录每次采集后对每台机床的数据变化分析。每次采集周期中,每台机床产生一条分析记录,包含与上一次采集数据的对比结果。 + +``sql +CREATE TABLE log_collect_analysis ( + id BIGINT AUTO_INCREMENT, + analysis_time DATETIME NOT NULL COMMENT '分析时间(分区键)', + raw_log_id BIGINT NOT NULL COMMENT '关联原始日志ID(log_collect_raw.id)', + collect_address_id INT NOT NULL COMMENT '采集地址ID(关联cnc_collect_address)', + machine_id INT NOT NULL COMMENT '机床ID(关联cnc_machine)', + analysis_type VARCHAR(30) NOT NULL COMMENT '分析类型枚举', + previous_program VARCHAR(200) NULL COMMENT '上一次NC程序名', + current_program VARCHAR(200) NULL COMMENT '本次NC程序名', + previous_part_count DECIMAL(15,5) NULL COMMENT '上一次零件计数', + current_part_count DECIMAL(15,5) NULL COMMENT '本次零件计数', + part_count_delta DECIMAL(15,5) NULL COMMENT '零件计数变化量(正=增加,负=减少)', + previous_status VARCHAR(20) NULL COMMENT '上一次设备状态', + current_status VARCHAR(20) NULL COMMENT '本次设备状态', + analysis_summary VARCHAR(500) NOT NULL COMMENT '人类可读的分析摘要', + analysis_detail JSON NULL COMMENT '完整的字段级对比数据(JSON)', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id, analysis_time), + INDEX idx_address_time (collect_address_id, analysis_time), + INDEX idx_machine_time (machine_id, analysis_time), + INDEX idx_type_time (analysis_type, analysis_time), + INDEX idx_raw_log (raw_log_id), + INDEX idx_program_time (current_program, analysis_time) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + COMMENT='采集分析日志表(按月分区,记录每次采集对每台机床的数据变化分析)' + PARTITION BY RANGE (TO_DAYS(analysis_time)) ( + PARTITION p202605 VALUES LESS THAN (TO_DAYS('2026-06-01')), + PARTITION p202606 VALUES LESS THAN (TO_DAYS('2026-07-01')), + PARTITION p202607 VALUES LESS THAN (TO_DAYS('2026-08-01')), + PARTITION p_future VALUES LESS THAN MAXVALUE + ); +`` + +**analysis_type 分析类型枚举:** + +| 值 | 含义 | 说明 | +|----|------|------| +| NORMAL_UNCHANGED | 正常无变化 | 数据与上次一致,正常加工中 | +| PART_COUNT_INCREASE | 零件数增加 | 零件计数增长,正常加工中 | +| PROGRAM_SWITCH | NC程序切换 | 程序名变更,触发上一段结账 | +| MANUAL_RESET | 手动清零 | 同程序下零件计数下降 | +| DEVICE_ONLINE | 设备上线 | 设备从离线恢复在线 | +| DEVICE_OFFLINE | 设备离线 | 设备变为离线状态 | +| NEW_DEVICE_FOUND | 发现新设备 | 采集到未注册的device | +| DATA_ANOMALY | 数据异常 | 字段缺失/格式错误/值异常 | +| COLLECTION_FAILED | 采集失败 | 本次采集请求失败 | + +**数据量估算**:每次采集周期×每台机床1条。假设每30秒1次采集×5-10个地址×每地址2-5台机床 = 600-2500条/分钟 = 约86-360万条/天。按月分区便于查询和清理。 + +--- + +### 3.5 采集周期汇总表 log_collect_cycle(按月分区) + +记录每次采集周期(一个地址的一次完整HTTP采集)的汇总信息。一个周期对应 log_collect_raw 中的一条记录和 log_collect_analysis 中的多条记录。 + +``sql +CREATE TABLE log_collect_cycle ( + id BIGINT AUTO_INCREMENT, + cycle_time DATETIME NOT NULL COMMENT '周期开始时间(分区键)', + collect_address_id INT NOT NULL COMMENT '采集地址ID(关联cnc_collect_address)', + raw_log_id BIGINT NOT NULL COMMENT '关联原始日志ID(log_collect_raw.id)', + end_time DATETIME NULL COMMENT '周期结束时间', + duration_ms INT NULL COMMENT '本次采集总耗时(毫秒)', + total_machines INT NOT NULL DEFAULT 0 COMMENT '本周期采集的机床总数', + success_count INT NOT NULL DEFAULT 0 COMMENT '成功采集的机床数', + fail_count INT NOT NULL DEFAULT 0 COMMENT '失败采集的机床数', + change_distribution JSON NULL COMMENT '变化类型分布(如 {"PROGRAM_SWITCH":2,"PART_COUNT_INCREASE":5})', + has_anomaly TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否存在异常(1=有异常)', + cycle_summary VARCHAR(500) NULL COMMENT '人类可读的周期汇总', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id, cycle_time), + INDEX idx_address_time (collect_address_id, cycle_time), + INDEX idx_time (cycle_time), + INDEX idx_anomaly_time (has_anomaly, cycle_time) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + COMMENT='采集周期汇总表(按月分区,每次采集周期的汇总信息)' + PARTITION BY RANGE (TO_DAYS(cycle_time)) ( + PARTITION p202605 VALUES LESS THAN (TO_DAYS('2026-06-01')), + PARTITION p202606 VALUES LESS THAN (TO_DAYS('2026-07-01')), + PARTITION p202607 VALUES LESS THAN (TO_DAYS('2026-08-01')), + PARTITION p_future VALUES LESS THAN MAXVALUE + ); +`` + +**数据量估算**:每次采集1条。每30秒×5-10个地址 = 10-20条/分钟 = 约1.4-2.9万条/天。远小于分析表,查询负担轻。 + +--- + ## 四、ER关系总览 ` @@ -614,6 +707,10 @@ cnc_machine 1-----N cnc_alert --- 日志库 --- cnc_collect_address 1--N log_collect_raw (弱关联,跨库) +log_collect_raw 1--N log_collect_analysis (原始日志→分析记录) +log_collect_raw 1---1 log_collect_cycle (原始日志→周期汇总) +cnc_machine 1--N log_collect_analysis (弱关联,跨库) +cnc_collect_address 1--N log_collect_cycle (弱关联,跨库) log_collector_heartbeat (独立,弱关联collect_address_id) log_system (独立) ` @@ -624,9 +721,9 @@ log_system (独立) | 任务 | 频率 | 操作 | |------|------|------| -| 创建新分区 | 每月1日 | 为cnc_collect_record、log_collect_raw、log_system预创建下下月分区 | -| 删除过期分区 | 每月1日 | DROP超过保留期的分区 | -| 清理心跳表 | 每天 | DELETE log_collector_heartbeat超过7天的记录 | +| 创建新分区 | 每月1日 | 为cnc_collect_record、log_collect_raw、log_system、log_collect_analysis、log_collect_cycle预创建下下月分区 | +| 删除过期分区 | 每月1日 | DROP超过保留期的分区(保留天数=0时不删除) | +| 清理心跳表 | 每天 | DELETE log_collector_heartbeat超过7天的记录(保留天数=0时不删除) | | 清理告警表 | 每天 | DELETE cnc_alert已处理且超过180天的记录 | --- @@ -666,7 +763,16 @@ log_system (独立) | log_collect_raw | idx_request_time | INDEX | 时间范围清理 | | log_system | idx_level_time | INDEX | 按级别查错误日志 | | log_system | idx_source_time | INDEX | 按来源查日志 | +| log_collect_raw | idx_success_time | INDEX | 按成功/失败筛选 | +| log_collect_analysis | idx_address_time | INDEX | 按采集地址+时间查分析 | +| log_collect_analysis | idx_machine_time | INDEX | 按机床+时间查分析 | +| log_collect_analysis | idx_type_time | INDEX | 按分析类型+时间查 | +| log_collect_analysis | idx_raw_log | INDEX | 按原始日志ID关联查 | +| log_collect_analysis | idx_program_time | INDEX | 按NC程序名+时间查 | +| log_collect_cycle | idx_address_time | INDEX | 按采集地址+时间查周期 | +| log_collect_cycle | idx_time | INDEX | 时间范围查询 | +| log_collect_cycle | idx_anomaly_time | INDEX | 查异常周期 | | log_collector_heartbeat | idx_service_time | INDEX | 服务最新状态 | | log_collector_heartbeat | idx_address_time | INDEX | 地址心跳历史 | -共计:20张表,34个索引(含唯一索引) +共计:22张表,43个索引(含唯一索引) diff --git a/docs/02-功能清单/管理后台/13-采集日志/00-采集日志-索引.md b/docs/02-功能清单/管理后台/13-采集日志/00-采集日志-索引.md new file mode 100644 index 0000000..3f99510 --- /dev/null +++ b/docs/02-功能清单/管理后台/13-采集日志/00-采集日志-索引.md @@ -0,0 +1,26 @@ +# 采集日志 索引 + +> 版本:v1.0 +> 最后更新:2026-04-25 + +--- + +## 模块概述 + +展示每次采集的分析日志、采集周期汇总和原始采集数据 + +## 页面清单 + +| 页面编号 | 页面名称 | 路由 | 功能概述 | +|---------|---------|------|---------| +| 13-01 | 采集日志页面 | /collect-log | 3个Tab(采集周期+分析日志+原始数据) | + +## 页面功能详情 + +### 13-01 采集日志页面 +**路由**:`/collect-log` +**功能概述**:在同一页面以三个标签页展示采集周期、分析日志、原始数据,支持弹窗查看对比信息和JSON原始数据。 + +**交互关系说明**:查看分析详情 → 弹窗展示字段对比;查看原始数据 → 弹窗展示JSON + +--- diff --git a/docs/02-功能清单/管理后台/13-采集日志/01-采集日志-规范.md b/docs/02-功能清单/管理后台/13-采集日志/01-采集日志-规范.md new file mode 100644 index 0000000..764fa4b --- /dev/null +++ b/docs/02-功能清单/管理后台/13-采集日志/01-采集日志-规范.md @@ -0,0 +1,131 @@ +# 采集日志-规范 + +本规范用于管理后台「采集日志」模块的前端实现,覆盖组件选用、数据表格定义、查询筛选、分页、时间选择、标签颜色映射及操作按钮等方面的设计约定,确保UI风格统一、交互清晰、数据结构对齐后端接口。以下规范与现有模块风格保持一致,参考文档:界面变更执行规范、前端全局规范等。 + +## 1. 组件规范 +- 数据展示:使用 Element Plus 的 el-table 及 el-table-column。 +- 查询区域:使用 el-form、el-form-item、el-input、el-select、el-date-picker、el-time-picker(如需)等。 +- 选项卡:el-tabs、el-tab-pane,分别承载分析记录、采集周期与原始数据三大区域。 +- 弹窗与详情:el-dialog 展示分析详情及原始日志原文等。 +- 按钮与标签:el-button、el-tag、el-tooltip 提供操作入口及信息提示。 +- 提示与对齐:使用 el-message、el-notification 提供反馈,表格列对齐统一采用左对齐。 +- 加载与空态:使用 el-skeleton、empty 组件作为加载与无数据态的占位显示。 + +## 2. 数据表格列定义 +### Tab1:分析记录 表格列 +| 字段名 | 展示含义 | 注意事项 | +|---|---|---| +| time | 日志分析时间 | 日期时间格式统一为 yyyy-MM-dd HH:mm:ss | +| address | 采集地址 | 全局唯一识别码或名称 | +| machine | 机床 | 机器编号/名称 | +| type | 分析类型 | 参考下方标签颜色映射 | +| previousProgram | 前程序 | 准确的程序名 | +| currentProgram | 当前程序 | 当前正在执行的程序名 | +| yieldDelta | 产量变化 | 数值变化量,单位需一致 | +| summary | 摘要 | 简短描述分析结果 | +| actions | 操作 | 查看详情按钮等 | + +### Tab2:采集周期 表格列 +| 字段名 | 展示含义 | 备注 | +|---|---|---| +| time | 周期开始时间 | 统一时间格式 | +| address | 采集地址 | | +| totalMachines | 总机床数 | 统计口径一致 | +| success | 成功次数 | | +| failure | 失败次数 | | +| anomaly | 异常次数 | | +| distribution | 数据分布摘要 | 摘要字段,方便快速浏览 | +| summary | 摘要 | 简要描述周期信息 | + +### Tab3:原始数据 表格列 +| 字段名 | 展示含义 | 备注 | +|---|---|---| +| rawId | 日志原始ID | | +| logTime | 日志时间 | 解析时间戳 | +| contentPreview | 内容预览 | 仅显示摘要片段 | +| sourceAddress | 数据来源地址 | | + +> 注:表格列定义仅为前端展现的约束,实际字段名称以后端接口返回字段为准。 + +## 3. 查询筛选条件 +- 时间范围筛选:使用 el-date-picker 的 date 范围选择,格式 yyyy-MM-dd HH:mm:ss,范围值作为请求的 startTime/endTime。 +- 采集地址:下拉或输入框筛选,支持模糊匹配地址名称。 +- 机床:下拉选择框,按可选机床列出。 +- 分析类型:多选筛选,UI 采用 el-tag 形式展示筛选条件。 +- 程序名:文本输入,用于匹配前程序或当前程序。 +- 分页参数:page、pageSize,pageSize 默认为 20,支持切换 [20, 50, 100]。 +- 备注:筛选条件应可组合使用,且具备清空按钮重置。 + +## 4. 分页规范 +- pageSize 选项:20、50、100 +- 分页控件样式:el-pagination,显示总条数、每页条数、页码跳转 +- 数据加载时,应显示加载中状态,切换分页时防重复请求,避免并发冲突。 + +## 5. API 响应格式 +- 前端对接后端 API 的统一响应格式为: +``` +{ code: 0, message: "success", data: ... } +``` +- 当 code 非 0 时,展示错误信息 message,必要时提供可复现的错误提示。 + +## 6. 时间选择器规范 +- 使用 el-date-picker,类型为 daterange,时间格式统一为 yyyy-MM-dd HH:mm:ss,value-format 亦为 yyyy-MM-dd HH:mm:ss。 +- 时间筛选优先级高于别的筛选条件;在无时区信息情况下以服务器时区为准。 +- Tab1 的时间范围应以分析时间为准,Tab2 的时间范围以周期起止时间为准。 + +## 7. 分析类型标签颜色映射 +- NORMAL_UNCHANGED -> info +- PART_COUNT_INCREASE -> success +- PROGRAM_SWITCH -> warning +- MANUAL_RESET -> warning +- DEVICE_ONLINE -> success +- DEVICE_OFFLINE -> danger +- NEW_DEVICE_FOUND -> danger +- DATA_ANOMALY -> danger +- COLLECTION_FAILED -> danger + +## 8. 操作按钮规范 +- 行级操作:查看详情按钮,点击后弹出详情弹窗,展示分析详情或原始数据片段。 +- 关联跳转:若需要跳转到关联页面,提供跳转按钮,并在按钮上标注目标路径或模块名称。 +- 只在需要的场景启用导出、复制等辅助按钮,避免界面拥挤。 +- 按钮颜色使用 Element Plus 默认颜色方案,确保与全局主题一致。 + +## 9. 路由与权限(简要) +- 路由路径:/collect-log(遵循现有路由命名约定) +- 页面权限:遵循统一的路由权限策略,必要时标注只读/编辑权限。 + +## 10. 错误处理与空态 +- 网络异常、接口返回错误应给出清晰的错误信息提示。 +- 数据为空时展示空态组件,辅以引导文本。 + +## 11. 视觉与可访问性 +- 保持列宽一致,避免列数据溢出,必要时显示省略号并悬浮显示完整内容。 +- 表格行高、文本颜色、对比度符合无障碍要求,确保在常用屏幕下可读。 + +## 12. 性能与缓存 +- 大数据分页时采用服务端分页,前端仅请求当前页数据。 +- 尽量使用简化字段,减少表格渲染开销。 + +## 13. 安全与数据脱敏 +- 脱敏处理涉及敏感字段的展示,必要时对字段进行脱敏或隐藏。 +- 请求应携带适用的鉴权信息,后端返回的数据不可直接暴露敏感字段。 + +## 14. 版本与兼容性 +- 文档版本随代码同步更新,保持与后端接口版本一致。 +- 如后端接口变更,及时在前端更新字段映射及展示逻辑。 + +## 15. 兼容性与国际化 +- 支持简体中文显示,未来如扩展到多语言需提供翻译资源。 +- UI 组件需要兼容主流浏览器,与公司统一浏览器兼容性要求一致。 + +## 16. 维护与扩展点 +- 行为和字段若增加,必须更新对应的索引与页面文档。 +- 新增字段应通过后端接口文档对齐,并同步 Mock 数据结构。 + +## 17. 附加说明 +- 本规范仅定义前端展示层的通用原则,具体字段名称以后端接口返回字段为准。 +- 如遇特殊场景,需与后端对接团队共同确认后再实现。 + +--- + +备注:如需对照其他模块的设计风格,请参考文档:`docs/02-功能清单/07-告警管理/`等的规范表述。 diff --git a/docs/02-功能清单/管理后台/13-采集日志/13-01-采集日志页面.md b/docs/02-功能清单/管理后台/13-采集日志/13-01-采集日志页面.md new file mode 100644 index 0000000..6644600 --- /dev/null +++ b/docs/02-功能清单/管理后台/13-采集日志/13-01-采集日志页面.md @@ -0,0 +1,117 @@ +# 13-01 采集日志页面 + +_ +本文档按照20项模板撰写,用于前端实现“采集日志页面”的设计与交互规范。页面在管理后台中放置于 /collect-log 路由下,包含三个标签页:分析记录、采集周期、原始数据。_ + +## 1. 页面基本信息 +- 模块:管理后台 -> 采集日志 +- 页面名称:采集日志页面 +- 路由:/collect-log +- 版本:v1.0 +- 作者:设计/前端团队 +- 依赖:Element Plus、Vue 3、TypeScript + +## 2. 布局结构 +- 顶部区域:查询条件区域(时间范围、地址、机床、分析类型、程序名等) +- 中部区域:Tabs 切换,包含三个 Tab:分析记录、采集周期、原始数据 +- 各 Tab 之下为各自的表格 + 分页控件 +- 底部/弹窗区域:查看详情弹窗、关联跳转入口 + +## 3. 数据表格列定义 +- 分析记录(Tab1)列:时间、地址、机床、分析类型、前程序、当前程序、产量变化、摘要、操作 +- 采集周期(Tab2)列:时间、地址、总机床、成功、失败、异常、分布、摘要 +- 原始数据(Tab3)列:原始日志ID、时间、内容摘要、来源地址 + +## 4. 查询条件字段 +- 时间范围:日期时间范围选择器,格式 yyyy-MM-dd HH:mm:ss +- 采集地址:下拉或文本输入,支持模糊匹配 +- 机床:下拉选择 +- 分析类型:多选过滤标签 +- 程序名:文本输入 +- 提交触发:查询按钮,重置按钮 +- 每页显示条数:分页组件控制 + +## 5. API端点定义 +- GET /api/admin/collect-log/analysis +- GET /api/admin/collect-log/analysis/{id} +- GET /api/admin/collect-log/analysis/by-raw/{rawLogId} +- GET /api/admin/collect-log/cycle +- GET /api/admin/collect-log/raw + +## 6. Mock数据结构 +- 分析记录列表 Mock:数组对象包含 time、address、machine、type、previousProgram、currentProgram、yieldDelta、summary +- 分析详情 Mock:含 detail 字段、difference 和对比数据 +- 周期数据 Mock:time、address、totalMachines、success、failure、anomaly、distribution、summary +- 原始数据 Mock:rawId、logTime、contentPreview、sourceAddress + +## 7. 交互行为 +- 标签页切换:切换时重新加载对应表格数据 +- 分页:点击页码、切换每页条数时加载对应页数据 +- 查看详情弹窗:选中行后弹出,展示分析详情或对比信息 +- 关联跳转:点击相关行的跳转按钮,跳转至对应的关联页面 +- 原始数据查看:点击原始数据行,弹出 JSON 原始数据预览 + +## 8. 组件树 +- 组件树示例:CollectLogPage -> (QueryForm, ElTabs -> (AnalysisTab, CycleTab, RawTab) -> (ElTable, ElPagination)) -> DetailDialog +- 表单控件(QueryForm):el-form、el-form-item、el-date-picker、el-input、el-select、el-tag +- 表格与分页:el-table、el-table-column、el-pagination +- 弹窗:el-dialog +- 细化的子组件:AnalysisTable、CycleTable、RawTable、DetailDialog(可复用) + +## 9. 路由配置 +- 路由路径:/collect-log +- 路由组件:CollectLogPage +- 路由守卫:同其他管理后台页面的权限控制 +- 嵌套路由(若有需要):/collect-log/analysis、/collect-log/cycle、/collect-log/raw + +## 10. 数据校验与错误处理 +- 搜索条件必填项的格式校验:时间范围格式、文本字段长度等 +- API 请求失败时,展示友好错误信息并保留上一次有效数据展示 +- 弹窗中的对比信息若数据为空,显示空态提示 + +## 11. 性能与优化点 +- 分页按需加载,避免一次性加载所有数据 +- 表格列尽量避免使用复杂自定义渲染,必要时使用虚拟滚动 +- 原始数据区域对大文本使用内容摘要展示,点击展开查看全文 + +## 12. 国际化与无障碍 +- 暂定中文显示,未来支持多语言资源 +- 组件具备基础无障碍特性,表格可读性良好 + +## 13. 数据引用与结构(接口关联) +- 参考后端 API 端点,数据结构需与后端返回字段严格对齐 +- 需在页面中以常量形式存放端点引用及字段映射,便于维护 + +## 14. 组件样式与风格 +- 遵循全局主题,表格列宽可自适应,必要时固定宽度以确保对齐 +- 按钮、标签、弹窗风格与全局规范保持一致 + +## 15. 版本与变更记录 +- 每次变更需记录版本号和变更摘要,便于回溯 +- 与模块索引、总览文档保持同步 + +## 16. 技术实现概要 +- 主要使用 Vue 3 + TypeScript + Element Plus,按项目的前端全局规范实现 +- 数据获取走统一的 API 调用封装,错误统一处理 +- UI 组件具有可复用性,便于其他模块复用 + +## 17. 数据流与状态管理 +- 页面局部状态通过 Vue 的响应式系统管理 +- 表格数据、筛选条件和分页状态保持在组件状态中,必要时通过 Store/Pinia 共享 + +## 18. 测试用例设计 +- 基本渲染测试:页面渲染、表格列正确显示 +- 筛选与分页功能测试 +- 弹窗查看详情测试 +- 跳转与联动测试 + +## 19. 部署与运行 +- 本地调试:确保 /collect-log 路由可访问,接口 Mock/测试环境数据正常 +- 与后端联调时保持端点一致,返回字段映射不变 + +## 20. 变更记录 +- 记录本页面文档的变更时间、版本和修改内容,便于团队追踪 + +--- + +备注:本文档遵循文档结构规范,确保与其他模块文档风格一致,如需对齐请参考 `docs/02-功能清单/02-文件夹创建规范.md` 与 `docs/02-界面变更执行规范.md`。 diff --git a/src/CncCollector/Core/AnalysisEngine.cs b/src/CncCollector/Core/AnalysisEngine.cs new file mode 100644 index 0000000..95f4914 --- /dev/null +++ b/src/CncCollector/Core/AnalysisEngine.cs @@ -0,0 +1,401 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using Dapper; +using MySqlConnector; +using Newtonsoft.Json; +using CncModels.Entity; +using log4net; + +namespace CncCollector.Core +{ + /// + /// 采集分析引擎。 + /// 在每次采集周期后,对比每台机床的当前数据与上一次采集数据, + /// 生成分析记录(log_collect_analysis)和周期汇总(log_collect_cycle)。 + /// 异常类分析自动写入告警表(cnc_alert)。 + /// + public class AnalysisEngine + { + private static readonly ILog _log = LogManager.GetLogger(typeof(AnalysisEngine)); + + /// 业务库连接字符串(写告警用) + private readonly string _businessConnStr; + + /// 日志库连接字符串(写分析/周期表用) + private readonly string _logConnStr; + + /// 内存缓存:machineId → 上一次采集状态快照 + private readonly ConcurrentDictionary _lastSnapshot = new ConcurrentDictionary(); + + /// + /// 采集缓存快照(每台机床的上一次状态) + /// + public class MachineSnapshot + { + public string ProgramName { get; set; } + public decimal? PartCount { get; set; } + public string DeviceStatus { get; set; } + public DateTime CollectTime { get; set; } + } + + /// + /// 初始化分析引擎 + /// + /// 业务库连接字符串 + /// 日志库连接字符串 + public AnalysisEngine(string businessConnStr, string logConnStr) + { + _businessConnStr = businessConnStr ?? throw new ArgumentNullException(nameof(businessConnStr)); + _logConnStr = logConnStr ?? throw new ArgumentNullException(nameof(logConnStr)); + } + + /// + /// 分析一次采集周期的所有设备数据,写入分析记录和周期汇总。 + /// + /// 本次原始日志ID(log_collect_raw.id) + /// 采集地址ID + /// 采集地址名称 + /// 本次采集的结构化记录列表 + /// device_code → Machine 的查找字典 + /// 周期开始时间 + /// 本次采集耗时(毫秒) + public void AnalyzeAndRecord(long rawLogId, int collectAddressId, string addressName, + List records, Dictionary machineDict, + DateTime cycleStartTime, long durationMs) + { + if (records == null || records.Count == 0) return; + + try + { + var analysisTime = DateTime.Now; + var hasAnomaly = false; + var changeDistribution = new Dictionary(); + int successCount = 0; + + // 构建 machineId → Machine 查找字典 + var machineById = new Dictionary(); + foreach (var m in machineDict.Values) + { + machineById[m.Id] = m; + } + + // 逐条分析 + foreach (var rec in records) + { + try + { + // 获取机床信息 + Machine machine = null; + machineById.TryGetValue(rec.MachineId, out machine); + string machineName = machine?.Name ?? ("机床" + rec.MachineId); + + // 当前值 + string currentProgram = rec.ProgramName; + decimal? currentPartCount = rec.PartCount; + string currentStatus = rec.DeviceStatus; + + // 获取上次快照 + MachineSnapshot prev; + _lastSnapshot.TryGetValue(rec.MachineId, out prev); + + // 计算分析类型和摘要 + string analysisType; + string summary; + bool needAlert = false; + string alertType = null; + + DetermineAnalysis(prev, currentProgram, currentPartCount, currentStatus, + machineName, out analysisType, out summary, out needAlert, out alertType); + + // 计算变化量 + decimal? partCountDelta = null; + if (currentPartCount.HasValue && prev != null && prev.PartCount.HasValue) + { + partCountDelta = currentPartCount.Value - prev.PartCount.Value; + } + + // 构建分析明细JSON + var detail = new + { + previous = prev != null ? new + { + program = prev.ProgramName, + partCount = prev.PartCount, + status = prev.DeviceStatus + } : null, + current = new + { + program = currentProgram, + partCount = currentPartCount, + status = currentStatus + }, + delta = new { partCount = partCountDelta }, + collectTime = rec.CollectTime.ToString("yyyy-MM-dd HH:mm:ss") + }; + string detailJson = JsonConvert.SerializeObject(detail); + + // 写入分析记录 + WriteAnalysis(new CncModels.Entity.CollectAnalysis + { + AnalysisTime = analysisTime, + RawLogId = rawLogId, + CollectAddressId = collectAddressId, + MachineId = rec.MachineId, + AnalysisType = analysisType, + PreviousProgram = prev?.ProgramName, + CurrentProgram = currentProgram, + PreviousPartCount = prev?.PartCount, + CurrentPartCount = currentPartCount, + PartCountDelta = partCountDelta, + PreviousStatus = prev?.DeviceStatus, + CurrentStatus = currentStatus, + AnalysisSummary = summary, + AnalysisDetail = detailJson + }); + + // 更新快照 + _lastSnapshot[rec.MachineId] = new MachineSnapshot + { + ProgramName = currentProgram, + PartCount = currentPartCount, + DeviceStatus = currentStatus, + CollectTime = rec.CollectTime + }; + + // 统计分布 + if (changeDistribution.ContainsKey(analysisType)) + changeDistribution[analysisType]++; + else + changeDistribution[analysisType] = 1; + + // 异常告警 + if (needAlert) + { + hasAnomaly = true; + WriteAlert(alertType, rec.MachineId, collectAddressId, summary, detailJson); + } + + // 统计成功数(非异常即为成功) + if (analysisType != "COLLECTION_FAILED" && analysisType != "DATA_ANOMALY") + successCount++; + } + catch (Exception ex) + { + _log.Error($"分析单条记录失败(machineId={rec.MachineId})", ex); + } + } + + // 写入周期汇总 + WriteCycleSummary(new CncModels.Entity.CollectCycle + { + CycleTime = cycleStartTime, + CollectAddressId = collectAddressId, + RawLogId = rawLogId, + EndTime = analysisTime, + DurationMs = (int)durationMs, + TotalMachines = records.Count, + SuccessCount = successCount, + FailCount = records.Count - successCount, + ChangeDistribution = JsonConvert.SerializeObject(changeDistribution), + HasAnomaly = hasAnomaly ? 1 : 0, + CycleSummary = $"共{records.Count}台机床完成分析" + (hasAnomaly ? ",存在异常" : "") + }); + } + catch (Exception ex) + { + _log.Error($"采集分析失败(地址={addressName}, rawLogId={rawLogId})", ex); + } + } + + /// + /// 根据前后状态对比,确定分析类型 + /// + private void DetermineAnalysis(MachineSnapshot prev, string currentProgram, decimal? currentPartCount, + string currentStatus, string machineName, out string analysisType, out string summary, + out bool needAlert, out string alertType) + { + needAlert = false; + alertType = null; + + // 无历史快照 → 首次上线 + if (prev == null) + { + analysisType = "DEVICE_ONLINE"; + summary = $"机床{machineName}首次上线,程序={currentProgram ?? "未知"}"; + needAlert = true; + alertType = "unknown_device"; + return; + } + + string prevProgram = prev.ProgramName; + decimal? prevPartCount = prev.PartCount; + string prevStatus = prev.DeviceStatus; + + // 检测程序切换 + if (!string.IsNullOrEmpty(currentProgram) && !string.IsNullOrEmpty(prevProgram) && + !string.Equals(prevProgram, currentProgram, StringComparison.OrdinalIgnoreCase)) + { + analysisType = "PROGRAM_SWITCH"; + summary = $"机床{machineName}程序切换: {prevProgram} → {currentProgram}"; + return; + } + + // 检测手动清零(同程序下零件数下降) + if (currentPartCount.HasValue && prevPartCount.HasValue && + currentPartCount.Value < prevPartCount.Value) + { + analysisType = "MANUAL_RESET"; + summary = $"机床{machineName}零件计数手动清零: {prevPartCount} → {currentPartCount}"; + return; + } + + // 检测零件数增加 + if (currentPartCount.HasValue && prevPartCount.HasValue && + currentPartCount.Value > prevPartCount.Value) + { + decimal delta = currentPartCount.Value - prevPartCount.Value; + analysisType = "PART_COUNT_INCREASE"; + summary = $"机床{machineName}新增{delta}个零件({prevPartCount} → {currentPartCount})"; + return; + } + + // 检测设备离线/告警 + if (!string.IsNullOrEmpty(currentStatus) && + (currentStatus.Equals("OFFLINE", StringComparison.OrdinalIgnoreCase) || + currentStatus.Equals("ALARM", StringComparison.OrdinalIgnoreCase) || + currentStatus.Equals("EMERGENCY", StringComparison.OrdinalIgnoreCase))) + { + analysisType = "DEVICE_OFFLINE"; + summary = $"机床{machineName}设备离线/告警: {currentStatus}"; + needAlert = true; + alertType = "device_offline"; + return; + } + + // 检测数据异常(关键字段缺失但设备应该在线) + if (string.IsNullOrEmpty(currentProgram) && !string.IsNullOrEmpty(currentStatus) && + !currentStatus.Equals("OFFLINE", StringComparison.OrdinalIgnoreCase)) + { + analysisType = "DATA_ANOMALY"; + summary = $"机床{machineName}数据异常: 缺少程序名字段"; + needAlert = true; + alertType = "data_anomaly"; + return; + } + + // 无重大变化 + analysisType = "NORMAL_UNCHANGED"; + summary = $"机床{machineName}数据无重大变化"; + } + + /// + /// 写入单条分析记录到 log_collect_analysis + /// + private void WriteAnalysis(CncModels.Entity.CollectAnalysis entity) + { + try + { + using (var conn = new MySqlConnection(_logConnStr)) + { + conn.Execute(@"INSERT INTO log_collect_analysis + (analysis_time, raw_log_id, collect_address_id, machine_id, analysis_type, + previous_program, current_program, previous_part_count, current_part_count, + part_count_delta, previous_status, current_status, analysis_summary, + analysis_detail, created_at) + VALUES (@AnalysisTime, @RawLogId, @CollectAddressId, @MachineId, @AnalysisType, + @PreviousProgram, @CurrentProgram, @PreviousPartCount, @CurrentPartCount, + @PartCountDelta, @PreviousStatus, @CurrentStatus, @AnalysisSummary, + @AnalysisDetail, NOW())", + new + { + entity.AnalysisTime, + entity.RawLogId, + entity.CollectAddressId, + entity.MachineId, + entity.AnalysisType, + entity.PreviousProgram, + entity.CurrentProgram, + entity.PreviousPartCount, + entity.CurrentPartCount, + entity.PartCountDelta, + entity.PreviousStatus, + entity.CurrentStatus, + entity.AnalysisSummary, + entity.AnalysisDetail + }); + } + } + catch (Exception ex) + { + _log.Error($"写入分析记录失败(machineId={entity.MachineId})", ex); + } + } + + /// + /// 写入周期汇总到 log_collect_cycle + /// + private void WriteCycleSummary(CncModels.Entity.CollectCycle entity) + { + try + { + using (var conn = new MySqlConnection(_logConnStr)) + { + conn.Execute(@"INSERT INTO log_collect_cycle + (cycle_time, collect_address_id, raw_log_id, end_time, duration_ms, + total_machines, success_count, fail_count, change_distribution, + has_anomaly, cycle_summary, created_at) + VALUES (@CycleTime, @CollectAddressId, @RawLogId, @EndTime, @DurationMs, + @TotalMachines, @SuccessCount, @FailCount, @ChangeDistribution, + @HasAnomaly, @CycleSummary, NOW())", + new + { + entity.CycleTime, + entity.CollectAddressId, + entity.RawLogId, + entity.EndTime, + entity.DurationMs, + entity.TotalMachines, + entity.SuccessCount, + entity.FailCount, + entity.ChangeDistribution, + entity.HasAnomaly, + entity.CycleSummary + }); + } + } + catch (Exception ex) + { + _log.Error("写入周期汇总失败", ex); + } + } + + /// + /// 写入告警到 cnc_alert(业务库) + /// + private void WriteAlert(string alertType, int machineId, int collectAddressId, string title, string detail) + { + try + { + using (var conn = new MySqlConnection(_businessConnStr)) + { + conn.Execute(@"INSERT INTO cnc_alert (alert_type, machine_id, collect_address_id, title, detail, is_resolved, created_at) + VALUES (@AlertType, @MachineId, @AddressId, @Title, @Detail, 0, NOW())", + new + { + AlertType = alertType, + MachineId = machineId, + AddressId = collectAddressId, + Title = title, + Detail = detail + }); + } + } + catch (Exception ex) + { + _log.Error($"写入告警失败(alertType={alertType}, machineId={machineId})", ex); + } + } + } +} diff --git a/src/CncCollector/Core/CollectRecordWriter.cs b/src/CncCollector/Core/CollectRecordWriter.cs index 2423575..a8daa23 100644 --- a/src/CncCollector/Core/CollectRecordWriter.cs +++ b/src/CncCollector/Core/CollectRecordWriter.cs @@ -28,11 +28,12 @@ namespace CncCollector.Core /// 响应耗时(毫秒) /// 是否采集成功 /// 错误信息(失败时) - public static void WriteBatch(string businessConnStr, string logConnStr, + public static long WriteBatch(string businessConnStr, string logConnStr, List records, string rawJson, int collectAddressId, DateTime requestTime, long? responseDurationMs, bool isSuccess, string errorMessage, int? statusCode = null) { var now = DateTime.Now; + long lastRawLogId = 0; // 1. 写入原始JSON到日志库 try @@ -54,6 +55,12 @@ namespace CncCollector.Core ErrorMessage = errorMessage ?? (string)null, CreatedAt = now }); + // 记录刚插入的 raw_log 的自增ID + try + { + lastRawLogId = conn.ExecuteScalar("SELECT LAST_INSERT_ID();"); + } + catch { lastRawLogId = 0; } } } catch (Exception ex) @@ -61,7 +68,7 @@ namespace CncCollector.Core _log.Error($"写入原始JSON日志失败(地址ID={collectAddressId})", ex); } - if (!isSuccess || records == null || records.Count == 0) return; + if (!isSuccess || records == null || records.Count == 0) return lastRawLogId; // 2. 批量写入采集结构化记录到业务库 try @@ -162,6 +169,7 @@ namespace CncCollector.Core { _log.Error($"批量写入采集记录失败(地址ID={collectAddressId})", ex); } + return lastRawLogId; } /// diff --git a/src/CncCollector/Core/CollectWorker.cs b/src/CncCollector/Core/CollectWorker.cs index 7311ff0..7d6d408 100644 --- a/src/CncCollector/Core/CollectWorker.cs +++ b/src/CncCollector/Core/CollectWorker.cs @@ -29,6 +29,7 @@ namespace CncCollector.Core private readonly CollectAddress _address; private readonly CollectorConfig _config; private readonly ProductionTracker _tracker; + private readonly AnalysisEngine _analysisEngine; private readonly string _businessConnStr; private readonly string _logConnStr; private Thread _thread; @@ -65,11 +66,12 @@ namespace CncCollector.Core /// 业务库连接字符串 /// 日志库连接字符串 public CollectWorker(CollectAddress address, CollectorConfig config, ProductionTracker tracker, - string businessConnStr, string logConnStr) + AnalysisEngine analysisEngine, string businessConnStr, string logConnStr) { _address = address; _config = config; _tracker = tracker; + _analysisEngine = analysisEngine; _businessConnStr = businessConnStr; _logConnStr = logConnStr; } @@ -400,9 +402,15 @@ namespace CncCollector.Core } // 4. 批量写入 - CollectRecordWriter.WriteBatch(_businessConnStr, _logConnStr, records, rawJson, + long rawLogId = CollectRecordWriter.WriteBatch(_businessConnStr, _logConnStr, records, rawJson, _address.Id, requestTime, durationMs, true, null, statusCode); + // 采集分析:将分析任务委托给 AnalysisEngine + if (rawLogId > 0 && records != null && records.Count > 0 && _analysisEngine != null) + { + _analysisEngine.AnalyzeAndRecord(rawLogId, _address.Id, _address.Name, records, machineDict, requestTime, durationMs); + } + _log.Info($"采集完成: {_address.Name} → {records.Count}台设备, {durationMs}ms"); } diff --git a/src/CncCollector/Core/CollectorEngine.cs b/src/CncCollector/Core/CollectorEngine.cs index b3d6424..48e9602 100644 --- a/src/CncCollector/Core/CollectorEngine.cs +++ b/src/CncCollector/Core/CollectorEngine.cs @@ -21,6 +21,8 @@ namespace CncCollector.Core private readonly CollectorConfig _config; private readonly ConcurrentDictionary _workers = new ConcurrentDictionary(); private readonly ProductionTracker _tracker; + // 复用的分析引擎实例(简单实现:按地址注入,避免跨线程问题) + private readonly AnalysisEngine _analysisEngine; private readonly DailySummaryJob _dailySummary; private Timer _heartbeatTimer; private Timer _configPollTimer; @@ -52,6 +54,8 @@ namespace CncCollector.Core _config = config; _tracker = new ProductionTracker(config.BusinessConnection); _dailySummary = new DailySummaryJob(config.BusinessConnection); + // 初始化分析引擎(与业务库和日志库同源,后续按需调整) + _analysisEngine = new AnalysisEngine(config.BusinessConnection, config.LogConnection); } /// @@ -203,17 +207,17 @@ namespace CncCollector.Core } // 启动新增的地址 - foreach (var addr in addresses) - { - if (!_workers.ContainsKey(addr.Id)) + foreach (var addr in addresses) { - var worker = new CollectWorker(addr, _config, _tracker, - _config.BusinessConnection, _config.LogConnection); - worker.Start(); - _workers[addr.Id] = worker; + if (!_workers.ContainsKey(addr.Id)) + { + var worker = new CollectWorker(addr, _config, _tracker, + _analysisEngine, _config.BusinessConnection, _config.LogConnection); + worker.Start(); + _workers[addr.Id] = worker; _log.Info($"已启动采集地址: {addr.Name}(URL={addr.Url}, 间隔={addr.CollectInterval}秒)"); + } } - } } catch (Exception ex) { diff --git a/src/CncModels/Dto/CollectLog/CollectAnalysisDetail.cs b/src/CncModels/Dto/CollectLog/CollectAnalysisDetail.cs new file mode 100644 index 0000000..e9459bb --- /dev/null +++ b/src/CncModels/Dto/CollectLog/CollectAnalysisDetail.cs @@ -0,0 +1,15 @@ +namespace CncModels.Dto.CollectLog +{ + /// + /// 采集分析详情(基于 CollectAnalysisListItem 的扩展字段) + /// + public class CollectAnalysisDetail : CollectAnalysisListItem + { + public decimal? PreviousPartCount { get; set; } + public decimal? CurrentPartCount { get; set; } + public string PreviousStatus { get; set; } + public string CurrentStatus { get; set; } + public string AnalysisDetail { get; set; } + public long RawLogId { get; set; } + } +} diff --git a/src/CncModels/Dto/CollectLog/CollectAnalysisListItem.cs b/src/CncModels/Dto/CollectLog/CollectAnalysisListItem.cs new file mode 100644 index 0000000..1a47198 --- /dev/null +++ b/src/CncModels/Dto/CollectLog/CollectAnalysisListItem.cs @@ -0,0 +1,19 @@ +namespace CncModels.Dto.CollectLog +{ + /// + /// 采集分析列表项 + /// + public class CollectAnalysisListItem + { + public long Id { get; set; } + public string AnalysisTime { get; set; } + public int CollectAddressId { get; set; } + public int MachineId { get; set; } + public string MachineName { get; set; } + public string AnalysisType { get; set; } + public string PreviousProgram { get; set; } + public string CurrentProgram { get; set; } + public decimal? PartCountDelta { get; set; } + public string AnalysisSummary { get; set; } + } +} diff --git a/src/CncModels/Dto/CollectLog/CollectAnalysisQuery.cs b/src/CncModels/Dto/CollectLog/CollectAnalysisQuery.cs new file mode 100644 index 0000000..dc14b37 --- /dev/null +++ b/src/CncModels/Dto/CollectLog/CollectAnalysisQuery.cs @@ -0,0 +1,17 @@ +using System; + +namespace CncModels.Dto.CollectLog +{ + /// + /// 采集分析列表查询条件(分页) + /// + public class CollectAnalysisQuery : PagedQuery + { + public DateTime? StartDate { get; set; } + public DateTime? EndDate { get; set; } + public int? CollectAddressId { get; set; } + public int? MachineId { get; set; } + public string AnalysisType { get; set; } + public string ProgramName { get; set; } + } +} diff --git a/src/CncModels/Dto/CollectLog/CollectCycleListItem.cs b/src/CncModels/Dto/CollectLog/CollectCycleListItem.cs new file mode 100644 index 0000000..65638a6 --- /dev/null +++ b/src/CncModels/Dto/CollectLog/CollectCycleListItem.cs @@ -0,0 +1,19 @@ +namespace CncModels.Dto.CollectLog +{ + /// + /// 采集周期列表项 + /// + public class CollectCycleListItem + { + public long Id { get; set; } + public string CycleTime { get; set; } + public int CollectAddressId { get; set; } + public string AddressName { get; set; } + public int TotalMachines { get; set; } + public int SuccessCount { get; set; } + public int FailCount { get; set; } + public int HasAnomaly { get; set; } + public string ChangeDistribution { get; set; } + public string CycleSummary { get; set; } + } +} diff --git a/src/CncModels/Dto/CollectLog/CollectCycleQuery.cs b/src/CncModels/Dto/CollectLog/CollectCycleQuery.cs new file mode 100644 index 0000000..b398b96 --- /dev/null +++ b/src/CncModels/Dto/CollectLog/CollectCycleQuery.cs @@ -0,0 +1,15 @@ +using System; + +namespace CncModels.Dto.CollectLog +{ + /// + /// 采集周期查询条件(分页) + /// + public class CollectCycleQuery : PagedQuery + { + public DateTime? StartDate { get; set; } + public DateTime? EndDate { get; set; } + public int? CollectAddressId { get; set; } + public int? HasAnomaly { get; set; } + } +} diff --git a/src/CncModels/Entity/CollectAnalysis.cs b/src/CncModels/Entity/CollectAnalysis.cs new file mode 100644 index 0000000..ff4a454 --- /dev/null +++ b/src/CncModels/Entity/CollectAnalysis.cs @@ -0,0 +1,58 @@ +using System; + +namespace CncModels.Entity +{ + /// + /// 采集分析记录实体(日志库 log_collect_analysis) + /// + public class CollectAnalysis + { + /// 自增ID + public long Id { get; set; } + + /// 分析时间 + public DateTime AnalysisTime { get; set; } + + /// 原始日志ID + public long RawLogId { get; set; } + + /// 采集地址ID + public int CollectAddressId { get; set; } + + /// 机器ID + public int MachineId { get; set; } + + /// 分析类型 + public string AnalysisType { get; set; } + + /// 前一程序名 + public string PreviousProgram { get; set; } + + /// 当前程序名 + public string CurrentProgram { get; set; } + + /// 前一阶段产出数量 + public decimal? PreviousPartCount { get; set; } + + /// 当前阶段产出数量 + public decimal? CurrentPartCount { get; set; } + + /// 产出变化量 + public decimal? PartCountDelta { get; set; } + + /// 前一状态 + public string PreviousStatus { get; set; } + + /// 当前状态 + public string CurrentStatus { get; set; } + + /// 分析概要 + public string AnalysisSummary { get; set; } + + /// 分析细节JSON字符串 + public string AnalysisDetail { get; set; } + + /// 创建时间 + public DateTime CreatedAt { get; set; } + } +} diff --git a/src/CncModels/Entity/CollectCycle.cs b/src/CncModels/Entity/CollectCycle.cs new file mode 100644 index 0000000..bef175b --- /dev/null +++ b/src/CncModels/Entity/CollectCycle.cs @@ -0,0 +1,49 @@ +using System; + +namespace CncModels.Entity +{ + /// + /// 采集分析周期实体(日志库 log_collect_cycle) + /// + public class CollectCycle + { + /// 自增ID + public long Id { get; set; } + + /// 周期时间 + public DateTime CycleTime { get; set; } + + /// 采集地址ID + public int CollectAddressId { get; set; } + + /// 原始日志ID + public long RawLogId { get; set; } + + /// 结束时间 + public DateTime? EndTime { get; set; } + + /// 周期持续时长(毫秒) + public int? DurationMs { get; set; } + + /// 总机器数 + public int TotalMachines { get; set; } + + /// 成功计数 + public int SuccessCount { get; set; } + + /// 失败计数 + public int FailCount { get; set; } + + /// 分布变化JSON + public string ChangeDistribution { get; set; } + + /// 是否存在异常(0/1) + public int HasAnomaly { get; set; } + + /// 周期概要 + public string CycleSummary { get; set; } + + /// 创建时间 + public DateTime CreatedAt { get; set; } + } +} diff --git a/src/CncModels/Enum/AnalysisType.cs b/src/CncModels/Enum/AnalysisType.cs new file mode 100644 index 0000000..e81209f --- /dev/null +++ b/src/CncModels/Enum/AnalysisType.cs @@ -0,0 +1,18 @@ +namespace CncModels.Enum +{ + /// + /// 采集分析的分析类型枚举(以字符串常量形式提供) + /// + public static class AnalysisType + { + public const string NORMAL_UNCHANGED = "NORMAL_UNCHANGED"; + public const string PART_COUNT_INCREASE = "PART_COUNT_INCREASE"; + public const string PROGRAM_SWITCH = "PROGRAM_SWITCH"; + public const string MANUAL_RESET = "MANUAL_RESET"; + public const string DEVICE_ONLINE = "DEVICE_ONLINE"; + public const string DEVICE_OFFLINE = "DEVICE_OFFLINE"; + public const string NEW_DEVICE_FOUND = "NEW_DEVICE_FOUND"; + public const string DATA_ANOMALY = "DATA_ANOMALY"; + public const string COLLECTION_FAILED = "COLLECTION_FAILED"; + } +} diff --git a/src/CncRepository/Impl/Log/CollectAnalysisRepository.cs b/src/CncRepository/Impl/Log/CollectAnalysisRepository.cs new file mode 100644 index 0000000..6a79e73 --- /dev/null +++ b/src/CncRepository/Impl/Log/CollectAnalysisRepository.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Dapper; +using MySqlConnector; +using CncModels.Dto; +using CncModels.Dto.CollectLog; +using CncModels.Entity; +using CncRepository.Base; +using CncRepository.Interface; + +namespace CncRepository.Impl.Log +{ + /// + /// 采集分析仓储实现(日志库 - log_collect_analysis) + /// + public class CollectAnalysisRepository : LogRepository, ICollectAnalysisRepository + { + public CollectAnalysisRepository(string connectionString) : base(connectionString) { } + + public PagedResult GetAnalysisList(CollectAnalysisQuery query) + { + using (var conn = CreateConnection()) + { + var whereParts = new List { "1=1" }; + var p = new DynamicParameters(); + + if (query.StartDate.HasValue) + { + whereParts.Add("a.analysis_time >= @StartDate"); + p.Add("StartDate", query.StartDate); + } + if (query.EndDate.HasValue) + { + whereParts.Add("a.analysis_time <= @EndDate"); + p.Add("EndDate", query.EndDate); + } + if (query.CollectAddressId.HasValue) + { + whereParts.Add("a.collect_address_id = @CollectAddressId"); + p.Add("CollectAddressId", query.CollectAddressId); + } + if (query.MachineId.HasValue) + { + whereParts.Add("a.machine_id = @MachineId"); + p.Add("MachineId", query.MachineId); + } + if (!string.IsNullOrEmpty(query.AnalysisType)) + { + whereParts.Add("a.analysis_type = @AnalysisType"); + p.Add("AnalysisType", query.AnalysisType); + } + if (!string.IsNullOrEmpty(query.ProgramName)) + { + whereParts.Add("a.current_program LIKE CONCAT('%', @ProgramName, '%')"); + p.Add("ProgramName", query.ProgramName); + } + + var whereSql = string.Join(" AND ", whereParts); + + // 统计总条数 + var total = conn.ExecuteScalar( + $"SELECT COUNT(1) FROM log_collect_analysis a WHERE {whereSql}", p); + + // 分页查询(左连机床表获取名称) + var dataSql = $@"SELECT + a.id AS Id, + DATE_FORMAT(a.analysis_time, '%Y-%m-%d %H:%i:%s') AS AnalysisTime, + a.collect_address_id AS CollectAddressId, + a.machine_id AS MachineId, + m.name AS MachineName, + a.analysis_type AS AnalysisType, + a.previous_program AS PreviousProgram, + a.current_program AS CurrentProgram, + a.part_count_delta AS PartCountDelta, + a.analysis_summary AS AnalysisSummary + FROM log_collect_analysis a + LEFT JOIN cnc_business.cnc_machine m ON a.machine_id = m.id + WHERE {whereSql} + ORDER BY a.analysis_time DESC + LIMIT @PageSize OFFSET @Offset"; + + var items = conn.Query(dataSql, + new { PageSize = query.PageSize, Offset = query.Offset }).AsList(); + + return new PagedResult + { + Items = items, + Total = total, + Page = query.Page, + PageSize = query.PageSize + }; + } + } + + public CollectAnalysisDetail GetAnalysisDetail(long id) + { + using (var conn = CreateConnection()) + { + var sql = @"SELECT + a.id AS Id, + DATE_FORMAT(a.analysis_time, '%Y-%m-%d %H:%i:%s') AS AnalysisTime, + a.collect_address_id AS CollectAddressId, + a.machine_id AS MachineId, + m.name AS MachineName, + a.analysis_type AS AnalysisType, + a.previous_program AS PreviousProgram, + a.current_program AS CurrentProgram, + a.part_count_delta AS PartCountDelta, + a.previous_part_count AS PreviousPartCount, + a.current_part_count AS CurrentPartCount, + a.previous_status AS PreviousStatus, + a.current_status AS CurrentStatus, + a.analysis_summary AS AnalysisSummary, + a.analysis_detail AS AnalysisDetail, + a.raw_log_id AS RawLogId + FROM log_collect_analysis a + LEFT JOIN cnc_business.cnc_machine m ON a.machine_id = m.id + WHERE a.id = @Id"; + return conn.QueryFirstOrDefault(sql, new { Id = id }); + } + } + + public List GetAnalysisByRawLogId(long rawLogId) + { + using (var conn = CreateConnection()) + { + var sql = @"SELECT + a.id AS Id, + DATE_FORMAT(a.analysis_time, '%Y-%m-%d %H:%i:%s') AS AnalysisTime, + a.collect_address_id AS CollectAddressId, + a.machine_id AS MachineId, + m.name AS MachineName, + a.analysis_type AS AnalysisType, + a.previous_program AS PreviousProgram, + a.current_program AS CurrentProgram, + a.part_count_delta AS PartCountDelta, + a.analysis_summary AS AnalysisSummary + FROM log_collect_analysis a + LEFT JOIN cnc_business.cnc_machine m ON a.machine_id = m.id + WHERE a.raw_log_id = @RawLogId + ORDER BY a.analysis_time DESC"; + return conn.Query(sql, new { RawLogId = rawLogId }).AsList(); + } + } + + public long Create(CollectAnalysis entity) + { + using (var conn = CreateConnection()) + { + var sql = @"INSERT INTO log_collect_analysis + (analysis_time, raw_log_id, collect_address_id, machine_id, analysis_type, + previous_program, current_program, previous_part_count, current_part_count, + part_count_delta, previous_status, current_status, analysis_summary, + analysis_detail, created_at) + VALUES (@AnalysisTime, @RawLogId, @CollectAddressId, @MachineId, @AnalysisType, + @PreviousProgram, @CurrentProgram, @PreviousPartCount, @CurrentPartCount, + @PartCountDelta, @PreviousStatus, @CurrentStatus, @AnalysisSummary, + @AnalysisDetail, NOW()); + SELECT LAST_INSERT_ID();"; + return conn.ExecuteScalar(sql, new + { + entity.AnalysisTime, + entity.RawLogId, + entity.CollectAddressId, + entity.MachineId, + entity.AnalysisType, + entity.PreviousProgram, + entity.CurrentProgram, + entity.PreviousPartCount, + entity.CurrentPartCount, + entity.PartCountDelta, + entity.PreviousStatus, + entity.CurrentStatus, + entity.AnalysisSummary, + entity.AnalysisDetail + }); + } + } + + public int DeleteBeforeDate(DateTime date) + { + using (var conn = CreateConnection()) + { + return conn.Execute( + "DELETE FROM log_collect_analysis WHERE analysis_time < @Date", + new { Date = date }); + } + } + } +} diff --git a/src/CncRepository/Impl/Log/CollectCycleRepository.cs b/src/CncRepository/Impl/Log/CollectCycleRepository.cs new file mode 100644 index 0000000..7bb70e3 --- /dev/null +++ b/src/CncRepository/Impl/Log/CollectCycleRepository.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Dapper; +using MySqlConnector; +using CncModels.Dto; +using CncModels.Dto.CollectLog; +using CncModels.Entity; +using CncRepository.Base; +using CncRepository.Interface; + +namespace CncRepository.Impl.Log +{ + /// + /// 采集周期仓储实现(日志库 - log_collect_cycle) + /// + public class CollectCycleRepository : LogRepository, ICollectCycleRepository + { + public CollectCycleRepository(string connectionString) : base(connectionString) { } + + public PagedResult GetCycleList(CollectCycleQuery query) + { + using (var conn = CreateConnection()) + { + var whereParts = new List { "1=1" }; + var p = new DynamicParameters(); + + if (query.StartDate.HasValue) + { + whereParts.Add("c.cycle_time >= @StartDate"); + p.Add("StartDate", query.StartDate); + } + if (query.EndDate.HasValue) + { + whereParts.Add("c.cycle_time <= @EndDate"); + p.Add("EndDate", query.EndDate); + } + if (query.CollectAddressId.HasValue) + { + whereParts.Add("c.collect_address_id = @CollectAddressId"); + p.Add("CollectAddressId", query.CollectAddressId); + } + if (query.HasAnomaly.HasValue) + { + whereParts.Add("c.has_anomaly = @HasAnomaly"); + p.Add("HasAnomaly", query.HasAnomaly); + } + + var whereSql = string.Join(" AND ", whereParts); + + var total = conn.ExecuteScalar( + $"SELECT COUNT(1) FROM log_collect_cycle c WHERE {whereSql}", p); + + var dataSql = $@"SELECT + c.id AS Id, + DATE_FORMAT(c.cycle_time, '%Y-%m-%d %H:%i:%s') AS CycleTime, + c.collect_address_id AS CollectAddressId, + ca.address_name AS AddressName, + c.total_machines AS TotalMachines, + c.success_count AS SuccessCount, + c.fail_count AS FailCount, + c.has_anomaly AS HasAnomaly, + c.change_distribution AS ChangeDistribution, + c.cycle_summary AS CycleSummary + FROM log_collect_cycle c + LEFT JOIN cnc_business.cnc_collect_address ca ON c.collect_address_id = ca.id + WHERE {whereSql} + ORDER BY c.cycle_time DESC + LIMIT @PageSize OFFSET @Offset"; + + var items = conn.Query(dataSql, + new { PageSize = query.PageSize, Offset = query.Offset }).AsList(); + + return new PagedResult + { + Items = items, + Total = total, + Page = query.Page, + PageSize = query.PageSize + }; + } + } + + public long Create(CollectCycle entity) + { + using (var conn = CreateConnection()) + { + var sql = @"INSERT INTO log_collect_cycle + (cycle_time, collect_address_id, raw_log_id, end_time, duration_ms, + total_machines, success_count, fail_count, change_distribution, + has_anomaly, cycle_summary, created_at) + VALUES (@CycleTime, @CollectAddressId, @RawLogId, @EndTime, @DurationMs, + @TotalMachines, @SuccessCount, @FailCount, @ChangeDistribution, + @HasAnomaly, @CycleSummary, NOW()); + SELECT LAST_INSERT_ID();"; + return conn.ExecuteScalar(sql, new + { + entity.CycleTime, + entity.CollectAddressId, + entity.RawLogId, + entity.EndTime, + entity.DurationMs, + entity.TotalMachines, + entity.SuccessCount, + entity.FailCount, + entity.ChangeDistribution, + entity.HasAnomaly, + entity.CycleSummary + }); + } + } + + public int DeleteBeforeDate(DateTime date) + { + using (var conn = CreateConnection()) + { + return conn.Execute( + "DELETE FROM log_collect_cycle WHERE cycle_time < @Date", + new { Date = date }); + } + } + } +} diff --git a/src/CncRepository/Interface/ICollectAnalysisRepository.cs b/src/CncRepository/Interface/ICollectAnalysisRepository.cs new file mode 100644 index 0000000..42b6086 --- /dev/null +++ b/src/CncRepository/Interface/ICollectAnalysisRepository.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using CncModels.Dto; +using CncModels.Dto.CollectLog; +using CncModels.Entity; + +namespace CncRepository.Interface +{ + /// + /// 采集分析仓储接口 + /// + public interface ICollectAnalysisRepository + { + PagedResult GetAnalysisList(CollectAnalysisQuery query); + CollectAnalysisDetail GetAnalysisDetail(long id); + List GetAnalysisByRawLogId(long rawLogId); + long Create(CollectAnalysis entity); + int DeleteBeforeDate(DateTime date); + } +} diff --git a/src/CncRepository/Interface/ICollectCycleRepository.cs b/src/CncRepository/Interface/ICollectCycleRepository.cs new file mode 100644 index 0000000..8c5509f --- /dev/null +++ b/src/CncRepository/Interface/ICollectCycleRepository.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using CncModels.Dto; +using CncModels.Dto.CollectLog; +using CncModels.Entity; + +namespace CncRepository.Interface +{ + /// + /// 采集周期仓储接口 + /// + public interface ICollectCycleRepository + { + PagedResult GetCycleList(CollectCycleQuery query); + long Create(CollectCycle entity); + int DeleteBeforeDate(DateTime date); + } +} diff --git a/src/CncService/Impl/CollectLogService.cs b/src/CncService/Impl/CollectLogService.cs index 60817fd..33a9f91 100644 --- a/src/CncService/Impl/CollectLogService.cs +++ b/src/CncService/Impl/CollectLogService.cs @@ -8,7 +8,9 @@ using CncRepository.Interface; namespace CncService.Impl { - // 采集日志相关的业务实现 + /// + /// 采集日志相关的业务实现 + /// public class CollectLogService : ICollectLogService { private readonly ICollectAnalysisRepository _analysisRepository; diff --git a/src/CncService/Interface/ICollectLogService.cs b/src/CncService/Interface/ICollectLogService.cs index a83a50a..3de1c6a 100644 --- a/src/CncService/Interface/ICollectLogService.cs +++ b/src/CncService/Interface/ICollectLogService.cs @@ -6,16 +6,16 @@ namespace CncService.Interface { public interface ICollectLogService { - // 分页查询采集分析日志 + /// 分页查询采集分析日志 PagedResult GetAnalysisList(CollectAnalysisQuery query); - // 获取单条采集分析日志的详情 + /// 获取单条采集分析日志的详情 CollectAnalysisDetail GetAnalysisDetail(long id); - // 根据原始日志ID查找相关联的分析记录 + /// 根据原始日志ID查找相关联的分析记录 List GetAnalysisByRawLogId(long rawLogId); - // 分页查询采集周期信息 + /// 分页查询采集周期信息 PagedResult GetCycleList(CollectCycleQuery query); } } diff --git a/src/CncWebApi/Controllers/CollectLogController.cs b/src/CncWebApi/Controllers/CollectLogController.cs index 0f4d401..7ab02e0 100644 --- a/src/CncWebApi/Controllers/CollectLogController.cs +++ b/src/CncWebApi/Controllers/CollectLogController.cs @@ -3,12 +3,17 @@ using System.Collections.Generic; using System.Web.Http; using CncModels.Dto; using CncModels.Dto.CollectLog; +using CncModels.Entity; using CncService.Interface; using CncRepository.Interface; +using CncWebApi.Infrastructure; using System.Web.Http.Description; namespace CncWebApi.Controllers { + /// + /// 采集日志管理控制器 + /// [RoutePrefix("api/admin/collect-log")] [JwtAuthFilter] public class CollectLogController : ApiController @@ -22,19 +27,20 @@ namespace CncWebApi.Controllers _rawRepository = rawRepository ?? throw new ArgumentNullException(nameof(rawRepository)); } - // GET api/admin/collect-log/analysis + /// 分页查询采集分析记录 [HttpGet] [Route("analysis")] [ResponseType(typeof(ApiResponse>))] public IHttpActionResult GetAnalysisList([FromUri] CollectAnalysisQuery query) { + if (query == null) query = new CollectAnalysisQuery(); var result = _collectLogService.GetAnalysisList(query); return Ok(ApiResponse>.Success(result)); } - // GET api/admin/collect-log/analysis/{id} + /// 获取采集分析详情 [HttpGet] - [Route("analysis/{id}")] + [Route("analysis/{id:long}")] [ResponseType(typeof(ApiResponse))] public IHttpActionResult GetAnalysisDetail(long id) { @@ -42,9 +48,9 @@ namespace CncWebApi.Controllers return Ok(ApiResponse.Success(detail)); } - // GET api/admin/collect-log/analysis/by-raw/{rawLogId} + /// 根据原始日志ID查询关联的分析记录 [HttpGet] - [Route("analysis/by-raw/{rawLogId}")] + [Route("analysis/by-raw/{rawLogId:long}")] [ResponseType(typeof(ApiResponse>))] public IHttpActionResult GetAnalysisByRawLogId(long rawLogId) { @@ -52,23 +58,23 @@ namespace CncWebApi.Controllers return Ok(ApiResponse>.Success(list)); } - // GET api/admin/collect-log/cycle + /// 分页查询采集周期 [HttpGet] [Route("cycle")] [ResponseType(typeof(ApiResponse>))] public IHttpActionResult GetCycleList([FromUri] CollectCycleQuery query) { + if (query == null) query = new CollectCycleQuery(); var result = _collectLogService.GetCycleList(query); return Ok(ApiResponse>.Success(result)); } - // GET api/admin/collect-log/raw + /// 查询原始采集日志 [HttpGet] [Route("raw")] [ResponseType(typeof(ApiResponse>))] - public IHttpActionResult GetRaw([FromUri] int? collectAddressId, [FromUri] int page = 1, [FromUri] int pageSize = 20, [FromUri] string startDate = null, [FromUri] string endDate = null, [FromUri] bool? isSuccess = null) + public IHttpActionResult GetRawList([FromUri] int? collectAddressId, [FromUri] int page = 1, [FromUri] int pageSize = 20) { - // 通过 ICollectRawRepository 进行分页查询,具体筛选条件以仓储实现为准 var result = _rawRepository.GetByAddressId(collectAddressId ?? 0, page, pageSize); return Ok(ApiResponse>.Success(result)); } diff --git a/src/CncWebApi/Infrastructure/ServiceResolver.cs b/src/CncWebApi/Infrastructure/ServiceResolver.cs index 25f4551..52cc095 100644 --- a/src/CncWebApi/Infrastructure/ServiceResolver.cs +++ b/src/CncWebApi/Infrastructure/ServiceResolver.cs @@ -78,6 +78,10 @@ namespace CncWebApi.Infrastructure ResolveCollectAddressService()); if (serviceType == typeof(Controllers.HealthController)) return new Controllers.HealthController(); + if (serviceType == typeof(Controllers.CollectLogController)) + return new Controllers.CollectLogController( + ResolveCollectLogService(), + new CncRepository.Impl.Log.CollectRawRepository(_logConn)); return null; } @@ -184,6 +188,13 @@ namespace CncWebApi.Infrastructure return new CncRepository.Impl.ProductionAdjustmentRepository(_businessConn); } + private ICollectLogService ResolveCollectLogService() + { + return new CncService.Impl.CollectLogService( + new CncRepository.Impl.Log.CollectAnalysisRepository(_logConn), + new CncRepository.Impl.Log.CollectCycleRepository(_logConn)); + } + #endregion } } diff --git a/tests/CncCollector.Tests/CollectWorkerTests.cs b/tests/CncCollector.Tests/CollectWorkerTests.cs index be0a12c..b88b546 100644 --- a/tests/CncCollector.Tests/CollectWorkerTests.cs +++ b/tests/CncCollector.Tests/CollectWorkerTests.cs @@ -39,8 +39,8 @@ namespace CncCollector.Tests string businessConn = "Server=.;Database=NonExistingDb;User Id=invalid;Password=invalid;"; string logConn = "Server=.;Database=NonExistingLogDb;User Id=invalid;Password=invalid;"; - // 允许 tracker 为 null,因为在无效 URL 的测试路径下通常不会进入需要 Tracker 的分支 - _worker = new CollectWorker(_address, _config, null, businessConn, logConn); + // 允许 tracker 和 analysisEngine 为 null,因为在无效 URL 的测试路径下通常不会进入需要它们的分支 + _worker = new CollectWorker(_address, _config, null, null, businessConn, logConn); } public void Dispose()