采集服务产量统计全流程测试验证:DB Schema同步、品牌映射修正、采集/模拟器代码修改、10场景测试全通过

main
haoliang 1 month ago
parent e1f086015c
commit 54910626b1

@ -83,6 +83,7 @@ CREATE TABLE IF NOT EXISTS cnc_machine (
last_run_status VARCHAR(20) NULL, last_run_status VARCHAR(20) NULL,
last_program_name VARCHAR(200) NULL, last_program_name VARCHAR(200) NULL,
last_part_count DECIMAL(15,5) NULL, last_part_count DECIMAL(15,5) NULL,
last_total_part_count DECIMAL(15,5) NULL,
last_operate_mode VARCHAR(20) NULL, last_operate_mode VARCHAR(20) NULL,
last_machining_status VARCHAR(20) NULL, last_machining_status VARCHAR(20) NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
@ -128,6 +129,7 @@ CREATE TABLE IF NOT EXISTS cnc_collect_record (
device_time DATETIME NULL, device_time DATETIME NULL,
program_name VARCHAR(200) NULL, program_name VARCHAR(200) NULL,
part_count DECIMAL(15,5) NULL, part_count DECIMAL(15,5) NULL,
total_part_count DECIMAL(15,5) NULL,
device_status VARCHAR(20) NULL, device_status VARCHAR(20) NULL,
run_status VARCHAR(20) NULL, run_status VARCHAR(20) NULL,
operate_mode VARCHAR(20) NULL, operate_mode VARCHAR(20) NULL,

@ -15,26 +15,18 @@ INSERT IGNORE INTO cnc_workshop (name, sort_order, is_enabled) VALUES
INSERT IGNORE INTO cnc_brand (brand_name, device_field, tags_path, is_enabled) VALUES INSERT IGNORE INTO cnc_brand (brand_name, device_field, tags_path, is_enabled) VALUES
('FANUC', 'device', 'tags', 1); ('FANUC', 'device', 'tags', 1);
-- FANUC字段映射预置 -- FANUC字段映射预置field_name = 实际FANUC采集JSON中的tag id
SET @fanuc_brand_id = (SELECT id FROM cnc_brand WHERE brand_name = 'FANUC'); SET @fanuc_brand_id = (SELECT id FROM cnc_brand WHERE brand_name = 'FANUC');
INSERT IGNORE INTO cnc_brand_field_mapping (brand_id, standard_field, field_name, match_by, data_type, is_required) VALUES INSERT IGNORE INTO cnc_brand_field_mapping (brand_id, standard_field, field_name, match_by, data_type, is_required) VALUES
(@fanuc_brand_id, 'program_name', 'program_name', 'id', 'string', 1), (@fanuc_brand_id, 'program_name', 'Tag5', 'id', 'string', 1),
(@fanuc_brand_id, 'part_count', 'part_count', 'id', 'number', 1), (@fanuc_brand_id, 'part_count', 'Tag8', 'id', 'number', 1),
(@fanuc_brand_id, 'device_status', 'device_status', 'id', 'number', 0), (@fanuc_brand_id, 'total_part_count', 'Tag1', 'id', 'number', 0),
(@fanuc_brand_id, 'run_status', 'run_status', 'id', 'number', 0), (@fanuc_brand_id, 'device_status', '_io_status', 'id', 'number', 0),
(@fanuc_brand_id, 'operate_mode', 'operate_mode', 'id', 'number', 0), (@fanuc_brand_id, 'run_status', 'Tag9', 'id', 'number', 0),
(@fanuc_brand_id, 'spindle_speed_set', 'spindle_speed_set', 'id', 'number', 0), (@fanuc_brand_id, 'operate_mode', 'Tag11', 'id', 'number', 0),
(@fanuc_brand_id, 'feed_speed_set', 'feed_speed_set', 'id', 'number', 0), (@fanuc_brand_id, 'power_on_time', 'Tag22', 'id', 'number', 0),
(@fanuc_brand_id, 'spindle_speed_actual', 'spindle_speed_actual', 'id', 'number', 0), (@fanuc_brand_id, 'run_time', 'Tag23', 'id', 'number', 0);
(@fanuc_brand_id, 'feed_speed_actual', 'feed_speed_actual', 'id', 'number', 0),
(@fanuc_brand_id, 'spindle_load', 'spindle_load', 'id', 'number', 0),
(@fanuc_brand_id, 'spindle_override', 'spindle_override', 'id', 'number', 0),
(@fanuc_brand_id, 'power_on_time', 'power_on_time', 'id', 'number', 0),
(@fanuc_brand_id, 'run_time', 'run_time', 'id', 'number', 0),
(@fanuc_brand_id, 'cutting_time', 'cutting_time', 'id', 'number', 0),
(@fanuc_brand_id, 'cycle_time', 'cycle_time', 'id', 'number', 0),
(@fanuc_brand_id, 'machining_status', 'machining_status', 'id', 'string', 0);
-- 系统配置预置 -- 系统配置预置
INSERT IGNORE INTO cnc_sys_config (config_key, config_value, value_type, description) VALUES INSERT IGNORE INTO cnc_sys_config (config_key, config_value, value_type, description) VALUES

@ -63,6 +63,9 @@
| 原始数据 | 存原始JSON到日志库解析后结构化数据存业务库 | | 原始数据 | 存原始JSON到日志库解析后结构化数据存业务库 |
| 时间标准 | 以服务器时间为准,机床时间仅存为参考字段 | | 时间标准 | 以服务器时间为准,机床时间仅存为参考字段 |
| 数值处理 | 自动去除.00000尾缀,转换为数值/字符串 | | 数值处理 | 自动去除.00000尾缀,转换为数值/字符串 |
| 数据有效性 | quality≠0的tag无效time以1970-01-01开头的tag无效采集失败_io_status始终有效不受quality/time约束 |
| 总零件数 | Tag1加工零件总数为历史终身累计值与Tag8当前程序零件数不同维度 |
| 变更检测 | 采集服务对比tag的value值判断变化不依赖time字段time和value独立变化 |
### 4.3 零件产量统计 ### 4.3 零件产量统计

@ -88,6 +88,7 @@ CREATE TABLE cnc_brand_field_mapping (
| cutting_time | 切削时间 | number | | cutting_time | 切削时间 | number |
| cycle_time | 循环时间 | number | | cycle_time | 循环时间 | number |
| machining_status | 加工状态 | string | | machining_status | 加工状态 | string |
| total_part_count | 历史累计加工零件总数 | number |
索引uk_brand_standard唯一约束同时作为索引保证同一品牌下标准字段不重复采集时按brand_id查询所有映射规则。 索引uk_brand_standard唯一约束同时作为索引保证同一品牌下标准字段不重复采集时按brand_id查询所有映射规则。
@ -137,6 +138,7 @@ CREATE TABLE cnc_machine (
last_run_status VARCHAR(20) NULL, last_run_status VARCHAR(20) NULL,
last_program_name VARCHAR(200) NULL, last_program_name VARCHAR(200) NULL,
last_part_count DECIMAL(15,5) NULL, last_part_count DECIMAL(15,5) NULL,
last_total_part_count DECIMAL(15,5) NULL,
last_operate_mode VARCHAR(20) NULL, last_operate_mode VARCHAR(20) NULL,
last_machining_status VARCHAR(20) NULL, last_machining_status VARCHAR(20) NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
@ -208,6 +210,7 @@ CREATE TABLE cnc_collect_record (
device_time DATETIME NULL, device_time DATETIME NULL,
program_name VARCHAR(200) NULL, program_name VARCHAR(200) NULL,
part_count DECIMAL(15,5) NULL, part_count DECIMAL(15,5) NULL,
total_part_count DECIMAL(15,5) NULL,
device_status VARCHAR(20) NULL, device_status VARCHAR(20) NULL,
run_status VARCHAR(20) NULL, run_status VARCHAR(20) NULL,
operate_mode VARCHAR(20) NULL, operate_mode VARCHAR(20) NULL,

@ -0,0 +1,508 @@
# 发那科采集数据结构变更 - 系统调整方案
> 创建时间2026-05-11
> 触发原因:发那科系统采集示例文件(根目录 `发那科系统采集示例.txt`更新实际FANUC采集数据结构与系统原有设计存在差异
> 涉及模块CncSimulator模拟器、数据库设计、采集服务设计、后台管理功能
---
## 一、实际FANUC数据结构2026-05-11 采自生产环境)
### 1.1 顶层结构
```json
[
{
"device": "fanake-1.2_1.2", // 设备编码,匹配 cnc_machine.device_code
"desc": "西-1.2", // 设备描述
"tags": [ ... ] // 10个Tag
},
...
]
```
共32台设备每台10个Tag。其中25台在线、7台离线。
### 1.2 Tag清单10个
| # | Tag ID | desc中文 | 类型 | 示例值 | 备注 |
|---|--------|-------------|------|--------|------|
| 1 | `_io_status` | 设备状态 | 数值 | 0.00000 / 1.00000 | 0=离线, 1=在线;**始终有效** |
| 2 | `Tag5` | 执行的NC主程序名 | 字符串 | O1, O9001, 1370.NC, 7, 1027, O2, 037.NC | 可为空 |
| 3 | `Tag6` | 执行的NC主程序号 | 字符串 | N0, N1, N9, N20 | |
| 4 | `Tag7` | 当前加工程序内容 | 字符串 | G代码片段 | 可为空 |
| 5 | `Tag8` | 当前加工零件数 | 数值 | 35.00000 | 当前程序的零件计数 |
| 6 | `Tag9` | 运行状态 | 数值 | 0.00000 / 1.00000 / 3.00000 | 0=待机, 1=运行, 3=加工中 |
| 7 | `Tag11` | 操作模式 | 数值 | 1 / 4 / 5 / 10 | 1=MEM, 4=?, 5=?, 10=JOG |
| 8 | `Tag22` | 开机时间 | 数值 | 45129840.00000 | 秒,历史累计 |
| 9 | `Tag23` | 运行时间 | 数值 | 12201.00000 | 秒,历史累计 |
| 10 | `Tag1` | 加工零件总数 | 数值 | 45930.00000 | **历史终身累计,新增字段** |
### 1.3 数据有效性规则
#### 规则1Quality字段
| quality值 | 含义 | 处理方式 |
|-----------|------|---------|
| `"0"` | 数据有效 | 正常解析 |
| `"1"` | 数据无效(采集失败/设备离线) | 忽略该tag的value |
#### 规则2Time字段采集失败标记
| time值 | 含义 | 处理方式 |
|--------|------|---------|
| `1970-01-01 08:00:00` | 采集失败 | 忽略该tag的value |
| 其他正常时间 | 数据时间戳 | 正常使用 |
#### 规则3_io_status 特殊处理
`_io_status` tag **不受规则1和规则2约束**始终读取value判断设备在线/离线:
- 即使设备离线,`_io_status` 的 quality=0、time=真实时间、value=0
#### 规则4Time与Value独立变化
每个tag的 `time``value` 更新频率不固定:
- time变了、value没变
- value变了、time没变
- 都变了
- 都没变
**采集服务的变更检测必须对比value值不能依赖time变化作为变更信号。**
### 1.4 离线设备特征
以 fanake-1.10 为例:
```
_io_status: quality=0, value=0.00000, time=2026-05-11 17:06:34 ← 始终有效
Tag5: quality=1, value="", time=1970-01-01 08:00:00 ← 无效
Tag6: quality=1, value="", time=1970-01-01 08:00:00 ← 无效
Tag7: quality=1, value="", time=1970-01-01 08:00:00 ← 无效
Tag8: quality=1, value=0.00000, time=1970-01-01 08:00:00 ← 无效
Tag9: quality=1, value=0.00000, time=1970-01-01 08:00:00 ← 无效
Tag11: quality=1, value=0.00000, time=1970-01-01 08:00:00 ← 无效
Tag22: quality=1, value=0.00000, time=1970-01-01 08:00:00 ← 无效
Tag23: quality=1, value=0.00000, time=1970-01-01 08:00:00 ← 无效
Tag1: quality=1, value=0.00000, time=1970-01-01 08:00:00 ← 无效
```
---
## 二、与现有设计的差异对比
### 2.1 模拟器FanucDataGenerator差异
| 差异项 | 现有模拟器18个Tag | 实际FANUC10个Tag | 影响 |
|--------|----------------------|---------------------|------|
| Tag2 (当前轴数) | ✅ 有 | ❌ 不存在 | 模拟器多余 |
| Tag14 (主轴倍率) | ✅ 有 | ❌ 不存在 | 模拟器多余 |
| Tag17 (主轴设定速度) | ✅ 有 | ❌ 不存在 | 模拟器多余 |
| Tag18 (进给设定速度) | ✅ 有 | ❌ 不存在 | 模拟器多余 |
| Tag19 (主轴实际速度) | ✅ 有 | ❌ 不存在 | 模拟器多余 |
| Tag20 (进给实际转速) | ✅ 有 | ❌ 不存在 | 模拟器多余 |
| Tag21 (主轴负载) | ✅ 有 | ❌ 不存在 | 模拟器多余 |
| Tag24 (切削时间) | ✅ 有 | ❌ 不存在 | 模拟器多余 |
| Tag25 (循环时间) | ✅ 有 | ❌ 不存在 | 模拟器多余 |
| Tag26 (加工状态) | ✅ 有 | ❌ 不存在 | 模拟器多余 |
| **Tag1 (加工零件总数)** | ❌ 缺少 | ✅ 有 | **模拟器缺少,需新增** |
| Tag6 desc | "执行的NC主程序号" | "执行的NC主程序号" | 一致 |
### 2.2 数据库设计差异
| 差异项 | 现有设计 | 实际需求 |
|--------|---------|---------|
| 标准字段 total_part_count | ❌ 无 | ✅ 需要新增对应Tag1 |
| cnc_collect_record.total_part_count | ❌ 无 | ✅ 需要新增列 |
| cnc_machine.last_total_part_count | ❌ 无 | ✅ 需要新增列 |
### 2.3 采集服务设计差异
| 差异项 | 现有设计假设 | 实际行为 |
|--------|-------------|---------|
| 数据有效性判断 | 未明确quality处理 | 需按quality+time双规则过滤 |
| _io_status处理 | 未明确特殊性 | 始终有效不受quality/time约束 |
| 变更检测 | 未明确time/value独立性 | 必须对比value不看time |
| Tag1(总零件数) | 无此字段 | 需解析并存储 |
### 2.4 后台管理差异
| 差异项 | 现有设计 | 实际需求 |
|--------|---------|---------|
| 速度/倍率/负载字段 | 设计了展示 | FANUC设备这些字段为NULL需处理空值 |
| 加工零件总数 | 未设计展示 | 可选展示 |
| 操作模式枚举 | 仅定义1=MEM, 10=JOG | 实际还有4、5值需补充定义 |
---
## 三、调整方案
### 3.1 CncSimulator 模拟器调整(优先级:高)
> 目标让模拟器生成的数据结构与实际FANUC完全一致
#### 3.1.1 DeviceState.cs 修改
```diff
public class DeviceState
{
// ===== 固定信息(来自配置) =====
public string DeviceCode { get; set; }
public string Desc { get; set; }
// ===== 动态状态 =====
public string CurrentScenario { get; set; } = "idle";
public bool IsOnline { get; set; } = true;
public string ProgramName { get; set; } = "O0001";
public int PartCount { get; set; } = 0;
+ /// <summary>历史终身累计零件总数对应Tag1</summary>
+ public decimal TotalPartCount { get; set; } = 0;
public int DeviceStatus { get; set; } = 1;
public int RunStatus { get; set; } = 0;
public int OperateMode { get; set; } = 1;
- public decimal SpindleSpeedSet { get; set; } = 450;
- public decimal FeedSpeedSet { get; set; } = 60;
- public decimal SpindleSpeedActual { get; set; } = 0;
- public decimal FeedSpeedActual { get; set; } = 0;
- public decimal SpindleLoad { get; set; } = 0;
- public decimal SpindleOverride { get; set; } = 100;
public decimal PowerOnTime { get; set; } = 0;
public decimal RunTime { get; set; } = 0;
- public decimal CuttingTime { get; set; } = 0;
- public decimal CycleTime { get; set; } = 0;
- public string MachiningStatus { get; set; } = "";
public string ProgramContent { get; set; } = "";
// ... 其余字段保留不变
}
```
**说明**移除实际FANUC不存在的字段SpindleSpeedSet、FeedSpeedSet、SpindleSpeedActual、FeedSpeedActual、SpindleLoad、SpindleOverride、CuttingTime、CycleTime、MachiningStatus新增TotalPartCount。
#### 3.1.2 FanucDataGenerator.cs 修改
```diff
private JArray GenerateTags(DeviceState state)
{
var tags = new JArray();
DateTime baseTime = DateTime.Now;
// 每个tag的时间基准上随机偏移 -5~0 秒
DateTime TagTime()
{
return baseTime.AddSeconds(-_rng.Next(0, 6));
}
void AddNumericTag(string id, string desc, decimal value) { /* 不变 */ }
void AddStringTag(string id, string desc, string value) { /* 不变 */ }
- // 18个tag → 10个tag与实际FANUC完全一致
AddNumericTag("_io_status", "设备状态", state.DeviceStatus);
- AddNumericTag("Tag2", "当前轴数", 4);
+ AddNumericTag("Tag1", "加工零件总数", state.TotalPartCount);
AddStringTag("Tag5", "执行的NC主程序名", state.ProgramName);
AddStringTag("Tag6", "执行的NC主程序号", "N0");
AddStringTag("Tag7", "当前加工程序内容", /* ... */);
AddNumericTag("Tag8", "当前加工零件数", state.PartCount);
AddNumericTag("Tag9", "运行状态", state.RunStatus);
AddNumericTag("Tag11", "操作模式", state.OperateMode);
- AddNumericTag("Tag14", "当前主轴倍率", state.SpindleOverride);
- AddNumericTag("Tag17", "主轴设定速度", state.SpindleSpeedSet);
- AddNumericTag("Tag18", "进给设定速度", state.FeedSpeedSet);
- AddNumericTag("Tag19", "主轴实际速度", state.SpindleSpeedActual);
- AddNumericTag("Tag20", "进给实际转速", state.FeedSpeedActual);
- AddNumericTag("Tag21", "主轴负载", state.SpindleLoad);
AddNumericTag("Tag22", "开机时间", state.PowerOnTime);
AddNumericTag("Tag23", "运行时间", state.RunTime);
- AddNumericTag("Tag24", "切削时间", state.CuttingTime);
- AddNumericTag("Tag25", "循环时间", state.CycleTime);
- AddStringTag("Tag26", "加工状态", state.MachiningStatus);
return tags;
}
```
#### 3.1.3 DeviceSimulator.cs 修改
- 构造函数:新增 `TotalPartCount` 初始值配置从simulator.json读取
- `machining` 场景:`TotalPartCount++`
- `ApplyProgramChange()`TotalPartCount 不清零(终身累计)
- `ApplyManualReset()`TotalPartCount 不清零
- `ApplyPowerOn()`TotalPartCount 不清零
- 移除所有已删除字段SpindleSpeed*、SpindleLoad、SpindleOverride、CuttingTime、CycleTime、MachiningStatus的模拟逻辑
#### 3.1.4 simulator.json 修改
devices配置中新增 `initialTotalPartCount` 字段:
```json
{
"deviceCode": "CNC-A001",
"desc": "西栋1号",
"initialProgram": "O0001",
"initialPartCount": 50,
"initialTotalPartCount": 45000
}
```
#### 3.1.5 模拟器离线行为
当设备断电power_off需要生成与实际一致的离线数据
- `_io_status`quality=0, value=0, time=当前时间
- 其他所有tagquality=1, value="" 或 "0.00000", time="1970-01-01 08:00:00"
---
### 3.2 数据库设计调整(优先级:高)
#### 3.2.1 cnc_brand_field_mapping 标准字段约定
`docs/01-数据库设计.md` 的标准字段约定表中新增:
| standard_field | 含义 | data_type |
|----------------|------|-----------|
| **total_part_count** | **历史累计加工零件总数** | **number** |
#### 3.2.2 cnc_collect_record 表
新增列:
```sql
ALTER TABLE cnc_collect_record
ADD COLUMN total_part_count DECIMAL(15,5) NULL COMMENT '历史累计加工零件总数'
AFTER part_count;
```
完整DDL变更位置`part_count` 列之后增加 `total_part_count` 列。
#### 3.2.3 cnc_machine 表
新增列:
```sql
ALTER TABLE cnc_machine
ADD COLUMN last_total_part_count DECIMAL(15,5) NULL COMMENT '最新历史累计加工零件总数'
AFTER last_part_count;
```
#### 3.2.4 FANUC品牌预置映射数据
`database/` 预置数据脚本中FANUC品牌的 field_mapping 新增一行:
| brand_id | standard_field | field_name | match_by | data_type | is_required |
|----------|---------------|------------|----------|-----------|-------------|
| (FANUC) | total_part_count | Tag1 | id | number | 0 |
同时以下标准字段在实际FANUC中不存在映射记录应标记 `is_required=0` 或不创建映射(由采集服务按实际数据决定):
- spindle_speed_set → 无对应Tag
- feed_speed_set → 无对应Tag
- spindle_speed_actual → 无对应Tag
- feed_speed_actual → 无对应Tag
- spindle_load → 无对应Tag
- spindle_override → 无对应Tag
- cutting_time → 无对应Tag
- cycle_time → 无对应Tag
- machining_status → 无对应Tag
---
### 3.3 采集服务设计调整(优先级:高)
#### 3.3.1 采集循环核心逻辑(伪代码)
```
每次采集循环(按采集地址配置的间隔):
1. Ping目标地址
- Ping失败 → 更新 cnc_collect_address.fail_count++
- 连续5次失败 → 写告警
- 跳过本次采集
2. HTTP GET 拉取JSON数组
3. 原始JSON存日志库cnc_raw_log每次都存不管有没有变化
4. 逐设备处理JSON数组中的每个元素
4.1 用 device 值匹配 cnc_machine.device_code
- 未匹配到 → 记录告警"发现未知设备",跳过
4.2 解析 _io_status特殊处理
→ 直接读取 value不管 quality 和 time
→ value=1 → 设备在线
→ value=0 → 设备离线
4.3 解析其他tag标准处理
for each tag in tags (排除 _io_status):
if tag.quality != "0" → 跳过(数据无效)
if tag.time 以 "1970-01-01" 开头 → 跳过(采集失败)
否则 → 按 brand_field_mapping 映射为标准字段,解析 value
4.4 写入结构化采集记录:
→ INSERT INTO cnc_collect_record (每台设备每次采集一条)
→ total_part_count 列写入 Tag1 的值(如果有效)
→ 速度/倍率/负载等字段写 NULLFANUC无此数据
4.5 业务变更检测(只对比 value不看 time
a. NC程序名变更检测
if 新Tag5.value != 旧 cnc_machine.last_program_name:
→ 结账当前产量段 (close_reason='program_change')
→ 开新产量段
b. 零件数下降检测(手动清零):
if 新Tag8.value < 旧 cnc_machine.last_part_count
AND 新Tag5.value == 旧 cnc_machine.last_program_name:
→ 结账当前产量段 (close_reason='manual_reset')
→ 开新产量段part_count 从新值开始
c. 运行状态变更:
if 新Tag9.value != 旧 cnc_machine.last_run_status:
→ 仅更新实时状态,不触发产量段逻辑
d. 总零件数:仅记录,不触发任何业务逻辑
4.6 更新机床实时状态cnc_machine 的 last_* 字段):
→ last_program_name = Tag5.value
→ last_part_count = Tag8.value
→ last_total_part_count = Tag1.value
→ last_run_status = Tag9.value
→ last_operate_mode = Tag11.value
→ last_device_status = _io_status.value
→ is_online = (_io_status.value == 1)
→ last_collect_time = 当前服务器时间
5. 更新采集地址状态:
→ last_collect_time = 当前时间
→ last_collect_status = 'success'
→ fail_count = 0
```
#### 3.3.2 数据有效性判断流程图
```
收到一个tag
├── tag.id == "_io_status" ?
│ ├── YES → 始终有效,直接读 value
│ └── NO → 进入标准判断 ↓
├── tag.quality != "0" ?
│ ├── YES → 无效,跳过
│ └── NO → 继续判断 ↓
└── tag.time 以 "1970-01-01" 开头 ?
├── YES → 无效,跳过
└── NO → 有效,解析 tag.value
```
---
### 3.4 后台管理功能调整(优先级:中)
#### 3.4.1 设备状态展示
`cnc_machine` 表已有 `last_device_status`、`last_run_status` 等字段,展示逻辑不变。新增:
- 设备详情中可选展示"累计加工总数"(来自 `last_total_part_count`
#### 3.4.2 大屏看板空值处理
FANUC设备的以下字段恒为NULL
- 主轴设定速度、进给设定速度
- 主轴实际速度、进给实际速度
- 主轴负载、主轴倍率
- 切削时间、循环时间
- 加工状态
大屏如果展示这些指标:
- 数值型 → 显示 `--``N/A`
- 图表型 → 该FANUC设备的线不画
#### 3.4.3 品牌字段映射管理
映射配置页面需支持新增的 `total_part_count` 标准字段。FANUC品牌预置映射中需包含 `Tag1 → total_part_count`
#### 3.4.4 前端Mock数据同步
前端Mock数据中与FANUC相关的设备状态Mock应同步为10个Tag的字段集移除速度/负载等新增total_part_count
---
### 3.5 设计文档更新(优先级:中)
| 文档 | 更新内容 |
|------|---------|
| `docs/01-数据库设计.md` | 标准字段约定表增加 total_part_countcnc_collect_record 和 cnc_machine 的DDL增加新列 |
| `docs/00-需求与设计文档.md` | 4.2数据格式章节补充quality/1970-time/Tag1说明 |
| `docs/03-API接口设计.md` | 如果有涉及采集记录字段的API响应格式需同步增加 total_part_count |
| `database/` | ALTER TABLE脚本 + FANUC预置映射数据新增 |
---
## 四、不需要调整的部分
| 项目 | 原因 |
|------|------|
| 顶层JSON结构 `[{device, desc, tags}]` | 实际数据与设计一致 |
| device字段匹配机床逻辑 | 设计正确device值匹配device_code |
| 零件分段计数核心逻辑基于Tag8 | 不受影响仍基于NC程序名切换+清零 |
| 产量分段记录表 cnc_production_segment | 结构不变total_part_count不影响分段计算 |
| 采集地址/品牌管理配置架构 | 不变 |
| 双库分离设计原始JSON存日志库 | 不变 |
| 采集间隔配置(地址级统一间隔) | 不变 |
| 日终汇总逻辑 | 不变 |
---
## 五、执行顺序
```
阶段1模拟器适配让测试数据与实际一致
├── 1.1 DeviceState.cs 新增 TotalPartCount移除多余字段
├── 1.2 FanucDataGenerator.cs 改为10个Tag
├── 1.3 DeviceSimulator.cs 调整场景逻辑
├── 1.4 simulator.json 新增 initialTotalPartCount
└── 1.5 编译验证
阶段2数据库变更数据基础
├── 2.1 cnc_collect_record 增加 total_part_count 列
├── 2.2 cnc_machine 增加 last_total_part_count 列
├── 2.3 标准字段约定表增加 total_part_count
├── 2.4 FANUC预置映射数据增加 Tag1 映射
└── 2.5 设计文档同步更新
阶段3采集服务设计定稿核心逻辑
├── 3.1 编写采集服务数据解析逻辑含quality/time过滤
├── 3.2 编写变更检测逻辑只看value不看time
└── 3.3 编写Tag1存储逻辑total_part_count仅记录不触发业务
阶段4后台管理适配展示层
├── 4.1 大屏空值处理FANUC设备速度/负载为NULL时显示N/A
├── 4.2 前端Mock数据同步为10个Tag结构
└── 4.3 品牌映射管理支持total_part_count字段
```
---
## 六、补充说明
### 6.1 操作模式枚举值
实际数据中出现了以下操作模式值,具体含义需与现场确认:
| 值 | 已知含义 | 出现设备 |
|----|---------|---------|
| 1 | MEM内存运行 | 加工中的设备 |
| 4 | 待确认 | fanake-1.19(运行状态=1 |
| 5 | 待确认 | fanake-1.24(运行状态=0 |
| 10 | JOG手动 | 待机中的设备 |
### 6.2 device字段格式
实际数据格式为 `fanake-1.2_1.2`(品牌-编号_编号机床表的 `device_code` 需按此格式录入。
### 6.3 数值精度
所有数值型Tag的value都带5位小数`35.00000`),采集服务解析时需 `Convert.ToDecimal` 后存储到 `DECIMAL(15,5)` 列。
### 6.4 time字段格式
统一格式 `yyyy-MM-dd HH:mm:ss`,需要按此格式解析。设备离线时为 `1970-01-01 08:00:00`UTC+8时区下的epoch时间

@ -1,35 +1,26 @@
import type { MockMethod } from './types' import type { MockMethod } from './types'
const brands = [ const brands = [
{ id: 1, brandName: 'FANUC', deviceField: 'device', tagsPath: 'tags', isEnabled: 1, fieldCount: 16 }, { id: 1, brandName: 'FANUC', deviceField: 'device', tagsPath: 'tags', isEnabled: 1, fieldCount: 8 },
{ id: 2, brandName: 'SIEMENS', deviceField: 'device', tagsPath: 'tags', isEnabled: 1, fieldCount: 12 }, { id: 2, brandName: 'SIEMENS', deviceField: 'device', tagsPath: 'tags', isEnabled: 1, fieldCount: 12 },
{ id: 3, brandName: 'MITSUBISHI', deviceField: 'device', tagsPath: 'tags', isEnabled: 0, fieldCount: 8 }, { id: 3, brandName: 'MITSUBISHI', deviceField: 'device', tagsPath: 'tags', isEnabled: 0, fieldCount: 8 },
] ]
const standardFields = [ const standardFields = [
'program_name', 'part_count', 'device_status', 'run_status', 'operate_mode', 'program_name', 'part_count', 'total_part_count', 'device_status', 'run_status', 'operate_mode',
'spindle_speed_set', 'feed_speed_set', 'spindle_speed_actual', 'feed_speed_actual', 'power_on_time', 'run_time',
'spindle_load', 'spindle_override', 'power_on_time', 'run_time', 'cutting_time',
'cycle_time', 'machining_status',
] ]
// FANUC字段映射与实际FANUC采集JSON中的tag id一致
const fanucMappings = [ const fanucMappings = [
{ id: 1, standardField: 'program_name', fieldName: 'Tag5', matchBy: 'id', dataType: 'string', isRequired: 1 }, { id: 1, standardField: 'program_name', fieldName: 'Tag5', matchBy: 'id', dataType: 'string', isRequired: 1 },
{ id: 2, standardField: 'part_count', fieldName: 'Tag8', matchBy: 'id', dataType: 'number', isRequired: 1 }, { id: 2, standardField: 'part_count', fieldName: 'Tag8', matchBy: 'id', dataType: 'number', isRequired: 1 },
{ id: 3, standardField: 'device_status', fieldName: '_io_status', matchBy: 'id', dataType: 'number', isRequired: 1 }, { id: 3, standardField: 'total_part_count', fieldName: 'Tag1', matchBy: 'id', dataType: 'number', isRequired: 0 },
{ id: 4, standardField: 'run_status', fieldName: 'Tag9', matchBy: 'id', dataType: 'number', isRequired: 0 }, { id: 4, standardField: 'device_status', fieldName: '_io_status', matchBy: 'id', dataType: 'number', isRequired: 1 },
{ id: 5, standardField: 'operate_mode', fieldName: 'Tag10', matchBy: 'id', dataType: 'number', isRequired: 0 }, { id: 5, standardField: 'run_status', fieldName: 'Tag9', matchBy: 'id', dataType: 'number', isRequired: 0 },
{ id: 6, standardField: 'spindle_speed_set', fieldName: 'Tag11', matchBy: 'id', dataType: 'number', isRequired: 0 }, { id: 6, standardField: 'operate_mode', fieldName: 'Tag11', matchBy: 'id', dataType: 'number', isRequired: 0 },
{ id: 7, standardField: 'feed_speed_set', fieldName: 'Tag12', matchBy: 'id', dataType: 'number', isRequired: 0 }, { id: 7, standardField: 'power_on_time', fieldName: 'Tag22', matchBy: 'id', dataType: 'number', isRequired: 0 },
{ id: 8, standardField: 'spindle_speed_actual', fieldName: 'Tag13', matchBy: 'id', dataType: 'number', isRequired: 0 }, { id: 8, standardField: 'run_time', fieldName: 'Tag23', matchBy: 'id', dataType: 'number', isRequired: 0 },
{ id: 9, standardField: 'feed_speed_actual', fieldName: 'Tag14', matchBy: 'id', dataType: 'number', isRequired: 0 },
{ id: 10, standardField: 'spindle_load', fieldName: 'Tag15', matchBy: 'id', dataType: 'number', isRequired: 0 },
{ id: 11, standardField: 'spindle_override', fieldName: 'Tag16', matchBy: 'id', dataType: 'number', isRequired: 0 },
{ id: 12, standardField: 'power_on_time', fieldName: 'Tag17', matchBy: 'id', dataType: 'number', isRequired: 0 },
{ id: 13, standardField: 'run_time', fieldName: 'Tag18', matchBy: 'id', dataType: 'number', isRequired: 0 },
{ id: 14, standardField: 'cutting_time', fieldName: 'Tag19', matchBy: 'id', dataType: 'number', isRequired: 0 },
{ id: 15, standardField: 'cycle_time', fieldName: 'Tag20', matchBy: 'id', dataType: 'number', isRequired: 0 },
{ id: 16, standardField: 'machining_status', fieldName: 'Tag21', matchBy: 'id', dataType: 'string', isRequired: 0 },
] ]
const mock: MockMethod[] = [ const mock: MockMethod[] = [

@ -59,7 +59,7 @@ const mock: MockMethod[] = [
method: 'get', method: 'get',
response: () => ({ response: () => ({
code: 0, code: 0,
data: { programName: '1566.NC', partCount: 580, runStatus: '运行中', operateMode: '自动', spindleSpeedSet: 3000, feedSpeedSet: 500, spindleSpeedActual: 2980, feedSpeedActual: 480, spindleLoad: 65, machiningStatus: 'G01', lastCollectTime: '2026-04-25T17:36:38' }, data: { programName: '1566.NC', partCount: 580, totalPartCount: 88624, runStatus: '运行中', operateMode: '自动', lastCollectTime: '2026-04-25T17:36:38' },
}), }),
}, },
{ {
@ -68,7 +68,7 @@ const mock: MockMethod[] = [
method: 'get', method: 'get',
response: () => ({ response: () => ({
code: 0, code: 0,
data: { programName: '1566.NC', partCount: 580, runStatus: '运行中', operateMode: '自动', spindleSpeedSet: 3000, feedSpeedSet: 500, spindleSpeedActual: 2980, feedSpeedActual: 480, spindleLoad: 65, machiningStatus: 'G01', lastCollectTime: '2026-04-25T17:36:38' }, data: { programName: '1566.NC', partCount: 580, totalPartCount: 88624, runStatus: '运行中', operateMode: '自动', lastCollectTime: '2026-04-25T17:36:38' },
}), }),
}, },
{ {

@ -45,11 +45,11 @@ const mockStatusList = [
} }
] ]
// 模拟设备状态 // 模拟设备状态与实际FANUC 10个Tag结构一致
const mockDevices = [ const mockDevices = [
{ deviceCode: 'fanake_1.2', desc: '西-1.2', scenario: 'machining', isOnline: true, programName: 'O504', partCount: 14, runStatus: 3, operateMode: 10, spindleSpeedSet: 3000, spindleSpeedActual: 2980, feedSpeedSet: 500, feedSpeedActual: 490, spindleLoad: 65, machiningStatus: 'cutting', scenarioTick: 45, scenarioDuration: 120 }, { deviceCode: 'fanake_1.2', desc: '西-1.2', scenario: 'machining', isOnline: true, programName: 'O504', partCount: 14, totalPartCount: 45930, runStatus: 3, operateMode: 1, scenarioTick: 45, scenarioDuration: 120 },
{ deviceCode: 'fanake_1.3', desc: '西-1.3', scenario: 'idle', isOnline: true, programName: 'O1', partCount: 53, runStatus: 1, operateMode: 10, spindleSpeedSet: 0, spindleSpeedActual: 0, feedSpeedSet: 0, feedSpeedActual: 0, spindleLoad: 5, machiningStatus: 'idle', scenarioTick: 12, scenarioDuration: 60 }, { deviceCode: 'fanake_1.3', desc: '西-1.3', scenario: 'idle', isOnline: true, programName: 'O1', partCount: 53, totalPartCount: 62324, runStatus: 0, operateMode: 10, scenarioTick: 12, scenarioDuration: 60 },
{ deviceCode: 'fanake_1.4', desc: '西-1.4', scenario: 'offline', isOnline: false, programName: 'O200', partCount: 0, runStatus: 0, operateMode: 0, spindleSpeedSet: 0, spindleSpeedActual: 0, feedSpeedSet: 0, feedSpeedActual: 0, spindleLoad: 0, machiningStatus: 'offline', scenarioTick: 0, scenarioDuration: 0 } { deviceCode: 'fanake_1.4', desc: '西-1.4', scenario: 'offline', isOnline: false, programName: '', partCount: 0, totalPartCount: 0, runStatus: 0, operateMode: 0, scenarioTick: 0, scenarioDuration: 0 }
] ]
// 模拟请求日志 // 模拟请求日志

@ -40,14 +40,9 @@ export interface DeviceStatus {
isOnline: boolean isOnline: boolean
programName: string programName: string
partCount: number partCount: number
totalPartCount: number
runStatus: number runStatus: number
operateMode: number operateMode: number
spindleSpeedSet: number
spindleSpeedActual: number
feedSpeedSet: number
feedSpeedActual: number
spindleLoad: number
machiningStatus: string
scenarioTick: number scenarioTick: number
scenarioDuration: number scenarioDuration: number
} }

@ -53,18 +53,20 @@ export interface Machine {
export interface MachineStatus { export interface MachineStatus {
programName: string programName: string
partCount: number partCount: number
/** 历史累计加工零件总数FANUC Tag1 */
totalPartCount?: number
runStatus: string runStatus: string
operationMode: string operationMode: string
/** 主轴设定转速 */ /** 主轴设定转速部分品牌无此数据时为null */
spindleSpeedSet?: number spindleSpeedSet?: number | null
/** 进给设定速度 */ /** 进给设定速度部分品牌无此数据时为null */
feedSpeedSet?: number feedSpeedSet?: number | null
/** 主轴实际转速 */ /** 主轴实际转速部分品牌无此数据时为null */
spindleSpeedActual?: number spindleSpeedActual?: number | null
spindleSpeed: number spindleSpeed?: number | null
feedRate: number feedRate?: number | null
spindleLoad: number spindleLoad?: number | null
machiningStatus: string machiningStatus?: string | null
lastCollectTime: string lastCollectTime: string
} }

@ -27,12 +27,9 @@
<el-descriptions :column="2" border size="small"> <el-descriptions :column="2" border size="small">
<el-descriptions-item label="NC程序名">{{status.programName||'-'}}</el-descriptions-item> <el-descriptions-item label="NC程序名">{{status.programName||'-'}}</el-descriptions-item>
<el-descriptions-item label="零件数">{{status.partCount??'-'}}</el-descriptions-item> <el-descriptions-item label="零件数">{{status.partCount??'-'}}</el-descriptions-item>
<el-descriptions-item label="累计总数">{{status.totalPartCount??'-'}}</el-descriptions-item>
<el-descriptions-item label="运行状态">{{status.runStatus||'-'}}</el-descriptions-item> <el-descriptions-item label="运行状态">{{status.runStatus||'-'}}</el-descriptions-item>
<el-descriptions-item label="操作模式">{{status.operationMode||'-'}}</el-descriptions-item> <el-descriptions-item label="操作模式">{{status.operationMode||'-'}}</el-descriptions-item>
<el-descriptions-item label="主轴设定">{{status.spindleSpeedSet??'-'}}</el-descriptions-item>
<el-descriptions-item label="进给设定">{{status.feedSpeedSet??'-'}}</el-descriptions-item>
<el-descriptions-item label="主轴实际">{{status.spindleSpeedActual??'-'}}</el-descriptions-item>
<el-descriptions-item label="主轴负载">{{status.spindleLoad??'-'}}%</el-descriptions-item>
</el-descriptions> </el-descriptions>
</el-card></el-col> </el-card></el-col>
</el-row> </el-row>

@ -8,9 +8,9 @@
"dataChangeInterval": 10, "dataChangeInterval": 10,
"scenarioMode": "auto", "scenarioMode": "auto",
"devices": [ "devices": [
{ "deviceCode": "CNC-A001", "desc": "西栋1号", "initialProgram": "O0001", "initialPartCount": 50 }, { "deviceCode": "CNC-A001", "desc": "西栋1号", "initialProgram": "O0001", "initialPartCount": 50, "initialTotalPartCount": 45000 },
{ "deviceCode": "CNC-006", "desc": "6号机床", "initialProgram": "O0002", "initialPartCount": 120 }, { "deviceCode": "CNC-006", "desc": "6号机床", "initialProgram": "O0002", "initialPartCount": 120, "initialTotalPartCount": 88000 },
{ "deviceCode": "CNC-008", "desc": "8号机床", "initialProgram": "O0003", "initialPartCount": 0 } { "deviceCode": "CNC-008", "desc": "8号机床", "initialProgram": "O0003", "initialPartCount": 0, "initialTotalPartCount": 126000 }
] ]
}, },
{ {
@ -20,8 +20,8 @@
"dataChangeInterval": 15, "dataChangeInterval": 15,
"scenarioMode": "auto", "scenarioMode": "auto",
"devices": [ "devices": [
{ "deviceCode": "CNC-B002", "desc": "B栋2号", "initialProgram": "1566.NC", "initialPartCount": 80 }, { "deviceCode": "CNC-B002", "desc": "B栋2号", "initialProgram": "1566.NC", "initialPartCount": 80, "initialTotalPartCount": 67000 },
{ "deviceCode": "CNC-005", "desc": "验证机床", "initialProgram": "TEST001", "initialPartCount": 10 } { "deviceCode": "CNC-005", "desc": "验证机床", "initialProgram": "TEST001", "initialPartCount": 10, "initialTotalPartCount": 23000 }
] ]
} }
] ]

@ -84,13 +84,11 @@ namespace CncCollector.Core
foreach (var r in records) foreach (var r in records)
{ {
conn.Execute(@"INSERT INTO cnc_collect_record (machine_id, collect_time, device_time, program_name, part_count, conn.Execute(@"INSERT INTO cnc_collect_record (machine_id, collect_time, device_time, program_name, part_count,
device_status, run_status, operate_mode, spindle_speed_set, feed_speed_set, total_part_count, device_status, run_status, operate_mode,
spindle_speed_actual, feed_speed_actual, spindle_load, spindle_override, power_on_time, run_time, extra_data, created_at)
power_on_time, run_time, cutting_time, cycle_time, machining_status, extra_data, created_at)
VALUES (@MachineId, @CollectTime, @DeviceTime, @ProgramName, @PartCount, VALUES (@MachineId, @CollectTime, @DeviceTime, @ProgramName, @PartCount,
@DeviceStatus, @RunStatus, @OperateMode, @SpindleSpeedSet, @FeedSpeedSet, @TotalPartCount, @DeviceStatus, @RunStatus, @OperateMode,
@SpindleSpeedActual, @FeedSpeedActual, @SpindleLoad, @SpindleOverride, @PowerOnTime, @RunTime, @ExtraData, @CreatedAt)",
@PowerOnTime, @RunTime, @CuttingTime, @CycleTime, @MachiningStatus, @ExtraData, @CreatedAt)",
new new
{ {
r.MachineId, r.MachineId,
@ -98,20 +96,12 @@ namespace CncCollector.Core
DeviceTime = r.DeviceTime, DeviceTime = r.DeviceTime,
ProgramName = r.ProgramName ?? (string)null, ProgramName = r.ProgramName ?? (string)null,
PartCount = r.PartCount, PartCount = r.PartCount,
TotalPartCount = r.TotalPartCount,
DeviceStatus = r.DeviceStatus ?? (string)null, DeviceStatus = r.DeviceStatus ?? (string)null,
RunStatus = r.RunStatus ?? (string)null, RunStatus = r.RunStatus ?? (string)null,
OperateMode = r.OperateMode ?? (string)null, OperateMode = r.OperateMode ?? (string)null,
SpindleSpeedSet = r.SpindleSpeedSet,
FeedSpeedSet = r.FeedSpeedSet,
SpindleSpeedActual = r.SpindleSpeedActual,
FeedSpeedActual = r.FeedSpeedActual,
SpindleLoad = r.SpindleLoad,
SpindleOverride = r.SpindleOverride,
PowerOnTime = r.PowerOnTime, PowerOnTime = r.PowerOnTime,
RunTime = r.RunTime, RunTime = r.RunTime,
CuttingTime = r.CuttingTime,
CycleTime = r.CycleTime,
MachiningStatus = r.MachiningStatus ?? (string)null,
ExtraData = r.ExtraData ?? (string)null, ExtraData = r.ExtraData ?? (string)null,
CreatedAt = now CreatedAt = now
}, tran); }, tran);
@ -133,7 +123,8 @@ namespace CncCollector.Core
conn.Execute(@"UPDATE cnc_machine SET last_collect_time = @CollectTime, conn.Execute(@"UPDATE cnc_machine SET last_collect_time = @CollectTime,
last_device_status = @DeviceStatus, last_run_status = @RunStatus, last_device_status = @DeviceStatus, last_run_status = @RunStatus,
last_program_name = @ProgramName, last_part_count = @PartCount, last_program_name = @ProgramName, last_part_count = @PartCount,
last_operate_mode = @OperateMode, last_machining_status = @MachiningStatus last_total_part_count = @TotalPartCount,
last_operate_mode = @OperateMode
WHERE id = @MachineId", WHERE id = @MachineId",
new new
{ {
@ -143,8 +134,8 @@ namespace CncCollector.Core
RunStatus = r.RunStatus ?? (string)null, RunStatus = r.RunStatus ?? (string)null,
ProgramName = r.ProgramName ?? (string)null, ProgramName = r.ProgramName ?? (string)null,
r.PartCount, r.PartCount,
OperateMode = r.OperateMode ?? (string)null, r.TotalPartCount,
MachiningStatus = r.MachiningStatus ?? (string)null OperateMode = r.OperateMode ?? (string)null
}); });
} }
catch (Exception ex) catch (Exception ex)

@ -447,32 +447,32 @@ namespace CncCollector.Core
// 解析字段 // 解析字段
var parsed = DataParser.ParseDevice(deviceObj, brand, mappings); var parsed = DataParser.ParseDevice(deviceObj, brand, mappings);
// 构建 CollectRecord // 构建 CollectRecord仅包含实际FANUC数据中存在的字段
var record = new CollectRecord var record = new CollectRecord
{ {
MachineId = machine.Id, MachineId = machine.Id,
CollectTime = collectTime, CollectTime = collectTime,
ProgramName = GetStringValue(parsed, "program_name"), ProgramName = GetStringValue(parsed, "program_name"),
PartCount = GetDecimalValue(parsed, "part_count"), PartCount = GetDecimalValue(parsed, "part_count"),
TotalPartCount = GetDecimalValue(parsed, "total_part_count"),
DeviceStatus = GetStringValue(parsed, "device_status"), DeviceStatus = GetStringValue(parsed, "device_status"),
RunStatus = GetStringValue(parsed, "run_status"), RunStatus = GetStringValue(parsed, "run_status"),
OperateMode = GetStringValue(parsed, "operate_mode"), OperateMode = GetStringValue(parsed, "operate_mode"),
SpindleSpeedSet = GetDecimalValue(parsed, "spindle_speed_set"),
FeedSpeedSet = GetDecimalValue(parsed, "feed_speed_set"),
SpindleSpeedActual = GetDecimalValue(parsed, "spindle_speed_actual"),
FeedSpeedActual = GetDecimalValue(parsed, "feed_speed_actual"),
SpindleLoad = GetDecimalValue(parsed, "spindle_load"),
SpindleOverride = GetDecimalValue(parsed, "spindle_override"),
PowerOnTime = GetDecimalValue(parsed, "power_on_time"), PowerOnTime = GetDecimalValue(parsed, "power_on_time"),
RunTime = GetDecimalValue(parsed, "run_time"), RunTime = GetDecimalValue(parsed, "run_time")
CuttingTime = GetDecimalValue(parsed, "cutting_time"),
CycleTime = GetDecimalValue(parsed, "cycle_time"),
MachiningStatus = GetStringValue(parsed, "machining_status")
}; };
// 断电设备过滤如果program_name和part_count都被过滤为空
// 说明设备处于断电状态quality≠0跳过此设备不生成记录
if (string.IsNullOrEmpty(record.ProgramName) && !record.PartCount.HasValue)
{
_log.Debug($"设备{deviceCode}所有有效tag为空可能断电跳过记录");
continue;
}
records.Add(record); records.Add(record);
// 产量跟踪 // 产量跟踪program_name为空时不触发分段逻辑
_tracker.Track(machine.Id, record.ProgramName, record.PartCount, collectTime); _tracker.Track(machine.Id, record.ProgramName, record.PartCount, collectTime);
} }

@ -33,7 +33,9 @@ namespace CncCollector.Core
} }
/// <summary> /// <summary>
/// 解析单台设备的 JSON tags 数据 /// 解析单台设备的 JSON tags 数据。
/// 根据数据有效性规则过滤无效tagquality≠0 或 time以1970-01-01开头的tag视为无效。
/// _io_status 始终有效不受quality/time约束。
/// </summary> /// </summary>
/// <param name="deviceObj">设备 JSON 对象</param> /// <param name="deviceObj">设备 JSON 对象</param>
/// <param name="brand">品牌配置</param> /// <param name="brand">品牌配置</param>
@ -57,16 +59,35 @@ namespace CncCollector.Core
var tagsArray = tagsToken as JArray; var tagsArray = tagsToken as JArray;
// 构建 tag id → value 的快速查找字典 // 构建 tag id → rawValue 的快速查找字典(经过有效性过滤)
var tagDict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); var tagDict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var tag in tagsArray) foreach (var tag in tagsArray)
{ {
string id = tag["id"]?.ToString() ?? ""; string id = tag["id"]?.ToString() ?? "";
string value = tag["value"]?.ToString() ?? ""; if (string.IsNullOrEmpty(id)) continue;
if (!string.IsNullOrEmpty(id))
// _io_status 始终有效,跳过过滤
if (string.Equals(id, "_io_status", StringComparison.OrdinalIgnoreCase))
{
tagDict[id] = tag["value"]?.ToString() ?? "";
continue;
}
// 数据有效性规则1quality ≠ 0 → 无效,忽略
string qualityStr = tag["quality"]?.ToString() ?? "0";
if (qualityStr != "0")
{ {
tagDict[id] = value; continue;
} }
// 数据有效性规则2time 以 1970-01-01 开头 → 采集失败标记,忽略
string timeStr = tag["time"]?.ToString() ?? "";
if (timeStr.StartsWith("1970-01-01", StringComparison.OrdinalIgnoreCase))
{
continue;
}
tagDict[id] = tag["value"]?.ToString() ?? "";
} }
// 按字段映射表提取 // 按字段映射表提取
@ -79,7 +100,7 @@ namespace CncCollector.Core
string rawValue; string rawValue;
if (!tagDict.TryGetValue(tagId, out rawValue)) if (!tagDict.TryGetValue(tagId, out rawValue))
{ {
// 字段不存在,跳过(非必填字段可能不存在) // 字段不存在或被过滤为无效,跳过
continue; continue;
} }
@ -90,10 +111,9 @@ namespace CncCollector.Core
StringValue = rawValue StringValue = rawValue
}; };
// 数值型:去除 .00000 尾缀 // 数值型:解析为 decimal
if (dataType == "number" && !string.IsNullOrEmpty(rawValue)) if (dataType == "number" && !string.IsNullOrEmpty(rawValue))
{ {
// 尝试解析为 decimal
string cleanValue = rawValue.Trim(); string cleanValue = rawValue.Trim();
decimal numVal; decimal numVal;
if (decimal.TryParse(cleanValue, System.Globalization.NumberStyles.Float, if (decimal.TryParse(cleanValue, System.Globalization.NumberStyles.Float,

@ -25,6 +25,9 @@ namespace CncModels.Entity
/// <summary>零件计数</summary> /// <summary>零件计数</summary>
public decimal? PartCount { get; set; } public decimal? PartCount { get; set; }
/// <summary>历史终身累计零件总数Tag1</summary>
public decimal? TotalPartCount { get; set; }
/// <summary>设备状态</summary> /// <summary>设备状态</summary>
public string DeviceStatus { get; set; } public string DeviceStatus { get; set; }

@ -73,5 +73,9 @@ namespace CncSimulator.Config
/// <summary>初始零件数</summary> /// <summary>初始零件数</summary>
[JsonProperty("initialPartCount")] [JsonProperty("initialPartCount")]
public int InitialPartCount { get; set; } = 0; public int InitialPartCount { get; set; } = 0;
/// <summary>初始历史累计零件总数</summary>
[JsonProperty("initialTotalPartCount")]
public int InitialTotalPartCount { get; set; } = 0;
} }
} }

@ -326,6 +326,67 @@ namespace CncSimulator.Core
_dbAddresses = _dbReader.ReadAddresses(); _dbAddresses = _dbReader.ReadAddresses();
SendResponse(ctx, 200, "{\"ok\":true,\"count\":" + _dbAddresses.Count + "}", "application/json"); SendResponse(ctx, 200, "{\"ok\":true,\"count\":" + _dbAddresses.Count + "}", "application/json");
} }
else if (path == "/admin/api/event")
{
// 转发事件到对应的SimulatorServer数据端口
string eventBody = ReadRequestBody(ctx);
var eventObj = JObject.Parse(eventBody);
string deviceId = eventObj["deviceId"]?.ToString();
string eventType = eventObj["eventType"]?.ToString();
if (string.IsNullOrEmpty(deviceId) || string.IsNullOrEmpty(eventType))
{
SendResponse(ctx, 400, "{\"error\":\"缺少deviceId或eventType\"}", "application/json");
return;
}
// 在所有server中查找目标设备并触发事件
bool found = false;
foreach (var server in _servers)
{
server.TriggerDeviceEvent(deviceId, eventType);
found = true;
break; // TriggerDeviceEvent内部只处理第一个匹配的设备
}
SendResponse(ctx, 200, found ? "{\"ok\":true}" : "{\"ok\":false,\"error\":\"设备未找到\"}", "application/json");
}
else if (path == "/admin/api/event-history")
{
// 转发事件历史查询
var allEvents = new JArray();
foreach (var server in _servers)
{
// 利用server的HandleEventHistoryApi逻辑提取数据
foreach (var dev in server.Devices)
{
foreach (var evt in dev.State.EventHistory)
{
allEvents.Add(new JObject
{
["timestamp"] = evt.Timestamp.ToString("yyyy-MM-dd HH:mm:ss"),
["deviceCode"] = evt.DeviceCode,
["eventType"] = evt.EventType,
["oldProgram"] = evt.OldProgram,
["newProgram"] = evt.NewProgram,
["partCountBefore"] = evt.PartCountBefore,
["partCountAfter"] = evt.PartCountAfter,
["detail"] = evt.Detail
});
}
}
}
SendResponse(ctx, 200, allEvents.ToString(), "application/json");
}
else if (path == "/admin/api/mode")
{
// 转发模式切换到所有SimulatorServer
string modeBody = ReadRequestBody(ctx);
var modeObj = JObject.Parse(modeBody);
string mode = modeObj["mode"]?.ToString() ?? "auto";
foreach (var server in _servers)
{
server.SetMode(mode);
}
SendResponse(ctx, 200, "{\"ok\":true,\"mode\":\"" + mode + "\"}", "application/json");
}
else else
{ {
SendResponse(ctx, 200, "CNC模拟采集服务网关。请访问 /admin 管理页面。", "text/plain"); SendResponse(ctx, 200, "CNC模拟采集服务网关。请访问 /admin 管理页面。", "text/plain");

@ -285,14 +285,9 @@ namespace CncSimulator.Core
["isOnline"] = s.IsOnline, ["isOnline"] = s.IsOnline,
["programName"] = s.ProgramName, ["programName"] = s.ProgramName,
["partCount"] = s.PartCount, ["partCount"] = s.PartCount,
["totalPartCount"] = s.TotalPartCount,
["runStatus"] = s.RunStatus, ["runStatus"] = s.RunStatus,
["operateMode"] = s.OperateMode, ["operateMode"] = s.OperateMode,
["spindleSpeedSet"] = s.SpindleSpeedSet,
["spindleSpeedActual"] = s.SpindleSpeedActual,
["feedSpeedSet"] = s.FeedSpeedSet,
["feedSpeedActual"] = s.FeedSpeedActual,
["spindleLoad"] = s.SpindleLoad,
["machiningStatus"] = s.MachiningStatus,
["scenarioTick"] = s.ScenarioTick, ["scenarioTick"] = s.ScenarioTick,
["scenarioDuration"] = s.ScenarioDuration ["scenarioDuration"] = s.ScenarioDuration
}); });
@ -318,7 +313,7 @@ namespace CncSimulator.Core
} }
} }
/// <summary>生成当前JSON响应</summary> /// <summary>生成当前JSON响应(包括在线和离线设备)</summary>
private string GenerateCurrentJson() private string GenerateCurrentJson()
{ {
var devices = new JArray(); var devices = new JArray();
@ -328,10 +323,66 @@ namespace CncSimulator.Core
{ {
devices.Add(_generator.GenerateDevice(dev.State)); devices.Add(_generator.GenerateDevice(dev.State));
} }
else
{
// 离线设备生成带有quality=1和1970时间的tag
devices.Add(GenerateOfflineDevice(dev.State));
}
} }
return devices.ToString(Formatting.None); return devices.ToString(Formatting.None);
} }
/// <summary>生成离线设备的JSON_io_status有效其余tag quality=1</summary>
private JObject GenerateOfflineDevice(DeviceState state)
{
var tags = new JArray();
DateTime now = DateTime.Now;
// _io_status 始终有效
tags.Add(new JObject
{
["id"] = "_io_status",
["desc"] = "设备状态",
["quality"] = "0",
["value"] = "0.00000",
["time"] = now.ToString("yyyy-MM-dd HH:mm:ss")
});
// 其余tag全部标记为无效quality=1, time=1970
string epoch = "1970-01-01 08:00:00";
var offlineTags = new[]
{
new { id = "Tag1", desc = "加工零件总数", value = "0.00000" },
new { id = "Tag5", desc = "执行的NC主程序名", value = "" },
new { id = "Tag6", desc = "执行的NC主程序号", value = "" },
new { id = "Tag7", desc = "当前加工程序内容", value = "" },
new { id = "Tag8", desc = "当前加工零件数", value = "0.00000" },
new { id = "Tag9", desc = "运行状态", value = "0.00000" },
new { id = "Tag11", desc = "操作模式", value = "0.00000" },
new { id = "Tag22", desc = "开机时间", value = "0.00000" },
new { id = "Tag23", desc = "运行时间", value = "0.00000" }
};
foreach (var t in offlineTags)
{
tags.Add(new JObject
{
["id"] = t.id,
["desc"] = t.desc,
["quality"] = "1",
["value"] = t.value,
["time"] = epoch
});
}
return new JObject
{
["device"] = state.DeviceCode,
["desc"] = state.Desc,
["tags"] = tags
};
}
/// <summary>生成关键数据摘要</summary> /// <summary>生成关键数据摘要</summary>
private string GenerateKeyData() private string GenerateKeyData()
{ {

@ -4,13 +4,13 @@ namespace CncSimulator.Device
{ {
/// <summary> /// <summary>
/// 单台设备的状态机。 /// 单台设备的状态机。
/// 维护设备当前状态,根据场景规则更新各字段。 /// 维护设备当前状态,根据场景规则更新字段。
/// 仅包含实际FANUC采集数据中存在的字段。
/// </summary> /// </summary>
public class DeviceSimulator public class DeviceSimulator
{ {
private readonly DeviceState _state; private readonly DeviceState _state;
private readonly Random _rng; private readonly Random _rng;
private static readonly string[] MachiningStatusOptions = { "G01", "G01", "G01", "G02", "G00" };
private static readonly string[] ProgramPool = { "O0001", "O0002", "1566.NC", "PART-A", "TEST-03" }; private static readonly string[] ProgramPool = { "O0001", "O0002", "1566.NC", "PART-A", "TEST-03" };
private int _programPoolIndex; private int _programPoolIndex;
@ -26,23 +26,17 @@ namespace CncSimulator.Device
Desc = config.Desc, Desc = config.Desc,
ProgramName = config.InitialProgram, ProgramName = config.InitialProgram,
PartCount = config.InitialPartCount, PartCount = config.InitialPartCount,
// LastPartCount removed - not in DeviceState // 历史累计零件总数:配置值优先,否则随机
TotalPartCount = config.InitialTotalPartCount > 0
? config.InitialTotalPartCount
: _rng.Next(40000, 50000),
DataChangeInterval = dataChangeInterval, DataChangeInterval = dataChangeInterval,
IsOnline = true, IsOnline = true,
DeviceStatus = 1, DeviceStatus = 1,
RunStatus = 0, RunStatus = 0,
OperateMode = 10, OperateMode = 10,
SpindleSpeedSet = 450,
FeedSpeedSet = 60,
SpindleSpeedActual = 0,
FeedSpeedActual = 0,
SpindleLoad = 0,
SpindleOverride = 100,
PowerOnTime = _rng.Next(20000000, 24000000), PowerOnTime = _rng.Next(20000000, 24000000),
RunTime = _rng.Next(15000, 20000), RunTime = _rng.Next(15000, 20000),
CuttingTime = _rng.Next(6000000, 7000000),
CycleTime = _rng.Next(500, 800),
MachiningStatus = "",
ProgramContent = "", ProgramContent = "",
CurrentScenario = "idle", CurrentScenario = "idle",
ScenarioTick = 0, ScenarioTick = 0,
@ -117,6 +111,7 @@ namespace CncSimulator.Device
{ {
case "machining": case "machining":
_state.PartCount++; _state.PartCount++;
_state.TotalPartCount++;
_state.TotalPartsSinceStart++; _state.TotalPartsSinceStart++;
// 按NC程序名累计零件数 // 按NC程序名累计零件数
if (!_state.PartsByProgram.ContainsKey(_state.ProgramName)) if (!_state.PartsByProgram.ContainsKey(_state.ProgramName))
@ -125,16 +120,8 @@ namespace CncSimulator.Device
_state.RunStatus = 3; _state.RunStatus = 3;
_state.DeviceStatus = 1; _state.DeviceStatus = 1;
_state.OperateMode = 1; _state.OperateMode = 1;
// 主轴实际速度 = 设定 ± 10%随机波动
_state.SpindleSpeedActual = _state.SpindleSpeedSet * (1m + (decimal)(_rng.NextDouble() * 0.2 - 0.1));
_state.FeedSpeedActual = _state.FeedSpeedSet * (1m + (decimal)(_rng.NextDouble() * 0.1 - 0.05));
_state.SpindleLoad = _rng.Next(15, 46);
_state.SpindleOverride = 100;
_state.MachiningStatus = MachiningStatusOptions[_rng.Next(MachiningStatusOptions.Length)];
_state.CuttingTime += interval;
_state.RunTime += interval; _state.RunTime += interval;
_state.PowerOnTime += interval; _state.PowerOnTime += interval;
_state.CycleTime += interval;
_state.ProgramContent = "<" + _state.ProgramName + ">\nG40G49G80\n( SIMULATOR )"; _state.ProgramContent = "<" + _state.ProgramName + ">\nG40G49G80\n( SIMULATOR )";
break; break;
@ -147,21 +134,12 @@ namespace CncSimulator.Device
case "idle": case "idle":
_state.RunStatus = 0; _state.RunStatus = 0;
_state.OperateMode = 10; _state.OperateMode = 10;
_state.SpindleSpeedActual = 0;
_state.FeedSpeedActual = 0;
_state.SpindleLoad = 0;
_state.FeedSpeedSet = 0;
_state.MachiningStatus = "";
_state.PowerOnTime += interval; _state.PowerOnTime += interval;
_state.RunTime += interval; _state.RunTime += interval;
break; break;
case "pause": case "pause":
_state.RunStatus = 1; _state.RunStatus = 1;
_state.SpindleSpeedActual = 0;
_state.FeedSpeedActual = 0;
_state.SpindleLoad = 0;
_state.MachiningStatus = "";
_state.PowerOnTime += interval; _state.PowerOnTime += interval;
_state.RunTime += interval; _state.RunTime += interval;
break; break;
@ -182,15 +160,11 @@ namespace CncSimulator.Device
_programPoolIndex = (_programPoolIndex + 1) % ProgramPool.Length; _programPoolIndex = (_programPoolIndex + 1) % ProgramPool.Length;
_state.ProgramName = ProgramPool[_programPoolIndex]; _state.ProgramName = ProgramPool[_programPoolIndex];
_state.PartCount = 0; _state.PartCount = 0;
_state.CycleTime = 0;
_state.RunStatus = 3; _state.RunStatus = 3;
_state.DeviceStatus = 1; _state.DeviceStatus = 1;
_state.OperateMode = 1; _state.OperateMode = 1;
_state.MachiningStatus = "G01";
_state.SpindleSpeedSet = _rng.Next(200, 801);
_state.FeedSpeedSet = _rng.Next(30, 151);
_state.SpindleOverride = 100;
_state.ProgramContent = "<" + _state.ProgramName + ">\nG40G49G80\n( SIMULATOR )"; _state.ProgramContent = "<" + _state.ProgramName + ">\nG40G49G80\n( SIMULATOR )";
// TotalPartCount不清零终身累计
// 记录事件历史 // 记录事件历史
_state.RecordEvent("program_change", oldProgram, _state.ProgramName, oldPartCount, 0); _state.RecordEvent("program_change", oldProgram, _state.ProgramName, oldPartCount, 0);
} }
@ -202,6 +176,7 @@ namespace CncSimulator.Device
_state.PartCount = 0; _state.PartCount = 0;
_state.RunStatus = 3; _state.RunStatus = 3;
_state.DeviceStatus = 1; _state.DeviceStatus = 1;
// TotalPartCount不清零终身累计
// 记录事件历史 // 记录事件历史
_state.RecordEvent("manual_reset", _state.ProgramName, _state.ProgramName, oldPartCount, 0); _state.RecordEvent("manual_reset", _state.ProgramName, _state.ProgramName, oldPartCount, 0);
} }
@ -222,13 +197,9 @@ namespace CncSimulator.Device
_state.IsOnline = true; _state.IsOnline = true;
_state.DeviceStatus = 1; _state.DeviceStatus = 1;
_state.PartCount = 0; _state.PartCount = 0;
_state.CycleTime = 0;
_state.RunStatus = 0; _state.RunStatus = 0;
_state.OperateMode = 10; _state.OperateMode = 10;
_state.SpindleSpeedActual = 0; // TotalPartCount不清零终身累计
_state.FeedSpeedActual = 0;
_state.SpindleLoad = 0;
_state.MachiningStatus = "";
// 记录事件历史 // 记录事件历史
_state.RecordEvent("power_on", _state.ProgramName, _state.ProgramName, oldPartCount, 0, "设备开机,零件数清零"); _state.RecordEvent("power_on", _state.ProgramName, _state.ProgramName, oldPartCount, 0, "设备开机,零件数清零");
} }

@ -26,6 +26,9 @@ namespace CncSimulator.Device
/// <summary>当前零件数</summary> /// <summary>当前零件数</summary>
public int PartCount { get; set; } = 0; public int PartCount { get; set; } = 0;
/// <summary>历史终身累计零件总数对应Tag1</summary>
public decimal TotalPartCount { get; set; } = 0;
/// <summary>设备状态 _io_status: 0=离线, 1=在线</summary> /// <summary>设备状态 _io_status: 0=离线, 1=在线</summary>
public int DeviceStatus { get; set; } = 1; public int DeviceStatus { get; set; } = 1;
@ -35,39 +38,12 @@ namespace CncSimulator.Device
/// <summary>操作模式: 1=MEM, 10=JOG</summary> /// <summary>操作模式: 1=MEM, 10=JOG</summary>
public int OperateMode { get; set; } = 1; public int OperateMode { get; set; } = 1;
/// <summary>主轴设定速度</summary>
public decimal SpindleSpeedSet { get; set; } = 450;
/// <summary>进给设定速度</summary>
public decimal FeedSpeedSet { get; set; } = 60;
/// <summary>主轴实际速度</summary>
public decimal SpindleSpeedActual { get; set; } = 0;
/// <summary>进给实际速度</summary>
public decimal FeedSpeedActual { get; set; } = 0;
/// <summary>主轴负载</summary>
public decimal SpindleLoad { get; set; } = 0;
/// <summary>主轴倍率</summary>
public decimal SpindleOverride { get; set; } = 100;
/// <summary>开机累计时间(秒)</summary> /// <summary>开机累计时间(秒)</summary>
public decimal PowerOnTime { get; set; } = 0; public decimal PowerOnTime { get; set; } = 0;
/// <summary>运行累计时间(秒)</summary> /// <summary>运行累计时间(秒)</summary>
public decimal RunTime { get; set; } = 0; public decimal RunTime { get; set; } = 0;
/// <summary>切削累计时间(秒)</summary>
public decimal CuttingTime { get; set; } = 0;
/// <summary>循环时间(秒)</summary>
public decimal CycleTime { get; set; } = 0;
/// <summary>加工状态: G01/G00/G02/等</summary>
public string MachiningStatus { get; set; } = "";
/// <summary>加工程序内容片段</summary> /// <summary>加工程序内容片段</summary>
public string ProgramContent { get; set; } = ""; public string ProgramContent { get; set; } = "";

@ -6,7 +6,7 @@ namespace CncSimulator.Generator
{ {
/// <summary> /// <summary>
/// FANUC品牌JSON数据生成器。 /// FANUC品牌JSON数据生成器。
/// 根据设备状态生成18个Tag的FANUC格式JSON /// 根据设备状态生成10个Tag的FANUC格式JSON与实际FANUC采集数据结构一致
/// </summary> /// </summary>
public class FanucDataGenerator : IBrandGenerator public class FanucDataGenerator : IBrandGenerator
{ {
@ -27,7 +27,7 @@ namespace CncSimulator.Generator
return device; return device;
} }
/// <summary>生成18个Tag的JArray</summary> /// <summary>生成10个Tag的JArray</summary>
private JArray GenerateTags(DeviceState state) private JArray GenerateTags(DeviceState state)
{ {
var tags = new JArray(); var tags = new JArray();
@ -65,8 +65,9 @@ namespace CncSimulator.Generator
}); });
} }
// 严格按实际FANUC采集数据的10个Tag生成
AddNumericTag("_io_status", "设备状态", state.DeviceStatus); AddNumericTag("_io_status", "设备状态", state.DeviceStatus);
AddNumericTag("Tag2", "当前轴数", 4); AddNumericTag("Tag1", "加工零件总数", state.TotalPartCount);
AddStringTag("Tag5", "执行的NC主程序名", state.ProgramName); AddStringTag("Tag5", "执行的NC主程序名", state.ProgramName);
AddStringTag("Tag6", "执行的NC主程序号", "N0"); AddStringTag("Tag6", "执行的NC主程序号", "N0");
AddStringTag("Tag7", "当前加工程序内容", AddStringTag("Tag7", "当前加工程序内容",
@ -76,17 +77,8 @@ namespace CncSimulator.Generator
AddNumericTag("Tag8", "当前加工零件数", state.PartCount); AddNumericTag("Tag8", "当前加工零件数", state.PartCount);
AddNumericTag("Tag9", "运行状态", state.RunStatus); AddNumericTag("Tag9", "运行状态", state.RunStatus);
AddNumericTag("Tag11", "操作模式", state.OperateMode); AddNumericTag("Tag11", "操作模式", state.OperateMode);
AddNumericTag("Tag14", "当前主轴倍率", state.SpindleOverride);
AddNumericTag("Tag17", "主轴设定速度", state.SpindleSpeedSet);
AddNumericTag("Tag18", "进给设定速度", state.FeedSpeedSet);
AddNumericTag("Tag19", "主轴实际速度", state.SpindleSpeedActual);
AddNumericTag("Tag20", "进给实际转速", state.FeedSpeedActual);
AddNumericTag("Tag21", "主轴负载", state.SpindleLoad);
AddNumericTag("Tag22", "开机时间", state.PowerOnTime); AddNumericTag("Tag22", "开机时间", state.PowerOnTime);
AddNumericTag("Tag23", "运行时间", state.RunTime); AddNumericTag("Tag23", "运行时间", state.RunTime);
AddNumericTag("Tag24", "切削时间", state.CuttingTime);
AddNumericTag("Tag25", "循环时间", state.CycleTime);
AddStringTag("Tag26", "加工状态", state.MachiningStatus);
return tags; return tags;
} }

