feat: 重构
This commit is contained in:
145
CLAUDE.md
Normal file
145
CLAUDE.md
Normal file
@@ -0,0 +1,145 @@
|
||||
# AMT - ARinera Minecraft Tool
|
||||
|
||||
Minecraft 实用 TUI 工具。Go + bubbletea,前后端分离。
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
main.go 入口,解析 exe 目录,tea.NewProgram(WithAltScreen)
|
||||
model.go 顶层 model 定义,page 枚举,Init/Update/View 路由
|
||||
pages.go 4 个页面的 update/view + resetExecState + describeAction
|
||||
scan.go 扫描 <exeDir>/*/.minecraft/versions/* 目录
|
||||
api.go HTTP 客户端 + Action/StepResponse 类型定义
|
||||
actions.go action 执行引擎(download/unzip/delete/copy/move/backup)
|
||||
items.go list.Item 实现:versionItem, menuItem, mirrorItem
|
||||
styles.go lipgloss 样式常量
|
||||
backend/ Python FastAPI 后端(独立进程,端口 3131)
|
||||
```
|
||||
|
||||
全部 Go 文件在项目根目录,package main。`go build -o dist/amt.exe .`
|
||||
|
||||
## 依赖
|
||||
|
||||
- `bubbletea` v1.3.10 / `bubbles` v1.0.0 / `lipgloss` v1.1.0
|
||||
- 标准库:`net/http`, `archive/zip`, `encoding/json`, `io`, `os`, `path/filepath`
|
||||
|
||||
## 架构:页面状态机
|
||||
|
||||
```
|
||||
pageVersionSelect ──Enter──▶ pageMainMenu ──"输入数字码"──▶ pageCodeInput ──Enter(4位)──▶ pageExecuting
|
||||
▲ │ │ │ │
|
||||
│ Esc/q=退出 │ Esc=返回 任意键=返回
|
||||
└──"切换版本"─────────────┘ └──────────────────────────┘◀──────────────────────────┘
|
||||
```
|
||||
|
||||
### model 核心字段
|
||||
|
||||
| 字段 | 类型 | 用途 |
|
||||
|------|------|------|
|
||||
| `currentPage` | `page` (iota 枚举) | 当前页面 |
|
||||
| `exeDir` | string | 可执行文件所在目录 |
|
||||
| `versionDir` | string | 选中的版本完整路径 |
|
||||
| `versionName` | string | 版本目录名 |
|
||||
| `versionList` | list.Model | 版本选择列表(Init 时预初始化,避免零值 panic) |
|
||||
| `menuList` | list.Model | 主菜单列表(同上) |
|
||||
| `codeInput` | textinput.Model | 4 位数字码输入框 |
|
||||
| `actions` | []Action | 当前执行的 action 列表 |
|
||||
| `actionIdx` | int | 当前执行到第几个 |
|
||||
| `logLines` | []string | 执行日志(也被 viewVersionSelect 用于显示扫描错误) |
|
||||
| `execErr` / `execDone` | error / bool | 执行状态 |
|
||||
| `choosingMirror` | bool | 是否在 mirror 选择子页面 |
|
||||
| `pendingAction` | *Action | mirror 选择时暂存的 action |
|
||||
| `backupDir` | string | 当次执行的备份相对路径 `amt/backup/<timestamp>` |
|
||||
| `progressCh` | chan float64 | 下载进度 channel(预留) |
|
||||
|
||||
### 消息类型
|
||||
|
||||
| 消息 | 来源 | 处理页面 |
|
||||
|------|------|----------|
|
||||
| `versionsFoundMsg` | `scanVersions()` | pageVersionSelect |
|
||||
| `scanErrorMsg` | `scanVersions()` | pageVersionSelect |
|
||||
| `actionsReceivedMsg` | `fetchActions()` | pageExecuting |
|
||||
| `apiErrorMsg` | `fetchActions()` | pageExecuting |
|
||||
| `actionCompleteMsg` | `executeAction()` | pageExecuting |
|
||||
| `actionErrorMsg` | `executeAction()` | pageExecuting |
|
||||
| `actionProgressMsg` | `waitForProgress()` | pageExecuting |
|
||||
| `mirrorChoiceMsg` | `executeAdd()` | pageExecuting |
|
||||
|
||||
### 状态清理规则
|
||||
|
||||
**关键约束**:`logLines` 被 `viewVersionSelect` 用于判断是否显示错误,必须在离开相关上下文时清空。
|
||||
|
||||
- **进入 pageExecuting (T4)**:调用 `resetExecState(m)` 清空所有执行状态
|
||||
- **离开 pageExecuting (T6)**:同上
|
||||
- **进入 pageVersionSelect (T3)**:清空 `logLines` + 清空列表项
|
||||
- **`versionsFoundMsg` 到达时**:清空 `logLines`(覆盖旧扫描错误)
|
||||
- **离开 pageVersionSelect (T1)**:清空 `logLines`
|
||||
- **mirror Esc 取消**:清空 `pendingAction`
|
||||
|
||||
`resetExecState` 清空字段:`actions`, `actionIdx`, `logLines`, `execErr`, `execDone`, `choosingMirror`, `pendingAction`, `backupDir`, `progressCh`
|
||||
|
||||
## 后端 API
|
||||
|
||||
地址:`http://localhost:3131`
|
||||
|
||||
### GET /tools?code=XXXX
|
||||
|
||||
返回 step 结构:
|
||||
|
||||
```json
|
||||
{
|
||||
"actions": [
|
||||
{"type": "add", "path": "相对路径", "unzip": false, "url": "https://...", "mirrors": ["https://..."]},
|
||||
{"type": "delete", "path": "相对路径"},
|
||||
{"type": "copy", "path": "源相对路径", "new_path": "目标相对路径"},
|
||||
{"type": "move", "path": "源相对路径", "new_path": "目标相对路径"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
所有 `path` 相对于 `versionDir`。
|
||||
|
||||
## Action 执行逻辑 (actions.go)
|
||||
|
||||
顺序执行,任一失败立即中断。
|
||||
|
||||
| type | 行为 | 备份 |
|
||||
|------|------|------|
|
||||
| `add` | 下载 url→path,unzip=true 时解压后删 zip | 目标已存在则备份 |
|
||||
| `delete` | 删除 path | 删前备份 |
|
||||
| `copy` | 复制 path→new_path | new_path 已存在则备份 |
|
||||
| `move` | os.Rename,失败则 copy+delete | new_path 已存在则备份 |
|
||||
|
||||
### 备份
|
||||
|
||||
目录:`<versionDir>/amt/backup/<YYYYMMDD_HHMMSS>/<原相对路径>`
|
||||
|
||||
每次执行创建一个时间戳目录,同次执行的所有备份共享。
|
||||
|
||||
### 下载失败 Mirror 处理
|
||||
|
||||
主 URL 失败 → 发送 `mirrorChoiceMsg` → 设 `choosingMirror=true` → 显示 mirror 列表 → 用户选择后用新 URL 重试。用户 Esc 取消则中断执行。
|
||||
|
||||
### 关键函数
|
||||
|
||||
```
|
||||
executeAction(versionDir, action, index, backupDir) → tea.Cmd → actionCompleteMsg | actionErrorMsg | mirrorChoiceMsg
|
||||
executeAdd(...) add 专用,处理下载/解压/mirror
|
||||
downloadFile(url, dest) 标准 http.Get + 临时文件 + rename
|
||||
unzipFile(zip, dir) archive/zip 解压,含 zip slip 防护
|
||||
backupPath(versionDir, relPath, backupDir) 备份单个文件/目录
|
||||
copyPath(src, dst) 递归复制文件/目录
|
||||
copyFile(src, dst) 单文件复制
|
||||
copyDir(src, dst) filepath.Walk 递归复制
|
||||
```
|
||||
|
||||
## 版本扫描 (scan.go)
|
||||
|
||||
`filepath.Glob(exeDir/*/.minecraft/versions/*)` → 过滤目录 → `versionsFoundMsg`
|
||||
|
||||
## 构建
|
||||
|
||||
```bash
|
||||
go build -o dist/amt.exe . # 或 make
|
||||
make backend # 启动后端 (uvicorn, port 3131)
|
||||
```
|
||||
Reference in New Issue
Block a user