Skip to content

Latest commit

 

History

History
344 lines (269 loc) · 12.3 KB

File metadata and controls

344 lines (269 loc) · 12.3 KB

PCState 技术文档

1. 项目概述

PCState 是一个运行在 Windows 系统托盘中的小工具,记录用户每天使用电脑的时间分布和各应用的运行时长。

核心功能

  • 空闲检测:通过 Windows API 检测用户是否有键鼠操作
  • 活动记录:每分钟记录活跃状态和预处理后的程序名
  • 数据可视化:Flask 后端 + React 前端,API 驱动的图表展示
  • 在线配置:Web 页面可直接修改黑名单、显示名映射、网站关键词等
  • 托盘运行:最小化到系统托盘,开机自启

2. 项目结构

pcstate/
├── main.py                   # 程序入口
├── build.py                  # 打包脚本
├── version.py                # 版本号 (如 3.0.1.0)
├── requirements.txt          # Python 依赖
│
├── src/                      # Python 后端模块
│   ├── main.py               # 托盘程序 + 主循环 + Flask 生命周期管理
│   ├── detector.py            # 空闲检测 + 活动窗口信息 (Windows API)
│   ├── preprocessor.py       # 数据预处理(显示名映射、网站嗅探、IDE/Office识别)
│   ├── sqlite.py             # SQLite 存储层 + 配置管理
│   ├── config.py             # 配置管理封装
│   ├── chart_data.py         # 图表数据聚合(按日期区间+图表类型构建数据)
│   ├── api_server.py         # Flask API 服务(图表API + 配置API + 静态托管)
│   ├── exporter_csv.py       # CSV 导出
│   ├── startup_manager.py    # 开机启动管理
│   ├── notifier.py           # Windows Toast 通知
│   └── utils.py              # 路径工具函数
│
├── frontend/                 # React 前端源码
│   └── src/
│       ├── main.tsx           # 前端入口 (dayjs locale + antd ConfigProvider)
│       ├── components/
│       │   ├── App.tsx         # 主界面
│       │   ├── ConfigPanel.tsx # 配置面板
│       │   ├── HeatmapChart.tsx
│       │   ├── AppPieChart.tsx
│       │   ├── AppBarChart.tsx
│       │   ├── MultiDayHeatmapChart.tsx
│       │   ├── MultiDayPieChart.tsx
│       │   └── MultiDayBarChart.tsx
│       ├── dataProcessor.ts   # 日期工具函数
│       └── index.css
│   ├── vite.config.ts        # Vite 配置 (singlefile 插件)
│   └── package.json
│
├── viewer/                   # 前端构建产物 (单文件 index.html)
├── public/                   # 静态资源 (图标 .ico)
└── pcstate.db                # SQLite 数据库 (运行时生成)

3. 架构概览