@ -18,12 +18,8 @@ namespace CncWebApi
/// </summary> /// </summary>
protected void Application_Start() protected void Application_Start()
{ {
// 初始化 log4net // 初始化 log4net从 Web.config 的 <log4net> 节读取)
var logConfig = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log4net.config"); log4net.Config.XmlConfigurator.Configure();
if (File.Exists(logConfig))
{
log4net.Config.XmlConfigurator.Configure(new FileInfo(logConfig));
}
GlobalConfiguration.Configure(WebApiConfig.Register); GlobalConfiguration.Configure(WebApiConfig.Register);
} }

@ -4,6 +4,40 @@
部署到 IIS 时,需要将此文件复制到站点根目录 部署到 IIS 时,需要将此文件复制到站点根目录
--> -->
<configuration> <configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<log4net>
<appender name="ErrorFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="App_Data\logs\error.log" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="10MB" />
<staticLogFileName value="true" />
<threshold value="ERROR" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline%exception%newline" />
</layout>
</appender>
<appender name="AllFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="App_Data\logs\webapi.log" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="10MB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline%exception%newline" />
</layout>
</appender>
<root>
<level value="INFO" />
<appender-ref ref="AllFileAppender" />
<appender-ref ref="ErrorFileAppender" />
</root>
</log4net>
<connectionStrings> <connectionStrings>
<!-- 业务库连接串 --> <!-- 业务库连接串 -->
<add name="BusinessConnection" <add name="BusinessConnection"

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save