# Web_AutoNotificatPhone_Server_Prod

## 1. 项目概述

这是一个基于 **ASP.NET Core 8 MVC** 的通知与巡检服务，核心职责是：

1. 提供 HTTP API，接收“电话/短信”通知请求并发布到 Redis 频道。
2. 运行后台定时任务（`TimerClass`），每分钟做系统状态检查。
3. 根据 Redis 指标和 PostgreSQL 心跳数据触发自动告警（短信 + 电话）。

项目本质上是一个“**告警编排层**”：
- 输入来源：HTTP 请求、Redis 指标、PostgreSQL 心跳表。
- 输出目标：Redis 发布（供下游呼叫/短信系统消费）。

---

## 2. 技术栈与依赖

- .NET: `net8.0`
- Web: ASP.NET Core MVC + API Controller
- Redis: `CSRedisCore`
- PostgreSQL: `Npgsql`
- Logging: `NLog`

`AutoNotificatPhone.csproj` 关键包：
- `CSRedisCore`
- `NLog`
- `Npgsql`

---

## 3. 目录与核心文件

- `Program.cs`：应用启动、CORS、路由、HostedService 注册。
- `Controllers/CallAndMsgController.cs`：通知入站 API（电话/短信）。
- `Models/TimerClass.cs`：后台巡检与告警主流程。
- `CSRedisCacheHelper.cs`：Redis 连接与基础操作封装。
- `Models/SmsRequest.cs`：通知请求模型。
- `Models/ReturnInfo.cs`：统一接口返回结构。
- `appsettings.json`：日志级别、Postgres 连接配置。
- `nlog.config`：日志落盘规则。

---

## 4. 启动流程（程序生命周期）

### 4.1 Program 启动阶段

`Program.cs` 主要行为：

1. `AddControllersWithViews()`：注册 MVC 和 API 支持。
2. 注册 CORS 策略 `AllowAnyOrigin`：
   - 允许任意源
   - 允许任意方法
   - 允许任意请求头
3. `AddHostedService<TimerClass>()`：注册后台巡检服务。
4. 管道顺序：
   - `UseStaticFiles()`
   - `UseRouting()`
   - `UseCors("AllowAnyOrigin")`
   - `UseAuthorization()`
   - `MapControllerRoute(...)`

### 4.2 后台服务启动

应用启动后，`TimerClass` 自动运行：
- 持续循环，直到应用停止。
- 每分钟固定在第 30 秒执行一次巡检逻辑。

---

## 5. API 处理流程（CallAndMsgController）

## 5.1 路由

Controller 路由前缀：
- `api/CallAndMsg/{action}`

核心接口：
- `POST api/CallAndMsg/SendToPhone`

## 5.2 请求模型（SmsRequest）

- `PhoneNumber`：目标手机号
- `CallerName`：任务标识/来电显示名
- `Content`：消息内容
- `StartingPoint`：开始时间（Unix 秒）
- `DeadLine`：截止时间（Unix 秒）
- `Type`：`1` 电话，`2` 短信

## 5.3 接口执行逻辑

`SendToPhone` 内部流程：

1. 接收 `SmsRequest`。
2. 组装 `notification` 对象。
3. 使用固定频道 `MsgAndCall` 发布到 Redis：
   - `CSRedisCacheHelper.Publish("MsgAndCall", payload)`
4. 返回 `ReturnInfo`：
   - 成功：`status=200` + 根据 `Type` 返回“已加入队列”文案
   - 失败：`status=500` + 异常信息

> 说明：`ConvertTimestampToIso` 名称为“转 ISO”，但实际仍返回 Unix 秒。

---

## 6. Redis 封装（CSRedisCacheHelper）

## 6.1 连接

静态初始化时连接本地 Redis：
- Host: `127.0.0.1`
- Port: `10079`
- 密码：代码中硬编码
- DB0: `redis`
- DB1: `redis1`

## 6.2 用途

- `DB0`：主要用于发布通知频道（`MsgAndCall`）。
- `DB1`：主要用于读取巡检指标（CPU、在线数、收发包等）。

## 6.3 方法

- `Set/Get/Forever/Del/Contains`
- `Publish`

---

## 7. 核心：TimerClass 业务流程详解

`TimerClass` 是项目核心，分为 **调度层**、**检查层**、**告警层**。

## 7.1 调度层（每分钟触发）

主循环执行顺序：

1. 计算下次执行时间（每分钟第 30 秒）。
2. 检查电话机进程是否在线（进程名 `Telephone`）。
3. 运行整点任务调度。
4. 依次执行检查任务：
   - CPU 阈值
   - RCU 在线数量
   - 总发送包
   - 总接收包
   - PostgreSQL 心跳

出现异常时：
- 记录日志
- 延迟 10 秒重试

## 7.2 整点任务调度

方法：`RunHourlyNotificationTask()`

规则：
- 使用北京时间（UTC+8）。
- 非整点（`Minute != 0`）不执行。
- 同一整点只执行一次（`_executedTasks` 去重）。
- 10/15/22 点执行每日任务，其余整点发整点短信。

### 7.2.1 整点短信（SendHourlySms）

- 给 `Mobile1` 发送一条短信任务。
- 文案包含当前“月/日/时”。

### 7.2.2 每日任务（ExecuteDailyTask）

- 给 `Mobile1` 和 `Mobile2` 发送短信。
- 给 `Mobile1` 发送电话任务。
- 任意发送失败记录错误日志。

## 7.3 Redis 指标检查

