Files
hanfuit 645b64fe21 DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
2026-11-04 14:31:55 +08:00

587 lines
24 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DNF90 单机模拟器 · 整合启动器 + GM 工具 开发文档
> 本文档用于**换机后快速恢复开发环境**,覆盖环境搭建、项目结构、架构原理、接口清单、打包部署、常见问题等全部关键信息。
>
> 最后更新:2026-11-03
---
## 目录
1. [项目概述](#一项目概述)
2. [开发环境搭建](#二开发环境搭建)
3. [项目结构与关键文件](#三项目结构与关键文件)
4. [技术架构与原理](#四技术架构与原理)
5. [服务端 API 契约](#五服务端-api-契约)
6. [启动器后端 API 清单](#六启动器后端-api-清单)
7. [关键业务逻辑与约定](#七关键业务逻辑与约定)
8. [打包与部署](#八打包与部署)
9. [参考资料位置](#九参考资料位置)
10. [常见问题与坑](#十常见问题与坑)
---
## 一、项目概述
一个**单 EXE** 的 DNF90 单机模拟器启动器,内置完整的 GM 管理工具。
- **技术栈**:Python 3.15 + pywebview 6.x(WebView2 内嵌窗口)+ 纯标准库 HTTP 后端 + 原生 HTML/CSS/JS 前端
- **交付物**:`launcher/dist/DNF90启动器.exe`(单文件,约 14.57MB)
- **部署位置**:EXE 始终放在 `Z:\90server\` 根目录(服务端目录内)
### 核心功能
**启动器主界面(4 按钮)**:
- ▶ 开始游戏:configure-client → 启动服务端 → 注册账号 → 自动登录进游戏
- ⚙ 打开 GM:切换到内置 GM 管理工具界面
- ■ 关闭服务:安全停止服务端
- ♥ 赞助我们:弹出赞助二维码
- 🔄 一键换装:启动 LoadoutTool/DNFLoadout.exe
**内置 GM 工具(功能页)**:
- 控制台(服务端/数据库/Native API 状态)
- 角色管理(等级/金币/职业转职/无限疲劳/删除角色)
- 道具发放(装备/道具两分区 + 武器子类型筛选 + 品级筛选)
- 邮件系统 / 背包装备 / 任务管理 / 副本控制 / 钱包点券(含黑钻)
- 删档管理 / 连接设置(SQLite / Native 数据源切换)
---
## 二、开发环境搭建
### 2.1 Python 环境
**本机有两个 Python,务必使用 Python 3.15:**
| Python | 路径 | 用途 |
|--------|------|------|
| **Python 3.15.2** | `C:\Python315-32\python.exe` | **主开发环境(必须用这个)** |
| Python 3.13.14 | `python`(PATH 默认) | WorkBuddy 内置,勿用于本项目 |
> ⚠️ 所有命令用 `C:\Python315-32\python.exe`,不要用裸 `python`(会指向 3.13)。
### 2.2 依赖包安装
```bash
# 切到 Python 3.15
C:/Python315-32/python.exe -m pip install pywebview pythonnet clr-loader pyinstaller pillow requests fastapi
```
关键依赖及版本:
| 包 | 版本 | 用途 |
|----|------|------|
| pywebview | 6.2.1 | 内嵌 WebView2 窗口 |
| pythonnet | 3.2.1 | .NET CLR 互操作(pywebview 依赖) |
| clr-loader | (latest) | pythonnet 的 CLR 加载器 |
| pyinstaller | 6.22.3 | 打包单 EXE |
| pillow | 13.0.0 | 二维码裁剪处理 |
| requests | 2.34.2 | Native API 客户端(native.py 用) |
| fastapi | 0.162.0 | 仅 gmtool 旧版用(launcher 主项目不用) |
### 2.3 pip 国内源(换机后需重新配置)
`C:\Users\Administrator\AppData\Roaming\pip\pip.ini`:
```ini
[global]
index-url = https://pypi.tuna.tsinghua.edu.cn/simple
extra-index-url = https://mirrors.aliyun.com/pypi/simple/
trusted-host = pypi.tuna.tsinghua.edu.cn
```
### 2.4 换机后检查清单
- [ ] 安装 Python 3.15(任意位置,更新 `.bat` 和打包命令里的路径)
- [ ] 配置 pip 清华源
- [ ] 安装上述依赖包
- [ ] 确认目标电脑有 **WebView2 运行时**(Win10/11 自带;Win7 需手动装)
- [ ] 将整个项目目录拷贝到新机(含 `launcher/` 源码和 `90server/` 运行环境)
---
## 三、项目结构与关键文件
### 3.1 工作区目录(开发源码所在地)
```
C:/Users/Administrator/WorkBuddy/DNF启动器和GM管理工具/
├── launcher/ # ★ 主项目(启动器 + GM 工具)
│ ├── server.py # ★ 整合后端(启动器动作 + GM HTTP 服务,纯标准库)
│ ├── native.py # ★ Native API 客户端(封装服务端全部 GM 端点)
│ ├── db.py # ★ SQLite 数据访问层(删角色/删档/疲劳)
│ ├── index.html # 双视图界面(启动器 + GM 工具)
│ ├── style.css # 深色 DNF 风格主题
│ ├── app.js # ★ 前端逻辑(启动器 + GM 路由)
│ ├── sponsor.png # 赞助二维码(已裁剪)
│ ├── sponsor_full_backup.png# 二维码原图备份
│ ├── sponsor_qr.png # 二维码中间产物
│ ├── DNF90启动器.spec # ★ PyInstaller 打包配置
│ ├── build/ # 打包中间产物(可删)
│ └── dist/ # 打包产物 DNF90启动器.exe
├── gmtool/ # 旧版 FastAPI GM 工具(已弃用,仅参考)
│ ├── main.py
│ ├── gm_client.py
│ └── static/
├── README.md # 项目简介(较简略)
├── 开发文档.md # 本文档
├── 2026-11-03.md # 当日详细工作日志(历史记录)
├── 启动启动器.bat # 源码方式运行启动器
├── 启动GM工具.bat # 源码方式运行旧 GM 工具(已弃用)
└── .workbuddy/ # WorkBuddy 项目数据(勿删)
└── memory/ # 工作记忆(日志)
```
### 3.2 运行环境(服务端所在,非源码)
```
Z:/90server/ # ★ 可运行模拟器 + 启动器部署位置
├── DNF90启动器.exe # ★ 打包后的启动器(放这里运行)
├── Server/ # 服务端
│ └── runtime/
│ ├── bin/DNF90Control.exe # 服务端控制器(start/stop/configure-client/launch-client/account)
│ ├── config/instance.json # Native API token 配置
│ └── data/dnf90.db # SQLite 数据库(首次启动生成)
├── Client/ # 游戏客户端
└── LoadoutTool/DNFLoadout.exe # 一键换装工具
```
### 3.3 服务端源码(Go,仅供参考 API 契约)
```
C:/Users/Administrator/Downloads/90轮回源码/90轮回源码/go-server/
└── internal/
├── app/dnf90/
│ ├── gm_tool_admin.go # ★ GM 工具 HTTP 路由(GET 查询类)
│ └── gm_admin.go # ★ 黑钻等写操作
├── services/dnfbridge/
│ ├── gm_tool.go # GM 业务逻辑(职业/任务结构定义)
│ ├── gm_tool_extended.go # 角色详情、分类逻辑
│ ├── gm_tool_items_extra.go# 道具发放 grant-options
│ ├── gm_tool_dungeon.go # 副本难度
│ └── gm_tool_item_customization.go
└── modules/dnf/
├── itemquality/quality.go # 品级定义
└── profession/profile.go # 职业 Option 结构
```
---
## 四、技术架构与原理
### 4.1 整体架构
```
┌─────────────────────────────────────────────────┐
│ DNF90启动器.exe(单文件,pywebview WebView2) │
│ │
│ ┌──────────────┐ HTTP(127.0.0.1:19527) │
│ │ 前端 app.js │ ◄──────────────────────────► │
│ │ index.html │ │
│ └──────────────┘ │
│ ▲ │
│ │ api()/post() │
│ ┌───────┴────────────────────────────────────┐ │
│ │ server.py(纯标准库 BaseHTTPRequestHandler)│ │
│ │ ├─ 启动器动作 flow_*() │ │
│ │ ├─ GM HTTP 路由 _api_*() │ │
│ │ ├─ SQLite 直读(DB, db.py) │ │
│ │ └─ Native 客户端(NATIVE, native.py) │ │
│ └───────┬──────────────────┬─────────────────┘ │
└──────────┼──────────────────┼───────────────────┘
│ SQLite 直读 │ HTTP(127.0.0.1:18111, X-Admin-Token)
▼ ▼
Server/runtime/data/dnf90.db DNF90Control.exe(运行中的服务端)
```
### 4.2 双数据源机制(核心设计)
GM 工具支持两种数据源,通过「连接设置」页切换:
| 数据源 | 条件 | 特点 |
|--------|------|------|
| **Native API** | 服务端运行时 | 实时操作,`127.0.0.1:18111` + `X-Admin-Token` |
| **SQLite 直读** | 服务端停止时 | 直接读写 `dnf90.db`,可删角色/删档/改疲劳 |
- 判断逻辑:`_use_sqlite()` 返回 `True` 时走 SQLite,否则走 Native
- **删角色/删档/改疲劳必须停服**(服务端运行时数据库被锁)
### 4.3 路径自动识别(重要)
`SERVER_ROOT` 按以下优先级自动定位(`server.py` 模块加载时执行一次):
1. 环境变量 `DNF90_SERVER_ROOT`(手动覆盖)
2. EXE 自身所在目录(若其下存在 `Server/`)—— **正常场景命中**
3. 向上逐级查找含 `Server/` 的目录(最多 4 层)
4. 兜底 EXE 所在目录 `BASE_DIR`
派生路径(全部基于 SERVER_ROOT):
```python
CONTROL = SERVER_ROOT + r"\Server\runtime\bin\DNF90Control.exe"
CLIENT_DIR = SERVER_ROOT + r"\Client"
LOADOUT_EXE = SERVER_ROOT + r"\LoadoutTool\DNFLoadout.exe"
INSTANCE_PATH = SERVER_ROOT + r"\Server\runtime\config\instance.json"
DB_PATH = SERVER_ROOT + r"\Server\runtime\data\dnf90.db"
```
**关键约定**:启动器 EXE 永远放在 `90server` 根目录,路径解析完全锚定 EXE 自身位置,不依赖任何写死盘符。
### 4.4 Native API 认证机制
- 服务端 GM API 监听 `127.0.0.1:18111`
- 认证头:`X-Admin-Token`
- 两套路由:`/dnf/gm/*`(新版)、`/local/dnf-gm/*`(兼容版)
- token 通过 `AUTO_GENERATE` 机制生成,存于 `instance.json`
- 危险写操作需 `X-Admin-Confirm` + `X-Admin-Idempotency-Key` 头(`_post` 默认 `confirm=True`)
---
## 五、服务端 API 契约
> 服务端是 Go 编写的二进制,源码在 `90轮回源码/go-server/`。以下契约是从源码反推,供 native.py 和 server.py 对接。
### 5.1 职业转职
```
GET /dnf/gm/characters/{id}/career → GMCareerState
POST /dnf/gm/characters/{id}/career {job, first, awakening}
```
`GMCareerState` 字段:`job`、`first_grow`(分支,0=未转职)、`awakening`、`profession_name`、`options[]`、`skills_reset`、`quest_reset_ids`
`options` 层级结构:
```
Option { job, name, branches[] }
BranchOption { value, name, awakenings[] }
AwakeningOption { value, name }
```
### 5.2 任务查询
```
GET /dnf/gm/characters/{id}/quests → {character, active[], completed[]}
GET /dnf/gm/quests/search?query=... → GMQuestSummary[]
GET /dnf/gm/quests/{id}/detail?quest_id=... → 详情
POST(任务操作 accept/ready/give-up/complete/uncomplete)
```
`GMQuestSummary` 字段:`quest_id`、`name`、`grade`、`type`、`level_min`、`level_max`、`status`、`active`、`completed`、`acceptable`
### 5.3 爆率 / 副本 / 黑钻
```
# 爆率(游戏玩法设置)
GET /dnf/gm/gameplay → GMGameplaySettings
POST /dnf/gm/gameplay/hell-drop-rate {percent}
POST /dnf/gm/gameplay/epic-potion-test-multiplier {percent}
# 副本
GET /dnf/gm/characters/{id}/dungeons → {difficulty_dungeons, difficulty_dungeons_unlocked, hell_areas, hell_permission_quests, hell_permission_quests_cleared}
POST /dnf/gm/characters/{id}/dungeons/unlock-difficulties
POST /dnf/gm/characters/{id}/dungeons/unlock-hell
# 黑钻
GET /local/dnf-gm/character/black-diamond
POST /local/dnf-gm/character/black-diamond {character_id, days}
```
`GMGameplaySettings` 字段:`hell_epic_drop_rate_percent`(当前爆率%)、`minimum_percent`、`maximum_percent`(上限 15000%)、`epic_drop_rate_potion_test_multiplier_percent`
黑钻返回:`active`、`expires_at`(时间戳,0=未开启)、`remaining_days`、`fatigue_limit`、`black_diamond_fatigue_limit`
### 5.4 品级(rarity)枚举
**rarity 0~6 共 7 档,权威映射:**
| 值 | 品级 | 示例装备 |
|----|------|---------|
| 0 | 普通 | 粗布衬衫 |
| 1 | 高级 | 棉布衬衫 |
| 2 | 稀有 | 荣誉者长袍 |
| 3 | 神器 | (旧) 雕翎长袍 |
| 4 | 史诗 | (旧) 银月少女T恤衫 |
| 5 | 传说 | 贤者的智慧之衣 |
| 6 | 神话 | 首领萨斯的阴影长袍 |
> ⚠️ **注意**:4=史诗、5=传说、6=神话(顺序易记错)。「勇者」不是独立品级档,是异界装备称呼,实际归类于神器(3)。
> 物品搜索的 `rarity` 参数是**精确匹配**(`item.Rarity != rarity` 过滤),范围 -1~6,-1=不限。
### 5.5 品级机制(装备发放时)
- `equipment_quality` 仅接受 `default` / `random` / `top` 三个值
- 品级本质是 `quality_seed` 数值:1=最下级,999999998(TopSeed)=最上级
- **直接进背包路径(grant-options)忽略品级参数**,只有**邮件(mail)路径**品级才真正生效
- 服务端不支持「最下级」,只能最上级(top)或随机(random)
### 5.6 装备分类与武器子类型
- 服务端 `kind` 值:`"equipment"`(装备)、`"stackable"`(道具)
- 武器子类型通过 `q=/weapon/<sub>/` 匹配 PVFPath(如 `equipment/character/swordman/weapon/katana/`)
---
## 六、启动器后端 API 清单
> `server.py` 提供,前端通过 `api()`(GET)和 `post()`(POST)调用。端口 `19527`。
### 6.1 GET 路由
| 路径 | 说明 |
|------|------|
| `/api/status` | 启动器状态(含 server_root/client_dir/各文件是否存在/native_available) |
| `/api/logs` | 运行日志 |
| `/api/gm/health` | GM 健康检查 |
| `/api/accounts-config` | 账号配置 |
| `/api/accounts` | 账号列表 |
| `/api/characters` | 角色列表(支持 ?account= 筛选) |
| `/api/character` | 角色详情 |
| `/api/character/inventory` | 背包 |
| `/api/character/equipment` | 装备 |
| `/api/character/mailbox` | 邮箱 |
| `/api/character/fatigue` | 疲劳(mode=query) |
| `/api/character/career` | 职业信息(转职查询) |
| `/api/character/dungeons` | 副本状态 |
| `/api/character/black-diamond` | 黑钻状态 |
| `/api/gameplay` | 爆率设置查询 |
| `/api/quests` | 任务概览 |
| `/api/quests/search` | 任务搜索 |
| `/api/quests/detail` | 任务详情 |
| `/api/wipe/preview` | 删档预览 |
| `/api/items/categories` | 道具分类 |
| `/api/item/search` | 道具搜索 |
### 6.2 POST 路由
| 路径 | 说明 |
|------|------|
| `/api/action` | 启动器动作(start/stop/gm/loadout) |
| `/api/accounts-config` | 账号配置写操作 |
| `/api/source` | 切换数据源(SQLite/Native) |
| `/api/character/save` | 保存角色 |
| `/api/character/stat` | 改属性 |
| `/api/character/fatigue` | 改疲劳 |
| `/api/character/gold` | 改金币 |
| `/api/character/level` | 改等级 |
| `/api/character/skill-points` | 改技能点 |
| `/api/character/career` | 应用转职 |
| `/api/dungeon/unlock` | 解锁副本难度 |
| `/api/black-diamond/activate` | 开启黑钻 |
| `/api/gameplay/hell-drop-rate` | 设置爆率 |
| `/api/gameplay/epic-potion-multiplier` | 设置药剂倍率 |
| `/api/quests/action` | 任务操作 |
| `/api/character/grant` | 发放道具 |
| `/api/character/delete` | 删除角色 |
| `/api/account/delete` | 删除账号 |
| `/api/item/search` | 道具搜索(POST body) |
| `/api/item/grant` | 发放道具 |
| `/api/item/grant-options` | 发放道具(高级选项) |
| `/api/item/mail` | 邮件发放道具 |
| `/api/mail/send` | 发送邮件 |
| `/api/mailbox/clear` | 清空邮箱 |
| `/api/inventory/clear` | 清空背包 |
| `/api/inventory/delete` | 删除背包物品 |
| `/api/wallet/query` | 钱包查询 |
| `/api/cera` | 点券充值 |
### 6.3 native.py 方法清单(服务端 GM 端点封装)
`native.py` 是服务端 GM API 的完整 Python 客户端封装,方法列表:
- **账号**:`list_accounts` / `get_account` / `set_account_value`
- **角色**:`list_characters` / `get_character` / `get_character_detail`
- **物品/背包**:`get_inventory` / `get_equipment` / `get_mailbox` / `get_item_detail` / `get_item_mail_options` / `get_item_categories` / `search_items` / `search_avatar_sets`
- **道具发放**:`grant_item` / `grant_item_options` / `grant_item_with_emblems` / `send_item_mail`
- **删除**:`delete_item` / `delete_inventory_item` / `clear_inventory` / `unequip_equipment` / `delete_equipment`
- **属性修改**:`set_gold` / `set_level` / `set_skill_bonus` / `set_career` / `get_career` / `set_cera` / `adjust_revive_coin`
- **钱包**:`query_wallet` / `update_wallet` / `set_account_cube` / `get_account_cubes`
- **邮件**:`send_system_mail` / `clear_mailbox` / `delete_mailbox_mails`
- **任务**:`get_quest_overview` / `search_quests` / `get_quest_detail` / `accept_quest` / `mark_quest_ready` / `give_up_quest` / `complete_quest` / `uncomplete_quest` / `complete_quest_batch`
- **副本**:`get_dungeons` / `unlock_dungeon_difficulties` / `unlock_hell_challenges`
- **黑钻**:`get_black_diamond` / `activate_black_diamond`
- **爆率**:`get_gameplay_settings` / `set_hell_drop_rate` / `set_epic_potion_multiplier`
- **宠物**:`update_pet`
---
## 七、关键业务逻辑与约定
### 7.1 疲劳值机制
- 角色 `stats` 里的字段:`fatigue`(当前疲劳)、`fatigue_limit`(上限,默认156/黑钻188)、`fatigue_display_update`(已用)
- 每天 6 点重置回上限
- **无限疲劳** = 把 `fatigue` 和 `fatigue_limit` 都设为超大值(999999)
- 服务端**无独立疲劳查询 GM 接口**,从 `get_character_detail()` 的 `stats` 里读
### 7.2 账号配置
- `accounts.json` 存 EXE 同目录(`BASE_DIR`)
- `load_accounts` / `save_accounts` / `get_default_account`
- 默认账号 `dnfplayer` 不可删除
### 7.3 窗口与 UI 约定
- **GM 窗口打开直接最大化**,但**不用 `maximize()`**(会遮挡任务栏)
- 用 `_get_work_area()`(`ctypes` 调 `SystemParametersInfoW(SPI_GETWORKAREA=0x0030)`)获取扣除任务栏的工作区,再 `move()` + `resize()`
- 主窗口和 GM 窗口都屏幕居中(`webview.screens[0].width/height` 计算 x/y)
- 深色 DNF 风格 + 紫青渐变 + 毛玻璃质感
### 7.4 危险操作约定
- 删除角色/删档/改疲劳:**需先停服**(SQLite 直读模式)
- 危险操作二次确认:输入确认词(如 `DELETE`、`转职`)
### 7.5 进程回收
- 关闭后需 `os._exit(0)` 强制退出 + `_kill_webview2_children()`(清理 WebView2 子进程)
- 打包前需先 `taskkill /F /IM "DNF90启动器.exe"`(否则 dist exe 被锁定)
---
## 八、打包与部署
### 8.1 打包命令
用 spec 文件打包(推荐,配置已固化):
```bash
cd "C:/Users/Administrator/WorkBuddy/DNF启动器和GM管理工具/launcher"
# 先杀掉残留进程(避免文件锁定)
taskkill /F /IM "DNF90启动器.exe"
# 打包
C:/Python315-32/python.exe -m PyInstaller --clean --noconfirm "DNF90启动器.spec"
```
产物:`launcher/dist/DNF90启动器.exe`
### 8.2 拷贝到运行目录
```bash
cp -f "C:/Users/Administrator/WorkBuddy/DNF启动器和GM管理工具/launcher/dist/DNF90启动器.exe" "Z:/90server/DNF90启动器.exe"
```
### 8.3 spec 文件要点
`DNF90启动器.spec` 关键配置:
- `datas`:打包 `index.html`、`style.css`、`app.js`、`sponsor.png`、`db.py`、`native.py`
- `hiddenimports`:`clr_loader`、`pythonnet`
- `collect_all('webview')`:pywebview 全部资源
- `console=False`:windowed 模式(无控制台)
- `upx=True`:UPX 压缩
- `name='DNF90启动器'`
> 如果不用 spec,等价命令行(注意 Windows 分隔符是 `;`)见 `README.md`。
### 8.4 部署要求
- EXE 放在 `90server` 根目录
- 目标电脑需 WebView2 运行时(Win10/11 自带)
- 首次运行请求管理员权限(UAC,启动服务端需要)
- 无需安装 Python 或其他依赖(单文件已全打包)
### 8.5 验证方法
打包后在同批命令内验证(后台进程在命令结束时会被回收):
```bash
cd "Z:/90server" && ./DNF90启动器.exe > /dev/null 2>&1 &
sleep 8
curl -s --noproxy "*" "http://127.0.0.1:19527/api/status"
```
预期返回:`server_root=Z:\90server`、`db_exists=true`、`native_available=true`、`control_exists=true`、`server_dir_exists=true`、`client_dir_exists=true`。
---
## 九、参考资料位置
| 资料 | 位置 |
|------|------|
| 服务端源码(Go) | `C:/Users/Administrator/Downloads/90轮回源码/90轮回源码/go-server/` |
| 旧 GM 工具源码备份 | WorkBuddy 资料库节点 `sfEHKi5MJIh0D9lSykGVoT`(DNF90工具与资料备份 web 页) |
| 旧 GM 工具(运行版) | `Z:/90server/gm_tool/`(已打包 DNF90GM工具.exe) |
| 可运行模拟器 | `Z:/90server/` |
| 当日工作日志 | `C:/Users/Administrator/WorkBuddy/DNF启动器和GM管理工具/2026-11-03.md` |
### 读取资料库参考资料的方法
```bash
# 查询节点信息
python3 "C:/Users/Administrator/AppData/Local/Programs/WorkBuddy/resources/app.asar.unpacked/resources/plugins/workbuddy-builtin/skills/library/space_api.py" space.workspace.node-info --node-id sfEHKi5MJIh0D9lSykGVoT
# 下载 artifacts
python3 ".../library/page/download_page_artifacts.py" --node-id "sfEHKi5MJIh0D9lSykGVoT" --out-dir "/tmp/gmref" --path "..."
```
---
## 十、常见问题与坑
### 10.1 打包相关
| 问题 | 解决 |
|------|------|
| 打包报 OSError safe-delete 失败 | dist 的 exe 被占用,先 `taskkill /F /IM "DNF90启动器.exe"` |
| `--add-data` 分隔符 | Windows 用 `;`(不是 `:`) |
| 打包后窗口变浏览器 | pywebview `transparent=True` 在 Windows 不支持,会导致降级 |
### 10.2 pywebview 相关
| 问题 | 解决 |
|------|------|
| 无边框窗口 maximize 遮挡任务栏 | 用 `_get_work_area()` 获取工作区再 resize,不要用 `maximize()` |
| 关闭后 WebView2 子进程残留 | `os._exit(0)` + `_kill_webview2_children()` |
| `create_window()` 报错 | 必须在 `webview.start()` 之前调用 |
### 10.3 后端参数解析
**重要教训**:`_api_character_fatigue` 曾有个 bug——用 `if "id" in data_or_qs` 判断,导致 GET 请求的 `parse_qs` 结果(值是 list)被当成标量,`char_id` 变成 `['1']` 列表,报「角色 ['1'] 不存在」。
**正确写法**:GET 请求统一用 `qs.get("id", [""])[0]` 取第一个值;POST body 才直接 `data.get("id")`。
### 10.4 数据源相关
| 问题 | 解决 |
|------|------|
| 删角色/删档报数据库锁定 | 需先停服(点「关闭服务」) |
| 疲劳值读不出 | 服务端无独立疲劳接口,用 `get_character_detail()` 的 `stats.fatigue` |
| 道具搜索 404 | 前端用 `post('/api/item/search')` 但后端原来只有 GET 路由,需补 POST 路由 |
### 10.5 品级相关
- rarity 精确匹配(`-1` = 不限),枚举 0~6:普通/高级/稀有/神器/史诗/传说/神话
- 品级 `equipment_quality` 只接受 default/random/top
- 品级只有邮件路径生效,直接进背包路径忽略品级
### 10.6 测试相关
- **后台进程在命令结束时会被回收**(非交互模式),验证必须把启动和测试放同一批命令内
- 服务端 `DNF90Server.exe` 是常驻进程,测试时**不要**误杀
- 默认测试角色:`character_id=1`,名「测试」,职业神枪手,1级
---
## 附:快速上手速查
```bash
# 1. 源码方式运行启动器(开发调试)
cd "C:/Users/Administrator/WorkBuddy/DNF启动器和GM管理工具/launcher"
C:/Python315-32/python.exe server.py
# 2. 校验语法
C:/Python315-32/python.exe -c "import ast; ast.parse(open('server.py',encoding='utf-8').read())"
node --check app.js
# 3. 打包 + 拷贝(换机后重复此流程)
taskkill /F /IM "DNF90启动器.exe"
cd "C:/Users/Administrator/WorkBuddy/DNF启动器和GM管理工具/launcher"
C:/Python315-32/python.exe -m PyInstaller --clean --noconfirm "DNF90启动器.spec"
cp -f dist/DNF90启动器.exe "Z:/90server/DNF90启动器.exe"
# 4. 用默认测试角色验证(同批命令内)
cd "Z:/90server" && ./DNF90启动器.exe > /dev/null 2>&1 &
sleep 8 && curl -s --noproxy "*" "http://127.0.0.1:19527/api/status"
```