Merge pull request #58 from Fairy-Oracle-Sanctuary/dev

Dev
This commit is contained in:
镀铬酸钾
2026-02-01 23:56:25 +08:00
committed by GitHub
8 changed files with 799 additions and 307 deletions

View File

@@ -14,15 +14,15 @@
### 核心特性 ### 核心特性
* **模块化插件架构**:所有功能都在 `plugins/` 目录 * **模块化插件架构**:所有功能都在 `plugins/` 目录,开发者可轻松扩展
* **极致性能优化** * **极致性能优化**
* **Python 3.14 JIT**pypy不支持那个浏览器扩展我只能用JIT了。。。 * **Python 3.14 JIT**运行时热点代码编译成机器码
* **Mypyc 编译**一些核心模块已经编译成机器码了 * **Mypyc AOT编译**:核心模块编译为C扩展
* **Playwright 页面池**:浏览器页面预热池 * **Playwright 页面池**:浏览器页面预热池,降低截图延迟
* **全局连接复用**HTTP 和 Redis 连接池化管理 * **全局连接复用**HTTP 和 Redis 连接池化管理
* **开发者友好**:完整的类型提示,清晰的 API 设计 * **开发者友好**:完整的类型提示,清晰的 API 设计
* **集成 Redis 缓存**缓存的都缓存了。群信息、用户信息、帮助图片 * **集成 Redis 缓存**:缓存帮助图片、权限数据、会话状态等
* **正向 WebSocket 连接**我只会支持正向WS连接。。。不要提意见,我不会听的。。。 * **正向 WebSocket 连接**支持正向WS连接模式Bot主动连接OneBot
### 技术栈 ### 技术栈
@@ -42,24 +42,40 @@
``` ```
. .
├── plugins/ # 插件目录,业务逻辑都在这 ├── plugins/ # 插件目录,业务逻辑都在这
│ ├── admin.py # 管理员指令 │ ├── admin.py # 权限管理Admin/User两级权限
│ ├── bili_parser.py # B站解析 (高性能版) │ ├── auto_approve.py # 自动同意好友请求和群邀请
│ ├── code_py.py # 代码沙箱 │ ├── bot_status.py # Bot运行状态查询图片形式
│ ├── echo.py # 复读机 │ ├── broadcast.py # 管理员专用广播功能
│ ├── forward_test.py # 合并转发测试 │ ├── code_py.py # Python代码沙箱执行
│ ├── jrcd.py # 今日运势 │ ├── echo.py # Echo/点赞功能
── thpic.py # 东方图片 ── furry.py # Furry图片获取
│ ├── github_parser.py # GitHub仓库链接解析
│ ├── jrcd.py # 今日人品/长度查询
│ ├── thpic.py # 东方Project随机图片
│ ├── web_parser/ # Web链接解析系统B站、抖音、GitHub等
│ └── sync_async_test_plugin.py # 异步同步混用测试插件
├── core/ # 框架核心,非请勿动 ├── core/ # 框架核心,非请勿动
│ ├── api/ # OneBot API 封装 │ ├── api/ # OneBot API 封装
│ ├── managers/ # 各种管理器 (指令, 浏览器, 图片, 插件) │ ├── handlers/ # 事件处理器
│ ├── managers/ # 各种管理器 (指令, 浏览器, 图片, 插件, 权限)
│ ├── utils/ # 工具函数 │ ├── utils/ # 工具函数
│ ├── ws.py # WebSocket 通信层 (已编译) │ ├── ws.py # WebSocket 通信层 (已编译)
── bot.py # Bot 实例 ── bot.py # Bot 实例
│ ├── config_loader.py # 配置加载
│ └── permission.py # 权限枚举
├── data/ # 数据存储 ├── data/ # 数据存储
│ ├── admin.json # 管理员名单 │ ├── admin.json # 管理员名单
│ └── permissions.json # 权限配置 │ └── permissions.json # 权限配置
├── templates/ # Jinja2 模板 ├── models/ # 数据模型
├── setup_mypyc.py # 编译脚本 │ ├── events/ # OneBot事件模型
│ ├── message.py # 消息段模型
│ ├── sender.py # 发送者信息
│ └── objects.py # API响应对象
├── templates/ # Jinja2模板用于图片生成
├── docs/ # 开发文档
├── tests/ # 单元测试
├── setup_mypyc.py # Mypyc编译脚本
├── config.toml # 配置文件
└── main.py # 启动入口 └── main.py # 启动入口
``` ```

View File

