This commit is contained in:
parent
be824e7f06
commit
996e1abc24
1
.kiro/specs/audit-log-chinese-fields/.config.kiro
Normal file
1
.kiro/specs/audit-log-chinese-fields/.config.kiro
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"specId": "a3f7c2e1-8d4b-4f9a-b6e3-1c5d8a2f7e90", "workflowType": "requirements-first", "specType": "feature"}
|
||||
528
.kiro/specs/audit-log-chinese-fields/design.md
Normal file
528
.kiro/specs/audit-log-chinese-fields/design.md
Normal file
|
|
@ -0,0 +1,528 @@
|
|||
# 设计文档:审计日志中文字段名
|
||||
|
||||
## 概述
|
||||
|
||||
本设计将审计日志系统的 OldData/NewData JSON 键名从 C# camelCase 属性名替换为中文字段名。核心改动分为两部分:
|
||||
|
||||
1. **后端**:修改 `BaseController` 的序列化辅助方法 + 每个 UPDATE 方法手动构造差量 JSON
|
||||
2. **前端**:修改 `translateJson()` 方法,兼容新格式(中文 key)和旧格式(camelCase key)
|
||||
|
||||
设计原则:INSERT/DELETE 使用通用辅助方法(字典映射)做全量中文序列化;UPDATE 由每个方法手动构造仅包含修改字段的 JSON。
|
||||
|
||||
## 架构
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph Backend["后端 - 控制器层"]
|
||||
BC[BaseController]
|
||||
BC -->|提供| SFA["SerializeForAudit(entity, mapping)"]
|
||||
BC -->|提供| BAL["BuildAuditLog()"]
|
||||
|
||||
C1[OdfCableFaultsController]
|
||||
C2[OdfCablesController]
|
||||
C3[OdfMarkerPolesController]
|
||||
C4[OdfRacksController]
|
||||
C5[OdfPortsController]
|
||||
C6[OdfFramesController]
|
||||
C7[OdfRoomsController]
|
||||
|
||||
C1 & C2 & C3 & C4 & C5 & C6 & C7 -->|INSERT/DELETE 调用| SFA
|
||||
C1 & C2 & C3 & C4 & C5 & C6 & C7 -->|UPDATE 手动构造 JSON| ManualJSON["手动构造差量 JSON"]
|
||||
end
|
||||
|
||||
subgraph Frontend["前端 - OdfAuditLogs.vue"]
|
||||
TJ["translateJson()"]
|
||||
TJ -->|检测第一个 key| Detect{是否中文?}
|
||||
Detect -->|是| Direct["直接格式化展示"]
|
||||
Detect -->|否| Translate["使用 fieldLabelMap 翻译"]
|
||||
end
|
||||
|
||||
Backend -->|写入 odf_audit_logs| DB[(数据库)]
|
||||
DB -->|读取| Frontend
|
||||
```
|
||||
|
||||
## 组件与接口
|
||||
|
||||
### 1. BaseController 改造
|
||||
|
||||
#### 1.1 新增 `BuildAuditJson` 方法
|
||||
|
||||
唯一的辅助方法,用于将 `Dictionary<string, object>` 序列化为紧凑 JSON,自动格式化 DateTime:
|
||||
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// 将字段字典序列化为审计 JSON(用于所有操作类型)
|
||||
/// </summary>
|
||||
protected static string BuildAuditJson(Dictionary<string, object> fields)
|
||||
{
|
||||
var result = new Dictionary<string, object>();
|
||||
foreach (var kv in fields)
|
||||
{
|
||||
if (kv.Value is DateTime dt)
|
||||
{
|
||||
result[kv.Key] = dt.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
}
|
||||
else
|
||||
{
|
||||
result[kv.Key] = kv.Value;
|
||||
}
|
||||
}
|
||||
return System.Text.Json.JsonSerializer.Serialize(result, new System.Text.Json.JsonSerializerOptions { WriteIndented = false });
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.2 保留 `BuildAuditLog()` 不变
|
||||
|
||||
`BuildAuditLog()` 方法逻辑完全不变,仍然负责构造 `OdfAuditLogs` 对象。
|
||||
|
||||
#### 1.3 不新增 `SerializeForAuditChinese` 方法
|
||||
|
||||
所有操作(INSERT/DELETE/UPDATE)都在每个方法内部手动构造 `Dictionary<string, object>`,通过 `BuildAuditJson` 序列化。不使用任何通用序列化 + 字段映射字典的方式。
|
||||
|
||||
### 2. 每个实体的中文字段名映射字典
|
||||
|
||||
每个控制器文件中定义一个 `static readonly Dictionary<string, string>` 作为该实体的字段映射。也可以统一放在 `BaseController` 或一个独立的静态类中。推荐放在各自控制器中,保持就近原则。
|
||||
|
||||
#### OdfCableFaults 映射
|
||||
```csharp
|
||||
private static readonly Dictionary<string, string> CableFaultsFieldMapping = new()
|
||||
{
|
||||
["id"] = "Id",
|
||||
["cableId"] = "关联光缆ID",
|
||||
["faultTime"] = "故障时间",
|
||||
["personnel"] = "人员",
|
||||
["faultReason"] = "故障原因",
|
||||
["mileage"] = "表显故障里程",
|
||||
["mileageCorrection"] = "表显里程矫正",
|
||||
["location"] = "地点描述",
|
||||
["latitude"] = "纬度",
|
||||
["longitude"] = "经度",
|
||||
["remark"] = "备注",
|
||||
["userId"] = "提交人用户ID",
|
||||
["createdAt"] = "创建时间",
|
||||
["updatedAt"] = "更新时间",
|
||||
["faultCount"] = "故障发生频次",
|
||||
};
|
||||
```
|
||||
|
||||
#### OdfCables 映射
|
||||
```csharp
|
||||
private static readonly Dictionary<string, string> CablesFieldMapping = new()
|
||||
{
|
||||
["id"] = "Id",
|
||||
["cableName"] = "光缆名称",
|
||||
["deptId"] = "部门ID",
|
||||
["deptName"] = "部门名称",
|
||||
["createdAt"] = "创建时间",
|
||||
["updatedAt"] = "更新时间",
|
||||
};
|
||||
```
|
||||
|
||||
#### OdfMarkerPoles 映射
|
||||
```csharp
|
||||
private static readonly Dictionary<string, string> MarkerPolesFieldMapping = new()
|
||||
{
|
||||
["id"] = "Id",
|
||||
["cableId"] = "关联光缆ID",
|
||||
["name"] = "名称",
|
||||
["recordTime"] = "记录时间",
|
||||
["personnel"] = "责任人",
|
||||
["latitude"] = "纬度",
|
||||
["longitude"] = "经度",
|
||||
["actualMileage"] = "实际里程",
|
||||
["deptId"] = "部门ID",
|
||||
["deptName"] = "部门名称",
|
||||
["userId"] = "提交人用户ID",
|
||||
["createdAt"] = "创建时间",
|
||||
["updatedAt"] = "更新时间",
|
||||
};
|
||||
```
|
||||
|
||||
#### OdfRacks 映射
|
||||
```csharp
|
||||
private static readonly Dictionary<string, string> RacksFieldMapping = new()
|
||||
{
|
||||
["id"] = "Id",
|
||||
["roomId"] = "机房ID",
|
||||
["sequenceNumber"] = "序号",
|
||||
["rackName"] = "ODF名称",
|
||||
["frameCount"] = "框数量",
|
||||
["createdAt"] = "创建时间",
|
||||
["updatedAt"] = "修改时间",
|
||||
["deptId"] = "部门ID",
|
||||
["rackType"] = "机架类型",
|
||||
};
|
||||
```
|
||||
|
||||
#### OdfPorts 映射
|
||||
```csharp
|
||||
private static readonly Dictionary<string, string> PortsFieldMapping = new()
|
||||
{
|
||||
["id"] = "Id",
|
||||
["name"] = "端口名称",
|
||||
["roomId"] = "机房ID",
|
||||
["roomName"] = "机房名称",
|
||||
["rackId"] = "机架ID",
|
||||
["rackName"] = "机架名称",
|
||||
["frameId"] = "框ID",
|
||||
["frameName"] = "框名称",
|
||||
["deptId"] = "部门ID",
|
||||
["deptName"] = "部门名称",
|
||||
["rowNumber"] = "行号",
|
||||
["portNumber"] = "端口号",
|
||||
["status"] = "连接状态",
|
||||
["remarks"] = "备注",
|
||||
["opticalAttenuation"] = "光衰值",
|
||||
["historyRemarks"] = "历史故障",
|
||||
["createdAt"] = "创建时间",
|
||||
["updatedAt"] = "修改时间",
|
||||
["opticalCableOffRemarks"] = "光缆断信息",
|
||||
["equipmentModel"] = "设备型号",
|
||||
["businessType"] = "业务类型",
|
||||
["portSide"] = "端口侧",
|
||||
};
|
||||
```
|
||||
|
||||
#### OdfFrames 映射
|
||||
```csharp
|
||||
private static readonly Dictionary<string, string> FramesFieldMapping = new()
|
||||
{
|
||||
["id"] = "Id",
|
||||
["rackId"] = "机架ID",
|
||||
["portsName"] = "名称",
|
||||
["deptId"] = "部门ID",
|
||||
["sequenceNumber"] = "序号",
|
||||
["portsCount"] = "端口数量",
|
||||
["portsCol"] = "列数量",
|
||||
["portsRow"] = "行数量",
|
||||
["createdAt"] = "创建时间",
|
||||
["updateAt"] = "修改时间",
|
||||
};
|
||||
```
|
||||
|
||||
#### OdfRooms 映射
|
||||
```csharp
|
||||
private static readonly Dictionary<string, string> RoomsFieldMapping = new()
|
||||
{
|
||||
["id"] = "Id",
|
||||
["deptId"] = "部门ID",
|
||||
["deptName"] = "部门名称",
|
||||
["roomName"] = "机房名称",
|
||||
["roomAddress"] = "机房位置",
|
||||
["racksCount"] = "机架数量",
|
||||
["remarks"] = "备注",
|
||||
["createdAt"] = "创建时间",
|
||||
["updatedAt"] = "修改时间",
|
||||
};
|
||||
```
|
||||
|
||||
### 3. INSERT/DELETE 操作模式
|
||||
|
||||
INSERT 和 DELETE 也使用 `BuildAuditJson` 手动构造,列出实体的所有字段。
|
||||
|
||||
#### INSERT 模式(以 OdfCableFaultsController.Add 为例)
|
||||
|
||||
```csharp
|
||||
// 查询插入后的完整记录
|
||||
var newRecord = _OdfCableFaultsService.GetFirst(f => f.Id == response);
|
||||
string newData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["Id"] = newRecord.Id,
|
||||
["关联光缆ID"] = newRecord.CableId,
|
||||
["故障时间"] = newRecord.FaultTime,
|
||||
["人员"] = newRecord.Personnel,
|
||||
["故障原因"] = newRecord.FaultReason,
|
||||
["表显故障里程"] = newRecord.Mileage,
|
||||
["表显里程矫正"] = newRecord.MileageCorrection,
|
||||
["地点描述"] = newRecord.Location,
|
||||
["纬度"] = newRecord.Latitude,
|
||||
["经度"] = newRecord.Longitude,
|
||||
["备注"] = newRecord.Remark,
|
||||
["提交人用户ID"] = newRecord.UserId,
|
||||
["创建时间"] = newRecord.CreatedAt,
|
||||
["更新时间"] = newRecord.UpdatedAt,
|
||||
["故障发生频次"] = newRecord.FaultCount,
|
||||
});
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_cable_faults", newRecord.Id, "INSERT", null, newData));
|
||||
```
|
||||
|
||||
#### DELETE 模式(以 OdfCableFaultsController.Delete 为例)
|
||||
|
||||
```csharp
|
||||
var oldRecord = _OdfCableFaultsService.GetFirst(f => f.Id == id);
|
||||
string oldData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["Id"] = oldRecord.Id,
|
||||
["关联光缆ID"] = oldRecord.CableId,
|
||||
["故障时间"] = oldRecord.FaultTime,
|
||||
// ... 所有字段
|
||||
});
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_cable_faults", id, "DELETE", oldData, null));
|
||||
```
|
||||
|
||||
### 4. UPDATE 操作模式 — 手动构造差量 JSON
|
||||
|
||||
每个 UPDATE 方法需要:
|
||||
1. 更新前:读取旧记录中该方法会修改的特定字段
|
||||
2. 更新后:读取新记录中相同字段
|
||||
3. 手动构造包含中文键名的 JSON
|
||||
|
||||
#### 辅助方法:`BuildAuditJson`
|
||||
|
||||
在 `BaseController` 中新增一个轻量辅助方法,用于将 `Dictionary<string, object>` 序列化为紧凑 JSON,并自动格式化 DateTime:
|
||||
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// 将字段字典序列化为审计 JSON(用于 UPDATE 差量记录)
|
||||
/// </summary>
|
||||
protected static string BuildAuditJson(Dictionary<string, object> fields)
|
||||
{
|
||||
var result = new Dictionary<string, object>();
|
||||
foreach (var kv in fields)
|
||||
{
|
||||
if (kv.Value is DateTime dt)
|
||||
{
|
||||
result[kv.Key] = dt.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
}
|
||||
else
|
||||
{
|
||||
result[kv.Key] = kv.Value;
|
||||
}
|
||||
}
|
||||
return System.Text.Json.JsonSerializer.Serialize(result, new System.Text.Json.JsonSerializerOptions { WriteIndented = false });
|
||||
}
|
||||
```
|
||||
|
||||
#### 示例:OdfCableFaultsController.IncrementFaultCount
|
||||
|
||||
需求 3.3 要求仅记录:故障发生频次, 更新时间
|
||||
|
||||
```csharp
|
||||
// 更新前
|
||||
var oldFault = _OdfCableFaultsService.GetFirst(f => f.Id == id);
|
||||
string oldData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["故障发生频次"] = oldFault.FaultCount,
|
||||
["更新时间"] = oldFault.UpdatedAt,
|
||||
});
|
||||
|
||||
// 执行更新
|
||||
var response = _OdfCableFaultsService.IncrementFaultCount(id);
|
||||
|
||||
// 更新后
|
||||
var newFault = _OdfCableFaultsService.GetFirst(f => f.Id == id);
|
||||
string newData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["故障发生频次"] = newFault.FaultCount,
|
||||
["更新时间"] = newFault.UpdatedAt,
|
||||
});
|
||||
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_cable_faults", id, "UPDATE", oldData, newData));
|
||||
```
|
||||
|
||||
#### 示例:OdfPortsController.SaveMOdfPorts
|
||||
|
||||
需求 3.10 要求仅记录:连接状态, 备注, 光衰值, 历史故障, 光缆断信息, 设备型号, 业务类型, 修改时间
|
||||
|
||||
```csharp
|
||||
// 更新前
|
||||
var oldRecord = _OdfPortsService.GetFirst(f => f.Id == parm.Id);
|
||||
string oldData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["连接状态"] = oldRecord.Status,
|
||||
["备注"] = oldRecord.Remarks,
|
||||
["光衰值"] = oldRecord.OpticalAttenuation,
|
||||
["历史故障"] = oldRecord.HistoryRemarks,
|
||||
["光缆断信息"] = oldRecord.OpticalCableOffRemarks,
|
||||
["设备型号"] = oldRecord.EquipmentModel,
|
||||
["业务类型"] = oldRecord.BusinessType,
|
||||
["修改时间"] = oldRecord.UpdatedAt,
|
||||
});
|
||||
|
||||
// ... 执行更新逻辑 ...
|
||||
|
||||
// 更新后
|
||||
var newRecord = _OdfPortsService.GetFirst(f => f.Id == parm.Id);
|
||||
string newData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["连接状态"] = newRecord.Status,
|
||||
["备注"] = newRecord.Remarks,
|
||||
["光衰值"] = newRecord.OpticalAttenuation,
|
||||
["历史故障"] = newRecord.HistoryRemarks,
|
||||
["光缆断信息"] = newRecord.OpticalCableOffRemarks,
|
||||
["设备型号"] = newRecord.EquipmentModel,
|
||||
["业务类型"] = newRecord.BusinessType,
|
||||
["修改时间"] = newRecord.UpdatedAt,
|
||||
});
|
||||
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_ports", parm.Id, "UPDATE", oldData, newData));
|
||||
```
|
||||
|
||||
### 5. 每个 UPDATE 方法的字段清单
|
||||
|
||||
| 控制器 | 方法 | 记录字段(中文键名) |
|
||||
|--------|------|---------------------|
|
||||
| OdfCableFaultsController | IncrementFaultCount | 故障发生频次, 更新时间 |
|
||||
| OdfCableFaultsController | UpdateMileageCorrection | 表显里程矫正, 更新时间 |
|
||||
| OdfCablesController | Update | 光缆名称, 部门ID, 部门名称, 更新时间 |
|
||||
| OdfMarkerPolesController | Update | 名称, 记录时间, 责任人, 纬度, 经度, 实际里程, 更新时间 |
|
||||
| OdfRacksController | UpdateOdfRacks | ODF名称, 序号, 框数量, 机架类型, 修改时间 |
|
||||
| OdfPortsController | GetOdfPortsStatus | 连接状态, 修改时间 |
|
||||
| OdfPortsController | UpdateOdfPorts | 端口名称, 连接状态, 备注, 光衰值, 历史故障, 光缆断信息, 设备型号, 业务类型, 修改时间 |
|
||||
| OdfPortsController | SaveMOdfPorts | 连接状态, 备注, 光衰值, 历史故障, 光缆断信息, 设备型号, 业务类型, 修改时间 |
|
||||
| OdfPortsController | GetOdfPortsEmpty | 连接状态, 备注, 历史故障, 光衰值, 光缆断信息, 修改时间 |
|
||||
| OdfFramesController | UpdateOdfFrames | 名称, 序号, 端口数量, 列数量, 行数量, 修改时间 |
|
||||
| OdfRoomsController | UpdateOdfRooms | 机房名称, 机房位置, 部门ID, 部门名称, 机架数量, 备注, 修改时间 |
|
||||
|
||||
|
||||
### 6. 前端 OdfAuditLogs.vue 适配
|
||||
|
||||
#### 6.1 简化 JSON 展示逻辑
|
||||
|
||||
后端已直接存储中文 key 的 JSON,前端不再需要翻译。移除 `translateJson` 方法和 `fieldLabelMap`,改为直接格式化展示。
|
||||
|
||||
```javascript
|
||||
// 格式化 JSON 字符串用于展示
|
||||
function formatJson(jsonStr) {
|
||||
if (!jsonStr) return '(无数据)'
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(jsonStr), null, 2)
|
||||
} catch {
|
||||
return jsonStr
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
对话框中的 `:model-value="translateJson(detailRow.oldData, detailRow.tableName)"` 改为 `:model-value="formatJson(detailRow.oldData)"`,NewData 同理。
|
||||
|
||||
#### 6.2 移除的代码
|
||||
|
||||
- 移除 `fieldLabelMap` 对象(整个字段映射定义)
|
||||
- 移除 `translateJson` 方法
|
||||
- 移除 `isChinese` 方法(如果有)
|
||||
|
||||
#### 6.3 保持不变的部分
|
||||
|
||||
- `tableNameMap` 保留(表名中文映射)
|
||||
- 对话框布局(左右对比)保留
|
||||
- 查询、分页、排序逻辑保留
|
||||
|
||||
## 数据模型
|
||||
|
||||
本需求不涉及数据库表结构变更。`odf_audit_logs` 表的 `OldData` 和 `NewData` 列仍为 JSON 字符串,仅 JSON 内容的键名从 camelCase 变为中文。
|
||||
|
||||
### JSON 格式对比
|
||||
|
||||
**INSERT 改造前:**
|
||||
```json
|
||||
{"id":1,"cableId":5,"faultTime":"2025-01-15T10:30:00","faultReason":"光缆断裂","mileage":"12.5"}
|
||||
```
|
||||
|
||||
**INSERT 改造后:**
|
||||
```json
|
||||
{"Id":1,"关联光缆ID":5,"故障时间":"2025-01-15 10:30:00","故障原因":"光缆断裂","表显故障里程":"12.5"}
|
||||
```
|
||||
|
||||
**UPDATE 改造前(全量):**
|
||||
```json
|
||||
{"id":1,"cableId":5,"faultTime":"...","personnel":"...","faultReason":"...","mileage":"...","mileageCorrection":"...","location":"...","latitude":0,"longitude":0,"remark":"...","userId":1,"createdAt":"...","updatedAt":"...","faultCount":1}
|
||||
```
|
||||
|
||||
**UPDATE 改造后(差量,IncrementFaultCount):**
|
||||
```json
|
||||
{"故障发生频次":2,"更新时间":"2025-01-15 10:30:00"}
|
||||
```
|
||||
|
||||
## 正确性属性
|
||||
|
||||
*正确性属性是在系统所有有效执行中都应成立的特征或行为——本质上是关于系统应该做什么的形式化陈述。属性是人类可读规范与机器可验证正确性保证之间的桥梁。*
|
||||
|
||||
### Property 1: 中文键名序列化正确性
|
||||
|
||||
*For any* 实体对象和对应的字段映射字典,调用 `SerializeForAuditChinese(entity, mapping)` 生成的 JSON 字符串中,所有键名都应存在于映射字典的值集合中(即全部为中文或 "Id"),且不应包含任何 camelCase 属性名。
|
||||
|
||||
**Validates: Requirements 1.1, 2.1, 3.2**
|
||||
|
||||
### Property 2: INSERT/DELETE 全量字段覆盖
|
||||
|
||||
*For any* 实体对象和对应的字段映射字典,调用 `SerializeForAuditChinese(entity, mapping)` 生成的 JSON 字符串中,键的数量应等于映射字典的条目数,且每个映射字典中的中文字段名都应作为键出现在 JSON 中。
|
||||
|
||||
**Validates: Requirements 1.2, 2.2, 6.1-6.7**
|
||||
|
||||
### Property 3: JSON 紧凑格式
|
||||
|
||||
*For any* 通过 `SerializeForAuditChinese` 或 `BuildAuditJson` 生成的 JSON 字符串,该字符串不应包含换行符(`\n`)或连续空格缩进。
|
||||
|
||||
**Validates: Requirements 5.1**
|
||||
|
||||
### Property 4: DateTime 格式化与 null 处理
|
||||
|
||||
*For any* 包含 DateTime 类型字段的实体,序列化后的 JSON 中对应值应匹配 `yyyy-MM-dd HH:mm:ss` 格式(正则 `^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$`)。*For any* 值为 null 的字段,序列化后 JSON 中该键的值应为 `null`。
|
||||
|
||||
**Validates: Requirements 5.4, 5.5**
|
||||
|
||||
### Property 5: 前端直接展示中文 key JSON
|
||||
|
||||
*For any* 审计日志的 OldData/NewData JSON 字符串,前端 `formatJson` 方法应直接解析并格式化展示,不做任何键名翻译。
|
||||
|
||||
**Validates: Requirements 8.1, 8.2**
|
||||
|
||||
## 错误处理
|
||||
|
||||
- 所有审计日志写入操作保持现有的 try-catch 模式,失败时通过 NLog 记录错误,不影响主业务流程
|
||||
- `SerializeForAuditChinese` 在 entity 为 null 时返回 null
|
||||
- `BuildAuditJson` 在字典值为 null 时保留 null 值
|
||||
- 前端 `translateJson` 在 JSON 解析失败时返回原始字符串
|
||||
|
||||
## 测试策略
|
||||
|
||||
### 单元测试
|
||||
|
||||
针对以下具体场景编写单元测试:
|
||||
|
||||
1. **SerializeForAuditChinese 方法**:
|
||||
- 测试每个实体类型(OdfPorts, OdfCableFaults 等)的序列化输出键名是否正确
|
||||
- 测试 null 字段的序列化
|
||||
- 测试 DateTime 字段的格式化
|
||||
|
||||
2. **BuildAuditJson 方法**:
|
||||
- 测试 DateTime 值的格式化
|
||||
- 测试 null 值的处理
|
||||
- 测试输出为紧凑 JSON
|
||||
|
||||
3. **每个 UPDATE 方法的字段覆盖**:
|
||||
- 验证每个 UPDATE 方法构造的 JSON 仅包含需求 3 中指定的字段
|
||||
|
||||
4. **前端 translateJson**:
|
||||
- 测试中文键名 JSON 直接展示
|
||||
- 测试 camelCase 键名 JSON 翻译展示
|
||||
- 测试空 JSON 和异常 JSON 的处理
|
||||
|
||||
### 属性测试
|
||||
|
||||
使用 **FsCheck**(C# 属性测试库)和 **fast-check**(JavaScript 属性测试库)。
|
||||
|
||||
- 每个属性测试至少运行 100 次迭代
|
||||
- 每个测试用注释标注对应的设计属性编号
|
||||
- 标签格式:**Feature: audit-log-chinese-fields, Property {number}: {property_text}**
|
||||
|
||||
属性测试覆盖:
|
||||
- Property 1: 生成随机实体 → 序列化 → 验证所有键为中文
|
||||
- Property 2: 生成随机实体 → 序列化 → 验证键数量等于映射字典大小
|
||||
- Property 3: 生成随机实体 → 序列化 → 验证无换行无缩进
|
||||
- Property 4: 生成包含随机 DateTime 和 null 的实体 → 序列化 → 验证格式
|
||||
- Property 5: 生成随机中文键/英文键 JSON → 调用 translateJson → 验证行为
|
||||
|
||||
## 需要修改的文件清单
|
||||
|
||||
| 文件 | 改动类型 |
|
||||
|------|---------|
|
||||
| `server/Infrastructure/Controllers/BaseController.cs` | 新增 `SerializeForAuditChinese` 和 `BuildAuditJson` 方法 |
|
||||
| `server/ZR.Admin.WebApi/Controllers/Business/OdfCableFaultsController.cs` | 新增映射字典 + 改造 4 个方法 |
|
||||
| `server/ZR.Admin.WebApi/Controllers/Business/OdfCablesController.cs` | 新增映射字典 + 改造 3 个方法 |
|
||||
| `server/ZR.Admin.WebApi/Controllers/Business/OdfMarkerPolesController.cs` | 新增映射字典 + 改造 3 个方法 |
|
||||
| `server/ZR.Admin.WebApi/Controllers/Business/OdfRacksController.cs` | 新增映射字典 + 改造 4 个方法 |
|
||||
| `server/ZR.Admin.WebApi/Controllers/Business/OdfPortsController.cs` | 新增映射字典 + 改造 7 个方法 |
|
||||
| `server/ZR.Admin.WebApi/Controllers/Business/OdfFramesController.cs` | 新增映射字典 + 改造 3 个方法 |
|
||||
| `server/ZR.Admin.WebApi/Controllers/Business/OdfRoomsController.cs` | 新增映射字典 + 改造 4 个方法 |
|
||||
| `server/ZR.Vue/src/views/business/OdfAuditLogs.vue` | 修改 `translateJson` 方法 + 新增 `isChinese` 函数 |
|
||||
141
.kiro/specs/audit-log-chinese-fields/requirements.md
Normal file
141
.kiro/specs/audit-log-chinese-fields/requirements.md
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
# 需求文档:审计日志中文字段名
|
||||
|
||||
## 简介
|
||||
|
||||
当前系统的审计日志(odf_audit_logs)中,OldData/NewData 的 JSON 使用 C# 属性的 camelCase 命名作为键名。本需求要求将所有审计日志的 JSON 键名替换为中文字段名(来源于 Model 的 XML 注释),并且 UPDATE 操作仅记录该方法实际修改的字段,而非整个实体。每个控制器方法需手动构造自己的审计 JSON,不使用通用序列化方法。
|
||||
|
||||
## 术语表
|
||||
|
||||
- **审计日志系统(Audit_Log_System)**: 负责记录 odf_audit_logs 表中 OldData/NewData JSON 数据的子系统
|
||||
- **中文字段名映射(Chinese_Field_Mapping)**: C# 属性名到中文名称的对应关系,来源于 Model 类的 XML 注释
|
||||
- **全量中文序列化(Full_Chinese_Serialization)**: 将实体的所有字段以中文键名序列化为 JSON 的操作,用于 INSERT 和 DELETE
|
||||
- **差量中文序列化(Diff_Chinese_Serialization)**: 仅将方法实际修改的字段以中文键名序列化为 JSON 的操作,用于 UPDATE
|
||||
- **OdfPorts**: 端口实体,对应 odf_ports 表
|
||||
- **OdfCableFaults**: 干线故障实体,对应 odf_cable_faults 表
|
||||
- **OdfCables**: 光缆实体,对应 odf_cables 表
|
||||
- **OdfRacks**: 机架实体,对应 odf_racks 表
|
||||
- **OdfFrames**: 框实体,对应 odf_frames 表
|
||||
- **OdfRooms**: 机房实体,对应 odf_rooms 表
|
||||
- **OdfMarkerPoles**: 标石/杆号牌实体,对应 odf_marker_poles 表
|
||||
|
||||
## 需求
|
||||
|
||||
### 需求 1:INSERT 操作使用中文字段名全量序列化
|
||||
|
||||
**用户故事:** 作为系统管理员,我希望 INSERT 审计日志的 NewData 使用中文字段名作为 JSON 键,以便在审计日志查看界面直接看懂每个字段的含义。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. WHEN 任意控制器方法执行 INSERT 操作并写入审计日志时,THE 审计日志系统 SHALL 将 NewData 的 JSON 键名全部替换为对应的中文字段名
|
||||
2. THE 审计日志系统 SHALL 在 INSERT 操作中记录实体的所有字段(全量),OldData 为 null
|
||||
3. WHEN OdfCableFaultsController.Add 执行 INSERT 时,THE 审计日志系统 SHALL 使用以下中文字段名映射生成 NewData:Id→Id, CableId→关联光缆ID, FaultTime→故障时间, Personnel→人员, FaultReason→故障原因, Mileage→表显故障里程, MileageCorrection→表显里程矫正, Location→地点描述, Latitude→纬度, Longitude→经度, Remark→备注, UserId→提交人用户ID, CreatedAt→创建时间, UpdatedAt→更新时间, FaultCount→故障发生频次
|
||||
4. WHEN OdfCablesController.Add 执行 INSERT 时,THE 审计日志系统 SHALL 使用以下中文字段名映射生成 NewData:Id→Id, CableName→光缆名称, DeptId→部门ID, DeptName→部门名称, CreatedAt→创建时间, UpdatedAt→更新时间
|
||||
5. WHEN OdfMarkerPolesController.Add 执行 INSERT 时,THE 审计日志系统 SHALL 使用以下中文字段名映射生成 NewData:Id→Id, CableId→关联光缆ID, Name→名称, RecordTime→记录时间, Personnel→责任人, Latitude→纬度, Longitude→经度, ActualMileage→实际里程, DeptId→部门ID, DeptName→部门名称, UserId→提交人用户ID, CreatedAt→创建时间, UpdatedAt→更新时间
|
||||
6. WHEN OdfRacksController.AddOdfRacks 或 AddOdfRacksExpert 执行 INSERT 时,THE 审计日志系统 SHALL 使用以下中文字段名映射生成 NewData:Id→Id, RoomId→机房ID, SequenceNumber→序号, RackName→ODF名称, FrameCount→框数量, CreatedAt→创建时间, UpdatedAt→修改时间, DeptId→部门ID, RackType→机架类型
|
||||
7. WHEN OdfPortsController.AddOdfPorts 执行 INSERT 时,THE 审计日志系统 SHALL 使用以下中文字段名映射生成 NewData:Id→Id, Name→端口名称, RoomId→机房ID, RoomName→机房名称, RackId→机架ID, RackName→机架名称, FrameId→框ID, FrameName→框名称, DeptId→部门ID, DeptName→部门名称, RowNumber→行号, PortNumber→端口号, Status→连接状态, Remarks→备注, OpticalAttenuation→光衰值, HistoryRemarks→历史故障, CreatedAt→创建时间, UpdatedAt→修改时间, OpticalCableOffRemarks→光缆断信息, EquipmentModel→设备型号, BusinessType→业务类型, PortSide→端口侧
|
||||
8. WHEN OdfFramesController.AddOdfFrames 执行 INSERT 时,THE 审计日志系统 SHALL 使用以下中文字段名映射生成 NewData:Id→Id, RackId→机架ID, PortsName→名称, DeptId→部门ID, SequenceNumber→序号, PortsCount→端口数量, PortsCol→列数量, PortsRow→行数量, CreatedAt→创建时间, UpdateAt→修改时间
|
||||
9. WHEN OdfRoomsController.AddOdfRooms 或 AddExpertOdfRooms 执行 INSERT 时,THE 审计日志系统 SHALL 使用以下中文字段名映射生成 NewData:Id→Id, DeptId→部门ID, DeptName→部门名称, RoomName→机房名称, RoomAddress→机房位置, RacksCount→机架数量, Remarks→备注, CreatedAt→创建时间, UpdatedAt→修改时间
|
||||
|
||||
|
||||
### 需求 2:DELETE 操作使用中文字段名全量序列化
|
||||
|
||||
**用户故事:** 作为系统管理员,我希望 DELETE 审计日志的 OldData 使用中文字段名作为 JSON 键,以便在审计日志中清晰看到被删除记录的完整内容。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. WHEN 任意控制器方法执行 DELETE 操作并写入审计日志时,THE 审计日志系统 SHALL 将 OldData 的 JSON 键名全部替换为对应的中文字段名
|
||||
2. THE 审计日志系统 SHALL 在 DELETE 操作中记录实体的所有字段(全量),NewData 为 null
|
||||
3. WHEN OdfCableFaultsController.Delete 执行 DELETE 时,THE 审计日志系统 SHALL 使用 OdfCableFaults 的中文字段名映射生成 OldData
|
||||
4. WHEN OdfCablesController.Delete 执行 DELETE 时,THE 审计日志系统 SHALL 使用 OdfCables 的中文字段名映射生成 OldData
|
||||
5. WHEN OdfMarkerPolesController.Delete 执行 DELETE 时,THE 审计日志系统 SHALL 使用 OdfMarkerPoles 的中文字段名映射生成 OldData
|
||||
6. WHEN OdfRacksController.DeleteOdfRacks 执行 DELETE 时,THE 审计日志系统 SHALL 使用 OdfRacks 的中文字段名映射生成 OldData
|
||||
7. WHEN OdfPortsController.DeleteOdfPorts 执行 DELETE 时,THE 审计日志系统 SHALL 使用 OdfPorts 的中文字段名映射生成 OldData
|
||||
8. WHEN OdfPortsController.DeleteAll 执行 DELETE 时,THE 审计日志系统 SHALL 对每个被删除的端口使用 OdfPorts 的中文字段名映射生成 OldData
|
||||
9. WHEN OdfFramesController.DeleteOdfFrames 执行 DELETE 时,THE 审计日志系统 SHALL 使用 OdfFrames 的中文字段名映射生成 OldData
|
||||
10. WHEN OdfRoomsController.DeleteOdfRooms 执行 DELETE 时,THE 审计日志系统 SHALL 使用 OdfRooms 的中文字段名映射生成 OldData
|
||||
|
||||
### 需求 3:UPDATE 操作仅记录实际修改字段并使用中文字段名
|
||||
|
||||
**用户故事:** 作为系统管理员,我希望 UPDATE 审计日志的 OldData/NewData 仅包含该方法实际修改的字段(而非整个实体),并使用中文字段名,以便快速定位每次操作具体改了什么。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. WHEN 任意控制器方法执行 UPDATE 操作并写入审计日志时,THE 审计日志系统 SHALL 仅在 OldData 和 NewData 中包含该方法实际修改的字段
|
||||
2. THE 审计日志系统 SHALL 在 UPDATE 操作的 OldData 和 NewData 中使用中文字段名作为 JSON 键
|
||||
3. WHEN OdfCableFaultsController.IncrementFaultCount 执行 UPDATE 时,THE 审计日志系统 SHALL 仅记录以下字段:故障发生频次, 更新时间
|
||||
4. WHEN OdfCableFaultsController.UpdateMileageCorrection 执行 UPDATE 时,THE 审计日志系统 SHALL 仅记录以下字段:表显里程矫正, 更新时间
|
||||
5. WHEN OdfCablesController.Update 执行 UPDATE 时,THE 审计日志系统 SHALL 仅记录以下字段:光缆名称, 部门ID, 部门名称, 更新时间
|
||||
6. WHEN OdfMarkerPolesController.Update 执行 UPDATE 时,THE 审计日志系统 SHALL 仅记录以下字段:名称, 记录时间, 责任人, 纬度, 经度, 实际里程, 更新时间
|
||||
7. WHEN OdfRacksController.UpdateOdfRacks 执行 UPDATE 时,THE 审计日志系统 SHALL 仅记录以下字段:ODF名称, 序号, 框数量, 机架类型, 修改时间
|
||||
8. WHEN OdfPortsController.GetOdfPortsStatus 执行 UPDATE 时,THE 审计日志系统 SHALL 仅记录以下字段:连接状态, 修改时间
|
||||
9. WHEN OdfPortsController.UpdateOdfPorts 执行 UPDATE 时,THE 审计日志系统 SHALL 仅记录以下字段:端口名称, 连接状态, 备注, 光衰值, 历史故障, 光缆断信息, 设备型号, 业务类型, 修改时间
|
||||
10. WHEN OdfPortsController.SaveMOdfPorts 执行 UPDATE 时,THE 审计日志系统 SHALL 仅记录以下字段:连接状态, 备注, 光衰值, 历史故障, 光缆断信息, 设备型号, 业务类型, 修改时间
|
||||
11. WHEN OdfPortsController.GetOdfPortsEmpty 执行 UPDATE 时,THE 审计日志系统 SHALL 仅记录以下字段:连接状态, 备注, 历史故障, 光衰值, 光缆断信息, 修改时间
|
||||
12. WHEN OdfFramesController.UpdateOdfFrames 执行 UPDATE 时,THE 审计日志系统 SHALL 仅记录以下字段:名称, 序号, 端口数量, 列数量, 行数量, 修改时间
|
||||
13. WHEN OdfRoomsController.UpdateOdfRooms 执行 UPDATE 时,THE 审计日志系统 SHALL 仅记录以下字段:机房名称, 机房位置, 部门ID, 部门名称, 机架数量, 备注, 修改时间
|
||||
|
||||
### 需求 4:每个方法手动构造审计 JSON
|
||||
|
||||
**用户故事:** 作为开发者,我希望每个控制器方法手动构造自己的审计 JSON(而非使用通用序列化方法),因为每个方法修改的字段和业务逻辑各不相同。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. THE 审计日志系统 SHALL 要求每个控制器方法在自身代码中手动构造 OldData/NewData 的 JSON 字符串
|
||||
2. THE 审计日志系统 SHALL 保留 BaseController 中的 BuildAuditLog 方法用于构造 OdfAuditLogs 对象
|
||||
3. THE 审计日志系统 SHALL 允许 INSERT 和 DELETE 操作使用辅助方法(如改造后的 SerializeForAudit 或新的中文序列化辅助方法)进行全量中文序列化
|
||||
4. THE 审计日志系统 SHALL 要求 UPDATE 操作在每个方法内部手动构造仅包含修改字段的 JSON
|
||||
5. IF 某个 UPDATE 方法的业务逻辑发生变化导致修改字段增减,THEN THE 审计日志系统 SHALL 要求开发者同步更新该方法的审计 JSON 构造代码
|
||||
|
||||
### 需求 5:审计日志 JSON 格式规范
|
||||
|
||||
**用户故事:** 作为系统管理员,我希望审计日志的 JSON 格式统一规范,以便前端展示和数据分析。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. THE 审计日志系统 SHALL 生成的 JSON 字符串不包含缩进(紧凑格式)
|
||||
2. THE 审计日志系统 SHALL 保持 odf_audit_logs 表的 OldData 和 NewData 列存储格式为 JSON 字符串
|
||||
3. THE 审计日志系统 SHALL 保持 odf_audit_logs 表的 TableName、RecordId、OperationType、OperatorId、OperatorName、SourceClient、OperationTime、DeptId、Remark 等字段的写入逻辑不变
|
||||
4. WHEN 实体字段值为 null 时,THE 审计日志系统 SHALL 在 JSON 中将该字段值序列化为 null
|
||||
5. WHEN 实体字段值为 DateTime 类型时,THE 审计日志系统 SHALL 将其序列化为 "yyyy-MM-dd HH:mm:ss" 格式的字符串
|
||||
|
||||
### 需求 6:中文字段名映射完整性
|
||||
|
||||
**用户故事:** 作为开发者,我希望所有 7 个实体的所有字段都有明确的中文字段名映射,以确保审计日志的一致性。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. THE 审计日志系统 SHALL 为 OdfPorts 的 22 个字段提供完整的中文字段名映射:Id→Id, Name→端口名称, RoomId→机房ID, RoomName→机房名称, RackId→机架ID, RackName→机架名称, FrameId→框ID, FrameName→框名称, DeptId→部门ID, DeptName→部门名称, RowNumber→行号, PortNumber→端口号, Status→连接状态, Remarks→备注, OpticalAttenuation→光衰值, HistoryRemarks→历史故障, CreatedAt→创建时间, UpdatedAt→修改时间, OpticalCableOffRemarks→光缆断信息, EquipmentModel→设备型号, BusinessType→业务类型, PortSide→端口侧
|
||||
2. THE 审计日志系统 SHALL 为 OdfCableFaults 的 15 个字段提供完整的中文字段名映射:Id→Id, CableId→关联光缆ID, FaultTime→故障时间, Personnel→人员, FaultReason→故障原因, Mileage→表显故障里程, MileageCorrection→表显里程矫正, Location→地点描述, Latitude→纬度, Longitude→经度, Remark→备注, UserId→提交人用户ID, CreatedAt→创建时间, UpdatedAt→更新时间, FaultCount→故障发生频次
|
||||
3. THE 审计日志系统 SHALL 为 OdfCables 的 6 个字段提供完整的中文字段名映射:Id→Id, CableName→光缆名称, DeptId→部门ID, DeptName→部门名称, CreatedAt→创建时间, UpdatedAt→更新时间
|
||||
4. THE 审计日志系统 SHALL 为 OdfRacks 的 8 个字段提供完整的中文字段名映射:Id→Id, RoomId→机房ID, SequenceNumber→序号, RackName→ODF名称, FrameCount→框数量, CreatedAt→创建时间, UpdatedAt→修改时间, DeptId→部门ID, RackType→机架类型
|
||||
5. THE 审计日志系统 SHALL 为 OdfFrames 的 10 个字段提供完整的中文字段名映射:Id→Id, RackId→机架ID, PortsName→名称, DeptId→部门ID, SequenceNumber→序号, PortsCount→端口数量, PortsCol→列数量, PortsRow→行数量, CreatedAt→创建时间, UpdateAt→修改时间
|
||||
6. THE 审计日志系统 SHALL 为 OdfRooms 的 9 个字段提供完整的中文字段名映射:Id→Id, DeptId→部门ID, DeptName→部门名称, RoomName→机房名称, RoomAddress→机房位置, RacksCount→机架数量, Remarks→备注, CreatedAt→创建时间, UpdatedAt→修改时间
|
||||
7. THE 审计日志系统 SHALL 为 OdfMarkerPoles 的 13 个字段提供完整的中文字段名映射:Id→Id, CableId→关联光缆ID, Name→名称, RecordTime→记录时间, Personnel→责任人, Latitude→纬度, Longitude→经度, ActualMileage→实际里程, DeptId→部门ID, DeptName→部门名称, UserId→提交人用户ID, CreatedAt→创建时间, UpdatedAt→更新时间
|
||||
|
||||
### 需求 7:涉及的控制器方法完整清单
|
||||
|
||||
**用户故事:** 作为开发者,我希望有一份完整的需要修改的控制器方法清单,以确保不遗漏任何审计日志调用点。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. THE 审计日志系统 SHALL 修改 OdfCableFaultsController 的以下 4 个方法:Add(INSERT)、IncrementFaultCount(UPDATE)、UpdateMileageCorrection(UPDATE)、Delete(DELETE)
|
||||
2. THE 审计日志系统 SHALL 修改 OdfCablesController 的以下 3 个方法:Add(INSERT)、Update(UPDATE)、Delete(DELETE)
|
||||
3. THE 审计日志系统 SHALL 修改 OdfMarkerPolesController 的以下 3 个方法:Add(INSERT)、Update(UPDATE)、Delete(DELETE)
|
||||
4. THE 审计日志系统 SHALL 修改 OdfRacksController 的以下 4 个方法:AddOdfRacks(INSERT)、AddOdfRacksExpert(INSERT)、UpdateOdfRacks(UPDATE)、DeleteOdfRacks(DELETE)
|
||||
5. THE 审计日志系统 SHALL 修改 OdfPortsController 的以下 7 个方法:GetOdfPortsStatus(UPDATE)、AddOdfPorts(INSERT)、UpdateOdfPorts(UPDATE)、SaveMOdfPorts(UPDATE)、GetOdfPortsEmpty(UPDATE)、DeleteOdfPorts(DELETE)、DeleteAll(DELETE)
|
||||
6. THE 审计日志系统 SHALL 修改 OdfFramesController 的以下 3 个方法:AddOdfFrames(INSERT)、UpdateOdfFrames(UPDATE)、DeleteOdfFrames(DELETE)
|
||||
7. THE 审计日志系统 SHALL 修改 OdfRoomsController 的以下 4 个方法:AddOdfRooms(INSERT)、AddExpertOdfRooms(INSERT)、UpdateOdfRooms(UPDATE)、DeleteOdfRooms(DELETE)
|
||||
8. THE 审计日志系统 SHALL 总计修改 7 个控制器中的 28 个方法
|
||||
|
||||
|
||||
### 需求 8:前端"修改统计"页面适配
|
||||
|
||||
**用户故事:** 作为系统管理员,我希望管理后台的"修改统计"页面直接展示后端存储的中文 key JSON,不再需要前端翻译。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. THE 前端 SHALL 移除 `translateJson` 方法,改为直接格式化展示 JSON(仅做 `JSON.stringify(obj, null, 2)`)
|
||||
2. THE 前端 SHALL 移除 `fieldLabelMap` 字段映射对象(不再需要前端翻译)
|
||||
3. THE 前端 SHALL 保持现有的表名中文映射(tableNameMap)不变
|
||||
4. THE 前端 SHALL 保持现有的对话框布局(左右对比 OldData/NewData)不变
|
||||
5. THE 前端 SHALL 移除未使用的 `formatJson` 方法
|
||||
115
.kiro/specs/audit-log-chinese-fields/tasks.md
Normal file
115
.kiro/specs/audit-log-chinese-fields/tasks.md
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
# 实施计划:审计日志中文字段名
|
||||
|
||||
## 概述
|
||||
|
||||
将审计日志系统的 OldData/NewData JSON 键名从 camelCase 替换为中文字段名。所有操作(INSERT/DELETE/UPDATE)都在每个方法内部手动构造 `Dictionary<string, object>`,通过 `BuildAuditJson` 序列化。不使用通用序列化方法,不使用字段映射字典。前端移除 `translateJson` 和 `fieldLabelMap`,直接用 `formatJson` 展示。
|
||||
|
||||
## 任务
|
||||
|
||||
- [x] 1. BaseController 新增 BuildAuditJson 方法
|
||||
- [x] 1.1 在 `server/Infrastructure/Controllers/BaseController.cs` 中新增 `BuildAuditJson(Dictionary<string, object> fields)` 方法
|
||||
- 遍历字典,DateTime 值格式化为 `yyyy-MM-dd HH:mm:ss`
|
||||
- 返回紧凑 JSON 字符串
|
||||
- _需求: 1.1, 3.1, 4.2, 5.1, 5.4, 5.5_
|
||||
|
||||
- [x] 2. OdfCableFaultsController 改造(4 个方法)
|
||||
- [x] 2.1 改造 `Add` 方法(INSERT):用 `BuildAuditJson` 手动构造 newData,列出全部 15 个字段(中文键名)
|
||||
- 字段:Id, 关联光缆ID, 故障时间, 人员, 故障原因, 表显故障里程, 表显里程矫正, 地点描述, 纬度, 经度, 备注, 提交人用户ID, 创建时间, 更新时间, 故障发生频次
|
||||
- _需求: 1.3, 7.1_
|
||||
- [x] 2.2 改造 `IncrementFaultCount` 方法(UPDATE):用 `BuildAuditJson` 手动构造 oldData/newData
|
||||
- 仅记录字段:故障发生频次, 更新时间
|
||||
- _需求: 3.3, 7.1_
|
||||
- [x] 2.3 改造 `UpdateMileageCorrection` 方法(UPDATE):用 `BuildAuditJson` 手动构造 oldData/newData
|
||||
- 仅记录字段:表显里程矫正, 更新时间
|
||||
- _需求: 3.4, 7.1_
|
||||
- [x] 2.4 改造 `Delete` 方法(DELETE):循环中用 `BuildAuditJson` 手动构造 oldData,列出全部 15 个字段
|
||||
- _需求: 2.3, 7.1_
|
||||
|
||||
- [x] 3. OdfCablesController 改造(3 个方法)
|
||||
- [x] 3.1 改造 `Add` 方法(INSERT):用 `BuildAuditJson` 手动构造 newData,列出全部 6 个字段
|
||||
- 字段:Id, 光缆名称, 部门ID, 部门名称, 创建时间, 更新时间
|
||||
- _需求: 1.4, 7.2_
|
||||
- [x] 3.2 改造 `Update` 方法(UPDATE):用 `BuildAuditJson` 手动构造 oldData/newData
|
||||
- 仅记录字段:光缆名称, 部门ID, 部门名称, 更新时间
|
||||
- _需求: 3.5, 7.2_
|
||||
- [x] 3.3 改造 `Delete` 方法(DELETE):用 `BuildAuditJson` 手动构造 oldData,列出全部 6 个字段
|
||||
- _需求: 2.4, 7.2_
|
||||
|
||||
- [x] 4. OdfMarkerPolesController 改造(3 个方法)
|
||||
- [x] 4.1 改造 `Add` 方法(INSERT):用 `BuildAuditJson` 手动构造 newData,列出全部 13 个字段
|
||||
- 字段:Id, 关联光缆ID, 名称, 记录时间, 责任人, 纬度, 经度, 实际里程, 部门ID, 部门名称, 提交人用户ID, 创建时间, 更新时间
|
||||
- _需求: 1.5, 7.3_
|
||||
- [x] 4.2 改造 `Update` 方法(UPDATE):用 `BuildAuditJson` 手动构造 oldData/newData
|
||||
- 仅记录字段:名称, 记录时间, 责任人, 纬度, 经度, 实际里程, 更新时间
|
||||
- _需求: 3.6, 7.3_
|
||||
- [x] 4.3 改造 `Delete` 方法(DELETE):循环中用 `BuildAuditJson` 手动构造 oldData,列出全部 13 个字段
|
||||
- _需求: 2.5, 7.3_
|
||||
|
||||
- [x] 5. OdfRacksController 改造(4 个方法)
|
||||
- [x] 5.1 改造 `AddOdfRacks` 方法(INSERT):用 `BuildAuditJson` 手动构造 newData,列出全部 9 个字段
|
||||
- 字段:Id, 机房ID, 序号, ODF名称, 框数量, 创建时间, 修改时间, 部门ID, 机架类型
|
||||
- _需求: 1.6, 7.4_
|
||||
- [x] 5.2 改造 `AddOdfRacksExpert` 方法(INSERT):用 `BuildAuditJson` 手动构造 newData,列出全部 9 个字段
|
||||
- _需求: 1.6, 7.4_
|
||||
- [x] 5.3 改造 `UpdateOdfRacks` 方法(UPDATE):用 `BuildAuditJson` 手动构造 oldData/newData
|
||||
- 仅记录字段:ODF名称, 序号, 框数量, 机架类型, 修改时间
|
||||
- _需求: 3.7, 7.4_
|
||||
- [x] 5.4 改造 `DeleteOdfRacks` 方法(DELETE):循环中用 `BuildAuditJson` 手动构造 oldData,列出全部 9 个字段
|
||||
- _需求: 2.6, 7.4_
|
||||
|
||||
- [x] 6. OdfPortsController 改造(7 个方法)
|
||||
- [x] 6.1 改造 `AddOdfPorts` 方法(INSERT):用 `BuildAuditJson` 手动构造 newData,列出全部 22 个字段
|
||||
- 字段:Id, 端口名称, 机房ID, 机房名称, 机架ID, 机架名称, 框ID, 框名称, 部门ID, 部门名称, 行号, 端口号, 连接状态, 备注, 光衰值, 历史故障, 创建时间, 修改时间, 光缆断信息, 设备型号, 业务类型, 端口侧
|
||||
- _需求: 1.7, 7.5_
|
||||
- [x] 6.2 改造 `GetOdfPortsStatus` 方法(UPDATE):用 `BuildAuditJson` 手动构造 oldData/newData
|
||||
- 仅记录字段:连接状态, 修改时间
|
||||
- _需求: 3.8, 7.5_
|
||||
- [x] 6.3 改造 `UpdateOdfPorts` 方法(UPDATE):用 `BuildAuditJson` 手动构造 oldData/newData
|
||||
- 仅记录字段:端口名称, 连接状态, 备注, 光衰值, 历史故障, 光缆断信息, 设备型号, 业务类型, 修改时间
|
||||
- _需求: 3.9, 7.5_
|
||||
- [x] 6.4 改造 `SaveMOdfPorts` 方法(UPDATE):用 `BuildAuditJson` 手动构造 oldData/newData
|
||||
- 仅记录字段:连接状态, 备注, 光衰值, 历史故障, 光缆断信息, 设备型号, 业务类型, 修改时间
|
||||
- _需求: 3.10, 7.5_
|
||||
- [x] 6.5 改造 `GetOdfPortsEmpty` 方法(UPDATE):用 `BuildAuditJson` 手动构造 oldData/newData
|
||||
- 仅记录字段:连接状态, 备注, 历史故障, 光衰值, 光缆断信息, 修改时间
|
||||
- _需求: 3.11, 7.5_
|
||||
- [x] 6.6 改造 `DeleteOdfPorts` 方法(DELETE):循环中用 `BuildAuditJson` 手动构造 oldData,列出全部 22 个字段
|
||||
- _需求: 2.7, 7.5_
|
||||
- [x] 6.7 改造 `DeleteAll` 方法(DELETE):三个分支(Level 2/3/4)中循环用 `BuildAuditJson` 手动构造 oldData,列出全部 22 个字段
|
||||
- _需求: 2.8, 7.5_
|
||||
|
||||
- [x] 7. OdfFramesController 改造(3 个方法)
|
||||
- [x] 7.1 改造 `AddOdfFrames` 方法(INSERT):用 `BuildAuditJson` 手动构造 newData,列出全部 10 个字段
|
||||
- 字段:Id, 机架ID, 名称, 部门ID, 序号, 端口数量, 列数量, 行数量, 创建时间, 修改时间
|
||||
- _需求: 1.8, 7.6_
|
||||
- [x] 7.2 改造 `UpdateOdfFrames` 方法(UPDATE):用 `BuildAuditJson` 手动构造 oldData/newData
|
||||
- 仅记录字段:名称, 序号, 端口数量, 列数量, 行数量, 修改时间
|
||||
- _需求: 3.12, 7.6_
|
||||
- [x] 7.3 改造 `DeleteOdfFrames` 方法(DELETE):循环中用 `BuildAuditJson` 手动构造 oldData,列出全部 10 个字段
|
||||
- _需求: 2.9, 7.6_
|
||||
|
||||
- [x] 8. OdfRoomsController 改造(4 个方法)
|
||||
- [x] 8.1 改造 `AddOdfRooms` 方法(INSERT):用 `BuildAuditJson` 手动构造 newData,列出全部 9 个字段
|
||||
- 字段:Id, 部门ID, 部门名称, 机房名称, 机房位置, 机架数量, 备注, 创建时间, 修改时间
|
||||
- _需求: 1.9, 7.7_
|
||||
- [x] 8.2 改造 `AddExpertOdfRooms` 方法(INSERT):用 `BuildAuditJson` 手动构造 newData,列出全部 9 个字段
|
||||
- _需求: 1.9, 7.7_
|
||||
- [x] 8.3 改造 `UpdateOdfRooms` 方法(UPDATE):用 `BuildAuditJson` 手动构造 oldData/newData
|
||||
- 仅记录字段:机房名称, 机房位置, 部门ID, 部门名称, 机架数量, 备注, 修改时间
|
||||
- _需求: 3.13, 7.7_
|
||||
- [x] 8.4 改造 `DeleteOdfRooms` 方法(DELETE):用 `BuildAuditJson` 手动构造 oldData,列出全部 9 个字段
|
||||
- _需求: 2.10, 7.7_
|
||||
|
||||
- [x] 9. 前端 OdfAuditLogs.vue 适配
|
||||
- [x] 9.1 移除 `fieldLabelMap` 对象
|
||||
- _需求: 8.2_
|
||||
- [x] 9.2 移除 `translateJson` 方法
|
||||
- _需求: 8.1_
|
||||
- [x] 9.3 将对话框中 `translateJson(...)` 调用替换为 `formatJson(...)` 调用
|
||||
- _需求: 8.1_
|
||||
- [x] 9.4 保留 `tableNameMap` 和 `formatJson` 方法不变
|
||||
- _需求: 8.3_
|
||||
|
||||
- [x] 10. 编译验证
|
||||
- 后端 `dotnet build` 编译通过
|
||||
- 前端无语法错误
|
||||
5
.kiro/specs/audit-log-manual/.config.kiro
Normal file
5
.kiro/specs/audit-log-manual/.config.kiro
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"specType": "feature",
|
||||
"workflowType": "requirements-first",
|
||||
"featureName": "audit-log-manual"
|
||||
}
|
||||
400
.kiro/specs/audit-log-manual/design.md
Normal file
400
.kiro/specs/audit-log-manual/design.md
Normal file
|
|
@ -0,0 +1,400 @@
|
|||
# 技术设计文档 — 审计日志手动化改造
|
||||
|
||||
## 概述
|
||||
|
||||
将 ODF 系统中所有业务数据的审计日志记录方式从 `OdfAuditLogFilter` 拦截器自动模式改为 Controller 中手动记录。改造涉及 7 个 Controller、27 个接口,改造完成后移除拦截器。
|
||||
|
||||
## 架构
|
||||
|
||||
### 当前架构(拦截器模式)
|
||||
|
||||
```
|
||||
Controller Action
|
||||
↓
|
||||
[ServiceFilter(typeof(OdfAuditLogFilter))]
|
||||
↓ 操作前:GetExistingDataJson() 查旧数据
|
||||
↓ 执行 Action
|
||||
↓ 操作后:GetNewDataFromActionArguments() 从请求参数序列化新数据
|
||||
↓ 写入 odf_audit_logs
|
||||
```
|
||||
|
||||
问题:`GetNewDataFromActionArguments` 只能序列化复杂类型 DTO,对 `int id`、`string ids` 等简单类型参数返回 null。
|
||||
|
||||
### 目标架构(手动模式)
|
||||
|
||||
```
|
||||
Controller Action 内部
|
||||
↓ 操作前:查询旧数据 → JSON 序列化为 OldData
|
||||
↓ 执行业务操作
|
||||
↓ 操作后:查询新数据 → JSON 序列化为 NewData
|
||||
↓ 构造 OdfAuditLogs 对象 → _auditLogsService.AddLog()
|
||||
```
|
||||
|
||||
## 涉及文件
|
||||
|
||||
### 需要修改的文件
|
||||
|
||||
| 文件 | 改动内容 |
|
||||
|------|---------|
|
||||
| `server/ZR.Admin.WebApi/Controllers/Business/OdfCableFaultsController.cs` | 去掉拦截器,手动审计(IncrementFaultCount 已完成,剩余 Add/UpdateMileageCorrection/Delete) |
|
||||
| `server/ZR.Admin.WebApi/Controllers/Business/OdfCablesController.cs` | 去掉拦截器,手动审计(Add/Update/Delete) |
|
||||
| `server/ZR.Admin.WebApi/Controllers/Business/OdfRacksController.cs` | 去掉拦截器,手动审计(Add/AddExpert/Update/Delete) |
|
||||
| `server/ZR.Admin.WebApi/Controllers/Business/OdfPortsController.cs` | 去掉拦截器,手动审计(Status/Add/Update/SaveM/Empty/Delete/DeleteAll) |
|
||||
| `server/ZR.Admin.WebApi/Controllers/Business/OdfMarkerPolesController.cs` | 去掉拦截器,手动审计(Add/Update/Delete) |
|
||||
| `server/ZR.Admin.WebApi/Controllers/Business/OdfFramesController.cs` | 新增审计日志(Add/Update/Delete) |
|
||||
| `server/ZR.Admin.WebApi/Controllers/Business/OdfRoomsController.cs` | 新增审计日志(Add/AddExpert/Update/Delete) |
|
||||
|
||||
### 需要删除的文件
|
||||
|
||||
| 文件 | 原因 |
|
||||
|------|------|
|
||||
| `server/ZR.Admin.WebApi/Filters/OdfAuditLogFilter.cs` | 拦截器不再使用 |
|
||||
|
||||
### 需要修改的配置
|
||||
|
||||
| 文件 | 改动内容 |
|
||||
|------|---------|
|
||||
| `server/ZR.Admin.WebApi/Program.cs` | 移除 `builder.Services.AddScoped<OdfAuditLogFilter>()` |
|
||||
|
||||
## 组件与接口
|
||||
|
||||
### 审计日志服务接口
|
||||
|
||||
已有 `IOdfAuditLogsService`,提供 `AddLog(OdfAuditLogs log)` 方法,无需修改。
|
||||
|
||||
### Controller 注入变更
|
||||
|
||||
每个需要审计的 Controller 需注入 `IOdfAuditLogsService`:
|
||||
|
||||
```csharp
|
||||
private readonly IOdfAuditLogsService _auditLogsService;
|
||||
|
||||
public XxxController(
|
||||
IXxxService xxxService,
|
||||
IOdfAuditLogsService auditLogsService)
|
||||
{
|
||||
_xxxService = xxxService;
|
||||
_auditLogsService = auditLogsService;
|
||||
}
|
||||
```
|
||||
|
||||
### JSON 序列化配置
|
||||
|
||||
每个 Controller 中定义统一的序列化选项(复用):
|
||||
|
||||
```csharp
|
||||
private static readonly JsonSerializerOptions _jsonOptions = new()
|
||||
{
|
||||
WriteIndented = false,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
```
|
||||
|
||||
### 操作来源判断
|
||||
|
||||
从 `HttpContext.Request.Headers["X-Source-Client"]` 获取,逻辑与原拦截器一致:
|
||||
|
||||
```csharp
|
||||
private string GetSourceClient()
|
||||
{
|
||||
if (HttpContext.Request.Headers.TryGetValue("X-Source-Client", out var src)
|
||||
&& !string.IsNullOrWhiteSpace(src.ToString()))
|
||||
{
|
||||
return src.ToString().Equals("App", StringComparison.OrdinalIgnoreCase) ? "App" : "Admin";
|
||||
}
|
||||
return "Admin";
|
||||
}
|
||||
```
|
||||
|
||||
建议将此方法放入 `BaseController` 中,避免每个 Controller 重复定义。同理 `_jsonOptions` 也可放入 `BaseController`。
|
||||
|
||||
## 各操作类型的审计模式
|
||||
|
||||
### INSERT(新增)
|
||||
|
||||
```csharp
|
||||
// 执行插入
|
||||
var entity = service.Add(model);
|
||||
|
||||
// 查询插入后的完整记录
|
||||
var newRecord = service.GetFirst(f => f.Id == entity.Id);
|
||||
string newData = newRecord != null ? JsonSerializer.Serialize(newRecord, _jsonOptions) : null;
|
||||
|
||||
_auditLogsService.AddLog(new OdfAuditLogs
|
||||
{
|
||||
TableName = "表名",
|
||||
RecordId = entity.Id,
|
||||
OperationType = "INSERT",
|
||||
OperatorId = HttpContext.GetUId(),
|
||||
OperatorName = HttpContext.GetName(),
|
||||
SourceClient = GetSourceClient(),
|
||||
OldData = null,
|
||||
NewData = newData,
|
||||
OperationTime = DateTime.Now,
|
||||
DeptId = HttpContext.GetDeptId() > 0 ? HttpContext.GetDeptId() : null,
|
||||
});
|
||||
```
|
||||
|
||||
### UPDATE(修改)
|
||||
|
||||
```csharp
|
||||
// 查旧数据
|
||||
var oldRecord = service.GetFirst(f => f.Id == id);
|
||||
string oldData = oldRecord != null ? JsonSerializer.Serialize(oldRecord, _jsonOptions) : null;
|
||||
|
||||
// 执行更新
|
||||
service.Update(model);
|
||||
|
||||
// 查新数据
|
||||
var newRecord = service.GetFirst(f => f.Id == id);
|
||||
string newData = newRecord != null ? JsonSerializer.Serialize(newRecord, _jsonOptions) : null;
|
||||
|
||||
_auditLogsService.AddLog(new OdfAuditLogs
|
||||
{
|
||||
TableName = "表名",
|
||||
RecordId = id,
|
||||
OperationType = "UPDATE",
|
||||
OperatorId = HttpContext.GetUId(),
|
||||
OperatorName = HttpContext.GetName(),
|
||||
SourceClient = GetSourceClient(),
|
||||
OldData = oldData,
|
||||
NewData = newData,
|
||||
OperationTime = DateTime.Now,
|
||||
DeptId = HttpContext.GetDeptId() > 0 ? HttpContext.GetDeptId() : null,
|
||||
});
|
||||
```
|
||||
|
||||
### DELETE(删除)
|
||||
|
||||
```csharp
|
||||
// 查旧数据(删除前)
|
||||
var oldRecord = service.GetFirst(f => f.Id == id);
|
||||
string oldData = oldRecord != null ? JsonSerializer.Serialize(oldRecord, _jsonOptions) : null;
|
||||
|
||||
// 执行删除
|
||||
service.Delete(id);
|
||||
|
||||
_auditLogsService.AddLog(new OdfAuditLogs
|
||||
{
|
||||
TableName = "表名",
|
||||
RecordId = id,
|
||||
OperationType = "DELETE",
|
||||
OperatorId = HttpContext.GetUId(),
|
||||
OperatorName = HttpContext.GetName(),
|
||||
SourceClient = GetSourceClient(),
|
||||
OldData = oldData,
|
||||
NewData = null,
|
||||
OperationTime = DateTime.Now,
|
||||
DeptId = HttpContext.GetDeptId() > 0 ? HttpContext.GetDeptId() : null,
|
||||
});
|
||||
```
|
||||
|
||||
### 批量删除
|
||||
|
||||
对于传 `string ids` 的批量删除接口,需要循环每条记录分别记录审计日志:
|
||||
|
||||
```csharp
|
||||
var idArr = Tools.SplitAndConvert<int>(ids);
|
||||
|
||||
// 批量查旧数据
|
||||
foreach (var id in idArr)
|
||||
{
|
||||
var oldRecord = service.GetFirst(f => f.Id == id);
|
||||
string oldData = oldRecord != null ? JsonSerializer.Serialize(oldRecord, _jsonOptions) : null;
|
||||
|
||||
// 记录每条的审计日志
|
||||
_auditLogsService.AddLog(new OdfAuditLogs
|
||||
{
|
||||
TableName = "表名",
|
||||
RecordId = id,
|
||||
OperationType = "DELETE",
|
||||
OperatorId = HttpContext.GetUId(),
|
||||
OperatorName = HttpContext.GetName(),
|
||||
SourceClient = GetSourceClient(),
|
||||
OldData = oldData,
|
||||
NewData = null,
|
||||
OperationTime = DateTime.Now,
|
||||
DeptId = HttpContext.GetDeptId() > 0 ? HttpContext.GetDeptId() : null,
|
||||
});
|
||||
}
|
||||
|
||||
// 执行批量删除
|
||||
service.Delete(idArr);
|
||||
```
|
||||
|
||||
## 各 Controller 接口与表名映射
|
||||
|
||||
### OdfCableFaultsController
|
||||
|
||||
| 方法 | 操作类型 | 表名 | 备注 |
|
||||
|------|---------|------|------|
|
||||
| Add | INSERT | odf_cable_faults | 异步方法,插入后从返回的 faultId 查新数据 |
|
||||
| IncrementFaultCount | UPDATE | odf_cable_faults | ✅ 已完成 |
|
||||
| UpdateMileageCorrection | UPDATE | odf_cable_faults | 有 DTO 参数,但改为手动更可靠 |
|
||||
| Delete | DELETE | odf_cable_faults | 批量删除 |
|
||||
|
||||
### OdfCablesController
|
||||
|
||||
| 方法 | 操作类型 | 表名 | 备注 |
|
||||
|------|---------|------|------|
|
||||
| Add | INSERT | odf_cables | 参数为 OdfCables 实体 |
|
||||
| Update | UPDATE | odf_cables | 参数为 OdfCables 实体 |
|
||||
| Delete | DELETE | odf_cables | 单条删除,int id |
|
||||
|
||||
### OdfRacksController
|
||||
|
||||
| 方法 | 操作类型 | 表名 | 备注 |
|
||||
|------|---------|------|------|
|
||||
| AddOdfRacks | INSERT | odf_racks | |
|
||||
| AddOdfRacksExpert | INSERT | odf_racks | 专家模式,同时创建框和端口 |
|
||||
| UpdateOdfRacks | UPDATE | odf_racks | |
|
||||
| DeleteOdfRacks | DELETE | odf_racks | 批量删除 |
|
||||
|
||||
### OdfPortsController
|
||||
|
||||
| 方法 | 操作类型 | 表名 | 备注 |
|
||||
|------|---------|------|------|
|
||||
| GetOdfPortsStatus | UPDATE | odf_ports | 参数为简单类型 int Id, int status |
|
||||
| AddOdfPorts | INSERT | odf_ports | |
|
||||
| UpdateOdfPorts | UPDATE | odf_ports | |
|
||||
| SaveMOdfPorts | UPDATE | odf_ports | APP端修改端口 |
|
||||
| GetOdfPortsEmpty | UPDATE | odf_ports | 清空端口数据,参数为 int Id |
|
||||
| DeleteOdfPorts | DELETE | odf_ports | 批量删除 |
|
||||
| DeleteAll | DELETE | odf_ports | 按条件批量删除,参数为 DTO |
|
||||
|
||||
### OdfMarkerPolesController
|
||||
|
||||
| 方法 | 操作类型 | 表名 | 备注 |
|
||||
|------|---------|------|------|
|
||||
| Add | INSERT | odf_marker_poles | |
|
||||
| Update | UPDATE | odf_marker_poles | |
|
||||
| Delete | DELETE | odf_marker_poles | 批量删除 |
|
||||
|
||||
### OdfFramesController(新增审计)
|
||||
|
||||
| 方法 | 操作类型 | 表名 | 备注 |
|
||||
|------|---------|------|------|
|
||||
| AddOdfFrames | INSERT | odf_frames | 同时批量插入端口,仅记录框的审计 |
|
||||
| UpdateOdfFrames | UPDATE | odf_frames | 可能级联更新端口 FrameName |
|
||||
| DeleteOdfFrames | DELETE | odf_frames | 批量删除 |
|
||||
|
||||
### OdfRoomsController(新增审计)
|
||||
|
||||
| 方法 | 操作类型 | 表名 | 备注 |
|
||||
|------|---------|------|------|
|
||||
| AddOdfRooms | INSERT | odf_rooms | |
|
||||
| AddExpertOdfRooms | INSERT | odf_rooms | 专家模式,同时创建机架/框/端口,仅记录机房的审计 |
|
||||
| UpdateOdfRooms | UPDATE | odf_rooms | |
|
||||
| DeleteOdfRooms | DELETE | odf_rooms | 批量删除 |
|
||||
|
||||
## BaseController 扩展
|
||||
|
||||
在 `BaseController` 中添加审计日志相关的公共方法和配置,避免各 Controller 重复代码:
|
||||
|
||||
```csharp
|
||||
public class BaseController : ControllerBase
|
||||
{
|
||||
// ... 现有代码 ...
|
||||
|
||||
/// <summary>
|
||||
/// 审计日志 JSON 序列化选项
|
||||
/// </summary>
|
||||
protected static readonly JsonSerializerOptions AuditJsonOptions = new()
|
||||
{
|
||||
WriteIndented = false,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 获取操作来源客户端标识
|
||||
/// </summary>
|
||||
protected string GetSourceClient()
|
||||
{
|
||||
if (HttpContext.Request.Headers.TryGetValue("X-Source-Client", out var src)
|
||||
&& !string.IsNullOrWhiteSpace(src.ToString()))
|
||||
{
|
||||
return src.ToString().Equals("App", StringComparison.OrdinalIgnoreCase) ? "App" : "Admin";
|
||||
}
|
||||
return "Admin";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造审计日志对象
|
||||
/// </summary>
|
||||
protected OdfAuditLogs BuildAuditLog(string tableName, int? recordId, string operationType, string oldData, string newData)
|
||||
{
|
||||
return new OdfAuditLogs
|
||||
{
|
||||
TableName = tableName,
|
||||
RecordId = recordId,
|
||||
OperationType = operationType,
|
||||
OperatorId = HttpContext.GetUId(),
|
||||
OperatorName = HttpContext.GetName(),
|
||||
SourceClient = GetSourceClient(),
|
||||
OldData = oldData,
|
||||
NewData = newData,
|
||||
OperationTime = DateTime.Now,
|
||||
DeptId = HttpContext.GetDeptId() > 0 ? HttpContext.GetDeptId() : null,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将实体序列化为审计日志 JSON
|
||||
/// </summary>
|
||||
protected static string SerializeForAudit<T>(T entity) where T : class
|
||||
{
|
||||
return entity != null ? JsonSerializer.Serialize(entity, AuditJsonOptions) : null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 使用 BaseController 后的简化写法
|
||||
|
||||
```csharp
|
||||
// UPDATE 示例
|
||||
var oldRecord = _service.GetFirst(f => f.Id == id);
|
||||
string oldData = SerializeForAudit(oldRecord);
|
||||
|
||||
_service.Update(model);
|
||||
|
||||
var newRecord = _service.GetFirst(f => f.Id == id);
|
||||
string newData = SerializeForAudit(newRecord);
|
||||
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_cable_faults", id, "UPDATE", oldData, newData));
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
| 场景 | 处理方式 |
|
||||
|------|---------|
|
||||
| 审计日志写入失败 | try-catch 包裹,记录 NLog 错误日志,不影响主业务返回 |
|
||||
| 查询旧数据失败 | OldData 设为 null,继续执行主业务 |
|
||||
| 查询新数据失败 | NewData 设为 null,继续执行主业务 |
|
||||
| JSON 序列化失败 | 对应字段设为 null,记录警告日志 |
|
||||
|
||||
每个审计日志记录操作都应包裹在 try-catch 中:
|
||||
|
||||
```csharp
|
||||
try
|
||||
{
|
||||
_auditLogsService.AddLog(BuildAuditLog(...));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 审计日志写入失败不影响主业务
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
```
|
||||
|
||||
## 改造顺序建议
|
||||
|
||||
1. 先扩展 `BaseController`,添加公共方法
|
||||
2. 改造 `OdfCableFaultsController`(剩余 3 个接口)
|
||||
3. 改造 `OdfCablesController`(3 个接口)
|
||||
4. 改造 `OdfMarkerPolesController`(3 个接口)
|
||||
5. 改造 `OdfRacksController`(4 个接口)
|
||||
6. 改造 `OdfPortsController`(7 个接口,最多)
|
||||
7. 新增 `OdfFramesController` 审计(3 个接口)
|
||||
8. 新增 `OdfRoomsController` 审计(4 个接口)
|
||||
9. 移除 `OdfAuditLogFilter` 及 Program.cs 注册
|
||||
10. 全量测试验证
|
||||
81
.kiro/specs/audit-log-manual/requirements.md
Normal file
81
.kiro/specs/audit-log-manual/requirements.md
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
# 需求:审计日志手动化改造
|
||||
|
||||
## 背景
|
||||
|
||||
当前系统通过 `OdfAuditLogFilter`(ActionFilter 拦截器)自动记录业务数据的增删改审计日志。该方案存在以下问题:
|
||||
|
||||
1. 对于只传简单类型参数(如 `int id`)的接口,拦截器无法序列化 NewData,导致审计日志中修改后数据为空
|
||||
2. 批量删除接口(传 `string ids`)只能记录第一条记录的 OldData
|
||||
3. 拦截器逻辑对不同接口的参数结构有隐式依赖,不够透明
|
||||
4. 部分 Controller(OdfFrames、OdfRooms)有增删改操作但完全没有审计日志
|
||||
|
||||
## 目标
|
||||
|
||||
将所有业务数据的审计日志记录从拦截器自动模式改为 Controller 中手动记录,确保每个增删改操作都能正确记录完整的 OldData 和 NewData。改造完成后移除 `OdfAuditLogFilter`。
|
||||
|
||||
## 需求列表
|
||||
|
||||
### 1. 干线故障模块(OdfCableFaultsController)
|
||||
|
||||
- 1.1 `IncrementFaultCount`(增加频次):手动记录审计日志,包含完整 OldData 和 NewData ✅ 已完成
|
||||
- 1.2 `Add`(新增故障):去掉拦截器,手动记录审计日志,NewData 为插入后的完整记录
|
||||
- 1.3 `UpdateMileageCorrection`(更新里程矫正):去掉拦截器,手动记录 OldData 和 NewData
|
||||
- 1.4 `Delete`(删除故障):去掉拦截器,手动记录 OldData,支持批量删除时每条记录单独记录
|
||||
|
||||
### 2. 光缆管理模块(OdfCablesController)
|
||||
|
||||
- 2.1 `Add`(新增光缆):去掉拦截器,手动记录审计日志
|
||||
- 2.2 `Update`(修改光缆):去掉拦截器,手动记录 OldData 和 NewData
|
||||
- 2.3 `Delete`(删除光缆):去掉拦截器,手动记录 OldData
|
||||
|
||||
### 3. 机架模块(OdfRacksController)
|
||||
|
||||
- 3.1 `AddOdfRacks`(新增机架):去掉拦截器,手动记录审计日志
|
||||
- 3.2 `AddOdfRacksExpert`(专家模式新增机架):去掉拦截器,手动记录审计日志
|
||||
- 3.3 `UpdateOdfRacks`(更新机架):去掉拦截器,手动记录 OldData 和 NewData
|
||||
- 3.4 `DeleteOdfRacks`(删除机架):去掉拦截器,手动记录 OldData,支持批量
|
||||
|
||||
### 4. 端口模块(OdfPortsController)
|
||||
|
||||
- 4.1 `GetOdfPortsStatus`(更新端口状态):去掉拦截器,手动记录 OldData 和 NewData
|
||||
- 4.2 `AddOdfPorts`(新增端口):去掉拦截器,手动记录审计日志
|
||||
- 4.3 `UpdateOdfPorts`(更新端口):去掉拦截器,手动记录 OldData 和 NewData
|
||||
- 4.4 `SaveMOdfPorts`(APP修改端口):去掉拦截器,手动记录 OldData 和 NewData
|
||||
- 4.5 `GetOdfPortsEmpty`(清空端口数据):去掉拦截器,手动记录 OldData 和 NewData
|
||||
- 4.6 `DeleteOdfPorts`(删除端口):去掉拦截器,手动记录 OldData,支持批量
|
||||
- 4.7 `DeleteAll`(批量删除端口):去掉拦截器,手动记录审计日志
|
||||
|
||||
### 5. 标石/杆号牌模块(OdfMarkerPolesController)
|
||||
|
||||
- 5.1 `Add`(新增标石):去掉拦截器,手动记录审计日志
|
||||
- 5.2 `Update`(编辑标石):去掉拦截器,手动记录 OldData 和 NewData
|
||||
- 5.3 `Delete`(删除标石):去掉拦截器,手动记录 OldData,支持批量
|
||||
|
||||
### 6. 框模块(OdfFramesController)— 新增审计
|
||||
|
||||
- 6.1 `AddOdfFrames`(新增框):新增手动审计日志记录
|
||||
- 6.2 `UpdateOdfFrames`(更新框):新增手动审计日志记录,包含 OldData 和 NewData
|
||||
- 6.3 `DeleteOdfFrames`(删除框):新增手动审计日志记录,包含 OldData
|
||||
|
||||
### 7. 机房模块(OdfRoomsController)— 新增审计
|
||||
|
||||
- 7.1 `AddOdfRooms`(新增机房):新增手动审计日志记录
|
||||
- 7.2 `AddExpertOdfRooms`(专家模式新增机房):新增手动审计日志记录
|
||||
- 7.3 `UpdateOdfRooms`(更新机房):新增手动审计日志记录,包含 OldData 和 NewData
|
||||
- 7.4 `DeleteOdfRooms`(删除机房):新增手动审计日志记录,包含 OldData
|
||||
|
||||
### 8. 清理工作
|
||||
|
||||
- 8.1 所有接口改造完成后,移除 `OdfAuditLogFilter.cs` 文件
|
||||
- 8.2 移除 `Program.cs` 中 `OdfAuditLogFilter` 的 DI 注册
|
||||
- 8.3 确认无其他文件引用 `OdfAuditLogFilter`
|
||||
|
||||
### 9. 不纳入审计范围的模块
|
||||
|
||||
以下模块不需要审计日志,不做改造:
|
||||
- OdfCheckinController(签到记录)
|
||||
- OdfAppUpdatesController(App版本管理)
|
||||
- OdfUserModulesController(用户模块权限)
|
||||
- OdfPortFaultController(错误日志,只读)
|
||||
- CosUploadController(文件上传)
|
||||
- OdfAuditLogsController(审计日志本身,只读)
|
||||
62
.kiro/specs/audit-log-manual/tasks.md
Normal file
62
.kiro/specs/audit-log-manual/tasks.md
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
# 审计日志手动化改造 — 任务列表
|
||||
|
||||
## Task 1: 扩展 BaseController 公共方法
|
||||
- [x] 在 `BaseController` 中添加 `AuditJsonOptions`、`GetSourceClient()`、`BuildAuditLog()`、`SerializeForAudit<T>()` 方法
|
||||
- [x] 添加 `using System.Text.Json` 和 `using ZR.Model.Business` 引用
|
||||
- [x] 验证编译通过
|
||||
|
||||
## Task 2: 改造 OdfCableFaultsController(干线故障)
|
||||
- [x] `IncrementFaultCount` — 已完成
|
||||
- [x] `Add` — 去掉 `[ServiceFilter(typeof(OdfAuditLogFilter))]`,手动记录 INSERT 审计日志(表名: odf_cable_faults)
|
||||
- [x] `UpdateMileageCorrection` — 去掉拦截器,手动记录 UPDATE 审计日志
|
||||
- [x] `Delete` — 去掉拦截器,手动记录 DELETE 审计日志,循环每条记录分别记录
|
||||
- [x] 注入 `IOdfAuditLogsService`(已注入)
|
||||
- [x] 移除 `IncrementFaultCount` 中的 `_jsonOptions` 和 sourceClient 内联逻辑,改用 BaseController 公共方法
|
||||
|
||||
## Task 3: 改造 OdfCablesController(光缆管理)
|
||||
- [x] 构造函数注入 `IOdfAuditLogsService`
|
||||
- [x] `Add` — 去掉拦截器,手动记录 INSERT 审计日志(表名: odf_cables)
|
||||
- [x] `Update` — 去掉拦截器,手动记录 UPDATE 审计日志
|
||||
- [x] `Delete` — 去掉拦截器,手动记录 DELETE 审计日志
|
||||
|
||||
## Task 4: 改造 OdfMarkerPolesController(标石/杆号牌)
|
||||
- [x] 构造函数注入 `IOdfAuditLogsService`
|
||||
- [x] `Add` — 去掉拦截器,手动记录 INSERT 审计日志(表名: odf_marker_poles)
|
||||
- [x] `Update` — 去掉拦截器,手动记录 UPDATE 审计日志
|
||||
- [x] `Delete` — 去掉拦截器,手动记录 DELETE 审计日志,循环每条记录
|
||||
|
||||
## Task 5: 改造 OdfRacksController(机架)
|
||||
- [x] 构造函数注入 `IOdfAuditLogsService`
|
||||
- [x] `AddOdfRacks` — 去掉拦截器,手动记录 INSERT 审计日志(表名: odf_racks)
|
||||
- [x] `AddOdfRacksExpert` — 去掉拦截器,手动记录 INSERT 审计日志
|
||||
- [x] `UpdateOdfRacks` — 去掉拦截器,手动记录 UPDATE 审计日志
|
||||
- [x] `DeleteOdfRacks` — 去掉拦截器,手动记录 DELETE 审计日志,循环每条记录
|
||||
|
||||
## Task 6: 改造 OdfPortsController(端口)
|
||||
- [x] 构造函数注入 `IOdfAuditLogsService`
|
||||
- [x] `GetOdfPortsStatus` — 去掉拦截器,手动记录 UPDATE 审计日志(表名: odf_ports)
|
||||
- [x] `AddOdfPorts` — 去掉拦截器,手动记录 INSERT 审计日志
|
||||
- [x] `UpdateOdfPorts` — 去掉拦截器,手动记录 UPDATE 审计日志
|
||||
- [x] `SaveMOdfPorts` — 去掉拦截器,手动记录 UPDATE 审计日志
|
||||
- [x] `GetOdfPortsEmpty` — 去掉拦截器,手动记录 UPDATE 审计日志
|
||||
- [x] `DeleteOdfPorts` — 去掉拦截器,手动记录 DELETE 审计日志,循环每条记录
|
||||
- [x] `DeleteAll` — 去掉拦截器,手动记录 DELETE 审计日志
|
||||
|
||||
## Task 7: 新增 OdfFramesController 审计(框)
|
||||
- [x] 构造函数注入 `IOdfAuditLogsService`
|
||||
- [x] `AddOdfFrames` — 新增 INSERT 审计日志(表名: odf_frames)
|
||||
- [x] `UpdateOdfFrames` — 新增 UPDATE 审计日志
|
||||
- [x] `DeleteOdfFrames` — 新增 DELETE 审计日志,循环每条记录
|
||||
|
||||
## Task 8: 新增 OdfRoomsController 审计(机房)
|
||||
- [x] 构造函数注入 `IOdfAuditLogsService`
|
||||
- [x] `AddOdfRooms` — 新增 INSERT 审计日志(表名: odf_rooms)
|
||||
- [x] `AddExpertOdfRooms` — 新增 INSERT 审计日志
|
||||
- [x] `UpdateOdfRooms` — 新增 UPDATE 审计日志
|
||||
- [x] `DeleteOdfRooms` — 新增 DELETE 审计日志,循环每条记录
|
||||
|
||||
## Task 9: 清理拦截器
|
||||
- [x] 删除 `server/ZR.Admin.WebApi/Filters/OdfAuditLogFilter.cs`
|
||||
- [x] 移除 `Program.cs` 中 `builder.Services.AddScoped<OdfAuditLogFilter>()` 注册
|
||||
- [x] 全局搜索确认无其他文件引用 `OdfAuditLogFilter`
|
||||
- [x] 编译验证通过
|
||||
|
|
@ -9,8 +9,10 @@ using Newtonsoft.Json.Serialization;
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using ZR.Model.Business;
|
||||
|
||||
namespace Infrastructure.Controllers
|
||||
{
|
||||
|
|
@ -22,6 +24,76 @@ namespace Infrastructure.Controllers
|
|||
{
|
||||
public static string TIME_FORMAT_FULL = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
/// <summary>
|
||||
/// 审计日志 JSON 序列化选项
|
||||
/// </summary>
|
||||
protected static readonly JsonSerializerOptions AuditJsonOptions = new()
|
||||
{
|
||||
WriteIndented = false,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 获取操作来源客户端标识
|
||||
/// </summary>
|
||||
protected string GetSourceClient()
|
||||
{
|
||||
if (HttpContext.Request.Headers.TryGetValue("X-Source-Client", out var src)
|
||||
&& !string.IsNullOrWhiteSpace(src.ToString()))
|
||||
{
|
||||
return src.ToString().Equals("App", StringComparison.OrdinalIgnoreCase) ? "App" : "Admin";
|
||||
}
|
||||
return "Admin";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造审计日志对象
|
||||
/// </summary>
|
||||
protected OdfAuditLogs BuildAuditLog(string tableName, int? recordId, string operationType, string oldData, string newData)
|
||||
{
|
||||
return new OdfAuditLogs
|
||||
{
|
||||
TableName = tableName,
|
||||
RecordId = recordId,
|
||||
OperationType = operationType,
|
||||
OperatorId = HttpContext.GetUId(),
|
||||
OperatorName = HttpContext.GetName(),
|
||||
SourceClient = GetSourceClient(),
|
||||
OldData = oldData,
|
||||
NewData = newData,
|
||||
OperationTime = DateTime.Now,
|
||||
DeptId = HttpContext.GetDeptId() > 0 ? HttpContext.GetDeptId() : null,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将实体序列化为审计日志 JSON
|
||||
/// </summary>
|
||||
protected static string SerializeForAudit<T>(T entity) where T : class
|
||||
{
|
||||
return entity != null ? System.Text.Json.JsonSerializer.Serialize(entity, AuditJsonOptions) : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字段字典序列化为审计 JSON(用于所有操作类型)
|
||||
/// </summary>
|
||||
protected static string BuildAuditJson(Dictionary<string, object> fields)
|
||||
{
|
||||
var result = new Dictionary<string, object>();
|
||||
foreach (var kv in fields)
|
||||
{
|
||||
if (kv.Value is DateTime dt)
|
||||
{
|
||||
result[kv.Key] = dt.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
}
|
||||
else
|
||||
{
|
||||
result[kv.Key] = kv.Value;
|
||||
}
|
||||
}
|
||||
return System.Text.Json.JsonSerializer.Serialize(result, new System.Text.Json.JsonSerializerOptions { WriteIndented = false });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回成功封装
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@
|
|||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ZR.Model\ZR.Model.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AspectCore.Abstractions" Version="2.4.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="8.0.2" />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using MiniExcelLibs;
|
||||
using ZR.Admin.WebApi.Filters;
|
||||
using ZR.Model.Business;
|
||||
using ZR.Model.Business.Dto;
|
||||
using ZR.Service.Business.IBusinessService;
|
||||
|
||||
|
|
@ -17,10 +17,14 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
/// 干线故障接口
|
||||
/// </summary>
|
||||
private readonly IOdfCableFaultsService _OdfCableFaultsService;
|
||||
private readonly IOdfAuditLogsService _auditLogsService;
|
||||
|
||||
public OdfCableFaultsController(IOdfCableFaultsService OdfCableFaultsService)
|
||||
public OdfCableFaultsController(
|
||||
IOdfCableFaultsService OdfCableFaultsService,
|
||||
IOdfAuditLogsService auditLogsService)
|
||||
{
|
||||
_OdfCableFaultsService = OdfCableFaultsService;
|
||||
_auditLogsService = auditLogsService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -57,11 +61,40 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
[HttpPost("add")]
|
||||
[ActionPermissionFilter(Permission = "odfcablefaults:add")]
|
||||
[Log(Title = "干线故障", BusinessType = BusinessType.INSERT)]
|
||||
[ServiceFilter(typeof(OdfAuditLogFilter))]
|
||||
public async Task<IActionResult> Add([FromBody] OdfCableFaultAddDto dto)
|
||||
{
|
||||
dto.UserId = HttpContext.GetUId();
|
||||
var response = await _OdfCableFaultsService.AddFault(dto);
|
||||
|
||||
// 手动记录 INSERT 审计日志
|
||||
try
|
||||
{
|
||||
var newRecord = _OdfCableFaultsService.GetFirst(f => f.Id == response);
|
||||
string newData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["Id"] = newRecord.Id,
|
||||
["关联光缆ID"] = newRecord.CableId,
|
||||
["故障时间"] = newRecord.FaultTime,
|
||||
["人员"] = newRecord.Personnel,
|
||||
["故障原因"] = newRecord.FaultReason,
|
||||
["表显故障里程"] = newRecord.Mileage,
|
||||
["表显里程矫正"] = newRecord.MileageCorrection,
|
||||
["地点描述"] = newRecord.Location,
|
||||
["纬度"] = newRecord.Latitude,
|
||||
["经度"] = newRecord.Longitude,
|
||||
["备注"] = newRecord.Remark,
|
||||
["提交人用户ID"] = newRecord.UserId,
|
||||
["创建时间"] = newRecord.CreatedAt,
|
||||
["更新时间"] = newRecord.UpdatedAt,
|
||||
["故障发生频次"] = newRecord.FaultCount,
|
||||
});
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_cable_faults", response, "INSERT", null, newData));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
|
||||
return ToResponse(response);
|
||||
}
|
||||
|
||||
|
|
@ -71,10 +104,37 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
[HttpPost("incrementFaultCount/{id}")]
|
||||
[ActionPermissionFilter(Permission = "odfcablefaults:edit")]
|
||||
[Log(Title = "干线故障", BusinessType = BusinessType.UPDATE)]
|
||||
[ServiceFilter(typeof(OdfAuditLogFilter))]
|
||||
public IActionResult IncrementFaultCount(int id)
|
||||
{
|
||||
// 记录旧数据
|
||||
var oldFault = _OdfCableFaultsService.GetFirst(f => f.Id == id);
|
||||
string oldData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["故障发生频次"] = oldFault.FaultCount,
|
||||
["更新时间"] = oldFault.UpdatedAt,
|
||||
});
|
||||
|
||||
// 执行更新
|
||||
var response = _OdfCableFaultsService.IncrementFaultCount(id);
|
||||
|
||||
// 记录新数据
|
||||
var newFault = _OdfCableFaultsService.GetFirst(f => f.Id == id);
|
||||
string newData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["故障发生频次"] = newFault.FaultCount,
|
||||
["更新时间"] = newFault.UpdatedAt,
|
||||
});
|
||||
|
||||
// 写入审计日志
|
||||
try
|
||||
{
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_cable_faults", id, "UPDATE", oldData, newData));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
|
||||
return SUCCESS(response);
|
||||
}
|
||||
|
||||
|
|
@ -84,10 +144,37 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
[HttpPost("updateMileageCorrection/{id}")]
|
||||
[ActionPermissionFilter(Permission = "odfcablefaults:edit")]
|
||||
[Log(Title = "干线故障", BusinessType = BusinessType.UPDATE)]
|
||||
[ServiceFilter(typeof(OdfAuditLogFilter))]
|
||||
public IActionResult UpdateMileageCorrection(int id, [FromBody] MileageCorrectionDto dto)
|
||||
{
|
||||
// 查旧数据
|
||||
var oldRecord = _OdfCableFaultsService.GetFirst(f => f.Id == id);
|
||||
string oldData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["表显里程矫正"] = oldRecord.MileageCorrection,
|
||||
["更新时间"] = oldRecord.UpdatedAt,
|
||||
});
|
||||
|
||||
// 执行更新
|
||||
var response = _OdfCableFaultsService.UpdateMileageCorrection(id, dto.MileageCorrection);
|
||||
|
||||
// 查新数据
|
||||
var newRecord = _OdfCableFaultsService.GetFirst(f => f.Id == id);
|
||||
string newData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["表显里程矫正"] = newRecord.MileageCorrection,
|
||||
["更新时间"] = newRecord.UpdatedAt,
|
||||
});
|
||||
|
||||
// 写入审计日志
|
||||
try
|
||||
{
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_cable_faults", id, "UPDATE", oldData, newData));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
|
||||
return ToResponse(response);
|
||||
}
|
||||
|
||||
|
|
@ -98,7 +185,6 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
[HttpPost("delete/{ids}")]
|
||||
[ActionPermissionFilter(Permission = "odfcablefaults:delete")]
|
||||
[Log(Title = "干线故障", BusinessType = BusinessType.DELETE)]
|
||||
[ServiceFilter(typeof(OdfAuditLogFilter))]
|
||||
public IActionResult Delete(string ids)
|
||||
{
|
||||
var idList = ids.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
|
|
@ -111,6 +197,38 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
return ToResponse(ResultCode.FAIL, "请选择要删除的数据");
|
||||
}
|
||||
|
||||
// 循环每条记录分别记录 DELETE 审计日志
|
||||
foreach (var id in idList)
|
||||
{
|
||||
var oldRecord = _OdfCableFaultsService.GetFirst(f => f.Id == id);
|
||||
string oldData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["Id"] = oldRecord.Id,
|
||||
["关联光缆ID"] = oldRecord.CableId,
|
||||
["故障时间"] = oldRecord.FaultTime,
|
||||
["人员"] = oldRecord.Personnel,
|
||||
["故障原因"] = oldRecord.FaultReason,
|
||||
["表显故障里程"] = oldRecord.Mileage,
|
||||
["表显里程矫正"] = oldRecord.MileageCorrection,
|
||||
["地点描述"] = oldRecord.Location,
|
||||
["纬度"] = oldRecord.Latitude,
|
||||
["经度"] = oldRecord.Longitude,
|
||||
["备注"] = oldRecord.Remark,
|
||||
["提交人用户ID"] = oldRecord.UserId,
|
||||
["创建时间"] = oldRecord.CreatedAt,
|
||||
["更新时间"] = oldRecord.UpdatedAt,
|
||||
["故障发生频次"] = oldRecord.FaultCount,
|
||||
});
|
||||
try
|
||||
{
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_cable_faults", id, "DELETE", oldData, null));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
}
|
||||
|
||||
int total = 0;
|
||||
foreach (var id in idList)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using ZR.Admin.WebApi.Filters;
|
||||
using ZR.Model.Business;
|
||||
using ZR.Model.Business.Dto;
|
||||
using ZR.Service.Business.IBusinessService;
|
||||
|
|
@ -17,10 +16,14 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
/// 光缆管理接口
|
||||
/// </summary>
|
||||
private readonly IOdfCablesService _OdfCablesService;
|
||||
private readonly IOdfAuditLogsService _auditLogsService;
|
||||
|
||||
public OdfCablesController(IOdfCablesService OdfCablesService)
|
||||
public OdfCablesController(
|
||||
IOdfCablesService OdfCablesService,
|
||||
IOdfAuditLogsService auditLogsService)
|
||||
{
|
||||
_OdfCablesService = OdfCablesService;
|
||||
_auditLogsService = auditLogsService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -56,12 +59,32 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
[HttpPost]
|
||||
[ActionPermissionFilter(Permission = "odfcables:add")]
|
||||
[Log(Title = "光缆管理", BusinessType = BusinessType.INSERT)]
|
||||
[ServiceFilter(typeof(OdfAuditLogFilter))]
|
||||
public IActionResult Add([FromBody] OdfCables parm)
|
||||
{
|
||||
parm.CreatedAt = DateTime.Now;
|
||||
parm.UpdatedAt = DateTime.Now;
|
||||
var response = _OdfCablesService.Add(parm);
|
||||
|
||||
// 手动记录 INSERT 审计日志
|
||||
try
|
||||
{
|
||||
var newRecord = _OdfCablesService.GetFirst(f => f.Id == parm.Id);
|
||||
string newData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["Id"] = newRecord.Id,
|
||||
["光缆名称"] = newRecord.CableName,
|
||||
["部门ID"] = newRecord.DeptId,
|
||||
["部门名称"] = newRecord.DeptName,
|
||||
["创建时间"] = newRecord.CreatedAt,
|
||||
["更新时间"] = newRecord.UpdatedAt,
|
||||
});
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_cables", parm.Id, "INSERT", null, newData));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
|
||||
return SUCCESS(response);
|
||||
}
|
||||
|
||||
|
|
@ -72,11 +95,39 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
[HttpPut]
|
||||
[ActionPermissionFilter(Permission = "odfcables:edit")]
|
||||
[Log(Title = "光缆管理", BusinessType = BusinessType.UPDATE)]
|
||||
[ServiceFilter(typeof(OdfAuditLogFilter))]
|
||||
public IActionResult Update([FromBody] OdfCables parm)
|
||||
{
|
||||
// 查旧数据(仅记录 UPDATE 实际修改的字段)
|
||||
var oldRecord = _OdfCablesService.GetFirst(f => f.Id == parm.Id);
|
||||
string oldData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["光缆名称"] = oldRecord.CableName,
|
||||
["部门ID"] = oldRecord.DeptId,
|
||||
["部门名称"] = oldRecord.DeptName,
|
||||
["更新时间"] = oldRecord.UpdatedAt,
|
||||
});
|
||||
|
||||
parm.UpdatedAt = DateTime.Now;
|
||||
var response = _OdfCablesService.Update(parm);
|
||||
|
||||
// 查新数据并记录 UPDATE 审计日志
|
||||
try
|
||||
{
|
||||
var newRecord = _OdfCablesService.GetFirst(f => f.Id == parm.Id);
|
||||
string newData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["光缆名称"] = newRecord.CableName,
|
||||
["部门ID"] = newRecord.DeptId,
|
||||
["部门名称"] = newRecord.DeptName,
|
||||
["更新时间"] = newRecord.UpdatedAt,
|
||||
});
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_cables", parm.Id, "UPDATE", oldData, newData));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
|
||||
return ToResponse(response);
|
||||
}
|
||||
|
||||
|
|
@ -100,9 +151,28 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
[HttpPost("delete/{id}")]
|
||||
[ActionPermissionFilter(Permission = "odfcables:delete")]
|
||||
[Log(Title = "光缆管理", BusinessType = BusinessType.DELETE)]
|
||||
[ServiceFilter(typeof(OdfAuditLogFilter))]
|
||||
public IActionResult Delete(int id)
|
||||
{
|
||||
// 删除前记录 DELETE 审计日志
|
||||
var oldRecord = _OdfCablesService.GetFirst(f => f.Id == id);
|
||||
string oldData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["Id"] = oldRecord.Id,
|
||||
["光缆名称"] = oldRecord.CableName,
|
||||
["部门ID"] = oldRecord.DeptId,
|
||||
["部门名称"] = oldRecord.DeptName,
|
||||
["创建时间"] = oldRecord.CreatedAt,
|
||||
["更新时间"] = oldRecord.UpdatedAt,
|
||||
});
|
||||
try
|
||||
{
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_cables", id, "DELETE", oldData, null));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
|
||||
var response = _OdfCablesService.Delete(id);
|
||||
return ToResponse(response);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,15 +27,18 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
/// </summary>
|
||||
private readonly IOdfRacksService _OdfRacksService;
|
||||
private readonly IOdfRoomsService _odfRooms;
|
||||
private readonly IOdfAuditLogsService _auditLogsService;
|
||||
|
||||
public OdfFramesController(IOdfRacksService OdfRacksService, IOdfRoomsService odfRooms,
|
||||
IOdfFramesService odfFramesService,
|
||||
IOdfPortsService odfPortsService)
|
||||
IOdfPortsService odfPortsService,
|
||||
IOdfAuditLogsService auditLogsService)
|
||||
{
|
||||
_OdfRacksService = OdfRacksService;
|
||||
_odfRooms = odfRooms;
|
||||
_OdfFramesService = odfFramesService;
|
||||
_OdfPortsService = odfPortsService;
|
||||
_auditLogsService = auditLogsService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -90,6 +93,31 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
modal.UpdateAt = DateTime.Now;
|
||||
modal.CreatedAt = DateTime.Now;
|
||||
var response = _OdfFramesService.AddOdfFrames(modal);
|
||||
|
||||
// INSERT 审计日志(仅记录框)
|
||||
try
|
||||
{
|
||||
var newRecord = _OdfFramesService.GetFirst(f => f.Id == response.Id);
|
||||
string newData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["Id"] = newRecord.Id,
|
||||
["机架ID"] = newRecord.RackId,
|
||||
["名称"] = newRecord.PortsName,
|
||||
["部门ID"] = newRecord.DeptId,
|
||||
["序号"] = newRecord.SequenceNumber,
|
||||
["端口数量"] = newRecord.PortsCount,
|
||||
["列数量"] = newRecord.PortsCol,
|
||||
["行数量"] = newRecord.PortsRow,
|
||||
["创建时间"] = newRecord.CreatedAt,
|
||||
["修改时间"] = newRecord.UpdateAt,
|
||||
});
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_frames", response.Id, "INSERT", null, newData));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
|
||||
var roomId = rooms.Id;
|
||||
var roomName = rooms.RoomName;
|
||||
|
||||
|
|
@ -155,8 +183,42 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
public async Task<IActionResult> UpdateOdfFrames([FromBody] OdfFramesDto parm)
|
||||
{
|
||||
var modal = parm.Adapt<OdfFrames>().ToUpdate(HttpContext);
|
||||
|
||||
// 查旧数据
|
||||
var oldRecord = _OdfFramesService.GetFirst(f => f.Id == parm.Id);
|
||||
string oldData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["名称"] = oldRecord.PortsName,
|
||||
["序号"] = oldRecord.SequenceNumber,
|
||||
["端口数量"] = oldRecord.PortsCount,
|
||||
["列数量"] = oldRecord.PortsCol,
|
||||
["行数量"] = oldRecord.PortsRow,
|
||||
["修改时间"] = oldRecord.UpdateAt,
|
||||
});
|
||||
|
||||
var oldModel = _OdfFramesService.GetById(parm.Id);
|
||||
var response = _OdfFramesService.UpdateOdfFrames(modal);
|
||||
|
||||
// UPDATE 审计日志(仅记录框)
|
||||
try
|
||||
{
|
||||
var newRecord = _OdfFramesService.GetFirst(f => f.Id == parm.Id);
|
||||
string newData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["名称"] = newRecord.PortsName,
|
||||
["序号"] = newRecord.SequenceNumber,
|
||||
["端口数量"] = newRecord.PortsCount,
|
||||
["列数量"] = newRecord.PortsCol,
|
||||
["行数量"] = newRecord.PortsRow,
|
||||
["修改时间"] = newRecord.UpdateAt,
|
||||
});
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_frames", parm.Id, "UPDATE", oldData, newData));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
|
||||
if (response > 0)
|
||||
{
|
||||
var rortsName = oldModel.PortsName;
|
||||
|
|
@ -189,6 +251,33 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
{
|
||||
var idArr = Tools.SplitAndConvert<int>(ids);
|
||||
|
||||
// 循环每条记录分别记录 DELETE 审计日志
|
||||
foreach (var id in idArr)
|
||||
{
|
||||
var oldRecord = _OdfFramesService.GetFirst(f => f.Id == id);
|
||||
string oldData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["Id"] = oldRecord.Id,
|
||||
["机架ID"] = oldRecord.RackId,
|
||||
["名称"] = oldRecord.PortsName,
|
||||
["部门ID"] = oldRecord.DeptId,
|
||||
["序号"] = oldRecord.SequenceNumber,
|
||||
["端口数量"] = oldRecord.PortsCount,
|
||||
["列数量"] = oldRecord.PortsCol,
|
||||
["行数量"] = oldRecord.PortsRow,
|
||||
["创建时间"] = oldRecord.CreatedAt,
|
||||
["修改时间"] = oldRecord.UpdateAt,
|
||||
});
|
||||
try
|
||||
{
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_frames", id, "DELETE", oldData, null));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
}
|
||||
|
||||
return ToResponse(_OdfFramesService.Delete(idArr, "删除框-信息"));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using ZR.Admin.WebApi.Filters;
|
||||
using ZR.Model.Business.Dto;
|
||||
using ZR.Service.Business.IBusinessService;
|
||||
|
||||
|
|
@ -16,10 +15,14 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
/// 标石/杆号牌接口
|
||||
/// </summary>
|
||||
private readonly IOdfMarkerPolesService _OdfMarkerPolesService;
|
||||
private readonly IOdfAuditLogsService _auditLogsService;
|
||||
|
||||
public OdfMarkerPolesController(IOdfMarkerPolesService OdfMarkerPolesService)
|
||||
public OdfMarkerPolesController(
|
||||
IOdfMarkerPolesService OdfMarkerPolesService,
|
||||
IOdfAuditLogsService auditLogsService)
|
||||
{
|
||||
_OdfMarkerPolesService = OdfMarkerPolesService;
|
||||
_auditLogsService = auditLogsService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -56,12 +59,39 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
[HttpPost("add")]
|
||||
[ActionPermissionFilter(Permission = "odfmarkerpoles:add")]
|
||||
[Log(Title = "标石/杆号牌", BusinessType = BusinessType.INSERT)]
|
||||
[ServiceFilter(typeof(OdfAuditLogFilter))]
|
||||
public IActionResult Add([FromBody] OdfMarkerPoleAddDto dto)
|
||||
{
|
||||
dto.UserId = HttpContext.GetUId();
|
||||
var deptId = HttpContext.GetDeptId();
|
||||
var response = _OdfMarkerPolesService.Add(dto, deptId);
|
||||
|
||||
// 手动记录 INSERT 审计日志
|
||||
try
|
||||
{
|
||||
var newRecord = _OdfMarkerPolesService.GetFirst(f => f.Id == response);
|
||||
string newData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["Id"] = newRecord.Id,
|
||||
["关联光缆ID"] = newRecord.CableId,
|
||||
["名称"] = newRecord.Name,
|
||||
["记录时间"] = newRecord.RecordTime,
|
||||
["责任人"] = newRecord.Personnel,
|
||||
["纬度"] = newRecord.Latitude,
|
||||
["经度"] = newRecord.Longitude,
|
||||
["实际里程"] = newRecord.ActualMileage,
|
||||
["部门ID"] = newRecord.DeptId,
|
||||
["部门名称"] = newRecord.DeptName,
|
||||
["提交人用户ID"] = newRecord.UserId,
|
||||
["创建时间"] = newRecord.CreatedAt,
|
||||
["更新时间"] = newRecord.UpdatedAt,
|
||||
});
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_marker_poles", response, "INSERT", null, newData));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
|
||||
return SUCCESS(response);
|
||||
}
|
||||
|
||||
|
|
@ -72,10 +102,47 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
[HttpPut("edit")]
|
||||
[ActionPermissionFilter(Permission = "odfmarkerpoles:edit")]
|
||||
[Log(Title = "标石/杆号牌", BusinessType = BusinessType.UPDATE)]
|
||||
[ServiceFilter(typeof(OdfAuditLogFilter))]
|
||||
public IActionResult Update([FromBody] OdfMarkerPoleEditDto dto)
|
||||
{
|
||||
// 查旧数据
|
||||
var oldRecord = _OdfMarkerPolesService.GetFirst(f => f.Id == dto.Id);
|
||||
string oldData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["名称"] = oldRecord.Name,
|
||||
["记录时间"] = oldRecord.RecordTime,
|
||||
["责任人"] = oldRecord.Personnel,
|
||||
["纬度"] = oldRecord.Latitude,
|
||||
["经度"] = oldRecord.Longitude,
|
||||
["实际里程"] = oldRecord.ActualMileage,
|
||||
["更新时间"] = oldRecord.UpdatedAt,
|
||||
});
|
||||
|
||||
// 执行更新
|
||||
var response = _OdfMarkerPolesService.Update(dto);
|
||||
|
||||
// 查新数据
|
||||
var newRecord = _OdfMarkerPolesService.GetFirst(f => f.Id == dto.Id);
|
||||
string newData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["名称"] = newRecord.Name,
|
||||
["记录时间"] = newRecord.RecordTime,
|
||||
["责任人"] = newRecord.Personnel,
|
||||
["纬度"] = newRecord.Latitude,
|
||||
["经度"] = newRecord.Longitude,
|
||||
["实际里程"] = newRecord.ActualMileage,
|
||||
["更新时间"] = newRecord.UpdatedAt,
|
||||
});
|
||||
|
||||
// 写入审计日志
|
||||
try
|
||||
{
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_marker_poles", dto.Id, "UPDATE", oldData, newData));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
|
||||
return ToResponse(response);
|
||||
}
|
||||
|
||||
|
|
@ -86,7 +153,6 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
[HttpDelete("delete/{ids}")]
|
||||
[ActionPermissionFilter(Permission = "odfmarkerpoles:delete")]
|
||||
[Log(Title = "标石/杆号牌", BusinessType = BusinessType.DELETE)]
|
||||
[ServiceFilter(typeof(OdfAuditLogFilter))]
|
||||
public IActionResult Delete(string ids)
|
||||
{
|
||||
var idList = ids.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
|
|
@ -99,6 +165,36 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
return ToResponse(ResultCode.FAIL, "请选择要删除的数据");
|
||||
}
|
||||
|
||||
// 循环每条记录分别记录 DELETE 审计日志
|
||||
foreach (var id in idList)
|
||||
{
|
||||
var oldRecord = _OdfMarkerPolesService.GetFirst(f => f.Id == id);
|
||||
string oldData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["Id"] = oldRecord.Id,
|
||||
["关联光缆ID"] = oldRecord.CableId,
|
||||
["名称"] = oldRecord.Name,
|
||||
["记录时间"] = oldRecord.RecordTime,
|
||||
["责任人"] = oldRecord.Personnel,
|
||||
["纬度"] = oldRecord.Latitude,
|
||||
["经度"] = oldRecord.Longitude,
|
||||
["实际里程"] = oldRecord.ActualMileage,
|
||||
["部门ID"] = oldRecord.DeptId,
|
||||
["部门名称"] = oldRecord.DeptName,
|
||||
["提交人用户ID"] = oldRecord.UserId,
|
||||
["创建时间"] = oldRecord.CreatedAt,
|
||||
["更新时间"] = oldRecord.UpdatedAt,
|
||||
});
|
||||
try
|
||||
{
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_marker_poles", id, "DELETE", oldData, null));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
}
|
||||
|
||||
int total = 0;
|
||||
foreach (var id in idList)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ using System.Text;
|
|||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using ZR.Admin.WebApi.Filters;
|
||||
using ZR.Model.Business;
|
||||
using ZR.Model.Business.Dto;
|
||||
using ZR.Model.System.Dto;
|
||||
|
|
@ -67,6 +66,8 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
/// </summary>
|
||||
private readonly IOdfPortFaultService _OdfPortFaultService;
|
||||
|
||||
private readonly IOdfAuditLogsService _auditLogsService;
|
||||
|
||||
|
||||
public OdfPortsController(IOdfRoomsService OdfRoomsService,
|
||||
ISysDeptService sysDeptService,
|
||||
|
|
@ -74,8 +75,8 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
IOdfPortsService odfPortsService,
|
||||
IOdfFramesService odfFramesService,
|
||||
IOdfRacksService odfRacksService,
|
||||
IOdfPortFaultService odfPortFaultService
|
||||
|
||||
IOdfPortFaultService odfPortFaultService,
|
||||
IOdfAuditLogsService auditLogsService
|
||||
)
|
||||
{
|
||||
_OdfRoomsService = OdfRoomsService;
|
||||
|
|
@ -85,6 +86,7 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
_OdfRacksService = odfRacksService;
|
||||
_SysDeptService = sysDeptService;
|
||||
_OdfPortFaultService = odfPortFaultService;
|
||||
_auditLogsService = auditLogsService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -362,13 +364,35 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
[HttpGet("status/{Id}/{status}")]
|
||||
[ActionPermissionFilter(Permission = "odfports:edit")]
|
||||
[Log(Title = "更新端口状态", BusinessType = BusinessType.UPDATE)]
|
||||
[ServiceFilter(typeof(OdfAuditLogFilter))]
|
||||
public IActionResult GetOdfPortsStatus(int Id, int status)
|
||||
{
|
||||
var oldRecord = _OdfPortsService.GetFirst(f => f.Id == Id);
|
||||
string oldData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["连接状态"] = oldRecord.Status,
|
||||
["修改时间"] = oldRecord.UpdatedAt,
|
||||
});
|
||||
|
||||
var response = _OdfPortsService.GetInfo(Id);
|
||||
response.Status = status;
|
||||
response.UpdatedAt = DateTime.Now;
|
||||
var s = _OdfPortsService.Update(response);
|
||||
|
||||
var newRecord = _OdfPortsService.GetFirst(f => f.Id == Id);
|
||||
string newData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["连接状态"] = newRecord.Status,
|
||||
["修改时间"] = newRecord.UpdatedAt,
|
||||
});
|
||||
try
|
||||
{
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_ports", Id, "UPDATE", oldData, newData));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
|
||||
return SUCCESS(s);
|
||||
}
|
||||
|
||||
|
|
@ -390,7 +414,6 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
[HttpPost]
|
||||
[ActionPermissionFilter(Permission = "odfports:add")]
|
||||
[Log(Title = "端口", BusinessType = BusinessType.INSERT)]
|
||||
[ServiceFilter(typeof(OdfAuditLogFilter))]
|
||||
public async Task<IActionResult> AddOdfPorts([FromBody] OdfPortsDto parm)
|
||||
{
|
||||
var modal = parm.Adapt<OdfPorts>().ToCreate(HttpContext);
|
||||
|
|
@ -412,6 +435,43 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
await _OdfPortFaultService.InsertAsync(o);
|
||||
}
|
||||
}
|
||||
|
||||
// 手动记录 INSERT 审计日志
|
||||
try
|
||||
{
|
||||
var newRecord = _OdfPortsService.GetFirst(f => f.Id == response.Id);
|
||||
string newData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["Id"] = newRecord.Id,
|
||||
["端口名称"] = newRecord.Name,
|
||||
["机房ID"] = newRecord.RoomId,
|
||||
["机房名称"] = newRecord.RoomName,
|
||||
["机架ID"] = newRecord.RackId,
|
||||
["机架名称"] = newRecord.RackName,
|
||||
["框ID"] = newRecord.FrameId,
|
||||
["框名称"] = newRecord.FrameName,
|
||||
["部门ID"] = newRecord.DeptId,
|
||||
["部门名称"] = newRecord.DeptName,
|
||||
["行号"] = newRecord.RowNumber,
|
||||
["端口号"] = newRecord.PortNumber,
|
||||
["连接状态"] = newRecord.Status,
|
||||
["备注"] = newRecord.Remarks,
|
||||
["光衰值"] = newRecord.OpticalAttenuation,
|
||||
["历史故障"] = newRecord.HistoryRemarks,
|
||||
["创建时间"] = newRecord.CreatedAt,
|
||||
["修改时间"] = newRecord.UpdatedAt,
|
||||
["光缆断信息"] = newRecord.OpticalCableOffRemarks,
|
||||
["设备型号"] = newRecord.EquipmentModel,
|
||||
["业务类型"] = newRecord.BusinessType,
|
||||
["端口侧"] = newRecord.PortSide,
|
||||
});
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_ports", response.Id, "INSERT", null, newData));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
|
||||
return SUCCESS(response);
|
||||
}
|
||||
|
||||
|
|
@ -422,9 +482,23 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
[HttpPut]
|
||||
[ActionPermissionFilter(Permission = "odfports:edit")]
|
||||
[Log(Title = "端口", BusinessType = BusinessType.UPDATE)]
|
||||
[ServiceFilter(typeof(OdfAuditLogFilter))]
|
||||
public async Task<IActionResult> UpdateOdfPorts([FromBody] OdfPortsDto parm)
|
||||
{
|
||||
// 查旧数据
|
||||
var oldRecord = _OdfPortsService.GetFirst(f => f.Id == parm.Id);
|
||||
string oldData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["端口名称"] = oldRecord.Name,
|
||||
["连接状态"] = oldRecord.Status,
|
||||
["备注"] = oldRecord.Remarks,
|
||||
["光衰值"] = oldRecord.OpticalAttenuation,
|
||||
["历史故障"] = oldRecord.HistoryRemarks,
|
||||
["光缆断信息"] = oldRecord.OpticalCableOffRemarks,
|
||||
["设备型号"] = oldRecord.EquipmentModel,
|
||||
["业务类型"] = oldRecord.BusinessType,
|
||||
["修改时间"] = oldRecord.UpdatedAt,
|
||||
});
|
||||
|
||||
var modal = parm.Adapt<OdfPorts>().ToUpdate(HttpContext);
|
||||
modal.UpdatedAt = DateTime.Now;
|
||||
modal.HistoryRemarks = ToHistoryString(parm.HistoryFault);
|
||||
|
|
@ -448,6 +522,30 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
await _OdfPortFaultService.InsertAsync(o);
|
||||
}
|
||||
}
|
||||
|
||||
// 查新数据
|
||||
var newRecord = _OdfPortsService.GetFirst(f => f.Id == parm.Id);
|
||||
string newData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["端口名称"] = newRecord.Name,
|
||||
["连接状态"] = newRecord.Status,
|
||||
["备注"] = newRecord.Remarks,
|
||||
["光衰值"] = newRecord.OpticalAttenuation,
|
||||
["历史故障"] = newRecord.HistoryRemarks,
|
||||
["光缆断信息"] = newRecord.OpticalCableOffRemarks,
|
||||
["设备型号"] = newRecord.EquipmentModel,
|
||||
["业务类型"] = newRecord.BusinessType,
|
||||
["修改时间"] = newRecord.UpdatedAt,
|
||||
});
|
||||
try
|
||||
{
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_ports", parm.Id, "UPDATE", oldData, newData));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
|
||||
return ToResponse(response);
|
||||
}
|
||||
|
||||
|
|
@ -458,9 +556,22 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
[HttpPost("save")]
|
||||
[ActionPermissionFilter(Permission = "odfports:edit")]
|
||||
[Log(Title = "APP修改端口", BusinessType = BusinessType.UPDATE)]
|
||||
[ServiceFilter(typeof(OdfAuditLogFilter))]
|
||||
public async Task<IActionResult> SaveMOdfPorts([FromBody] OdfPortsMMDto parm)
|
||||
{
|
||||
// 查旧数据
|
||||
var oldRecord = _OdfPortsService.GetFirst(f => f.Id == parm.Id);
|
||||
string oldData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["连接状态"] = oldRecord.Status,
|
||||
["备注"] = oldRecord.Remarks,
|
||||
["光衰值"] = oldRecord.OpticalAttenuation,
|
||||
["历史故障"] = oldRecord.HistoryRemarks,
|
||||
["光缆断信息"] = oldRecord.OpticalCableOffRemarks,
|
||||
["设备型号"] = oldRecord.EquipmentModel,
|
||||
["业务类型"] = oldRecord.BusinessType,
|
||||
["修改时间"] = oldRecord.UpdatedAt,
|
||||
});
|
||||
|
||||
var port = _OdfPortsService.GetById(parm.Id);
|
||||
if (port == null)
|
||||
{
|
||||
|
|
@ -528,6 +639,29 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
await _OdfPortFaultService.InsertAsync(o);
|
||||
}
|
||||
}
|
||||
|
||||
// 查新数据
|
||||
var newRecord = _OdfPortsService.GetFirst(f => f.Id == parm.Id);
|
||||
string newData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["连接状态"] = newRecord.Status,
|
||||
["备注"] = newRecord.Remarks,
|
||||
["光衰值"] = newRecord.OpticalAttenuation,
|
||||
["历史故障"] = newRecord.HistoryRemarks,
|
||||
["光缆断信息"] = newRecord.OpticalCableOffRemarks,
|
||||
["设备型号"] = newRecord.EquipmentModel,
|
||||
["业务类型"] = newRecord.BusinessType,
|
||||
["修改时间"] = newRecord.UpdatedAt,
|
||||
});
|
||||
try
|
||||
{
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_ports", parm.Id, "UPDATE", oldData, newData));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
|
||||
return ToResponse(response);
|
||||
}
|
||||
|
||||
|
|
@ -567,9 +701,19 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
[HttpGet("empty/{Id}")]
|
||||
[ActionPermissionFilter(Permission = "odfports:edit")]
|
||||
[Log(Title = "清空数据", BusinessType = BusinessType.UPDATE)]
|
||||
[ServiceFilter(typeof(OdfAuditLogFilter))]
|
||||
public IActionResult GetOdfPortsEmpty(int Id)
|
||||
{
|
||||
var oldRecord = _OdfPortsService.GetFirst(f => f.Id == Id);
|
||||
string oldData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["连接状态"] = oldRecord.Status,
|
||||
["备注"] = oldRecord.Remarks,
|
||||
["历史故障"] = oldRecord.HistoryRemarks,
|
||||
["光衰值"] = oldRecord.OpticalAttenuation,
|
||||
["光缆断信息"] = oldRecord.OpticalCableOffRemarks,
|
||||
["修改时间"] = oldRecord.UpdatedAt,
|
||||
});
|
||||
|
||||
var response = _OdfPortsService.GetInfo(Id);
|
||||
response.Status = 0;
|
||||
response.Remarks = "";
|
||||
|
|
@ -578,6 +722,26 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
response.UpdatedAt = DateTime.Now;
|
||||
response.OpticalCableOffRemarks = "";
|
||||
var s = _OdfPortsService.Update(response);
|
||||
|
||||
var newRecord = _OdfPortsService.GetFirst(f => f.Id == Id);
|
||||
string newData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["连接状态"] = newRecord.Status,
|
||||
["备注"] = newRecord.Remarks,
|
||||
["历史故障"] = newRecord.HistoryRemarks,
|
||||
["光衰值"] = newRecord.OpticalAttenuation,
|
||||
["光缆断信息"] = newRecord.OpticalCableOffRemarks,
|
||||
["修改时间"] = newRecord.UpdatedAt,
|
||||
});
|
||||
try
|
||||
{
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_ports", Id, "UPDATE", oldData, newData));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
|
||||
return SUCCESS(s);
|
||||
}
|
||||
|
||||
|
|
@ -588,11 +752,49 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
[HttpPost("delete/{ids}")]
|
||||
[ActionPermissionFilter(Permission = "odfports:delete")]
|
||||
[Log(Title = "端口", BusinessType = BusinessType.DELETE)]
|
||||
[ServiceFilter(typeof(OdfAuditLogFilter))]
|
||||
public IActionResult DeleteOdfPorts([FromRoute] string ids)
|
||||
{
|
||||
var idArr = Tools.SplitAndConvert<int>(ids);
|
||||
|
||||
// 循环每条记录分别记录 DELETE 审计日志
|
||||
foreach (var id in idArr)
|
||||
{
|
||||
var oldRecord = _OdfPortsService.GetFirst(f => f.Id == id);
|
||||
string oldData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["Id"] = oldRecord.Id,
|
||||
["端口名称"] = oldRecord.Name,
|
||||
["机房ID"] = oldRecord.RoomId,
|
||||
["机房名称"] = oldRecord.RoomName,
|
||||
["机架ID"] = oldRecord.RackId,
|
||||
["机架名称"] = oldRecord.RackName,
|
||||
["框ID"] = oldRecord.FrameId,
|
||||
["框名称"] = oldRecord.FrameName,
|
||||
["部门ID"] = oldRecord.DeptId,
|
||||
["部门名称"] = oldRecord.DeptName,
|
||||
["行号"] = oldRecord.RowNumber,
|
||||
["端口号"] = oldRecord.PortNumber,
|
||||
["连接状态"] = oldRecord.Status,
|
||||
["备注"] = oldRecord.Remarks,
|
||||
["光衰值"] = oldRecord.OpticalAttenuation,
|
||||
["历史故障"] = oldRecord.HistoryRemarks,
|
||||
["创建时间"] = oldRecord.CreatedAt,
|
||||
["修改时间"] = oldRecord.UpdatedAt,
|
||||
["光缆断信息"] = oldRecord.OpticalCableOffRemarks,
|
||||
["设备型号"] = oldRecord.EquipmentModel,
|
||||
["业务类型"] = oldRecord.BusinessType,
|
||||
["端口侧"] = oldRecord.PortSide,
|
||||
});
|
||||
try
|
||||
{
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_ports", id, "DELETE", oldData, null));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
}
|
||||
|
||||
return ToResponse(_OdfPortsService.Delete(idArr, "删除端口"));
|
||||
}
|
||||
|
||||
|
|
@ -1023,7 +1225,6 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
[HttpPost("deleteAll")]
|
||||
[ActionPermissionFilter(Permission = "odfports:delete")]
|
||||
[Log(Title = "删除端口数据", BusinessType = BusinessType.DELETE)]
|
||||
[ServiceFilter(typeof(OdfAuditLogFilter))]
|
||||
public async Task<IActionResult> DeleteAll([FromBody] OdfRoomsTreeDto treeDto)
|
||||
{
|
||||
if (treeDto.Level == 2)
|
||||
|
|
@ -1034,6 +1235,46 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
{
|
||||
return ToResponse(ResultCode.FAIL, "删除失败");
|
||||
}
|
||||
|
||||
// 记录机房下所有端口的 DELETE 审计日志
|
||||
var ports = await _OdfPortsService.AsQueryable().Where(it => it.RoomId == room.Id).ToListAsync();
|
||||
foreach (var port in ports)
|
||||
{
|
||||
string oldData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["Id"] = port.Id,
|
||||
["端口名称"] = port.Name,
|
||||
["机房ID"] = port.RoomId,
|
||||
["机房名称"] = port.RoomName,
|
||||
["机架ID"] = port.RackId,
|
||||
["机架名称"] = port.RackName,
|
||||
["框ID"] = port.FrameId,
|
||||
["框名称"] = port.FrameName,
|
||||
["部门ID"] = port.DeptId,
|
||||
["部门名称"] = port.DeptName,
|
||||
["行号"] = port.RowNumber,
|
||||
["端口号"] = port.PortNumber,
|
||||
["连接状态"] = port.Status,
|
||||
["备注"] = port.Remarks,
|
||||
["光衰值"] = port.OpticalAttenuation,
|
||||
["历史故障"] = port.HistoryRemarks,
|
||||
["创建时间"] = port.CreatedAt,
|
||||
["修改时间"] = port.UpdatedAt,
|
||||
["光缆断信息"] = port.OpticalCableOffRemarks,
|
||||
["设备型号"] = port.EquipmentModel,
|
||||
["业务类型"] = port.BusinessType,
|
||||
["端口侧"] = port.PortSide,
|
||||
});
|
||||
try
|
||||
{
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_ports", port.Id, "DELETE", oldData, null));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
}
|
||||
|
||||
//删除机房下的所有端口
|
||||
await _OdfPortsService.AsDeleteable().Where(it => it.RoomId == room.Id).ExecuteCommandAsync();
|
||||
// delete odf_frames where exists ( select id from odf_racks where roomid=1 and id=odf_frames.RackId )
|
||||
|
|
@ -1056,6 +1297,46 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
{
|
||||
return ToResponse(ResultCode.FAIL, "删除失败");
|
||||
}
|
||||
|
||||
// 记录机架下所有端口的 DELETE 审计日志
|
||||
var ports = await _OdfPortsService.AsQueryable().Where(it => it.RackId == racks.Id).ToListAsync();
|
||||
foreach (var port in ports)
|
||||
{
|
||||
string oldData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["Id"] = port.Id,
|
||||
["端口名称"] = port.Name,
|
||||
["机房ID"] = port.RoomId,
|
||||
["机房名称"] = port.RoomName,
|
||||
["机架ID"] = port.RackId,
|
||||
["机架名称"] = port.RackName,
|
||||
["框ID"] = port.FrameId,
|
||||
["框名称"] = port.FrameName,
|
||||
["部门ID"] = port.DeptId,
|
||||
["部门名称"] = port.DeptName,
|
||||
["行号"] = port.RowNumber,
|
||||
["端口号"] = port.PortNumber,
|
||||
["连接状态"] = port.Status,
|
||||
["备注"] = port.Remarks,
|
||||
["光衰值"] = port.OpticalAttenuation,
|
||||
["历史故障"] = port.HistoryRemarks,
|
||||
["创建时间"] = port.CreatedAt,
|
||||
["修改时间"] = port.UpdatedAt,
|
||||
["光缆断信息"] = port.OpticalCableOffRemarks,
|
||||
["设备型号"] = port.EquipmentModel,
|
||||
["业务类型"] = port.BusinessType,
|
||||
["端口侧"] = port.PortSide,
|
||||
});
|
||||
try
|
||||
{
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_ports", port.Id, "DELETE", oldData, null));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
}
|
||||
|
||||
//删除机房下的所有端口
|
||||
await _OdfPortsService.AsDeleteable().Where(it => it.RackId == racks.Id).ExecuteCommandAsync();
|
||||
//删除机房下所有的框
|
||||
|
|
@ -1070,6 +1351,46 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
{
|
||||
return ToResponse(ResultCode.FAIL, "删除失败");
|
||||
}
|
||||
|
||||
// 记录框下所有端口的 DELETE 审计日志
|
||||
var ports = await _OdfPortsService.AsQueryable().Where(it => it.FrameId == frames.Id).ToListAsync();
|
||||
foreach (var port in ports)
|
||||
{
|
||||
string oldData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["Id"] = port.Id,
|
||||
["端口名称"] = port.Name,
|
||||
["机房ID"] = port.RoomId,
|
||||
["机房名称"] = port.RoomName,
|
||||
["机架ID"] = port.RackId,
|
||||
["机架名称"] = port.RackName,
|
||||
["框ID"] = port.FrameId,
|
||||
["框名称"] = port.FrameName,
|
||||
["部门ID"] = port.DeptId,
|
||||
["部门名称"] = port.DeptName,
|
||||
["行号"] = port.RowNumber,
|
||||
["端口号"] = port.PortNumber,
|
||||
["连接状态"] = port.Status,
|
||||
["备注"] = port.Remarks,
|
||||
["光衰值"] = port.OpticalAttenuation,
|
||||
["历史故障"] = port.HistoryRemarks,
|
||||
["创建时间"] = port.CreatedAt,
|
||||
["修改时间"] = port.UpdatedAt,
|
||||
["光缆断信息"] = port.OpticalCableOffRemarks,
|
||||
["设备型号"] = port.EquipmentModel,
|
||||
["业务类型"] = port.BusinessType,
|
||||
["端口侧"] = port.PortSide,
|
||||
});
|
||||
try
|
||||
{
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_ports", port.Id, "DELETE", oldData, null));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
}
|
||||
|
||||
//删除机房下的所有端口
|
||||
await _OdfPortsService.AsDeleteable().Where(it => it.FrameId == frames.Id).ExecuteCommandAsync();
|
||||
_OdfFramesService.Delete(frames);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using ZR.Admin.WebApi.Filters;
|
||||
using ZR.Model.Business.Dto;
|
||||
using ZR.Model.Business;
|
||||
using ZR.Service.Business.IBusinessService;
|
||||
|
|
@ -29,15 +28,18 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
/// 端口
|
||||
/// </summary>
|
||||
private readonly IOdfPortsService _OdfPortsService;
|
||||
private readonly IOdfAuditLogsService _auditLogsService;
|
||||
|
||||
public OdfRacksController(IOdfRacksService OdfRacksService, IOdfRoomsService odfRooms,
|
||||
IOdfFramesService odfFramesService,
|
||||
IOdfPortsService odfPortsService)
|
||||
IOdfPortsService odfPortsService,
|
||||
IOdfAuditLogsService auditLogsService)
|
||||
{
|
||||
_OdfRacksService = OdfRacksService;
|
||||
_odfRooms = odfRooms;
|
||||
_OdfFramesService = odfFramesService;
|
||||
_OdfPortsService = odfPortsService;
|
||||
_auditLogsService = auditLogsService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -76,7 +78,6 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
[HttpPost]
|
||||
[ActionPermissionFilter(Permission = "odfracks:add")]
|
||||
[Log(Title = "机架列表", BusinessType = BusinessType.INSERT)]
|
||||
[ServiceFilter(typeof(OdfAuditLogFilter))]
|
||||
public IActionResult AddOdfRacks([FromBody] OdfRacksDto parm)
|
||||
{
|
||||
var modal = parm.Adapt<OdfRacks>().ToCreate(HttpContext);
|
||||
|
|
@ -91,6 +92,29 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
}
|
||||
var response = _OdfRacksService.AddOdfRacks(modal);
|
||||
|
||||
// 手动记录 INSERT 审计日志
|
||||
try
|
||||
{
|
||||
var newRecord = _OdfRacksService.GetFirst(f => f.Id == response.Id);
|
||||
string newData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["Id"] = newRecord.Id,
|
||||
["机房ID"] = newRecord.RoomId,
|
||||
["序号"] = newRecord.SequenceNumber,
|
||||
["ODF名称"] = newRecord.RackName,
|
||||
["框数量"] = newRecord.FrameCount,
|
||||
["创建时间"] = newRecord.CreatedAt,
|
||||
["修改时间"] = newRecord.UpdatedAt,
|
||||
["部门ID"] = newRecord.DeptId,
|
||||
["机架类型"] = newRecord.RackType,
|
||||
});
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_racks", response.Id, "INSERT", null, newData));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
|
||||
return SUCCESS(response);
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +126,6 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
[HttpPost("expert")]
|
||||
[ActionPermissionFilter(Permission = "odfracks:add")]
|
||||
[Log(Title = "机架列表", BusinessType = BusinessType.INSERT)]
|
||||
[ServiceFilter(typeof(OdfAuditLogFilter))]
|
||||
public async Task<IActionResult> AddOdfRacksExpert([FromBody] OdfRacksExpertDto parm)
|
||||
{
|
||||
var modal = parm.Adapt<OdfRacks>().ToCreate(HttpContext);
|
||||
|
|
@ -271,6 +294,29 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
}
|
||||
}
|
||||
|
||||
// 手动记录 INSERT 审计日志(仅记录机架)
|
||||
try
|
||||
{
|
||||
var newRecord = _OdfRacksService.GetFirst(f => f.Id == response.Id);
|
||||
string newData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["Id"] = newRecord.Id,
|
||||
["机房ID"] = newRecord.RoomId,
|
||||
["序号"] = newRecord.SequenceNumber,
|
||||
["ODF名称"] = newRecord.RackName,
|
||||
["框数量"] = newRecord.FrameCount,
|
||||
["创建时间"] = newRecord.CreatedAt,
|
||||
["修改时间"] = newRecord.UpdatedAt,
|
||||
["部门ID"] = newRecord.DeptId,
|
||||
["机架类型"] = newRecord.RackType,
|
||||
});
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_racks", response.Id, "INSERT", null, newData));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
|
||||
return SUCCESS(response);
|
||||
}
|
||||
|
||||
|
|
@ -281,12 +327,43 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
[HttpPut]
|
||||
[ActionPermissionFilter(Permission = "odfracks:edit")]
|
||||
[Log(Title = "机架列表", BusinessType = BusinessType.UPDATE)]
|
||||
[ServiceFilter(typeof(OdfAuditLogFilter))]
|
||||
public async Task<IActionResult> UpdateOdfRacks([FromBody] OdfRacksDto parm)
|
||||
{
|
||||
var modal = parm.Adapt<OdfRacks>().ToUpdate(HttpContext);
|
||||
|
||||
// 查旧数据
|
||||
var oldRecord = _OdfRacksService.GetFirst(f => f.Id == parm.Id);
|
||||
string oldData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["ODF名称"] = oldRecord.RackName,
|
||||
["序号"] = oldRecord.SequenceNumber,
|
||||
["框数量"] = oldRecord.FrameCount,
|
||||
["机架类型"] = oldRecord.RackType,
|
||||
["修改时间"] = oldRecord.UpdatedAt,
|
||||
});
|
||||
|
||||
var oldModel = _OdfRacksService.GetById(parm.Id);
|
||||
var response = _OdfRacksService.UpdateOdfRacks(modal);
|
||||
|
||||
// 查新数据并记录 UPDATE 审计日志
|
||||
try
|
||||
{
|
||||
var newRecord = _OdfRacksService.GetFirst(f => f.Id == parm.Id);
|
||||
string newData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["ODF名称"] = newRecord.RackName,
|
||||
["序号"] = newRecord.SequenceNumber,
|
||||
["框数量"] = newRecord.FrameCount,
|
||||
["机架类型"] = newRecord.RackType,
|
||||
["修改时间"] = newRecord.UpdatedAt,
|
||||
});
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_racks", parm.Id, "UPDATE", oldData, newData));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
|
||||
if (response > 0)
|
||||
{
|
||||
var rackName = oldModel.RackName;
|
||||
|
|
@ -315,10 +392,36 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
[HttpPost("delete/{ids}")]
|
||||
[ActionPermissionFilter(Permission = "odfracks:delete")]
|
||||
[Log(Title = "机架列表", BusinessType = BusinessType.DELETE)]
|
||||
[ServiceFilter(typeof(OdfAuditLogFilter))]
|
||||
public async Task<IActionResult> DeleteOdfRacks([FromRoute] string ids)
|
||||
{
|
||||
var idArr = Tools.SplitAndConvert<int>(ids);
|
||||
|
||||
// 循环每条记录分别记录 DELETE 审计日志
|
||||
foreach (var racksId in idArr)
|
||||
{
|
||||
var oldRecord = _OdfRacksService.GetFirst(f => f.Id == racksId);
|
||||
string oldData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["Id"] = oldRecord.Id,
|
||||
["机房ID"] = oldRecord.RoomId,
|
||||
["序号"] = oldRecord.SequenceNumber,
|
||||
["ODF名称"] = oldRecord.RackName,
|
||||
["框数量"] = oldRecord.FrameCount,
|
||||
["创建时间"] = oldRecord.CreatedAt,
|
||||
["修改时间"] = oldRecord.UpdatedAt,
|
||||
["部门ID"] = oldRecord.DeptId,
|
||||
["机架类型"] = oldRecord.RackType,
|
||||
});
|
||||
try
|
||||
{
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_racks", racksId, "DELETE", oldData, null));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var racksId in idArr)
|
||||
{
|
||||
var racks = _OdfRacksService.GetById(racksId);
|
||||
|
|
|
|||
|
|
@ -53,13 +53,15 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
/// 端口
|
||||
/// </summary>
|
||||
private readonly IOdfPortsService _OdfPortsService;
|
||||
private readonly IOdfAuditLogsService _auditLogsService;
|
||||
|
||||
public OdfRoomsController(IOdfRoomsService OdfRoomsService,
|
||||
ISysDeptService sysDeptService,
|
||||
ISysUserService sysUserService,
|
||||
IOdfPortsService odfPortsService,
|
||||
IOdfFramesService odfFramesService,
|
||||
IOdfRacksService odfRacksService)
|
||||
IOdfRacksService odfRacksService,
|
||||
IOdfAuditLogsService auditLogsService)
|
||||
{
|
||||
_OdfRoomsService = OdfRoomsService;
|
||||
this.sysDeptService = sysDeptService;
|
||||
|
|
@ -67,6 +69,7 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
_OdfPortsService = odfPortsService;
|
||||
_OdfFramesService = odfFramesService;
|
||||
_OdfRacksService = odfRacksService;
|
||||
_auditLogsService = auditLogsService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -201,6 +204,29 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
var modal = parm.Adapt<OdfRooms>().ToCreate(HttpContext);
|
||||
var response = _OdfRoomsService.AddOdfRooms(modal);
|
||||
|
||||
// INSERT 审计日志
|
||||
try
|
||||
{
|
||||
var newRecord = _OdfRoomsService.GetFirst(f => f.Id == response.Id);
|
||||
string newData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["Id"] = newRecord.Id,
|
||||
["部门ID"] = newRecord.DeptId,
|
||||
["部门名称"] = newRecord.DeptName,
|
||||
["机房名称"] = newRecord.RoomName,
|
||||
["机房位置"] = newRecord.RoomAddress,
|
||||
["机架数量"] = newRecord.RacksCount,
|
||||
["备注"] = newRecord.Remarks,
|
||||
["创建时间"] = newRecord.CreatedAt,
|
||||
["修改时间"] = newRecord.UpdatedAt,
|
||||
});
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_rooms", response.Id, "INSERT", null, newData));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
|
||||
return SUCCESS(response);
|
||||
}
|
||||
/// <summary>
|
||||
|
|
@ -276,6 +302,30 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
modal.DeptId = parm.DeptId;
|
||||
modal.DeptName = parm.DeptName;
|
||||
var response = _OdfRoomsService.AddOdfRooms(modal);
|
||||
|
||||
// INSERT 审计日志(仅记录机房)
|
||||
try
|
||||
{
|
||||
var newRecord = _OdfRoomsService.GetFirst(f => f.Id == response.Id);
|
||||
string newData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["Id"] = newRecord.Id,
|
||||
["部门ID"] = newRecord.DeptId,
|
||||
["部门名称"] = newRecord.DeptName,
|
||||
["机房名称"] = newRecord.RoomName,
|
||||
["机房位置"] = newRecord.RoomAddress,
|
||||
["机架数量"] = newRecord.RacksCount,
|
||||
["备注"] = newRecord.Remarks,
|
||||
["创建时间"] = newRecord.CreatedAt,
|
||||
["修改时间"] = newRecord.UpdatedAt,
|
||||
});
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_rooms", response.Id, "INSERT", null, newData));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
|
||||
if (parm.RacksCount > 0)
|
||||
{
|
||||
var roomId = response.Id;
|
||||
|
|
@ -477,8 +527,43 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
parm.DeptName = dept.DeptName;
|
||||
}
|
||||
var oldModel = _OdfRoomsService.GetById(parm.Id);
|
||||
|
||||
// 查旧数据
|
||||
string oldData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["机房名称"] = oldModel.RoomName,
|
||||
["机房位置"] = oldModel.RoomAddress,
|
||||
["部门ID"] = oldModel.DeptId,
|
||||
["部门名称"] = oldModel.DeptName,
|
||||
["机架数量"] = oldModel.RacksCount,
|
||||
["备注"] = oldModel.Remarks,
|
||||
["修改时间"] = oldModel.UpdatedAt,
|
||||
});
|
||||
|
||||
var modal = parm.Adapt<OdfRooms>().ToUpdate(HttpContext);
|
||||
var response = _OdfRoomsService.UpdateOdfRooms(modal);
|
||||
|
||||
// UPDATE 审计日志
|
||||
try
|
||||
{
|
||||
var newRecord = _OdfRoomsService.GetFirst(f => f.Id == parm.Id);
|
||||
string newData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["机房名称"] = newRecord.RoomName,
|
||||
["机房位置"] = newRecord.RoomAddress,
|
||||
["部门ID"] = newRecord.DeptId,
|
||||
["部门名称"] = newRecord.DeptName,
|
||||
["机架数量"] = newRecord.RacksCount,
|
||||
["备注"] = newRecord.Remarks,
|
||||
["修改时间"] = newRecord.UpdatedAt,
|
||||
});
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_rooms", parm.Id, "UPDATE", oldData, newData));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
|
||||
if (response > 0)
|
||||
{
|
||||
var deptId = modal.DeptId ?? 0;
|
||||
|
|
@ -549,6 +634,29 @@ namespace ZR.Admin.WebApi.Controllers.Business
|
|||
{
|
||||
return ToResponse(ResultCode.FAIL, "删除失败,未找到机房数据");
|
||||
}
|
||||
|
||||
// DELETE 审计日志
|
||||
string oldData = BuildAuditJson(new Dictionary<string, object>
|
||||
{
|
||||
["Id"] = room.Id,
|
||||
["部门ID"] = room.DeptId,
|
||||
["部门名称"] = room.DeptName,
|
||||
["机房名称"] = room.RoomName,
|
||||
["机房位置"] = room.RoomAddress,
|
||||
["机架数量"] = room.RacksCount,
|
||||
["备注"] = room.Remarks,
|
||||
["创建时间"] = room.CreatedAt,
|
||||
["修改时间"] = room.UpdatedAt,
|
||||
});
|
||||
try
|
||||
{
|
||||
_auditLogsService.AddLog(BuildAuditLog("odf_rooms", roomId, "DELETE", oldData, null));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NLog.LogManager.GetCurrentClassLogger().Error(ex, "审计日志写入失败");
|
||||
}
|
||||
|
||||
//删除机房下的所有端口
|
||||
await _OdfPortsService.AsDeleteable().Where(it => it.RoomId == room.Id).ExecuteCommandAsync();
|
||||
// delete odf_frames where exists ( select id from odf_racks where roomid=1 and id=odf_frames.RackId )
|
||||
|
|
|
|||
|
|
@ -1,337 +0,0 @@
|
|||
using Infrastructure;
|
||||
using Infrastructure.Attribute;
|
||||
using Infrastructure.Enums;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using SqlSugar;
|
||||
using SqlSugar.IOC;
|
||||
using System.Text.Json;
|
||||
using ZR.Model.Business;
|
||||
using ZR.Service.Business.IBusinessService;
|
||||
|
||||
namespace ZR.Admin.WebApi.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// ODF 审计日志 ActionFilter
|
||||
/// 拦截标石/ODF/干线相关 Controller 的增删改操作,自动记录审计日志。
|
||||
/// 使用方式:在 Action 上添加 [ServiceFilter(typeof(OdfAuditLogFilter))]
|
||||
/// </summary>
|
||||
public class OdfAuditLogFilter : IAsyncActionFilter
|
||||
{
|
||||
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
private readonly IOdfAuditLogsService _auditLogsService;
|
||||
|
||||
/// <summary>
|
||||
/// 控制器名称到数据库表名的映射
|
||||
/// </summary>
|
||||
private static readonly Dictionary<string, string> ControllerTableMap = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "OdfMarkerPoles", "odf_marker_poles" },
|
||||
{ "OdfCables", "odf_cables" },
|
||||
{ "OdfCableFaults", "odf_cable_faults" },
|
||||
{ "OdfPorts", "odf_ports" },
|
||||
{ "OdfRacks", "odf_racks" },
|
||||
{ "OdfFrames", "odf_frames" },
|
||||
{ "OdfRooms", "odf_rooms" },
|
||||
};
|
||||
|
||||
public OdfAuditLogFilter(IOdfAuditLogsService auditLogsService)
|
||||
{
|
||||
_auditLogsService = auditLogsService;
|
||||
}
|
||||
|
||||
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||
{
|
||||
string oldData = null;
|
||||
string operationType = null;
|
||||
string tableName = null;
|
||||
int? recordId = null;
|
||||
|
||||
try
|
||||
{
|
||||
// 从 [Log] 特性获取 BusinessType
|
||||
var businessType = GetBusinessType(context);
|
||||
operationType = businessType switch
|
||||
{
|
||||
BusinessType.INSERT => "INSERT",
|
||||
BusinessType.UPDATE => "UPDATE",
|
||||
BusinessType.DELETE => "DELETE",
|
||||
_ => null
|
||||
};
|
||||
|
||||
// 仅拦截增删改操作
|
||||
if (operationType == null)
|
||||
{
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取表名
|
||||
tableName = GetTableName(context);
|
||||
|
||||
// 获取记录 ID(从路由参数或请求体)
|
||||
recordId = GetRecordId(context);
|
||||
|
||||
// 操作前:对于 UPDATE/DELETE,尝试读取现有数据作为 OldData
|
||||
if ((operationType == "UPDATE" || operationType == "DELETE") && recordId.HasValue && recordId.Value > 0)
|
||||
{
|
||||
oldData = await GetExistingDataJson(tableName, recordId.Value);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error(ex, "审计日志:操作前记录数据失败");
|
||||
}
|
||||
|
||||
// 执行实际的 Action
|
||||
var resultContext = await next();
|
||||
|
||||
// 操作后:记录审计日志
|
||||
try
|
||||
{
|
||||
// 如果 Action 执行出错,不记录审计日志
|
||||
if (resultContext.Exception != null && !resultContext.ExceptionHandled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (operationType == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取新数据
|
||||
string newData = null;
|
||||
if (operationType == "INSERT" || operationType == "UPDATE")
|
||||
{
|
||||
newData = GetNewDataFromActionArguments(context);
|
||||
}
|
||||
|
||||
// 对于 INSERT,尝试从返回结果获取新记录 ID
|
||||
if (operationType == "INSERT" && (!recordId.HasValue || recordId.Value <= 0))
|
||||
{
|
||||
recordId = GetRecordIdFromResult(resultContext);
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
var operatorId = context.HttpContext.GetUId();
|
||||
var operatorName = context.HttpContext.GetName();
|
||||
var deptId = context.HttpContext.GetDeptId();
|
||||
|
||||
// 判断操作来源
|
||||
var sourceClient = GetSourceClient(context.HttpContext);
|
||||
|
||||
var auditLog = new OdfAuditLogs
|
||||
{
|
||||
TableName = tableName ?? "unknown",
|
||||
RecordId = recordId,
|
||||
OperationType = operationType,
|
||||
OperatorId = operatorId,
|
||||
OperatorName = operatorName,
|
||||
SourceClient = sourceClient,
|
||||
OldData = oldData,
|
||||
NewData = newData,
|
||||
OperationTime = DateTime.Now,
|
||||
DeptId = deptId > 0 ? deptId : null,
|
||||
};
|
||||
|
||||
_auditLogsService.AddLog(auditLog);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 审计日志写入失败不影响主业务
|
||||
logger.Error(ex, "审计日志:写入审计日志失败");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 [Log] 特性获取 BusinessType
|
||||
/// </summary>
|
||||
private static BusinessType? GetBusinessType(ActionExecutingContext context)
|
||||
{
|
||||
if (context.ActionDescriptor is not ControllerActionDescriptor descriptor)
|
||||
return null;
|
||||
|
||||
var logAttr = descriptor.MethodInfo
|
||||
.GetCustomAttributes(inherit: true)
|
||||
.OfType<LogAttribute>()
|
||||
.FirstOrDefault();
|
||||
|
||||
return logAttr?.BusinessType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从控制器名称映射到数据库表名
|
||||
/// </summary>
|
||||
private static string GetTableName(ActionExecutingContext context)
|
||||
{
|
||||
if (context.ActionDescriptor is ControllerActionDescriptor descriptor)
|
||||
{
|
||||
var controllerName = descriptor.ControllerName;
|
||||
if (ControllerTableMap.TryGetValue(controllerName, out var table))
|
||||
{
|
||||
return table;
|
||||
}
|
||||
// 回退:使用控制器名称
|
||||
return controllerName;
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从路由参数或请求体中提取记录 ID
|
||||
/// </summary>
|
||||
private static int? GetRecordId(ActionExecutingContext context)
|
||||
{
|
||||
// 尝试从路由参数获取 id
|
||||
if (context.ActionArguments.TryGetValue("id", out var idObj))
|
||||
{
|
||||
if (idObj is int intId) return intId;
|
||||
if (int.TryParse(idObj?.ToString(), out var parsed)) return parsed;
|
||||
}
|
||||
|
||||
// 尝试从路由参数获取 ids(批量删除场景,取第一个)
|
||||
if (context.ActionArguments.TryGetValue("ids", out var idsObj) && idsObj is string idsStr)
|
||||
{
|
||||
var first = idsStr.Split(',', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
|
||||
if (int.TryParse(first?.Trim(), out var firstId)) return firstId;
|
||||
}
|
||||
|
||||
// 尝试从请求体 DTO 中获取 Id 属性
|
||||
foreach (var arg in context.ActionArguments.Values)
|
||||
{
|
||||
if (arg == null) continue;
|
||||
var idProp = arg.GetType().GetProperty("Id");
|
||||
if (idProp != null && idProp.PropertyType == typeof(int))
|
||||
{
|
||||
var val = (int)(idProp.GetValue(arg) ?? 0);
|
||||
if (val > 0) return val;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 Action 参数中获取新数据(请求体 DTO 序列化为 JSON)
|
||||
/// </summary>
|
||||
private static string GetNewDataFromActionArguments(ActionExecutingContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 查找请求体中的 DTO 对象(排除简单类型)
|
||||
foreach (var arg in context.ActionArguments)
|
||||
{
|
||||
if (arg.Value == null) continue;
|
||||
var type = arg.Value.GetType();
|
||||
if (type.IsClass && type != typeof(string) && !type.IsPrimitive)
|
||||
{
|
||||
return JsonSerializer.Serialize(arg.Value, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = false,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Warn(ex, "审计日志:序列化新数据失败");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 Action 返回结果中尝试获取新记录 ID
|
||||
/// </summary>
|
||||
private static int? GetRecordIdFromResult(ActionExecutedContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (context.Result is ObjectResult objResult && objResult.Value != null)
|
||||
{
|
||||
// 尝试从返回对象中获取 data 或 result 属性
|
||||
var resultType = objResult.Value.GetType();
|
||||
var dataProp = resultType.GetProperty("Data") ?? resultType.GetProperty("data");
|
||||
if (dataProp != null)
|
||||
{
|
||||
var dataVal = dataProp.GetValue(objResult.Value);
|
||||
if (dataVal is int intVal && intVal > 0) return intVal;
|
||||
if (int.TryParse(dataVal?.ToString(), out var parsed) && parsed > 0) return parsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断操作来源:App 端或管理后台端
|
||||
/// 优先从自定义 Header "X-Source-Client" 判断,
|
||||
/// 其次从 User-Agent 判断(包含 uni-app 或移动端标识则为 App)
|
||||
/// </summary>
|
||||
private static string GetSourceClient(HttpContext httpContext)
|
||||
{
|
||||
// 优先检查自定义 Header
|
||||
if (httpContext.Request.Headers.TryGetValue("X-Source-Client", out var sourceHeader))
|
||||
{
|
||||
var val = sourceHeader.ToString().Trim();
|
||||
if (!string.IsNullOrEmpty(val))
|
||||
{
|
||||
return val.Equals("App", StringComparison.OrdinalIgnoreCase) ? "App" : "Admin";
|
||||
}
|
||||
}
|
||||
|
||||
// 从 User-Agent 判断
|
||||
var userAgent = httpContext.Request.Headers["User-Agent"].ToString();
|
||||
if (!string.IsNullOrEmpty(userAgent))
|
||||
{
|
||||
if (userAgent.Contains("uni-app", StringComparison.OrdinalIgnoreCase)
|
||||
|| userAgent.Contains("Android", StringComparison.OrdinalIgnoreCase)
|
||||
|| userAgent.Contains("iPhone", StringComparison.OrdinalIgnoreCase)
|
||||
|| userAgent.Contains("iPad", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "App";
|
||||
}
|
||||
}
|
||||
|
||||
// 默认为管理后台
|
||||
return "Admin";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询现有记录数据并序列化为 JSON(用于 OldData)
|
||||
/// 通过 SqlSugar 直接查询对应表
|
||||
/// </summary>
|
||||
private async Task<string> GetExistingDataJson(string tableName, int recordId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var db = DbScoped.SugarScope;
|
||||
if (db == null) return null;
|
||||
|
||||
var data = await db.Queryable<dynamic>()
|
||||
.AS(tableName)
|
||||
.Where("Id = @id", new { id = recordId })
|
||||
.FirstAsync();
|
||||
|
||||
if (data == null) return null;
|
||||
|
||||
return JsonSerializer.Serialize(data, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = false,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Warn(ex, $"审计日志:查询表 {tableName} 记录 {recordId} 的旧数据失败");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -62,8 +62,6 @@ builder.Services.AddJwt();
|
|||
builder.Services.AddSingleton(new AppSettings(builder.Configuration));
|
||||
//app服务注册
|
||||
builder.Services.AddAppService();
|
||||
// 注册 ODF 审计日志 Filter(ServiceFilter 需要 DI 注入)
|
||||
builder.Services.AddScoped<ZR.Admin.WebApi.Filters.OdfAuditLogFilter>();
|
||||
var wxMp = builder.Configuration.GetSection("WxMp");
|
||||
var mp_appid = wxMp.GetSection("AppID").Value ?? "";//默认值
|
||||
var mp_appSecret = wxMp.GetSection("AppSecret").Value ?? "";//默认值
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@
|
|||
<el-input
|
||||
type="textarea"
|
||||
:rows="15"
|
||||
:model-value="translateJson(detailRow.oldData, detailRow.tableName)"
|
||||
:model-value="formatJson(detailRow.oldData)"
|
||||
readonly />
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
|
|
@ -100,7 +100,7 @@
|
|||
<el-input
|
||||
type="textarea"
|
||||
:rows="15"
|
||||
:model-value="translateJson(detailRow.newData, detailRow.tableName)"
|
||||
:model-value="formatJson(detailRow.newData)"
|
||||
readonly />
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
|
@ -197,81 +197,6 @@ function handleViewDetail(row) {
|
|||
detailVisible.value = true
|
||||
}
|
||||
|
||||
// 字段名中文映射(按表名分组,通用字段放 _common)
|
||||
const fieldLabelMap = {
|
||||
_common: {
|
||||
id: 'ID',
|
||||
name: '名称',
|
||||
deptId: '部门ID',
|
||||
deptName: '所属公司',
|
||||
createdAt: '创建时间',
|
||||
updatedAt: '修改时间',
|
||||
remarks: '备注说明',
|
||||
status: '连接状态',
|
||||
latitude: '纬度',
|
||||
longitude: '经度',
|
||||
userId: '用户ID',
|
||||
personnel: '责任人',
|
||||
},
|
||||
odf_ports: {
|
||||
roomId: '机房ID',
|
||||
roomName: '机房名称',
|
||||
rackId: '机架ID',
|
||||
rackName: '机架名称',
|
||||
frameId: '框ID',
|
||||
frameName: '框名称',
|
||||
rowNumber: '行号',
|
||||
portNumber: '端口号',
|
||||
opticalAttenuation: '光衰值(dB)',
|
||||
historyRemarks: '历史故障',
|
||||
opticalCableOffRemarks: '光缆断信息',
|
||||
equipmentModel: '设备型号',
|
||||
businessType: '业务类型',
|
||||
portSide: '端口侧',
|
||||
rackType: '机架类型',
|
||||
},
|
||||
odf_rooms: {
|
||||
roomName: '机房名称',
|
||||
roomAddress: '机房位置',
|
||||
racksCount: '机架数量',
|
||||
orderby: '排序',
|
||||
},
|
||||
odf_racks: {
|
||||
roomId: '机房ID',
|
||||
sequenceNumber: '序号',
|
||||
rackName: '机架名称',
|
||||
frameCount: '框数量',
|
||||
rackType: '机架类型',
|
||||
},
|
||||
odf_frames: {
|
||||
rackId: '机架ID',
|
||||
portsName: '框名称',
|
||||
sequenceNumber: '序号',
|
||||
portsCount: '端口数量',
|
||||
portsCol: '列数量',
|
||||
portsRow: '行数量',
|
||||
updateAt: '修改时间',
|
||||
},
|
||||
odf_cables: {
|
||||
cableName: '光缆名称',
|
||||
},
|
||||
odf_cable_faults: {
|
||||
cableId: '光缆ID',
|
||||
faultTime: '故障时间',
|
||||
faultReason: '故障原因',
|
||||
mileage: '表显故障里程',
|
||||
mileageCorrection: '表显里程矫正',
|
||||
location: '地点描述',
|
||||
remark: '备注',
|
||||
faultCount: '故障频次',
|
||||
},
|
||||
odf_marker_poles: {
|
||||
cableId: '光缆ID',
|
||||
recordTime: '记录时间',
|
||||
actualMileage: '实际里程',
|
||||
},
|
||||
}
|
||||
|
||||
// 表名中文映射
|
||||
const tableNameMap = {
|
||||
odf_ports: '端口',
|
||||
|
|
@ -283,24 +208,6 @@ const tableNameMap = {
|
|||
odf_marker_poles: '标石/杆号牌',
|
||||
}
|
||||
|
||||
// 将 JSON 中的字段名替换为中文标签
|
||||
function translateJson(jsonStr, tableName) {
|
||||
if (!jsonStr) return '(无数据)'
|
||||
try {
|
||||
const obj = JSON.parse(jsonStr)
|
||||
const tableMap = fieldLabelMap[tableName] || {}
|
||||
const commonMap = fieldLabelMap._common
|
||||
const translated = {}
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
const label = tableMap[key] || commonMap[key] || key
|
||||
translated[label] = value
|
||||
}
|
||||
return JSON.stringify(translated, null, 2)
|
||||
} catch {
|
||||
return jsonStr
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化 JSON 字符串
|
||||
function formatJson(jsonStr) {
|
||||
if (!jsonStr) return '(无数据)'
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user