1 外部控制
zhbaor edited this page 2025-07-18 15:38:42 +08:00

mower-ng 控制核心机制文档

本文档聚焦 lockfile.json 和 Web API 的核心控制机制。

1. lockfile 工作机制

1.1 文件结构

lockfile.json 是一个简单的 JSON 文件,包含三个关键信息:

{
  "pid": 12345,       // 当前运行的 mower-ng 进程 ID
  "port": 5000,       // HTTP 服务使用的端口号
  "token": "abc123"   // API 访问认证令牌
}

1.2 生命周期

lockfile 在 mower-ng 进程启动时自动创建,记录当前进程的关键信息。当进程正常退出时,lockfile 会被自动删除。这个文件只在进程运行时存在。

1.3 文件位置

lockfile 始终存放在当前运行的 mower-ng 实例配置目录下,文件名固定为 lockfile.json。例如当配置目录为 ~/mower-profiles/default 时,lockfile 的完整路径就是 ~/mower-profiles/default/lockfile.json

1.4 异常情况处理

lockfile 的存在并不总是意味着 mower-ng 进程正在正常运行。当进程因意外情况(如系统崩溃、强制终止或程序错误)而退出时,lockfile 可能会残留在系统中。这种情况下,我们需要通过以下验证流程来判断进程的真实状态:

首先,检查 lockfile 中记录的 PID 对应的系统进程是否仍然存在。如果进程不存在,说明 mower-ng 已经异常退出,lockfile 是残留文件。

如果进程存在,我们需要进一步验证它是否是真正的 mower-ng 实例:

  • 检查进程的执行文件路径是否匹配 mower-ng 的主程序
  • 确认进程的命令行参数中包含的配置目录路径与当前 lockfile 所在目录一致

当出现以下情况时,可以安全删除残留的 lockfile:

  • 系统进程不存在(mower-ng 已退出)
  • 进程存在但不是 mower-ng 实例(PID 被其他进程占用)
  • 配置目录路径不匹配当前实例(可能是其他实例的残留)

这种验证机制有效处理了多种异常场景:

  • 系统意外断电导致进程退出但 lockfile 残留
  • 用户手动强制终止进程后未清理文件
  • 不同 mower-ng 实例的 PID 偶然重复造成混淆

以下是实现该验证逻辑的 Python 代码示例(参考 webui.py 中的实现):

import json
import os
import psutil

def validate_lockfile(lockfile_path, current_profile_path):
    """验证 lockfile 的有效性"""
    if not os.path.exists(lockfile_path):
        return False, "lockfile 不存在"
    
    try:
        # 读取 lockfile 内容
        with open(lockfile_path, "r", encoding="utf-8") as f:
            lock_data = json.load(f)
        
        pid = lock_data["pid"]
        
        # 检查进程是否存在
        if not psutil.pid_exists(pid):
            return False, f"进程 {pid} 不存在"
        
        # 获取进程详细信息
        process = psutil.Process(pid)
        cmdline = process.cmdline()
        
        # 验证执行文件路径
        current_file_path = os.path.abspath(__file__)
        running_file_path = os.path.abspath(cmdline[1])
        if current_file_path != running_file_path:
            return False, "执行文件路径不匹配"
        
        # 验证配置目录路径
        if len(cmdline) >= 3:  # 确保有配置目录参数
            running_profile_path = os.path.abspath(cmdline[2])
            if current_profile_path != running_profile_path:
                return False, "配置目录路径不匹配"
        
        return True, "验证通过"
    
    except Exception as e:
        return False, f"验证过程中出错: {str(e)}"

# 使用示例
lockfile_path = "/path/to/profile/lockfile.json"
current_profile = "/path/to/profile"

is_valid, message = validate_lockfile(lockfile_path, current_profile)
if not is_valid:
    print(f"lockfile 无效: {message}")
    # 安全删除残留的 lockfile
    os.remove(lockfile_path)

2. Web API 控制接口

2.1 认证机制

所有请求需携带 header:

headers = {"token": lockfile["token"]}

认证规则说明

  • 需要认证的接口:所有控制类接口(进程控制、调度器控制、配置修改等)
  • 无需认证的接口:状态查询、日志流等只读接口
  • 认证失败:返回 HTTP 403 状态码

2.2 核心端点说明

2.2.1 进程控制

GET /exit
  • 功能:安全停止进程
  • 状态转换:STOPPING → STOPPED
  • 响应格式:text/plain ("true"表示成功)
  • 认证要求:需要 token

2.2.2 调度器控制

GET /start
  • 功能:启动任务调度
  • 状态转换:STOPPED → RUNNING
  • 认证要求:需要 token
GET /stop
  • 功能:停止任务调度
  • 状态转换:RUNNING → STOPPING → STOPPED
  • 认证要求:需要 token

2.2.3 状态查询

GET /scheduler
  • 认证要求:无需 token

响应示例:

{
  "idle": false,
  "next_time": "2025-07-18T12:34:56+08:00"
}

返回值说明

thread_status 状态 状态说明 idle 字段值 next_time 字段值
STOPPED 调度器已停止 true null
IDLE 调度器空闲等待中 true 下一个任务时间(非null)
RUNNING 调度器正在运行 false 下一个任务时间(非null)
STOPPING 调度器正在停止中 false 可能为非null(如果队列中还有任务)或 null(如果队列已空)

2.3 实时日志流

WebSocket /ws
  • 认证要求:无需 token

消息格式:

{
  "type": "log",
  "data": "2025-07-18 11:45:23 [INFO] 开始执行基建任务"
}