diff --git a/.cursor/agents/backend-developer.md b/.cursor/agents/backend-developer.md
index e930cf0..3179144 100644
--- a/.cursor/agents/backend-developer.md
+++ b/.cursor/agents/backend-developer.md
@@ -58,7 +58,7 @@ model: fast
- 分页参数与逻辑在统计与列表间保持一致
### 数据库与文档
-- **表命名**:业务前缀 + 功能名(如 `lq_`);字段驼峰;时间用 DateTime
+- **表命名**:业务前缀 + 功能名;字段驼峰;时间用 DateTime
- **统计类 SQL**:提交前用 MCP MySQL 工具执行验证,确认语法、字段、JOIN、统计逻辑正确后再写入代码
### API 与接口
diff --git a/.cursor/rules/project_rules.mdc b/.cursor/rules/project_rules.mdc
index 337e9eb..c8e061f 100644
--- a/.cursor/rules/project_rules.mdc
+++ b/.cursor/rules/project_rules.mdc
@@ -87,7 +87,7 @@ Id = Guid.NewGuid().ToString()
## 🗄️ 数据库规范
### 命名规范
-- **表命名**: 业务前缀 + 功能名称 (如: lq_)
+- **表命名**: 业务前缀 + 功能名称
- **字段命名**: 驼峰化
- **时间字段**: 统一使用 DateTime 类型
diff --git a/.cursor/skills/api-interface-testing/SKILL.md b/.cursor/skills/api-interface-testing/SKILL.md
index ee331c0..6dec21a 100644
--- a/.cursor/skills/api-interface-testing/SKILL.md
+++ b/.cursor/skills/api-interface-testing/SKILL.md
@@ -11,11 +11,21 @@ description: 按项目规范执行接口测试,包含获取 Token、使用 cur
- 用户明确要求进行接口测试或提供测试示例
- 提交代码前确认接口符合「必须测试」规范
+## 执行方式(必须按此执行)
+
+当用户本地 API 已启动(如 `http://localhost:2011`)时,**必须直接使用 Shell 执行 curl 命令**进行接口测试,而不是只给出 curl 示例让用户自己执行。
+
+1. **登录获取 Token**:用 Shell 执行登录 curl,从返回 JSON 中解析 `data.token`
+2. **调用目标接口**:用 Shell 依次执行 curl,请求头带上 `Authorization: {data.token}`
+3. **数据库验证**:涉及增删改的接口,**必须**用 MCP MySQL 工具执行 SELECT 查库验证(遵循 `mcp-mysql-and-sql-validation` skill)
+4. **输出测试报告**:汇总 HTTP 状态、返回值、数据库校验结果,给出通过/不通过结论
+
## 测试流程
1. **获取 Token**:先调用登录接口拿到 `data.token`
2. **调用目标接口**:请求头带上 `Authorization: {data.token}`
3. **验证结果**:按下方清单检查返回值与行为
+4. **查库验证**:对新增/修改/删除类接口,用 MCP 执行 SQL 验证数据是否正确落库
## 获取 Token
@@ -69,3 +79,36 @@ curl -X POST "http://localhost:2011/api/xxx/YourAction" \
## 工具
可使用 curl、Postman、Swagger 等;给出示例时优先提供 **curl**,便于在终端直接执行。
+
+## 完整执行示例(Shell + MCP 查库)
+
+以下为 SbRecords StartViewRecord / EndViewRecord 接口的实测流程,**以后接口测试均按此方式执行**:
+
+1. **Shell 执行登录**,获取 token:
+ ```bash
+ curl -s -X POST "http://localhost:2011/api/oauth/Login" \
+ -H "Content-Type: application/x-www-form-urlencoded" \
+ -d "account=admin&password=66762a3ccde2a2cff3060d7a4a0a576b"
+ ```
+ 从返回 JSON 中提取 `data.token`,后续请求头使用 `Authorization: $TOKEN`
+
+2. **Shell 执行目标接口**(如 StartViewRecord):
+ ```bash
+ curl -s -X POST "http://localhost:2011/api/Extend/SbRecords/Actions/StartViewRecord" \
+ -H "Authorization: $TOKEN" \
+ -H "Content-Type: application/x-www-form-urlencoded" \
+ -d "reId=xxx&type=查看设备"
+ ```
+ 记录返回的 `data`(如 recordId)
+
+3. **Shell 执行后续接口**(如 EndViewRecord):
+ ```bash
+ curl -s -X POST "http://localhost:2011/api/Extend/SbRecords/Actions/EndViewRecord" \
+ -H "Authorization: $TOKEN" \
+ -H "Content-Type: application/x-www-form-urlencoded" \
+ -d "recordId=xxx"
+ ```
+
+4. **MCP 查库验证**:调用 `mcp_HongHua-JJ-my-sql-db_query` 执行 SELECT,校验关键字段(如 F_LeaveTime、F_DurationSeconds)是否正确写入
+
+5. **输出测试报告**:表格汇总各用例的预期 vs 实际、数据库校验结果,给出「通过/不通过」结论
diff --git a/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/SbRecordsEntity.cs b/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/SbRecordsEntity.cs
index 44840eb..e419647 100644
--- a/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/SbRecordsEntity.cs
+++ b/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/SbRecordsEntity.cs
@@ -1,4 +1,4 @@
-using NCC.Common.Const;
+using NCC.Common.Const;
using SqlSugar;
using System;
@@ -28,6 +28,18 @@ namespace NCC.Extend.Entitys
///
[SugarColumn(ColumnName = "F_AddTime")]
public DateTime? AddTime { get; set; }
+
+ ///
+ /// 离开时间
+ ///
+ [SugarColumn(ColumnName = "F_LeaveTime")]
+ public DateTime? LeaveTime { get; set; }
+
+ ///
+ /// 停留时长(秒)
+ ///
+ [SugarColumn(ColumnName = "F_DurationSeconds")]
+ public long DurationSeconds { get; set; }
///
/// 查看用户
diff --git a/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend.Entitys/NCC.Extend.Entitys.xml b/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend.Entitys/NCC.Extend.Entitys.xml
index d19ddb2..bdeea26 100644
--- a/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend.Entitys/NCC.Extend.Entitys.xml
+++ b/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend.Entitys/NCC.Extend.Entitys.xml
@@ -8728,6 +8728,16 @@
记录时间
+
+
+ 离开时间
+
+
+
+
+ 停留时长(秒)
+
+
查看用户
diff --git a/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/ISbRecordsService.cs b/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/ISbRecordsService.cs
index ec34379..eaebcad 100644
--- a/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/ISbRecordsService.cs
+++ b/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/ISbRecordsService.cs
@@ -1,6 +1,24 @@
-namespace NCC.Extend.Interfaces.SbRecords
+using System.Threading.Tasks;
+
+namespace NCC.Extend.Interfaces.SbRecords
{
public interface ISbRecordsService
{
+ ///
+ /// 开始一条设备查看会话记录(进入页面时调用)。
+ ///
+ /// 关联记录 Id,一般为设备 Id。
+ /// 记录类型,例如“查看设备”。
+ /// 备用字段1,可选。
+ /// 备用字段2,可选。
+ /// 返回新建记录的 Id(后续结束会话时使用)。
+ Task StartViewRecordAsync(string reId, string type, string item1 = null, string item2 = null);
+
+ ///
+ /// 结束一条设备查看会话记录(离开页面时调用),并计算停留时长。
+ ///
+ /// 开始会话时返回的记录 Id。
+ /// 返回是否更新成功。
+ Task EndViewRecordAsync(string recordId);
}
}
\ No newline at end of file
diff --git a/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend/SbRecordsService.cs b/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend/SbRecordsService.cs
index 9a90715..3600689 100644
--- a/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend/SbRecordsService.cs
+++ b/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend/SbRecordsService.cs
@@ -50,6 +50,80 @@ namespace NCC.Extend.SbRecords
}
///
+ /// 开始一条设备查看会话记录(进入页面时调用)。
+ ///
+ /// 关联记录 Id,一般为设备 Id。
+ /// 记录类型,例如“查看设备”。
+ /// 备用字段1,可选。
+ /// 备用字段2,可选。
+ /// 返回新建记录的 Id(后续结束会话时使用)。
+ [HttpPost("Actions/StartViewRecord")]
+ public async Task StartViewRecordAsync(string reId, string type, string item1 = null, string item2 = null)
+ {
+ var now = DateTime.Now;
+ var entity = new SbRecordsEntity
+ {
+ Id = YitIdHelper.NextId().ToString(),
+ ReId = reId,
+ AddTime = now,
+ LeaveTime = null,
+ DurationSeconds = 0,
+ AddUser = _userManager.UserId,
+ Enable = true,
+ Item1 = item1,
+ Item2 = item2,
+ Type = type
+ };
+
+ var isOk = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteCommandAsync();
+ if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
+
+ return entity.Id;
+ }
+
+ ///
+ /// 结束一条设备查看会话记录(离开页面时调用),并计算停留时长。
+ ///
+ /// 开始会话时返回的记录 Id。
+ /// 返回是否更新成功。
+ [HttpPost("Actions/EndViewRecord")]
+ public async Task EndViewRecordAsync(string recordId)
+ {
+ var now = DateTime.Now;
+
+ var entity = await _db.Queryable().FirstAsync(p => p.Id == recordId);
+ if (entity == null)
+ {
+ return false;
+ }
+
+ if (entity.AddTime == null)
+ {
+ entity.AddTime = now;
+ }
+
+ if (entity.LeaveTime != null)
+ {
+ return true;
+ }
+
+ entity.LeaveTime = now;
+ var seconds = (now - entity.AddTime.Value).TotalSeconds;
+ if (seconds < 0)
+ {
+ seconds = 0;
+ }
+
+ entity.DurationSeconds = (long)Math.Round(seconds, MidpointRounding.AwayFromZero);
+
+ var result = await _db.Updateable(entity)
+ .UpdateColumns(x => new { x.LeaveTime, x.DurationSeconds })
+ .ExecuteCommandAsync();
+
+ return result > 0;
+ }
+
+ ///
/// 获取查看历史记录
///
/// 参数