┌─────────────────────────────────────────────────────────────────┐
│  check_and_report() [60s循环]                                    │
│    detector → preprocessor → sqlite.write()                       │
│                                                                  │
│  Flask 线程 (懒启动, 10分钟空闲超时自动关闭)                       │
│    GET  /            → viewer/index.html                         │
│    GET  /api/chart   → chart_data 聚合 → JSON                    │
│    GET  /api/config  → DB 配置 → JSON                             │
│    PUT  /api/config/* → 更新 DB + 重载 Preprocessor               │
│    GET  /api/version → 版本号                                    │
│                                                                  │
│  React 前端                                                      │
│    fetch API → 渲染图表 / 配置管理                                │
└─────────────────────────────────────────────────────────────────┘

4. 核心模块详解

4.1 主循环 (main.py)

程序启动后:

  1. 从 DB 加载预处理配置,初始化 Preprocessor 全局实例
  2. 创建系统托盘图标
  3. 启动 check_and_report() 采集线程(daemon)
  4. 进入 PumpMessages() 等待托盘事件

首次点击"查看报表"时懒启动 Flask 服务线程(daemon,10分钟无请求自动关闭)。

采集循环

def check_and_report():
    while running:
        idle_time = detector.get_idle_duration()
        is_active = idle_time < 60
        window_title, process_name = detector.get_active_window_info()
        check_time = datetime.now() - timedelta(minutes=1)
        # 预处理:显示名映射 + IDE/Office/浏览器识别
        base_name = process_name[:-4] if ... else process_name
        if base_name not in _blacklist:
            prog_name = get_global().process(process_name, window_title)
            backend.write(check_time.hour, check_time.minute, is_active, prog_name, check_time.date())
        time.sleep(60)

4.2 空闲检测 (detector.py)

调用 Windows API:

  • GetLastInputInfo — 获取最后输入时间
  • GetTickCount64 — 当前系统运行时间
  • GetForegroundWindow + GetWindowThreadProcessId + GetModuleFileNameEx — 前台窗口信息

4.3 数据预处理 (preprocessor.py)

Preprocessor 类,在采集-入库之间处理进程名+窗口标题:

步骤 说明 示例
去 .exe 规范化进程名 WINWORD.EXEWINWORD
显示名映射 查 DB 配置表 WINWORDWord
IDE 增强 解析 VSCode 标题中的项目名 Code.exe + "App.tsx - pcstate - VSCode"VSCode - pcstate
Office 增强 解析标题中的文件名 WINWORD.EXE + "报告.docx - Word"Word - 报告.docx
浏览器嗅探 窗口标题关键字匹配网站名 chrome.exe + "知乎 - Chrome"Chrome浏览器 - 知乎

配置(display_names, site_names, blacklist)存储在 DB config 表,可通过 Web UI 修改后即时生效(reload_global())。

4.4 数据库存储 (sqlite.py)

表结构

CREATE TABLE activity (
    time INTEGER PRIMARY KEY,    -- 分钟级 Unix 时间戳
    is_active INTEGER NOT NULL,  -- 0/1
    prog_name TEXT,              -- 预处理后的程序名
    win_title TEXT               -- 保留字段(已废弃)
);

CREATE TABLE config (
    key TEXT PRIMARY KEY,        -- 配置键
    value TEXT                   -- 配置值 (JSON 或 简单值)
);

核心接口

  • write(hour, minute, is_active, prog_name, date) — 写入记录
  • get_slots(date) — 288 槽位活跃数据
  • get_hourly_app_durations(date) — 每小时应用数据
  • get_config(key) / set_config(key, value) — 键值配置
  • get_config_json(key) — JSON 配置反序列化
  • load_preprocessor_config() — 加载预处理全套配置

Config 表存储的键

类型 说明
day_start_hour int 一天起始小时 (0/4)
timezone int 时区偏移
display_names JSON [{prog_name, display_name}]
site_names JSON ["知乎","GitHub",...]
blacklist JSON ["LockApp","LogonUI"]

4.5 图表数据聚合 (chart_data.py)

根据图表类型和日期区间,从 DB 查询并按需聚合:

函数 单日输出 多日输出
build_heatmap {slots: [288]} {hourlyActivity: N×24, days: [...]}
build_pie {appTotals: {...}} {appTotals: {...}}
build_bar {hourlyAppData: [24], ...} {hourlyAppData: N×24, ...}

所有函数自动处理 dayStartHour 跨日拼接和 <5% 小应用合并。

4.6 Flask API 服务 (api_server.py)

懒启动模式:首次调用 open_viewer() 时才创建 daemon 线程启动 Flask。10 分钟无请求自动调用 server.shutdown() 关闭。

端点

方法 路径 说明
GET / 托管前端页面
GET /api/chart?start=&end=&type= 图表数据
GET /api/config 获取全部配置
PUT /api/config/displayNames 更新显示名映射
PUT /api/config/siteNames 更新网站关键词
PUT /api/config/blacklist 更新黑名单
PUT /api/config/dayStartHour 更新一天起始时间
GET /api/version 获取版本号

4.7 开机启动 (startup_manager.py)

通过 Windows 启动文件夹管理快捷方式,自动检测并修复指向旧版本的快捷方式。


5. 前端架构

技术栈

  • React 18 + TypeScript
  • Ant Design 5 (DatePicker, Segmented, Collapse, Result, Tooltip, notification)
  • ECharts 6 (热力图 custom series、饼图、柱状图)
  • Vite 5 + vite-plugin-singlefile (单文件 HTML 输出)
  • dayjs (日期处理,周一为周首日)

数据流

Flask localhost:21520
    │
    │  fetch /api/chart?start=&end=&type=
    ▼
App.tsx (useState: dateRange + chartType)
    │
    ├── chartData → 单日组件 (HeatmapChart/AppPieChart/AppBarChart)
    └── chartData → 多日组件 (MultiDay*)

组件结构

App.tsx
├── 标题栏 + 配置模式按钮
├── 选择区域卡片(日期区间 + 图表类型,仅在报表模式)
│   ├── RangePicker + InfoTip
│   └── Segmented + InfoTip
├── 配置面板卡片(仅在配置模式)
│   └── ConfigPanel
│       ├── Collapse
│       │   ├── 一天起始时间 (Radio.Button, 即时生效)
│       │   ├── 进程黑名单 (TextArea + 保存)
│       │   ├── 显示名映射 (Input 对 + 增删 + 保存)
│       │   └── 网站名关键词 (TextArea + 保存)
│       └── 返回报表按钮
├── 图表区域卡片
│   ├── 图标题
│   ├── 图表主体 (单日/多日组件)
│   └── 图说明 (关键数据 + 说明文字)
└── 页脚 (版本号 + 项目主页)

配置管理

配置模式下通过 Collapse 折叠面板管理四项设置:

  • 一天起始时间:Radio 选择,修改即时调 PUT API 保存
  • 黑名单 / 显示名 / 网站名:本地缓存编辑,点击"保存设置"调 PUT API
  • 保存成功后 antd notification.success 反馈

6. 打包与发布

构建命令

# 完整构建
python build.py --release

# 跳过前端构建 (仅重打包 Python)
python build.py --skip-frontend

构建流程

  1. 版本同步: build.py 读取 version.py,同步到 package.json
  2. 前端构建: npm run buildviewer/index.html(单文件)
  3. Python 打包: PyInstaller 打包为单文件 exe
  4. 生成发布: 复制到 release/pcstate-{版本}/

PyInstaller 配置

--name=PCStateMonitor
--onefile
--windowed
--add-data=viewer;viewer
--add-data=src;src
--add-data=public;public

7. 运行时文件

程序目录/
├── pcstate.db               # SQLite 数据库 (活动记录 + 配置)
└── PCStateMonitor.exe       # 主程序 (单文件)

不再生成 temp/ 目录。前端页面由 Flask 直接托管。


8. 关键设计决策

8.1 为什么用 Flask API 而非静态文件注入?

  • 数据按需拉取,无需每次打开页面都重新生成全量数据
  • 后端可做聚合运算,前端只负责渲染
  • API 端点可复用(配置管理、版本号等)
  • 懒启动 + 超时自毁,不占用资源

8.2 为什么预处理放在采集-入库之间?

  • 存储层不应包含业务逻辑
  • 预处理后入库,查询时无需重复解析
  • 配置变更后 reload_global() 即时生效

8.3 为什么配置表存 JSON?

  • 灵活扩展,无需修改表结构
  • 一条 SQL 读写完整配置
  • 前端通过 REST API 直接操作

9. 开发指南

本地运行

pip install -r requirements.txt
cd frontend && npm install && npm run build && cd ..
python main.py

添加新图表类型

  1. chart_data.py 中添加 build_xxx() 函数
  2. api_server.pybuilders 字典中注册
  3. 在前端创建图表组件并在 App.tsx 中渲染

添加新配置项

  1. preprocessor.py 的默认数据中添加
  2. sqlite.py_init_db()INSERT OR IGNORE
  3. api_server.pyapi_set_config 中处理对应 key
  4. ConfigPanel.tsx 中添加 UI