### 7.3.1 CPU 检查（CheckCpuThreshold）

步骤：
1. 读取 `UDPPackage_DetectTime`：
   - 若超过 10 分钟未更新，触发“监控程序无法访问”告警。
2. 读取：
   - `UDPPackage_CPUMax`
   - `UDPPackage_CPUAvg`
   - `UDPPackage_CPUMin`
3. 若 `CPUAvg` 中满足“>=80 的点数达到 6 个”，触发 CPU 告警。

### 7.3.2 通用 Redis 规则（CheckRedisValue）

用于在线数/发送包/接收包的基线跌落判断：

1. 读取 CSV 序列。
2. 取前 `baselineCount` 个点做平均。
3. 阈值 = 平均值 × `thresholdRatio`。
4. 若后续两个点都低于阈值，触发告警。

具体调用：
- `RCUOnLine`：`baselineCount=8`，`ratio=0.8`
- `UDPPackage_TotalSendPackage`：`baselineCount=8`，`ratio=0.6`
- `UDPPackage_TotalRecvPackage`：`baselineCount=8`，`ratio=0.75`

### 7.3.3 接收包特殊规则（CheckTotalRecvPackage）

在通用规则之前，先判断：
- 若最后 3 个值都 `< 70000`，立即触发告警。

## 7.4 PostgreSQL 心跳检查

方法：`CheckKafkaHeartbeatAsync()`

目标：判断“Kafka 入库是否活跃”。

流程：
1. 从 `appsettings.json` 的 `Postgres` 节生成连接串。
2. 查询 `heartbeat.heartbeat_events`：
   - 先按 `ts_ms desc` 取最近 3000 条
   - 再按 `write_ts_ms desc` 取最新一条
3. 结果为空或解析失败：走“数据库连接失败”告警路径。
4. 若 `nowMs - lastTsMs > KafkaStaleMinutes`：触发“入库停滞”告警。
5. 异常（连接失败/SQL 异常）同样走“数据库连接失败”告警。

### 7.4.1 数据库连接失败节流

- 字段：`_kafkaDbConnectionAlertCount`
- 逻辑：失败累计到 `KafkaDbAlertTriggerCount=8` 才真正发一次告警，之后计数清零。

---

## 8. 告警发送机制

统一通过 `SendAlert(...)`：

1. 计算过期时间：
   - 普通：短信 1800 秒、电话 900 秒
   - 扩展：短信 3600 秒、电话 1800 秒
2. 构造 3 条任务：
   - 短信：`Mobile1`
   - 短信：`Mobile2`
   - 电话：`Mobile1`
3. 调用 `CallAndMsgController.SendToPhone` 投递。
4. 任一失败，记录错误日志。

---

## 9. 配置说明

## 9.1 appsettings.json

当前包含：

- `Logging`
- `AllowedHosts`
- `Postgres`
  - `Host`
  - `Port`
  - `Database`
  - `User`
  - `Password`
  - `MaxConnections`
  - `IdleTimeoutMs`

## 9.2 nlog.config

- 文件落盘目录：`{basedir}/Logs/{shortdate}`
- 文件：`info_*.txt`、`error_*.txt`
- 当前规则：
  - `FATAL` 写入 info 文件
  - `Error` 及以上写入 error 文件

---

## 10. 快速运行

## 10.1 开发运行

```bash
dotnet restore
dotnet build AutoNotificatPhone.csproj
dotnet run --project AutoNotificatPhone.csproj
```

默认开发地址见 `Properties/launchSettings.json`：
- `http://localhost:5080`

## 10.2 VS Code Tasks

项目已配置任务：
- `build`
- `publish`
- `watch`

---

## 11. 对外接口示例

请求：

```json
POST /api/CallAndMsg/SendToPhone
Content-Type: application/json

{
  "PhoneNumber": "13500000000",
  "CallerName": "测试任务",
  "Content": "测试短信内容",
  "StartingPoint": 1760000000,
  "DeadLine": 1760001800,
  "Type": "2"
}
```

成功响应：

```json
{
  "isok": true,
  "message": "短信已加入发送队列",
  "status": 200,
  "response": "success"
}
```

---

## 12. 关键数据流（一句话版）

外部请求/定时巡检 -> 生成 `SmsRequest` -> `CallAndMsgController` -> Redis 频道 `MsgAndCall` -> 下游系统消费执行。

---

## 13. 当前实现中的注意点

1. `CallAndMsgController` 中 `ConvertTimestampToIso` 命名与实现含义不一致（返回 Unix 秒）。
2. `CSRedisCacheHelper` 中 Redis 密码硬编码在代码，不利于安全与环境切换。
3. `TimerClass` 的 `KafkaStaleMinutes` 与告警文案“3分钟内入库数据为0”存在语义不一致风险（建议统一）。
4. 当前 CORS 为完全放开，生产环境建议收敛来源。

---

## 14. 后续可优化方向（可选）

1. 将手机号、阈值、执行小时、告警文案全部配置化。
2. 让 `TimerClass` 通过接口服务发送通知，避免直接 new Controller。
3. 增加单元测试：
   - Redis 阈值计算
   - Kafka 心跳判定
   - 告警节流逻辑
4. 增加健康检查端点（数据库/Redis 连通性）。

---

## 15. 维护建议

每次修改巡检规则后，至少验证：

1. `dotnet build` 无编译错误。
2. Redis 有模拟数据时告警触发正确。
3. PostgreSQL 心跳超时/失败路径可复现。
4. `MsgAndCall` 频道消息格式未破坏下游兼容。