@@ -1,59 +1,81 @@
# # 架构设计
Neobot是面向内部开发者的我会开源但是写的很烂。。 NEO Bot 是一个现代化的、高性能的异步 QQ 机器人框架。本文介绍其核心架构和设计理念
## 1. 动力核心 ## 1. 性能优化体系
### Python 3.14 + JIT ### Python 3.14 JITJust-In-Time 编译)
镀铬酸钾创项目的时候用的 Python 3.14 3.14兼容JIT那就这样吧
* **何原理**: 运行时把热点代码编译成机器码Just-In-Time
* **何用途**: 密集CPU运算能提升一些尤其是插件里的循环和函数调用
* **怎么开**: 启动时加 `-X jit` 参数
### Mypyc 编译 (AOT) **原理**Python 3.14 内置 JIT 编译器,运行时将高频调用的代码编译成机器码。
光 JIT 还不够。。核心模块(`core/ws.py`, `core/managers/*.py`我编译成了C扩展
* **何原理**: 因为这个项目有很多类型提示然后我就编译成C库了。。。
* **何用途**: WS和manager下边的模块都是机器码运行或许会快一些。。。
### 异步 IO 模型 **适用场景**
* **Linux**: uvloop - 插件业务逻辑(循环、函数调用密集)
* **Windows**:IOCP - 消息处理流程
* **: `winloop` 死了,会和面具打架。。。
## 2. 连接模式 **启用方法**
### 正向 WebSocket 模式 ```bash
这是一种简单直接的模式 python -X jit main.py
* **主动出击 (Client)**: Bot 是个客户端
* **好处**: 你电脑能上网就行实际上是因为没公网ip哈。。。
```mermaid
graph LR
subgraph Local [你的电脑/服务器]
Bot[NEO Bot]
Browser[Playwright 页面池]
end
subgraph Remote [外部]
NapCat[NapCatQQ]
end
Bot -- "WebSocket (主动连接)" --> NapCat
Bot -- "内部调用" --> Browser
``` ```
## 3. 资源管理 预期性能提升2-5 倍(取决于代码热点)。
### Mypyc 编译AOT - Ahead-Of-Time
**原理**:将类型注解的 Python 代码编译为 C 扩展,生成平台相关的二进制文件。
**编译范围**
- `core/ws.py` - WebSocket 通信
- `core/managers/` - 各种管理器
- `core/api/` - API 封装
- `models/` - 数据模型
**启用方法**
```bash
python setup_mypyc.py build_ext --inplace
```
预期性能提升3-10 倍(核心模块)。
**注意**:编译产物平台相关,必须在目标环境编译。
### 异步 IO 模型
**Linux**`uvloop`libev 绑定,比 asyncio 快 2-4 倍)
**Windows**IOCPWindows 原生高性能 IO
## 2. 连接架构
### 正向 WebSocket 连接
NEO Bot 采用**正向 WebSocket 连接**模式Bot 主动连接 OneBot 实现(如 NapCatQQ
**流程**
```
Bot 启动 → 连接到 NapCatQQ (ws://127.0.0.1:3001)
监听消息事件
分发到处理器
调用 API 回复
```
## 3. 资源管理架构
### 单例管理器 ### 单例管理器
所有东西(指令、权限、浏览器、图片)都是全局独一份的。
* **随叫随到**: 在哪都能直接 `import`
* **绝对权威**: 全局就一份数据
### 资源池化 所有全局资源通过单例管理器统一管理,避免重复创建和资源泄漏。
别几把开多个实例。。。
* **Browser Pool**: 浏览器页面提前开好,用完洗干净放回去 ### Playwright 页面池
* **Connection Pool**: Redis 和 HTTP 请求都用连接池
预初始化页面,无需每次都启动浏览器,大幅降低延迟。
### HTTP 连接复用
全局 aiohttp.ClientSession 支持 Keep-Alive减少连接建立开销。
## 4. 技术栈全景 ## 4. 技术栈全景

View File

@@ -1,107 +1,240 @@
# 部署指南 # 生产环境部署
Bot 到服务器长期运行,比在自己电脑上跑要多几个步骤 将 NEO Bot 部署到服务器长期运行,只需要几个额外的步骤。本指南以 Linux 服务器为例
## 1. 环境准备 ## 1. 环境准备
### a. 安装 Python 3.14 ### a. 安装 Python 3.14
用3.14。。。 在 Linux 服务器上安装 Python 3.14 及开发工具:
### b. 安装依赖
```bash ```bash
# 切换到项目目录 # Ubuntu/Debian
cd /path/to/your/bot sudo apt update
sudo apt install python3.14 python3.14-venv python3.14-dev gcc
# 创建虚拟环境 (强烈建议) # CentOS/RHEL
sudo yum install python3.14 python3.14-devel gcc
```
### b. 克隆项目并创建虚拟环境
```bash
# 切换到项目目录(或新建)
cd /opt/neobot
git clone https://github.com/Fairy-Oracle-Sanctuary/NeoBot.git .
# 创建虚拟环境(强烈建议)
python3.14 -m venv venv python3.14 -m venv venv
source venv/bin/activate source venv/bin/activate
# 安装依赖 # 安装依赖
pip install -r requirements.txt pip install -r requirements.txt
playwright install chromium
``` ```
### c. 编译核心模块 (可选,但为获得最佳性能强烈建议) ### c. 编译核心模块(可选但强烈推荐)
为了最大化性能,你可以将项目中的核心 Python 模块编译成 C 语言扩展。这将大幅提升机器人的响应速度和处理效率。 为了最大化性能,建议在部署环境上编译 Mypyc 扩展:
```bash ```bash
# 确保你在虚拟环境 # 确保已激活虚拟环境
python setup_mypyc.py python setup_mypyc.py build_ext --inplace
``` ```
该脚本会自动编译 `core``models` 目录下的指定模块。编译后的文件(`.pyd``.so`)会直接生成在源码旁边 **注意**:编译产物是平台相关的,必须在目标服务器上执行。详见 [性能优化](../core-concepts/performance.md)
> **注意**: 编译产物是平台相关的(例如,在 Windows 上编译的 `.pyd` 文件不能在 Linux 上使用)。因此,**请务必在你最终部署的服务器环境(例如 Linux上执行此编译步骤**。更多关于 Mypyc 编译的细节,请参考 [性能优化详解](core-concepts/performance.md)。 ## 2. 进程管理
## 2. 使用进程管理器 直接运行 `python main.py` 然后关闭 SSH 会导致 Bot 停止。需要用进程管理器来守护 Bot。
你想直接 `python main.py` 然后关掉 SSH那机器人也跟着停了。必须用进程管理器来守护它 推荐使用 `systemd`Linux 原生方案)或 `pm2`
这里推荐用 `pm2`,虽然是 Node.js 的工具,但管 Python 程序一样好用。 ### 方案 Asystemd推荐
### a. 安装 pm2 创建 `/etc/systemd/system/neobot.service` 文件:
```ini
[Unit]
Description=NEO Bot Service
After=network.target redis.service
[Service]
Type=simple
User=bot
WorkingDirectory=/opt/neobot
ExecStart=/opt/neobot/venv/bin/python -X jit main.py
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
Environment="PYTHONUNBUFFERED=1"
[Install]
WantedBy=multi-user.target
```
然后启动服务:
```bash
sudo systemctl daemon-reload
sudo systemctl enable neobot
sudo systemctl start neobot
# 查看状态
sudo systemctl status neobot
# 查看日志
sudo journalctl -u neobot -f
```
### 方案 Bpm2
如果你习惯用 pm2Node.js 工具),也可以:
```bash ```bash
# 你需要先装 Node.js 和 npm
npm install pm2 -g npm install pm2 -g
``` ```
### b. 启动 Bot 创建 `ecosystem.config.js`
在项目根目录,创建一个 `ecosystem.config.js` 文件:
```javascript ```javascript
module.exports = { module.exports = {
apps : [{ apps : [{
name : "neobot", name : "neobot",
script : "main.py", script : "main.py",
interpreter: "/path/to/your/bot/venv/bin/python", // 指定虚拟环境里的 python interpreter: "/opt/neobot/venv/bin/python",
max_memory_restart: "500M", // 内存超过 500M 自动重启 args: "-X jit",
max_memory_restart: "512M",
env: { env: {
"PYTHONUNBUFFERED": "1" // 禁用 python 输出缓冲,日志能实时看 "PYTHONUNBUFFERED": "1"
} },
error_file: "./logs/pm2-error.log",
out_file: "./logs/pm2-out.log"
}] }]
} }
``` ```
然后启动: 启动:
```bash ```bash
pm2 start ecosystem.config.js pm2 start ecosystem.config.js
pm2 logs neobot
pm2 save
pm2 startup
``` ```
### c. 常用 pm2 命令 ## 3. 配置 OneBot 客户端
```bash Bot 使用 **正向 WebSocket 连接**,即 Bot 主动连接 OneBot 实现(如 NapCatQQ
pm2 list # 查看所有进程状态
pm2 logs neobot # 查看 neobot 的实时日志 `config.toml` 中配置:
pm2 restart neobot# 重启 neobot
pm2 stop neobot # 停止 neobot ```toml
pm2 delete neobot # 删除 neobot [napcat_ws]
# OneBot 客户端的 WebSocket 服务地址
uri = "ws://127.0.0.1:3001"
token = "your_token_here"
reconnect_interval = 5
``` ```
## 3. 配置 NapCatQQ ### NapCatQQ 配置示例
最后一步,修改 NapCatQQ 的配置文件,让它把消息推送到你的服务器上。 NapCatQQ 的 `config/onebot11.json` 中,启用正向 WebSocket 服务器
找到 NapCatQQ 的 `config/onebot11.json` 文件,修改 `ws_reverse_servers` 部分:
```json ```json
"ws_reverse_servers": [ {
{ "ws": {
"url": "ws://你的服务器IP:8080/onebot/v11/ws", "enable": true,
"access_token": "你的访问令牌" "host": "127.0.0.1",
} "port": 3001
] },
"token": "your_token_here"
}
``` ```
* `url`: 改成你服务器的 IP 和 `main.py` 里配置的端口 然后重启 NapCatQQBot 启动后应该能正常连接
* `access_token`: 如果你在 `main.py` 里设置了 `ACCESS_TOKEN`,这里要保持一致。
## 4. 扩展配置
或者你也可以用napcat的webui不多赘述了。。。 ### Redis 连接
确保 Redis 服务运行在可访问的地址,在 `config.toml` 配置:
改完后重启 NapCatQQBot 应该就能收到消息了。 ```toml
[redis]
host = "127.0.0.1"
port = 6379
db = 0
password = "redis_password" # 如果有密码
```
### Docker 代码沙箱(可选)
若要使用 code_py 插件,需要配置 Docker
```toml
[docker]
base_url = "unix:///var/run/docker.sock" # Linux socket
sandbox_image = "python-sandbox:latest"
timeout = 10
concurrency_limit = 5
```
## 5. 监控和日志
### 查看日志
日志文件位于 `logs/` 目录,使用 `tail` 实时查看:
```bash
tail -f logs/bot.log
```
### 监控系统资源
使用 systemd 时:
```bash
# 查看内存和 CPU 使用
systemctl status neobot
```
### 重启 Bot
```bash
# systemd
sudo systemctl restart neobot
# pm2
pm2 restart neobot
```
## 6. 常见问题
### Redis 连接失败
检查 Redis 是否运行:
```bash
redis-cli ping # 应返回 PONG
```
### Playwright 缓存问题
如果更新后图片渲染出现问题,清空 Playwright 缓存:
```bash
rm -rf ~/.cache/ms-playwright
playwright install chromium
```
### 内存持续增长
检查是否有内存泄漏。在 systemd 中添加内存限制:
```ini
[Service]
MemoryLimit=512M
MemoryAccounting=yes
```

View File

@@ -1,30 +1,24 @@
# 快速上手 # 快速上手
runit
## 1. 你需要准备 ## 1. 你需要准备
* **Python 3.14**: 必须是这个版本别问我为什么。。。 * **Python 3.14**必须是这个版本JIT编译需要
* **Git**: 拉取代码 * **Git**拉取代码
* **Redis**: 装一个 * **Redis**:缓存和权限管理,需要单独安装
* **脑子和手**: 这个最重要,或者你去问问镀铬酸钾,会给你一对一教学的。。。 * **Docker** (可选)用于代码沙箱执行code_py插件
* **OneBot v11 客户端**: 机器人本体,推荐用 [NapCatQQ](https://github.com/NapNeko/NapCatQQ) * **OneBot v11 客户端**机器人本体,推荐用 [NapCatQQ](https://github.com/NapNeko/NapCatQQ)
## 2. 搭起来 ## 2. 搭环境
### a. 克隆代码 ### a. 克隆代码
找个你喜欢的地方,把代码从 GitHub 上clone下来
```bash ```bash
git clone [项目仓库地址] git clone https://github.com/Fairy-Oracle-Sanctuary/NeoBot.git
cd [项目目录] cd NeoBot
``` ```
### b. 创建虚拟环境 ### b. 创建虚拟环境
别把你的系统环境搞得乱七八糟
```bash ```bash
# Windows # Windows
python -m venv venv python -m venv venv
@@ -39,17 +33,12 @@ source venv/bin/activate
### c. 安装依赖 ### c. 安装依赖
```bash ```bash
pip install -r requirements.txt pip install -r requirements.txt
``` ```
这个文件里包含了所有需要的 Python 库,比如 `aiohttp` (HTTP 请求), `orjson` (JSON 解析), `jinja2` (模板渲染), `psutil` (系统监控) 等等。
### d. 安装 Playwright 依赖 ### d. 安装 Playwright 依赖
我们用 Playwright 来截图画画,它需要一个浏览器核心。
```bash ```bash
playwright install chromium playwright install chromium
``` ```

View File

@@ -1,39 +1,38 @@
# NEO Bot 开发文档 # NEO Bot 开发文档
嘿,朋友,欢迎来到 NEO Bot 欢迎来到 NEO Bot Framework 开发文档!
里没那么多规矩。这份文档是我写给你——未来的插件开发者、或者单纯好奇想拆开看看的家伙——的一份地图 是一个现代化的 Python QQ 机器人框架,基于 OneBot v11 协议,采用异步架构和性能优化技术。无论你是想快速搭建机器人,还是深入了解框架设计,这份文档都能帮助你。
## 📖 地图导览 ## 📖 文档导览
### 1. 准备阶段 ### 🚀 快速开始
* [快速上手](./getting-started.md): 搭环境、装东西、启动。跟着走一遍,能省不少事。 * [快速上手](./getting-started.md) - 5分钟搭建开发环境
* [项目怎么样](./project-structure.md): 看看各个文件夹都是干嘛的,免得迷路。 * [项目结构](./project-structure.md) - 了解代码组织方式
* [生产环境](./deployment.md): 怎么把你调教好的 Bot 扔服务器上,让它自己 7x24 小时跑。 * [生产部署](./deployment.md) - 将Bot部署到服务器
### 2. 核心探秘 ### 💡 核心概念
* [](./core-concepts/architecture.md): 看看镀铬酸钾和python打架嗯。。。 * [构设计](./core-concepts/architecture.md) - 了解框架的设计理念
* [性能优化](./core-concepts/performance.md): 页面池、JIT、Mypyc... * [性能优化](./core-concepts/performance.md) - JIT、Mypyc、页面池等优化技术
* [消息流](./core-concepts/event-flow.md): 看看一条消息从接收到回复是如何运行 * [事件流程](./core-concepts/event-flow.md) - 一条消息从接收到回复的完整流程
* [核心](./core-concepts/singleton-managers.md): `matcher`, `browser_manager`... 认识这些核心模块。 * [核心管理器](./core-concepts/singleton-managers.md) - matcher、权限管理、浏览器池等
* [Redis 原子操作与数据一致性](./core-concepts/redis-atomic-operations.md): 权限管理系统的原子操作实现,确保数据一致性 * [Redis原子操作](./core-concepts/redis-atomic-operations.md) - 权限管理的分布式实现
* [错误处理](./core-concepts/error-handling.md): 了解系统的错误处理机制和错误码定义。 * [错误处理](./core-concepts/error-handling.md) - 异常处理和错误码体系
### 3. API 参考 ### 🔌 API 参考
* [API 总览](./api/index.md): 所有 API 的快速导航和调用方式 * [API 总览](./api/index.md) - API 调用方式和快速导航
* [消息 API](./api/message.md): 发消息、撤回、转发、合并转发 * [消息 API](./api/message.md) - 发送、撤回、转发消息
* [群组 API](./api/group.md): 管群、禁言、踢人、改名片 * [群组 API](./api/group.md) - 群管理、禁言、踢人
* [好友 API](./api/friend.md): 好友列表、点赞、加好友请求 * [好友 API](./api/friend.md) - 好友列表、点赞
* [账号 API](./api/account.md): 机器人自己的信息、状态设置 * [账号 API](./api/account.md) - 机器人自身信息获取
* [媒体 API](./api/media.md): 图片、语音相关 * [媒体 API](./api/media.md) - 图片、语音、视频处理
### 4. 插件开发 ### 📚 插件开发
* [插件开发第一步](./plugin-development/index.md): 带你写第一个插件 * [插件入门](./plugin-development/index.md) - 写你的第一个插件
* [](./plugin-development/command-handling.md): 怎么教你的 Bot 听懂指令和参数。 * [令处理](./plugin-development/command-handling.md) - 参数解析、权限控制等
* [绝对不要做的事情](./plugin-development/best-practices.md): **(必读!)** * [最佳实践](./plugin-development/best-practices.md) - 避免常见的坑
* [插件案例:状态监控](./plugin-development/status-plugin.md) - 深入学习复杂插件实现
## 贡献 ### 📋 开发规范
* [开发规范](./development-standards.md) - 代码风格、异步编程、错误处理规范
发现 Bug 了?觉得文档写得烂?
直接提 Issue 或者 PR。代码质量是第一位的Talk is cheap, show me the code.

View File

@@ -5,44 +5,136 @@
``` ```
. .
├── core/ # 核心代码,别乱动 ├── core/ # 核心代码,别乱动
│ ├── api/ # OneBot API 封装(消息、群组、好友、账号、媒体)
│ ├── handlers/ # 底层事件处理器 │ ├── handlers/ # 底层事件处理器
│ ├── managers/ # 全局单例管理器 │ ├── managers/ # 全局单例管理器
│ ├── utils/ # 工具函数 │ ├── command_manager.py # 指令分发和事件处理
└── ws.py # WebSocket 连接实现 │ ├── plugin_manager.py # 插件加载和热重载
├── data/ # 存放持久化数据 ├── permission_manager.py # 权限管理Admin/User两级
│ │ ├── browser_manager.py # Playwright页面池
│ │ ├── image_manager.py # 图片/HTML模板渲染
│ │ └── redis_manager.py # Redis缓存管理
│ ├── utils/ # 工具函数和异常类
│ │ ├── logger.py # 日志系统Loguru
│ │ ├── performance.py # 性能分析工具
│ │ ├── executor.py # 代码沙箱执行引擎Docker
│ │ ├── exceptions.py # 自定义异常类
│ │ └── singleton.py # 单例模式基类
│ ├── ws.py # WebSocket 连接和消息处理已Mypyc编译
│ ├── bot.py # Bot 核心实例
│ ├── config_loader.py # 配置文件加载
│ ├── config_models.py # 配置数据模型
│ └── permission.py # 权限枚举类
├── data/ # 持久化数据
│ ├── admin.json # 管理员列表 │ ├── admin.json # 管理员列表
│ └── permissions.json # 用户权限列表 │ └── permissions.json # 用户权限配置
├── docs/ # 开发文档
├── logs/ # 日志文件
├── models/ # 数据模型 ├── models/ # 数据模型
── events/ # OneBot 事件模型 ── events/ # OneBot 11 事件模型
├── plugins/ # 你的插件都放这 ├── message.py # 消息事件
├── templates/ # 图片渲染用的网页模板 ├── notice.py # 通知事件
├── venv/ # Python 虚拟环境 ├── request.py # 请求事件
├── .gitignore # Git 忽略配置 └── factory.py # 事件工厂
├── main.py # 主入口文件 ├── message.py # 消息段CQ码
├── requirements.txt # Python 依赖列表 │ ├── sender.py # 发送者信息
└── setup_mypyc.py # [可选] Mypyc 编译脚本,用于将核心模块编译为 C 扩展以提升性能 │ └── objects.py # API响应对象群信息、用户信息等
├── plugins/ # 你的插件都放这(最常修改的地方)
│ ├── admin.py # 权限管理Admin/User两级权限
│ ├── auto_approve.py # 自动同意好友请求和群邀请
│ ├── bot_status.py # Bot运行状态查询图片形式
│ ├── broadcast.py # 管理员专用广播功能(隐藏插件)
│ ├── code_py.py # Python代码沙箱执行多行输入、图片输出
│ ├── echo.py # Echo和点赞功能
│ ├── furry.py # Furry图片获取
│ ├── github_parser.py # GitHub仓库链接自动解析
│ ├── jrcd.py # 今日人品/长度查询(随机生成)
│ ├── thpic.py # 东方Project随机图片
│ ├── web_parser/ # 综合Web链接解析系统
│ │ ├── __init__.py # 主入口,自动检测链接
│ │ ├── parsers/ # 各平台解析器
│ │ │ ├── bili.py # B站视频/直播解析
│ │ │ ├── douyin.py # 抖音视频解析
│ │ │ └── github.py # GitHub仓库解析
│ │ └── utils.py # 解析工具函数
│ ├── sync_async_test_plugin.py # 异步同步混用测试(开发用)
│ └── resource/ # 插件资源文件
├── templates/ # Jinja2 HTML模板
│ ├── code_execution.html # 代码执行结果展示
│ ├── github_repo.html # GitHub仓库信息展示
│ ├── help.html # 帮助页面
│ └── status.html # Bot状态页面
├── web_static/ # 静态资源
│ └── html/ # HTML资源文件
├── logs/ # 日志输出目录
│ └── bot.log # 主日志文件
├── tests/ # 单元测试
│ ├── test_api.py # API功能测试
│ ├── test_bot.py # Bot核心测试
│ ├── test_command_manager.py # 指令管理器测试
│ ├── test_performance.py # 性能测试
│ └── ... # 其他测试文件
├── docs/ # 开发文档
│ ├── index.md # 文档首页
│ ├── getting-started.md # 快速上手
│ ├── project-structure.md # 项目结构(本文件)
│ ├── deployment.md # 生产环境部署
│ ├── core-concepts/ # 核心概念详解
│ ├── api/ # API参考文档
│ └── plugin-development/ # 插件开发指南
├── scripts/ # 工具脚本
│ ├── check_python_env.py # Python环境检查
│ ├── compile_machine_code.py # 机器码编译
│ └── export_requirements.py # 依赖导出
├── venv/ # Python 虚拟环境git忽略
├── __pycache__/ # Python缓存git忽略
├── .gitignore # Git忽略配置
├── main.py # 启动入口
├── config.toml # 配置文件包含WS、Redis、Docker配置
├── pytest.ini # 测试配置
├── requirements.txt # Python依赖列表
├── requirements-dev.txt # 开发依赖包括pytest、mypy等
├── setup_mypyc.py # Mypyc编译脚本可选性能优化
├── check_syntax.py # 语法检查脚本
├── profile_main.py # 性能分析脚本
├── test_performance_simple.py # 简单性能测试
├── sandbox.Dockerfile # 代码沙箱Docker镜像
├── LICENSE # 许可证
└── README.md # 项目README
``` ```
## 重点目录说明 ## 核心目录说明
### `core/` ### `core/` - 框架核心
不用修改这里,除非你想优化框架本身。所有功能都由这里的管理器提供:
- **managers/** - 全局单例matcher、permission_manager、browser_manager等
- **api/** - OneBot API 封装
- **handlers/** - 事件处理逻辑
这是框架的心脏。除非你知道自己在干嘛,否则别碰这里面的东西。大部分功能都由 `managers` 里的管理器提供,你只需要 `import` 它们就行。 ### `plugins/` - 插件目录
**这是你最常待的地方**。所有业务功能都在这里包括现有的15+个插件。
### `data/` 新建插件只需在这里添加 `.py` 文件Bot 启动时会自动加载。支持热重载修改后无需重启Bot。
存放一些 JSON 格式的数据。管理员和用户权限默认存在这里。如果你用 Redis这些文件会作为备份。 ### `data/` - 持久化数据
- `admin.json` - 管理员QQ号列表
- `permissions.json` - 用户权限配置
### `plugins/` 这些文件也会自动同步到 Redis 以加快访问速度。
**这是你最常待的地方**。你写的所有插件(`.py` 文件都扔在这个目录里。Bot 启动时会自动加载这里的所有插件。 ### `templates/` - 图片模板
使用 `ImageManager` 生成图片时HTML模板放在这里。支持 Jinja2 模板语法。
### `templates/` ### `main.py` - 程序入口
- 加载配置文件
如果你要用 `ImageManager` 画图,就需要把 HTML 模板文件放在这里。 - 初始化各管理器和 WebSocket 连接
- 启动插件加载器和文件监控(热重载)
### `main.py` - 处理程序生命周期
程序的入口。负责加载配置、初始化管理器、启动 WebSocket 连接和 FastAPI 服务。

View File

@@ -7,7 +7,11 @@ import os
import psutil import psutil
import time import time
import asyncio import asyncio
import socket
import platform
from datetime import datetime, timedelta from datetime import datetime, timedelta
from functools import lru_cache
from typing import Optional
from core.bot import Bot from core.bot import Bot
from core.managers.command_manager import matcher from core.managers.command_manager import matcher
@@ -16,7 +20,7 @@ from core.managers.redis_manager import redis_manager
from core.utils.executor import run_in_thread_pool from core.utils.executor import run_in_thread_pool
from core.utils.logger import logger from core.utils.logger import logger
from models.events.message import MessageEvent, MessageSegment from models.events.message import MessageEvent, MessageSegment
from models.objects import LoginInfo, Status, VersionInfo from models.objects import Status, VersionInfo
__plugin_meta__ = { __plugin_meta__ = {
"name": "bot_status", "name": "bot_status",
@@ -28,14 +32,17 @@ __plugin_meta__ = {
START_TIME = time.time() START_TIME = time.time()
# 获取当前进程 # 获取当前进程
PROCESS = psutil.Process(os.getpid()) PROCESS = psutil.Process(os.getpid())
# 缓存bot昵称12小时过期
_nickname_cache: dict[str, tuple[str, float]] = {}
def _get_system_info(): def _get_system_info():
""" """
同步函数:使用 psutil 获取系统信息,避免阻塞事件循环。 同步函数:使用 psutil 获取系统信息,避免阻塞事件循环。
优化:使用 interval=None 获取自上次调用以来的平均 CPU 使用率
""" """
try: try:
# interval=1 会阻塞1秒必须在线程池中运行 # interval=None 会返回自上次调用以来的平均值,不会阻塞
cpu_percent = psutil.cpu_percent(interval=1) cpu_percent = psutil.cpu_percent(interval=None)
mem_info = psutil.virtual_memory() mem_info = psutil.virtual_memory()
bot_mem_mb = PROCESS.memory_info().rss / (1024 * 1024) bot_mem_mb = PROCESS.memory_info().rss / (1024 * 1024)
@@ -45,159 +52,333 @@ def _get_system_info():
# 网络信息 # 网络信息
net_io = psutil.net_io_counters() net_io = psutil.net_io_counters()
# 进程数
process_count = len(psutil.pids())
# CPU核心数
cpu_count = psutil.cpu_count(logical=True)
cpu_count_physical = psutil.cpu_count(logical=False)
return { return {
"cpu_percent": f"{cpu_percent:.1f}", "cpu_percent": f"{cpu_percent:.1f}",
"cpu_count": cpu_count,
"cpu_count_physical": cpu_count_physical,
"mem_percent": f"{mem_info.percent:.1f}", "mem_percent": f"{mem_info.percent:.1f}",
"mem_total": f"{mem_info.total / (1024**3):.1f}", "mem_total": f"{mem_info.total / (1024**3):.1f}",
"mem_used": f"{mem_info.used / (1024**3):.1f}", "mem_used": f"{mem_info.used / (1024**3):.1f}",
"mem_available": f"{mem_info.available / (1024**3):.1f}",
"bot_mem_mb": f"{bot_mem_mb:.2f}", "bot_mem_mb": f"{bot_mem_mb:.2f}",
"disk_percent": f"{disk_usage.percent:.1f}", "disk_percent": f"{disk_usage.percent:.1f}",
"disk_total": f"{disk_usage.total / (1024**3):.1f}", "disk_total": f"{disk_usage.total / (1024**3):.1f}",
"disk_used": f"{disk_usage.used / (1024**3):.1f}", "disk_used": f"{disk_usage.used / (1024**3):.1f}",
"disk_free": f"{disk_usage.free / (1024**3):.1f}",
"net_sent": f"{net_io.bytes_sent / (1024**2):.1f}", "net_sent": f"{net_io.bytes_sent / (1024**2):.1f}",
"net_recv": f"{net_io.bytes_recv / (1024**2):.1f}", "net_recv": f"{net_io.bytes_recv / (1024**2):.1f}",
"process_count": process_count,
} }
except Exception as e: except Exception as e:
logger.error(f"获取系统信息失败: {e}") logger.error(f"获取系统信息失败: {e}")
return _create_error_system_info("N/A")
async def _get_bot_nickname(bot: Bot) -> str:
"""
异步获取bot昵称带缓存机制12小时过期
"""
cache_key = f"bot_{bot.self_id}"
now = time.time()
# 检查缓存是否有效
if cache_key in _nickname_cache:
nickname, timestamp = _nickname_cache[cache_key]
if now - timestamp < 43200: # 12小时
return nickname
# 优先使用 get_stranger_info更轻量
try:
stranger_info = await bot.get_stranger_info(user_id=bot.self_id)
nickname = stranger_info.nickname
except Exception:
try:
login_info = await bot.get_login_info()
nickname = login_info.nickname
except Exception as e:
logger.warning(f"获取bot昵称失败: {e}")
nickname = "获取失败"
_nickname_cache[cache_key] = (nickname, now)
return nickname
async def _get_bot_info(bot: Bot, start_time: float) -> dict:
"""
收集bot信息id、昵称、头像、启动时间等
"""
nickname = await _get_bot_nickname(bot)
uptime_seconds = int(time.time() - start_time)
uptime_delta = timedelta(seconds=uptime_seconds)
days = uptime_delta.days
hours, remainder = divmod(uptime_delta.seconds, 3600)
minutes, seconds = divmod(remainder, 60)
uptime_str = f"{days}{hours:02}:{minutes:02}:{seconds:02}"
return {
"user_id": bot.self_id,
"nickname": nickname,
"avatar_url": f"https://q1.qlogo.cn/g?b=qq&nk={bot.self_id}&s=640",
"start_time": datetime.fromtimestamp(start_time).strftime("%Y-%m-%d %H:%M:%S"),
"uptime": uptime_str,
}
async def _get_version_info(bot: Bot) -> dict:
"""
获取版本信息,失败时返回默认值
"""
try:
version_info = await bot.get_version_info()
return { return {
"cpu_percent": "N/A", "app_name": version_info.app_name,
"mem_percent": "N/A", "app_version": version_info.app_version,
"mem_total": "N/A", "protocol_version": version_info.protocol_version,
"mem_used": "N/A", }
"bot_mem_mb": "N/A", except Exception as e:
"disk_percent": "N/A", logger.warning(f"获取版本信息失败: {e}")
"disk_total": "N/A", return {
"disk_used": "N/A", "app_name": "获取失败",
"net_sent": "N/A", "app_version": "N/A",
"net_recv": "N/A", "protocol_version": "N/A",
}
async def _get_stats(redis_manager) -> tuple[dict, list]:
"""
获取统计数据和命令排行
"""
try:
msgs_recv = await redis_manager.get("neobot:stats:messages_received") or 0
msgs_sent = await redis_manager.get("neobot:stats:messages_sent") or 0
command_stats_raw = await redis_manager.redis.hgetall("neobot:command_stats")
total_commands = sum(int(v) for v in command_stats_raw.values()) if command_stats_raw else 0
stats_data = {
"messages_received": int(msgs_recv),
"messages_sent": int(msgs_sent),
"total_commands": total_commands,
}
command_stats_data = sorted(
[{"name": k, "count": int(v)} for k, v in command_stats_raw.items()],
key=lambda x: x["count"],
reverse=True
) if command_stats_raw else []
return stats_data, command_stats_data
except Exception as e:
logger.error(f"获取统计数据失败: {e}")
return {
"messages_received": 0,
"messages_sent": 0,
"total_commands": 0,
}, []
async def _get_system_info_async(timeout: float = 3.0) -> dict:
"""
异步获取系统信息,带超时控制
"""
try:
system_data = await asyncio.wait_for(
run_in_thread_pool(_get_system_info),
timeout=timeout
)
return system_data
except asyncio.TimeoutError:
logger.error("获取系统信息超时")
return _create_error_system_info("Timeout")
except Exception as e:
logger.error(f"获取系统信息异常: {e}")
return _create_error_system_info("Error")
async def _get_network_info_async() -> dict:
"""
异步获取网络信息
"""
try:
return await asyncio.wait_for(
run_in_thread_pool(_get_network_info),
timeout=2.0
)
except Exception as e:
logger.error(f"获取网络信息异常: {e}")
return {
"hostname": "获取失败",
"local_ip": "获取失败",
"public_ip": "获取失败",
}
async def _get_os_info_async() -> dict:
"""
异步获取操作系统信息
"""
try:
return await asyncio.wait_for(
run_in_thread_pool(_get_os_info),
timeout=2.0
)
except Exception as e:
logger.error(f"获取操作系统信息异常: {e}")
return {
"os_name": "获取失败",
"os_version": "获取失败",
"os_arch": "获取失败",
"python_version": "获取失败",
}
def _create_error_system_info(error_msg: str = "N/A") -> dict:
"""
创建错误状态的系统信息字典
"""
return {
"cpu_percent": error_msg,
"cpu_count": error_msg,
"cpu_count_physical": error_msg,
"mem_percent": error_msg,
"mem_total": error_msg,
"mem_used": error_msg,
"mem_available": error_msg,
"bot_mem_mb": error_msg,
"disk_percent": error_msg,
"disk_total": error_msg,
"disk_used": error_msg,
"disk_free": error_msg,
"net_sent": error_msg,
"net_recv": error_msg,
"process_count": error_msg,
}
def _get_network_info():
"""
获取网络信息IP地址、主机名等
"""
try:
hostname = socket.gethostname()
# 获取本地IP
try:
local_ip = socket.gethostbyname(hostname)
except:
local_ip = "获取失败"
# 尝试获取公网IP通过连接外部DNS
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
public_ip = s.getsockname()[0]
s.close()
except:
public_ip = "无法获取"
return {
"hostname": hostname,
"local_ip": local_ip,
"public_ip": public_ip,
}
except Exception as e:
logger.error(f"获取网络信息失败: {e}")
return {
"hostname": "获取失败",
"local_ip": "获取失败",
"public_ip": "获取失败",
}
def _get_os_info():
"""
获取操作系统信息
"""
try:
os_name = platform.system()
os_version = platform.release()
os_arch = platform.machine()
python_version = platform.python_version()
return {
"os_name": os_name,
"os_version": os_version,
"os_arch": os_arch,
"python_version": python_version,
}
except Exception as e:
logger.error(f"获取操作系统信息失败: {e}")
return {
"os_name": "获取失败",
"os_version": "获取失败",
"os_arch": "获取失败",
"python_version": "获取失败",
} }
@matcher.command("status", "状态") @matcher.command("status", "状态")
async def handle_status(bot: Bot, event: MessageEvent, args: list[str]): async def handle_status(bot: Bot, event: MessageEvent, args: list[str]):
""" """
处理 status 指令,生成并回复机器人状态图片。 处理 status 指令,生成并回复机器人状态图片。
优化:并发获取各项数据,提升响应速度
""" """
logger.info(f"收到用户 {event.user_id} 的状态查询指令,开始生成状态图...") logger.info(f"收到用户 {event.user_id} 的状态查询指令,开始生成状态图...")
try: try:
# 1. 获取API信息 (增加独立错误处理) # 并发获取所有数据,提升性能
# 尝试获取或更新 bot.nickname bot_info, version_info, stats_result, system_data, network_info, os_info = await asyncio.gather(
if not hasattr(bot, "nickname") or not bot.nickname: _get_bot_info(bot, START_TIME),
try: _get_version_info(bot),
# 优先使用 get_stranger_info 获取自身信息,比 get_login_info 更轻量 _get_stats(redis_manager),
stranger_info = await bot.get_stranger_info(user_id=bot.self_id) _get_system_info_async(timeout=3.0),
bot.nickname = stranger_info.nickname _get_network_info_async(),
except Exception as e: _get_os_info_async(),
logger.warning(f"获取 stranger_info 失败: {e}, 将回退到 login_info") return_exceptions=False
try: )
login_info = await bot.get_login_info()
bot.nickname = login_info.nickname
except Exception as e2:
logger.warning(f"获取 login_info 也失败了: {e2}")
bot.nickname = "获取失败"
nickname = bot.nickname # 处理 _get_stats 返回的元组
if isinstance(stats_result, Exception):
logger.error(f"获取统计数据失败: {stats_result}")
stats_data, command_stats_data = {"messages_received": 0, "messages_sent": 0, "total_commands": 0}, []
else:
stats_data, command_stats_data = stats_result
# 状态信息:如果能响应此命令,说明机器人必然在线且状态良好 # 处理异常返回值
# 这避免了依赖可能超时或未实现的 get_status API if isinstance(system_data, Exception):
logger.debug("正在推断机器人状态...") logger.error(f"获取系统信息失败: {system_data}")
system_data = _create_error_system_info("Error")
if isinstance(network_info, Exception):
logger.error(f"获取网络信息失败: {network_info}")
network_info = {
"hostname": "获取失败",
"local_ip": "获取失败",
"public_ip": "获取失败",
}
if isinstance(os_info, Exception):
logger.error(f"获取操作系统信息失败: {os_info}")
os_info = {
"os_name": "获取失败",
"os_version": "获取失败",
"os_arch": "获取失败",
"python_version": "获取失败",
}
# 推断机器人状态(能响应此命令说明在线且状态良好)
status_info = Status(online=True, good=True) status_info = Status(online=True, good=True)
logger.debug(f"推断状态成功: online={status_info.online}, good={status_info.good}")
try: # 准备模板数据
version_info = await bot.get_version_info()
except Exception as e:
logger.warning(f"获取 version_info 失败: {e}")
version_info = VersionInfo(app_name="获取失败", app_version="N/A", protocol_version="N/A")
# 2. 计算运行时长
uptime_seconds = int(time.time() - START_TIME)
uptime_delta = timedelta(seconds=uptime_seconds)
days = uptime_delta.days
hours, remainder = divmod(uptime_delta.seconds, 3600)
minutes, seconds = divmod(remainder, 60)
uptime_str = f"{days}{hours:02}:{minutes:02}:{seconds:02}"
bot_info_data = {
"user_id": bot.self_id,
"nickname": nickname,
"avatar_url": f"https://q1.qlogo.cn/g?b=qq&nk={bot.self_id}&s=640",
"start_time": datetime.fromtimestamp(START_TIME).strftime("%Y-%m-%d %H:%M:%S"),
"uptime": uptime_str,
}
# 3. 获取统计数据
try:
msgs_recv = await redis_manager.get("neobot:stats:messages_received") or 0
msgs_sent = await redis_manager.get("neobot:stats:messages_sent") or 0
command_stats_raw = await redis_manager.redis.hgetall("neobot:command_stats")
total_commands = sum(int(v) for v in command_stats_raw.values())
stats_data = {
"messages_received": int(msgs_recv),
"messages_sent": int(msgs_sent),
"total_commands": total_commands,
}
command_stats_data = sorted(
[{"name": k, "count": int(v)} for k, v in command_stats_raw.items()],
key=lambda x: x["count"],
reverse=True
)
except Exception as e:
logger.error(f"获取Redis统计数据失败: {e}")
stats_data = {
"messages_received": 0,
"messages_sent": 0,
"total_commands": 0,
}
command_stats_data = []
# 4. 异步获取系统信息
# 设置超时,防止 psutil 阻塞过久
try:
system_data = await asyncio.wait_for(run_in_thread_pool(_get_system_info), timeout=5.0)
except asyncio.TimeoutError:
logger.error("获取系统信息超时")
system_data = {
"cpu_percent": "Timeout",
"mem_percent": "Timeout",
"mem_total": "Timeout",
"mem_used": "Timeout",
"bot_mem_mb": "Timeout",
"disk_percent": "Timeout",
"disk_total": "Timeout",
"disk_used": "Timeout",
"net_sent": "Timeout",
"net_recv": "Timeout",
}
except Exception as e:
logger.error(f"获取系统信息异常: {e}")
system_data = {
"cpu_percent": "Error",
"mem_percent": "Error",
"mem_total": "Error",
"mem_used": "Error",
"bot_mem_mb": "Error",
"disk_percent": "Error",
"disk_total": "Error",
"disk_used": "Error",
"net_sent": "Error",
"net_recv": "Error",
}
# 5. 准备模板所需的所有数据
template_data = { template_data = {
"bot_info": bot_info_data, "bot_info": bot_info,
"status_info": status_info, "status_info": status_info,
"version_info": version_info, "version_info": version_info,
"stats": stats_data, "stats": stats_data,
"system": system_data, "system": system_data,
"network": network_info,
"os": os_info,
"command_stats": command_stats_data, "command_stats": command_stats_data,
} }
# 6. 渲染图片 # 渲染图片
try: try:
base64_str = await image_manager.render_template_to_base64( base64_str = await image_manager.render_template_to_base64(
template_name="status.html", template_name="status.html",
@@ -209,8 +390,7 @@ async def handle_status(bot: Bot, event: MessageEvent, args: list[str]):
if base64_str: if base64_str:
await event.reply(MessageSegment.image(base64_str)) await event.reply(MessageSegment.image(base64_str))
else: else:
# 如果渲染失败image_manager 内部会记录错误,这里给用户一个通用提示 await event.reply("状态图片生成失败,请稍后重试或联系管理员。")
await event.reply("状态图片生成失败,可能是渲染服务出现问题,请联系管理员。")
except Exception as e: except Exception as e:
logger.error(f"渲染图片失败: {e}") logger.error(f"渲染图片失败: {e}")
await event.reply("状态图片渲染过程中发生错误。") await event.reply("状态图片渲染过程中发生错误。")

View File

@@ -141,6 +141,11 @@
column-count: 2; column-count: 2;
column-gap: 24px; column-gap: 24px;
} }
.multi-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 24px;
}
.footer { .footer {
margin-top: auto; margin-top: auto;
padding: 32px 40px; padding: 32px 40px;
@@ -221,10 +226,18 @@
<span class="info-label">CPU 占用</span> <span class="info-label">CPU 占用</span>
<span class="info-value">{{ system.cpu_percent }}%</span> <span class="info-value">{{ system.cpu_percent }}%</span>
</li> </li>
<li class="info-item">
<span class="info-label">CPU 核心</span>
<span class="info-value">{{ system.cpu_count }} ({{ system.cpu_count_physical }} 物理)</span>
</li>
<li class="info-item"> <li class="info-item">
<span class="info-label">内存占用</span> <span class="info-label">内存占用</span>
<span class="info-value">{{ system.mem_percent }}% ({{ system.mem_used }}G / {{ system.mem_total }}G)</span> <span class="info-value">{{ system.mem_percent }}% ({{ system.mem_used }}G / {{ system.mem_total }}G)</span>
</li> </li>
<li class="info-item">
<span class="info-label">可用内存</span>
<span class="info-value">{{ system.mem_available }}G</span>
</li>
<li class="info-item"> <li class="info-item">
<span class="info-label">Bot 进程内存</span> <span class="info-label">Bot 进程内存</span>
<span class="info-value">{{ system.bot_mem_mb }} MB</span> <span class="info-value">{{ system.bot_mem_mb }} MB</span>
@@ -233,10 +246,18 @@
<span class="info-label">磁盘占用</span> <span class="info-label">磁盘占用</span>
<span class="info-value">{{ system.disk_percent }}% ({{ system.disk_used }}G / {{ system.disk_total }}G)</span> <span class="info-value">{{ system.disk_percent }}% ({{ system.disk_used }}G / {{ system.disk_total }}G)</span>
</li> </li>
<li class="info-item">
<span class="info-label">磁盘可用</span>
<span class="info-value">{{ system.disk_free }}G</span>
</li>
<li class="info-item"> <li class="info-item">
<span class="info-label">网络流量 (↑/↓)</span> <span class="info-label">网络流量 (↑/↓)</span>
<span class="info-value">{{ system.net_sent }}MB / {{ system.net_recv }}MB</span> <span class="info-value">{{ system.net_sent }}MB / {{ system.net_recv }}MB</span>
</li> </li>
<li class="info-item">
<span class="info-label">进程总数</span>
<span class="info-value">{{ system.process_count }}</span>
</li>
</ul> </ul>
</div> </div>
@@ -257,6 +278,46 @@
</li> </li>
</ul> </ul>
</div> </div>
<div class="status-card">
<h2 class="card-title">网络信息 (Network)</h2>
<ul class="info-list">
<li class="info-item">
<span class="info-label">主机名</span>
<span class="info-value">{{ network.hostname }}</span>
</li>
<li class="info-item">
<span class="info-label">本地 IP</span>
<span class="info-value">{{ network.local_ip }}</span>
</li>
<li class="info-item">
<span class="info-label">公网 IP</span>
<span class="info-value">{{ network.public_ip }}</span>
</li>
</ul>
</div>
<div class="status-card">
<h2 class="card-title">操作系统 (OS)</h2>
<ul class="info-list">
<li class="info-item">
<span class="info-label">系统</span>
<span class="info-value">{{ os.os_name }}</span>
</li>
<li class="info-item">
<span class="info-label">版本</span>
<span class="info-value">{{ os.os_version }}</span>
</li>
<li class="info-item">
<span class="info-label">架构</span>
<span class="info-value">{{ os.os_arch }}</span>
</li>
<li class="info-item">
<span class="info-label">Python 版本</span>
<span class="info-value">{{ os.python_version }}</span>
</li>
</ul>
</div>
</div> </div>
<div class="status-card"> <div class="status-card">