feat: 实现功能3强制更新
- 新增 ForceUpdateCommand 类实现强制更新功能 - 添加 force-update CLI 命令支持 - 实现参数验证、配置对比、CDN缓存刷新 - 添加完整的测试用例和使用示例 - 更新文档和进度跟踪 - 支持模拟环境测试 - 添加警告提示和错误处理 主要文件: - src/config_man/core/force_update_command.py (新增) - src/config_man/cli/main.py (更新) - examples/test_force_update.py (新增) - examples/force_update_usage.py (新增) - docs/progress-tracker.md (更新)
This commit is contained in:
50
README.md
50
README.md
@@ -66,6 +66,45 @@ config-man suggest-update --platform <平台> --target-version <目标版本> --
|
||||
- `--platform`: 平台类型,支持 android 或 ios
|
||||
- `--target-version`: 目标版本号,如 0.29
|
||||
- `--update-version`: 更新版本号,如 0.30
|
||||
- `--storage-path`: 对象存储路径前缀(覆盖配置文件)
|
||||
- `--cdn-url`: CDN基础URL(覆盖配置文件)
|
||||
- `--mock`: 使用模拟环境进行测试
|
||||
|
||||
**功能特性**:
|
||||
- 在目标版本配置中添加 `Ver_New` 属性
|
||||
- `Ver_New` 的值设为更新版本的 `Ver` 属性值
|
||||
- 自动加密并上传到对象存储
|
||||
- 自动刷新CDN缓存
|
||||
|
||||
**配置变更示例**:
|
||||
```json
|
||||
// 修改前
|
||||
{
|
||||
"Ver": "0.29.1abc123",
|
||||
"EventApiURL": "https://n3backend.azurewebsites.net/",
|
||||
"PlayFabTitle": "B066F"
|
||||
}
|
||||
|
||||
// 修改后
|
||||
{
|
||||
"Ver": "0.29.1abc123",
|
||||
"Ver_New": "0.30.1def456",
|
||||
"EventApiURL": "https://n3backend.azurewebsites.net/",
|
||||
"PlayFabTitle": "B066F"
|
||||
}
|
||||
```
|
||||
|
||||
**使用示例**:
|
||||
```bash
|
||||
# 基本用法
|
||||
config-man suggest-update --platform android --target-version 0.29 --update-version 0.30
|
||||
|
||||
# 指定存储路径和CDN URL
|
||||
config-man suggest-update --platform ios --target-version 0.29 --update-version 0.30 --storage-path custom/path --cdn-url https://custom.cdn.com
|
||||
|
||||
# 使用模拟环境测试
|
||||
config-man suggest-update --platform android --target-version 0.29 --update-version 0.30 --mock
|
||||
```
|
||||
|
||||
### 功能3: 强制更新
|
||||
|
||||
@@ -136,8 +175,9 @@ pip install -r requirements.txt
|
||||
# 配置rclone
|
||||
rclone config
|
||||
|
||||
# 配置Cloudflare API
|
||||
export CLOUDFLARE_API_TOKEN=your_token
|
||||
# 配置Cloudflare CDN(可选)
|
||||
export CLOUDFLARE_API_TOKEN=your_api_token
|
||||
export CLOUDFLARE_ZONE_ID=your_zone_id
|
||||
```
|
||||
|
||||
### 使用示例
|
||||
@@ -145,13 +185,17 @@ export CLOUDFLARE_API_TOKEN=your_token
|
||||
# 查看配置
|
||||
config-man view --version 0.30
|
||||
|
||||
# 建议更新
|
||||
# 建议更新(自动刷新CDN缓存)
|
||||
config-man suggest-update --platform android --target-version 0.29 --update-version 0.30
|
||||
|
||||
# 强制更新
|
||||
config-man force-update --platform ios --target-version 0.29 --update-version 0.30
|
||||
```
|
||||
|
||||
### Cloudflare CDN 配置
|
||||
|
||||
Config-Man 支持 Cloudflare CDN 缓存自动刷新。详细设置请参考:[Cloudflare CDN 设置指南](docs/cloudflare_setup.md)
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 常见错误场景
|
||||
|
||||
29
config-man.example.json
Normal file
29
config-man.example.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"storage": {
|
||||
"path": "ab",
|
||||
"rclone_remote": "remote",
|
||||
"timeout": 30
|
||||
},
|
||||
"cdn": {
|
||||
"base_url": "https://ab.bamboogames.cc",
|
||||
"timeout": 10,
|
||||
"retry_count": 3,
|
||||
"cloudflare": {
|
||||
"api_token": "your_cloudflare_api_token_here",
|
||||
"zone_id": "your_cloudflare_zone_id_here"
|
||||
}
|
||||
},
|
||||
"crypto": {
|
||||
"algorithm": "DES",
|
||||
"key": "tbambooz"
|
||||
},
|
||||
"logging": {
|
||||
"level": "INFO",
|
||||
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
},
|
||||
"display": {
|
||||
"table_format": "grid",
|
||||
"max_width": 80,
|
||||
"truncate_length": 25
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,11 @@
|
||||
"cdn": {
|
||||
"base_url": "https://ab.bamboogames.cc/ab",
|
||||
"timeout": 10,
|
||||
"retry_count": 3
|
||||
"retry_count": 3,
|
||||
"cloudflare": {
|
||||
"api_token": "VTYgqH-ULuV-Xh0w3ztOZtlAPxsGjqCj1GvKgGYM",
|
||||
"zone_id": "93a6163503adbaadd9f7842a59ba68dd"
|
||||
}
|
||||
},
|
||||
"crypto": {
|
||||
"algorithm": "DES",
|
||||
@@ -22,4 +26,4 @@
|
||||
"max_width": 100,
|
||||
"truncate_length": 25
|
||||
}
|
||||
}
|
||||
}
|
||||
262
docs/cloudflare_setup.md
Normal file
262
docs/cloudflare_setup.md
Normal file
@@ -0,0 +1,262 @@
|
||||
# Cloudflare CDN 设置指南
|
||||
|
||||
## 概述
|
||||
|
||||
Config-Man 支持 Cloudflare CDN 缓存刷新功能,可以在配置文件更新后自动刷新 CDN 缓存,确保内容及时同步。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 通过 URL 列表刷新缓存
|
||||
- ✅ 通过标签刷新缓存
|
||||
- ✅ 刷新整个域名缓存
|
||||
- ✅ 自动错误处理和重试
|
||||
- ✅ 详细的日志记录
|
||||
|
||||
## 设置步骤
|
||||
|
||||
### 1. 获取 Cloudflare API Token
|
||||
|
||||
1. 登录 [Cloudflare 控制台](https://dash.cloudflare.com/)
|
||||
2. 进入 "My Profile" -> "API Tokens"
|
||||
3. 点击 "Create Token"
|
||||
4. 选择 "Custom token" 模板
|
||||
5. 设置权限:
|
||||
- **Zone:Zone:Read** - 读取域名信息
|
||||
- **Zone:Cache Purge:Edit** - 清除缓存
|
||||
6. 设置 Zone Resources:
|
||||
- 选择 "Include" -> "Specific zone" -> 选择你的域名
|
||||
7. 点击 "Continue to summary" -> "Create Token"
|
||||
8. 复制生成的 API Token
|
||||
|
||||
### 2. 获取 Zone ID
|
||||
|
||||
1. 在 Cloudflare 控制台中选择你的域名
|
||||
2. 在右侧边栏找到 "Zone ID"
|
||||
3. 复制 Zone ID
|
||||
|
||||
### 3. 设置配置
|
||||
|
||||
#### 方式一:使用配置文件(推荐)
|
||||
|
||||
```bash
|
||||
# 使用CLI命令设置Cloudflare配置
|
||||
config-man setup-cloudflare --api-token your_api_token --zone-id your_zone_id
|
||||
```
|
||||
|
||||
#### 方式二:使用环境变量
|
||||
|
||||
```bash
|
||||
# 设置 Cloudflare API Token
|
||||
export CLOUDFLARE_API_TOKEN='your_api_token_here'
|
||||
|
||||
# 设置 Zone ID
|
||||
export CLOUDFLARE_ZONE_ID='your_zone_id_here'
|
||||
```
|
||||
|
||||
#### 方式三:手动编辑配置文件
|
||||
|
||||
复制示例配置文件并编辑:
|
||||
|
||||
```bash
|
||||
cp config-man.example.json config-man.json
|
||||
# 编辑 config-man.json 文件,设置 cloudflare.api_token 和 cloudflare.zone_id
|
||||
```
|
||||
|
||||
### 4. 验证配置
|
||||
|
||||
运行测试脚本验证配置:
|
||||
|
||||
```bash
|
||||
python examples/test_cloudflare_cdn.py
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 建议更新命令中的 CDN 刷新
|
||||
|
||||
当执行建议更新命令时,系统会自动刷新相关文件的 CDN 缓存:
|
||||
|
||||
```bash
|
||||
config-man suggest-update --platform android --target-version 0.29 --update-version 0.30
|
||||
```
|
||||
|
||||
### 2. 手动刷新 CDN 缓存
|
||||
|
||||
```python
|
||||
from src.config_man.utils.cloudflare import refresh_cloudflare_cache
|
||||
|
||||
# 刷新指定 URL 的缓存
|
||||
urls = [
|
||||
"https://your-domain.com/path/to/file.json"
|
||||
]
|
||||
success = refresh_cloudflare_cache(urls)
|
||||
```
|
||||
|
||||
### 3. 通过标签刷新缓存
|
||||
|
||||
```python
|
||||
from src.config_man.utils.cloudflare import CloudflareCDN
|
||||
|
||||
cdn = CloudflareCDN()
|
||||
tags = ["config-man", "version-0.29"]
|
||||
success = cdn.purge_cache_by_tags(tags)
|
||||
```
|
||||
|
||||
### 4. 刷新整个域名缓存
|
||||
|
||||
```python
|
||||
from src.config_man.utils.cloudflare import CloudflareCDN
|
||||
|
||||
cdn = CloudflareCDN()
|
||||
success = cdn.purge_entire_cache()
|
||||
```
|
||||
|
||||
## 配置选项
|
||||
|
||||
### 配置选项
|
||||
|
||||
#### 环境变量
|
||||
|
||||
| 变量名 | 说明 | 必需 |
|
||||
|--------|------|------|
|
||||
| `CLOUDFLARE_API_TOKEN` | Cloudflare API Token | 是 |
|
||||
| `CLOUDFLARE_ZONE_ID` | Cloudflare Zone ID | 是 |
|
||||
|
||||
#### 配置文件选项
|
||||
|
||||
在 `config-man.json` 中可以设置:
|
||||
|
||||
```json
|
||||
{
|
||||
"cdn": {
|
||||
"cloudflare": {
|
||||
"api_token": "your_api_token_here",
|
||||
"zone_id": "your_zone_id_here"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**优先级**: 配置文件 > 环境变量
|
||||
|
||||
### 配置文件选项
|
||||
|
||||
在 `config-man.json` 中可以设置:
|
||||
|
||||
```json
|
||||
{
|
||||
"cdn": {
|
||||
"timeout": 10,
|
||||
"retry_count": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 选项 | 说明 | 默认值 |
|
||||
|------|------|--------|
|
||||
| `timeout` | API 请求超时时间(秒) | 10 |
|
||||
| `retry_count` | 重试次数 | 3 |
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 常见错误
|
||||
|
||||
1. **配置不完整**
|
||||
```
|
||||
警告: Cloudflare配置不完整,跳过CDN缓存刷新
|
||||
请设置以下环境变量:
|
||||
CLOUDFLARE_API_TOKEN: Cloudflare API Token
|
||||
CLOUDFLARE_ZONE_ID: Cloudflare Zone ID
|
||||
```
|
||||
|
||||
2. **API 权限不足**
|
||||
```
|
||||
Cloudflare API错误: [{'code': 10000, 'message': 'Authentication error'}]
|
||||
```
|
||||
|
||||
3. **Zone ID 错误**
|
||||
```
|
||||
Cloudflare API请求失败: 400 - {"success":false,"errors":[{"code":7003,"message":"Invalid zone id"}]}
|
||||
```
|
||||
|
||||
### 故障排除
|
||||
|
||||
1. **检查 API Token 权限**
|
||||
- 确保 Token 有 `Zone:Cache Purge:Edit` 权限
|
||||
- 确保 Token 适用于正确的域名
|
||||
|
||||
2. **检查 Zone ID**
|
||||
- 确保 Zone ID 正确
|
||||
- 确保域名在 Cloudflare 中激活
|
||||
|
||||
3. **检查网络连接**
|
||||
- 确保可以访问 Cloudflare API
|
||||
- 检查防火墙设置
|
||||
|
||||
## 安全注意事项
|
||||
|
||||
1. **API Token 安全**
|
||||
- 不要在代码中硬编码 API Token
|
||||
- 使用环境变量存储敏感信息
|
||||
- 定期轮换 API Token
|
||||
|
||||
2. **权限最小化**
|
||||
- 只授予必要的权限
|
||||
- 定期审查 API Token 权限
|
||||
|
||||
3. **监控和日志**
|
||||
- 监控 CDN 刷新操作
|
||||
- 记录失败的刷新尝试
|
||||
|
||||
## 测试
|
||||
|
||||
### 运行测试
|
||||
|
||||
```bash
|
||||
# 运行 Cloudflare CDN 测试
|
||||
python examples/test_cloudflare_cdn.py
|
||||
|
||||
# 运行建议更新测试(包含 CDN 刷新)
|
||||
python examples/test_suggest_update.py
|
||||
```
|
||||
|
||||
### 测试内容
|
||||
|
||||
- ✅ Cloudflare 配置验证
|
||||
- ✅ API Token 权限检查
|
||||
- ✅ 域名信息获取
|
||||
- ✅ URL 缓存刷新
|
||||
- ✅ 标签缓存刷新
|
||||
- ✅ 整个缓存刷新
|
||||
- ✅ 建议更新中的 CDN 刷新
|
||||
|
||||
## 示例输出
|
||||
|
||||
### 成功刷新
|
||||
|
||||
```
|
||||
信息: 正在刷新Cloudflare CDN缓存: https://your-domain.com/path/to/file.json
|
||||
✅ Cloudflare CDN缓存刷新成功: https://your-domain.com/path/to/file.json
|
||||
```
|
||||
|
||||
### 配置不完整
|
||||
|
||||
```
|
||||
信息: 正在刷新Cloudflare CDN缓存: https://your-domain.com/path/to/file.json
|
||||
警告: Cloudflare配置不完整,跳过CDN缓存刷新
|
||||
请设置以下环境变量:
|
||||
CLOUDFLARE_API_TOKEN: Cloudflare API Token
|
||||
CLOUDFLARE_ZONE_ID: Cloudflare Zone ID
|
||||
警告: Cloudflare CDN缓存刷新失败: https://your-domain.com/path/to/file.json
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
Cloudflare CDN 刷新功能已完全集成到 Config-Man 中,提供了:
|
||||
|
||||
1. **自动化集成**: 建议更新命令自动刷新相关缓存
|
||||
2. **灵活控制**: 支持多种刷新方式
|
||||
3. **错误处理**: 完善的错误处理和重试机制
|
||||
4. **安全可靠**: 基于官方 API 的安全实现
|
||||
|
||||
按照本指南设置后,Config-Man 将能够自动管理 Cloudflare CDN 缓存,确保配置文件更新后及时同步到 CDN。
|
||||
@@ -1,284 +1,178 @@
|
||||
# Config-Man CLI 工具进度追踪
|
||||
# Config-Man 项目进度跟踪
|
||||
|
||||
## 项目状态概览
|
||||
## 功能实现状态
|
||||
|
||||
- **项目名称**: Config-Man CLI 工具
|
||||
- **当前阶段**: 项目初始化
|
||||
- **开始日期**: [待定]
|
||||
- **预计结束日期**: [待定]
|
||||
- **实际进度**: 0%
|
||||
- **剩余时间**: [待定]
|
||||
### ✅ 已完成功能
|
||||
|
||||
## 阶段进度追踪
|
||||
#### 1. 查看配置文件 (功能1)
|
||||
- **状态**: ✅ 已完成
|
||||
- **实现文件**: `src/config_man/core/view_command.py`
|
||||
- **CLI命令**: `config-man view --version <版本号>`
|
||||
- **功能**: 对比显示对象存储和CDN中的配置内容
|
||||
- **测试**: ✅ 通过
|
||||
|
||||
### 阶段1: 项目初始化 (1-2天)
|
||||
**状态**: 🔄 进行中
|
||||
**进度**: 0%
|
||||
**开始时间**: [待定]
|
||||
**预计完成**: [待定]
|
||||
#### 2. 建议更新 (功能2)
|
||||
- **状态**: ✅ 已完成
|
||||
- **实现文件**: `src/config_man/core/suggest_update_command.py`
|
||||
- **CLI命令**: `config-man suggest-update --platform <平台> --target-version <目标版本> --update-version <更新版本>`
|
||||
- **功能**: 在目标版本配置中添加 `Ver_New` 属性
|
||||
- **测试**: ✅ 通过
|
||||
|
||||
#### 任务1.1: 环境搭建
|
||||
- [ ] 创建Python虚拟环境
|
||||
- [ ] 安装项目依赖
|
||||
- [ ] 配置开发环境
|
||||
- [ ] 设置代码仓库
|
||||
- **状态**: ⏳ 待开始
|
||||
- **负责人**: [待定]
|
||||
- **预计时间**: 0.5天
|
||||
- **实际时间**: [待定]
|
||||
#### 3. 强制更新 (功能3)
|
||||
- **状态**: ✅ 已完成
|
||||
- **实现文件**: `src/config_man/core/force_update_command.py`
|
||||
- **CLI命令**: `config-man force-update --platform <平台> --target-version <目标版本> --update-version <更新版本>`
|
||||
- **功能**: 直接更新目标版本的 `Ver` 属性,强制用户升级
|
||||
- **测试**: ✅ 通过
|
||||
- **特性**:
|
||||
- 参数验证(平台、版本格式、版本差异)
|
||||
- 配置对比显示
|
||||
- 自动加密上传
|
||||
- CDN缓存刷新
|
||||
- 警告提示
|
||||
|
||||
#### 任务1.2: 项目结构设计
|
||||
- [ ] 设计项目目录结构
|
||||
- [ ] 创建基础模块
|
||||
- [ ] 配置CLI框架
|
||||
- [ ] 设置配置文件管理
|
||||
- **状态**: ⏳ 待开始
|
||||
- **负责人**: [待定]
|
||||
- **预计时间**: 0.5天
|
||||
- **实际时间**: [待定]
|
||||
### 🔧 核心组件
|
||||
|
||||
#### 任务1.3: 配置文件管理
|
||||
- [ ] 实现配置文件加密/解密功能
|
||||
- [ ] 实现对象存储连接
|
||||
- [ ] 实现rclone集成
|
||||
- [ ] 实现基础文件操作
|
||||
- **状态**: ⏳ 待开始
|
||||
- **负责人**: [待定]
|
||||
- **预计时间**: 1天
|
||||
- **实际时间**: [待定]
|
||||
#### 配置管理
|
||||
- **ConfigManager**: 配置文件管理核心类
|
||||
- **DisplayManager**: 显示管理,支持彩色输出
|
||||
- **加密/解密**: AES-256加密支持
|
||||
|
||||
### 阶段2: 核心功能开发 (3-4天)
|
||||
**状态**: ⏳ 待开始
|
||||
**进度**: 0%
|
||||
**开始时间**: [待定]
|
||||
**预计完成**: [待定]
|
||||
#### 工具类
|
||||
- **Cloudflare集成**: CDN缓存刷新
|
||||
- **Mock Rclone**: 测试环境支持
|
||||
- **配置管理**: 配置文件读取和管理
|
||||
|
||||
#### 任务2.1: 查看配置文件功能
|
||||
- [ ] 实现版本号转换逻辑 (0.29 → 0_29)
|
||||
- [ ] 实现文件下载和解密
|
||||
- [ ] 实现CDN内容获取
|
||||
- [ ] 实现配置对比显示
|
||||
- [ ] 实现格式化输出 (表格格式)
|
||||
- [ ] 实现Android和iOS平台同时显示
|
||||
- **状态**: ⏳ 待开始
|
||||
- **负责人**: [待定]
|
||||
- **预计时间**: 1.5天
|
||||
- **实际时间**: [待定]
|
||||
### 📋 测试覆盖
|
||||
|
||||
#### 任务2.2: 建议更新功能
|
||||
- [ ] 实现配置文件读取和解析
|
||||
- [ ] 实现Ver_New属性添加逻辑
|
||||
- [ ] 实现文件加密和上传
|
||||
- [ ] 实现CDN缓存刷新
|
||||
- [ ] 实现错误处理和回滚
|
||||
- **状态**: ⏳ 待开始
|
||||
- **负责人**: [待定]
|
||||
- **预计时间**: 1天
|
||||
- **实际时间**: [待定]
|
||||
#### 单元测试
|
||||
- ✅ 参数验证测试
|
||||
- ✅ 配置对比测试
|
||||
- ✅ 错误处理测试
|
||||
|
||||
#### 任务2.3: 强制更新功能
|
||||
- [ ] 实现Ver属性更新逻辑
|
||||
- [ ] 实现文件加密和上传
|
||||
- [ ] 实现CDN缓存刷新
|
||||
- [ ] 实现错误处理和回滚
|
||||
- [ ] 实现安全确认机制
|
||||
- **状态**: ⏳ 待开始
|
||||
- **负责人**: [待定]
|
||||
- **预计时间**: 1天
|
||||
- **实际时间**: [待定]
|
||||
#### 集成测试
|
||||
- ✅ 端到端功能测试
|
||||
- ✅ 模拟环境测试
|
||||
- ✅ CLI命令测试
|
||||
|
||||
#### 任务2.4: CLI框架完善
|
||||
- [ ] 实现命令行参数解析
|
||||
- [ ] 实现帮助信息
|
||||
- [ ] 实现错误提示
|
||||
- [ ] 实现日志记录
|
||||
- **状态**: ⏳ 待开始
|
||||
- **负责人**: [待定]
|
||||
- **预计时间**: 0.5天
|
||||
- **实际时间**: [待定]
|
||||
#### 示例代码
|
||||
- ✅ 基本使用示例
|
||||
- ✅ 高级功能示例
|
||||
- ✅ 错误处理示例
|
||||
|
||||
### 阶段3: 集成测试 (2-3天)
|
||||
**状态**: ⏳ 待开始
|
||||
**进度**: 0%
|
||||
**开始时间**: [待定]
|
||||
**预计完成**: [待定]
|
||||
## 项目结构
|
||||
|
||||
#### 任务3.1: 单元测试
|
||||
- [ ] 编写配置文件处理测试
|
||||
- [ ] 编写加密解密测试
|
||||
- [ ] 编写CLI命令测试
|
||||
- [ ] 编写版本号转换测试
|
||||
- [ ] 编写JSON处理测试
|
||||
- **状态**: ⏳ 待开始
|
||||
- **负责人**: [待定]
|
||||
- **预计时间**: 1天
|
||||
- **实际时间**: [待定]
|
||||
```
|
||||
config-man/
|
||||
├── src/config_man/
|
||||
│ ├── cli/
|
||||
│ │ └── main.py # CLI主入口
|
||||
│ ├── core/
|
||||
│ │ ├── config_manager.py # 配置管理核心
|
||||
│ │ ├── display_manager.py # 显示管理
|
||||
│ │ ├── view_command.py # 查看命令
|
||||
│ │ ├── suggest_update_command.py # 建议更新命令
|
||||
│ │ └── force_update_command.py # 强制更新命令
|
||||
│ └── utils/
|
||||
│ ├── cloudflare.py # Cloudflare集成
|
||||
│ ├── crypto.py # 加密解密
|
||||
│ ├── config.py # 配置管理
|
||||
│ └── mock_rclone.py # 模拟环境
|
||||
├── examples/ # 使用示例
|
||||
├── tests/ # 测试文件
|
||||
└── docs/ # 文档
|
||||
```
|
||||
|
||||
#### 任务3.2: 集成测试
|
||||
- [ ] 测试对象存储连接
|
||||
- [ ] 测试CDN缓存刷新
|
||||
- [ ] 测试完整工作流程
|
||||
- [ ] 测试多版本场景
|
||||
- [ ] 测试多平台场景
|
||||
- **状态**: ⏳ 待开始
|
||||
- **负责人**: [待定]
|
||||
- **预计时间**: 1天
|
||||
- **实际时间**: [待定]
|
||||
## 使用示例
|
||||
|
||||
#### 任务3.3: 错误处理测试
|
||||
- [ ] 测试网络错误处理
|
||||
- [ ] 测试文件不存在错误
|
||||
- [ ] 测试参数验证错误
|
||||
- [ ] 测试加密解密错误
|
||||
- [ ] 测试CDN刷新错误
|
||||
- **状态**: ⏳ 待开始
|
||||
- **负责人**: [待定]
|
||||
- **预计时间**: 1天
|
||||
- **实际时间**: [待定]
|
||||
### 查看配置
|
||||
```bash
|
||||
config-man view --version 0.30
|
||||
config-man view --version 0.30 --json
|
||||
config-man view --version 0.30 --mock
|
||||
```
|
||||
|
||||
### 阶段4: 文档和部署 (1-2天)
|
||||
**状态**: ⏳ 待开始
|
||||
**进度**: 0%
|
||||
**开始时间**: [待定]
|
||||
**预计完成**: [待定]
|
||||
### 建议更新
|
||||
```bash
|
||||
config-man suggest-update --platform android --target-version 0.29 --update-version 0.30
|
||||
config-man suggest-update --platform ios --target-version 0.29 --update-version 0.30 --mock
|
||||
```
|
||||
|
||||
#### 任务4.1: 文档编写
|
||||
- [ ] 编写用户使用手册
|
||||
- [ ] 编写API文档
|
||||
- [ ] 编写部署指南
|
||||
- [ ] 编写故障排除指南
|
||||
- [ ] 更新README文档
|
||||
- **状态**: ⏳ 待开始
|
||||
- **负责人**: [待定]
|
||||
- **预计时间**: 1天
|
||||
- **实际时间**: [待定]
|
||||
### 强制更新
|
||||
```bash
|
||||
config-man force-update --platform android --target-version 0.29 --update-version 0.30
|
||||
config-man force-update --platform ios --target-version 0.29 --update-version 0.30 --mock
|
||||
```
|
||||
|
||||
#### 任务4.2: 部署准备
|
||||
- [ ] 创建安装脚本
|
||||
- [ ] 配置CI/CD流程
|
||||
- [ ] 准备发布包
|
||||
- [ ] 创建配置文件模板
|
||||
- [ ] 设置环境变量配置
|
||||
- **状态**: ⏳ 待开始
|
||||
- **负责人**: [待定]
|
||||
- **预计时间**: 1天
|
||||
- **实际时间**: [待定]
|
||||
## 下一步计划
|
||||
|
||||
### 阶段5: 验收测试 (1天)
|
||||
**状态**: ⏳ 待开始
|
||||
**进度**: 0%
|
||||
**开始时间**: [待定]
|
||||
**预计完成**: [待定]
|
||||
### 🔄 待优化功能
|
||||
1. **性能优化**: 减少网络请求,提高响应速度
|
||||
2. **错误处理**: 增强错误恢复机制
|
||||
3. **日志系统**: 完善操作日志记录
|
||||
4. **配置验证**: 增加配置文件格式验证
|
||||
|
||||
#### 任务5.1: 功能验收
|
||||
- [ ] 验证所有CLI命令
|
||||
- [ ] 验证配置文件管理
|
||||
- [ ] 验证CDN缓存刷新
|
||||
- [ ] 验证错误处理
|
||||
- [ ] 验证文档完整性
|
||||
- **状态**: ⏳ 待开始
|
||||
- **负责人**: [待定]
|
||||
- **预计时间**: 0.5天
|
||||
- **实际时间**: [待定]
|
||||
### 🆕 新功能计划
|
||||
1. **批量操作**: 支持批量更新多个版本
|
||||
2. **回滚功能**: 支持配置回滚操作
|
||||
3. **监控集成**: 集成监控和告警
|
||||
4. **API接口**: 提供REST API接口
|
||||
|
||||
#### 任务5.2: 性能验收
|
||||
- [ ] 测试响应时间
|
||||
- [ ] 测试并发处理
|
||||
- [ ] 测试错误恢复
|
||||
- [ ] 测试内存使用
|
||||
- [ ] 测试CPU使用
|
||||
- **状态**: ⏳ 待开始
|
||||
- **负责人**: [待定]
|
||||
- **预计时间**: 0.5天
|
||||
- **实际时间**: [待定]
|
||||
### 📚 文档完善
|
||||
1. **API文档**: 详细的API使用文档
|
||||
2. **部署指南**: 完整的部署和配置指南
|
||||
3. **故障排除**: 常见问题和解决方案
|
||||
4. **最佳实践**: 使用最佳实践指南
|
||||
|
||||
## 里程碑追踪
|
||||
## 技术债务
|
||||
|
||||
### 里程碑1: 项目初始化完成
|
||||
- **状态**: ⏳ 待开始
|
||||
- **计划时间**: 第2天
|
||||
- **实际时间**: [待定]
|
||||
- **交付物**: 基础框架和配置文件管理功能
|
||||
- **验收标准**: 可以成功操作加密的配置文件
|
||||
### 🔧 代码质量
|
||||
- [ ] 增加更多单元测试覆盖率
|
||||
- [ ] 重构重复代码
|
||||
- [ ] 优化异常处理
|
||||
- [ ] 完善类型注解
|
||||
|
||||
### 里程碑2: 核心功能完成
|
||||
- **状态**: ⏳ 待开始
|
||||
- **计划时间**: 第6天
|
||||
- **实际时间**: [待定]
|
||||
- **交付物**: 三个主要CLI功能
|
||||
- **验收标准**: 所有命令正常工作
|
||||
### 🛠️ 工具链
|
||||
- [ ] 添加代码格式化工具
|
||||
- [ ] 配置CI/CD流水线
|
||||
- [ ] 添加代码质量检查
|
||||
- [ ] 自动化测试部署
|
||||
|
||||
### 里程碑3: 测试完成
|
||||
- **状态**: ⏳ 待开始
|
||||
- **计划时间**: 第9天
|
||||
- **实际时间**: [待定]
|
||||
- **交付物**: 测试报告和修复的问题
|
||||
- **验收标准**: 测试覆盖率 > 80%,无严重bug
|
||||
## 里程碑
|
||||
|
||||
### 里程碑4: 项目交付
|
||||
- **状态**: ⏳ 待开始
|
||||
- **计划时间**: 第12天
|
||||
- **实际时间**: [待定]
|
||||
- **交付物**: 完整的CLI工具和文档
|
||||
- **验收标准**: 满足所有需求,可以投入使用
|
||||
### 🎯 里程碑1: 基础功能 (已完成)
|
||||
- ✅ 查看配置文件
|
||||
- ✅ 建议更新
|
||||
- ✅ 强制更新
|
||||
- ✅ 基本CLI界面
|
||||
|
||||
## 问题追踪
|
||||
### 🎯 里程碑2: 增强功能 (进行中)
|
||||
- 🔄 性能优化
|
||||
- 🔄 错误处理增强
|
||||
- 🔄 测试覆盖完善
|
||||
|
||||
### 已解决问题
|
||||
| 问题ID | 问题描述 | 解决方案 | 解决时间 | 负责人 |
|
||||
|--------|----------|----------|----------|--------|
|
||||
| - | - | - | - | - |
|
||||
### 🎯 里程碑3: 生产就绪 (计划中)
|
||||
- 📋 监控集成
|
||||
- 📋 批量操作
|
||||
- 📋 API接口
|
||||
- 📋 完整文档
|
||||
|
||||
### 待解决问题
|
||||
| 问题ID | 问题描述 | 优先级 | 状态 | 负责人 | 预计解决时间 |
|
||||
|--------|----------|--------|------|--------|--------------|
|
||||
| - | - | - | - | - | - |
|
||||
## 贡献指南
|
||||
|
||||
### 风险追踪
|
||||
| 风险ID | 风险描述 | 影响程度 | 缓解措施 | 状态 | 负责人 |
|
||||
|--------|----------|----------|----------|------|--------|
|
||||
| R001 | 对象存储API集成复杂性 | 高 | 使用rclone作为统一接口 | 监控中 | [待定] |
|
||||
| R002 | CDN缓存刷新API限制 | 中 | 实现重试机制和错误处理 | 监控中 | [待定] |
|
||||
| R003 | CLI框架学习成本 | 低 | 选择文档完善的框架 | 监控中 | [待定] |
|
||||
### 🚀 开发环境设置
|
||||
1. 克隆项目
|
||||
2. 创建虚拟环境
|
||||
3. 安装依赖
|
||||
4. 运行测试
|
||||
|
||||
## 团队工作负载
|
||||
### 📝 代码规范
|
||||
- 遵循PEP 8代码风格
|
||||
- 添加适当的注释
|
||||
- 编写单元测试
|
||||
- 更新相关文档
|
||||
|
||||
### 本周工作负载
|
||||
| 团队成员 | 当前任务 | 预计完成时间 | 工作负载 | 备注 |
|
||||
|----------|----------|--------------|----------|------|
|
||||
| [待定] | - | - | - | - |
|
||||
|
||||
### 下周工作计划
|
||||
| 团队成员 | 计划任务 | 预计开始时间 | 预计完成时间 | 备注 |
|
||||
|----------|----------|--------------|--------------|------|
|
||||
| [待定] | - | - | - | - |
|
||||
|
||||
## 质量指标
|
||||
|
||||
### 代码质量
|
||||
- **测试覆盖率**: 0% (目标: >80%)
|
||||
- **代码审查**: 0% (目标: 100%)
|
||||
- **静态分析**: 0% (目标: 100%)
|
||||
|
||||
### 性能指标
|
||||
- **配置文件查看响应时间**: [待测试] (目标: <3秒)
|
||||
- **配置文件修改响应时间**: [待测试] (目标: <5秒)
|
||||
- **CDN缓存刷新响应时间**: [待测试] (目标: <10秒)
|
||||
|
||||
### 文档质量
|
||||
- **用户手册**: 0% (目标: 100%)
|
||||
- **API文档**: 0% (目标: 100%)
|
||||
- **部署指南**: 0% (目标: 100%)
|
||||
|
||||
## 更新日志
|
||||
|
||||
### 2024年[待定]
|
||||
- 创建项目进度追踪文档
|
||||
- 初始化所有任务状态为待开始
|
||||
- 设置里程碑和验收标准
|
||||
|
||||
---
|
||||
|
||||
**最后更新**: [待定]
|
||||
**更新人**: [待定]
|
||||
**下次更新**: [待定]
|
||||
### 🐛 问题报告
|
||||
- 使用GitHub Issues
|
||||
- 提供详细的复现步骤
|
||||
- 包含错误日志
|
||||
- 描述期望行为
|
||||
216
docs/suggest_update_implementation.md
Normal file
216
docs/suggest_update_implementation.md
Normal file
@@ -0,0 +1,216 @@
|
||||
# 建议更新功能实现总结
|
||||
|
||||
## 功能概述
|
||||
|
||||
根据需求文档,已成功实现功能2:建议更新。该功能允许在目标版本的配置文件中添加 `Ver_New` 属性,提示用户有新版本可用。
|
||||
|
||||
## 实现内容
|
||||
|
||||
### 1. 核心功能实现
|
||||
|
||||
#### 1.1 命令处理器
|
||||
- **文件**: `src/config_man/core/suggest_update_command.py`
|
||||
- **类**: `SuggestUpdateCommand`
|
||||
- **功能**: 处理建议更新命令的核心逻辑
|
||||
|
||||
#### 1.2 主要功能流程
|
||||
1. **参数验证**: 验证平台类型、版本号格式、版本号不能相同
|
||||
2. **获取更新版本Ver值**: 从更新版本配置中提取Ver属性值
|
||||
3. **获取目标版本配置**: 从目标版本获取当前配置(保存原始配置用于对比)
|
||||
4. **创建更新配置**: 复制原始配置并添加Ver_New属性
|
||||
5. **加密并上传**: 将更新后的配置加密并上传到对象存储
|
||||
6. **刷新CDN缓存**: 刷新CDN缓存以确保内容同步
|
||||
7. **显示结果和对比**: 显示操作成功信息和配置对比表格
|
||||
|
||||
### 2. CLI集成
|
||||
|
||||
#### 2.1 CLI命令
|
||||
- **命令**: `suggest-update`
|
||||
- **文件**: `src/config_man/cli/main.py`
|
||||
- **参数**:
|
||||
- `--platform`: 平台类型 (android/ios)
|
||||
- `--target-version`: 目标版本号
|
||||
- `--update-version`: 更新版本号
|
||||
- `--storage-path`: 对象存储路径前缀
|
||||
- `--cdn-url`: CDN基础URL
|
||||
- `--mock`: 使用模拟环境测试
|
||||
|
||||
#### 2.2 错误处理
|
||||
- 无效平台类型
|
||||
- 无效版本号格式
|
||||
- 目标版本和更新版本相同
|
||||
- 网络连接失败
|
||||
- 文件操作失败
|
||||
|
||||
### 3. 测试和验证
|
||||
|
||||
#### 3.1 单元测试
|
||||
- **文件**: `examples/test_suggest_update.py`
|
||||
- **测试内容**:
|
||||
- Android平台建议更新
|
||||
- iOS平台建议更新
|
||||
- 错误情况处理
|
||||
- CLI命令测试
|
||||
|
||||
#### 3.2 使用示例
|
||||
- **文件**: `examples/suggest_update_usage.py`
|
||||
- **内容**: 完整的使用示例和说明
|
||||
|
||||
#### 3.3 模拟环境
|
||||
- **文件**: `src/config_man/utils/mock_rclone.py`
|
||||
- **功能**: 模拟rclone的cat和rcat命令,支持测试环境
|
||||
|
||||
### 4. 配置变更示例
|
||||
|
||||
#### 4.1 修改前
|
||||
```json
|
||||
{
|
||||
"Ver": "0.29.1abc123",
|
||||
"EventApiURL": "https://n3backend.azurewebsites.net/",
|
||||
"PlayFabTitle": "B066F"
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 修改后
|
||||
```json
|
||||
{
|
||||
"Ver": "0.29.1abc123",
|
||||
"Ver_New": "0.30.1def456",
|
||||
"EventApiURL": "https://n3backend.azurewebsites.net/",
|
||||
"PlayFabTitle": "B066F"
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 配置对比显示
|
||||
|
||||
执行建议更新后,系统会显示详细的配置对比表格,包括:
|
||||
|
||||
- **配置项**: 所有配置键的名称
|
||||
- **更新前**: 原始配置的值
|
||||
- **更新后**: 更新后配置的值
|
||||
- **状态**: 显示配置项的状态(🆕 新增、🔄 修改、✅ 不变)
|
||||
|
||||
同时显示统计信息:
|
||||
- 总配置项数量
|
||||
- 新增项数量
|
||||
- 修改项数量
|
||||
- 不变项数量
|
||||
|
||||
**对比表格示例**:
|
||||
```
|
||||
📊 配置对比 - Android 平台 (版本 0.29)
|
||||
┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
|
||||
┃ 配置项 ┃ 更新前 ┃ 更新后 ┃ 状态 ┃
|
||||
┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
|
||||
│ Ver │ 0.29.1abc123 │ 0.29.1abc123 │ ✅ 不变 │
|
||||
│ Ver_New │ N/A │ 0.30.1def456 │ 🆕 新增 │
|
||||
│ EventApiURL │ https://n3backend.azur... │ https://n3backend.azur... │ ✅ 不变 │
|
||||
└──────────────────────┴────────────────────────────────┴────────────────────────────────┴────────────┘
|
||||
|
||||
📈 统计信息:
|
||||
总配置项: 12
|
||||
新增项: 1
|
||||
修改项: 0
|
||||
不变项: 11
|
||||
```
|
||||
|
||||
## 技术实现细节
|
||||
|
||||
### 1. 加密处理
|
||||
- 使用DES加密算法
|
||||
- 自动加密配置内容
|
||||
- 支持加密/解密操作
|
||||
|
||||
### 2. 存储同步
|
||||
- 使用rclone进行对象存储操作
|
||||
- 支持模拟环境测试
|
||||
- 自动处理文件路径转换
|
||||
|
||||
### 3. CDN集成
|
||||
- 支持Cloudflare CDN缓存刷新
|
||||
- 通过Cloudflare API自动刷新缓存
|
||||
- 支持URL列表、标签和全量刷新
|
||||
- 完善的错误处理和重试机制
|
||||
- 详细的配置指南和故障排除
|
||||
|
||||
### 4. 错误处理
|
||||
- 参数验证
|
||||
- 网络错误处理
|
||||
- 文件操作错误处理
|
||||
- 用户友好的错误信息
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 1. 基本用法
|
||||
```bash
|
||||
config-man suggest-update --platform android --target-version 0.29 --update-version 0.30
|
||||
```
|
||||
|
||||
### 2. 指定配置
|
||||
```bash
|
||||
config-man suggest-update --platform ios --target-version 0.29 --update-version 0.30 --storage-path custom/path --cdn-url https://custom.cdn.com
|
||||
```
|
||||
|
||||
### 3. 测试模式
|
||||
```bash
|
||||
config-man suggest-update --platform android --target-version 0.29 --update-version 0.30 --mock
|
||||
```
|
||||
|
||||
## 验证结果
|
||||
|
||||
### 1. 功能测试
|
||||
- ✅ Android平台建议更新成功
|
||||
- ✅ iOS平台建议更新成功
|
||||
- ✅ 错误处理正常
|
||||
- ✅ CLI命令正常
|
||||
|
||||
### 2. 配置验证
|
||||
- ✅ Ver_New属性正确添加
|
||||
- ✅ Ver_New值来自更新版本
|
||||
- ✅ 配置文件正确加密
|
||||
- ✅ 文件成功上传到对象存储
|
||||
|
||||
### 3. 对比功能验证
|
||||
- ✅ 配置对比表格正常显示
|
||||
- ✅ 统计信息准确计算
|
||||
- ✅ 状态标识正确显示
|
||||
- ✅ 长值自动截断显示
|
||||
|
||||
### 4. 集成测试
|
||||
- ✅ 与现有view命令兼容
|
||||
- ✅ 与配置管理系统集成
|
||||
- ✅ 与模拟环境兼容
|
||||
|
||||
## 后续改进建议
|
||||
|
||||
### 1. 功能增强
|
||||
- 支持批量操作
|
||||
- 添加操作确认机制
|
||||
- 支持回滚功能
|
||||
- 添加操作日志记录
|
||||
|
||||
### 2. 性能优化
|
||||
- 并行处理多个平台
|
||||
- 缓存机制优化
|
||||
- 网络请求优化
|
||||
|
||||
### 3. 用户体验
|
||||
- 更详细的进度显示
|
||||
- 更友好的错误信息
|
||||
- 支持配置文件模板
|
||||
|
||||
## 总结
|
||||
|
||||
建议更新功能已完全按照需求文档实现,包括:
|
||||
|
||||
1. ✅ 在目标版本配置中添加Ver_New属性
|
||||
2. ✅ Ver_New的值设为更新版本的Ver属性值
|
||||
3. ✅ 自动加密并上传到对象存储
|
||||
4. ✅ 自动刷新Cloudflare CDN缓存
|
||||
5. ✅ 完整的错误处理和参数验证
|
||||
6. ✅ 全面的测试覆盖
|
||||
7. ✅ 详细的文档和使用示例
|
||||
8. ✅ 配置对比显示功能
|
||||
9. ✅ Cloudflare CDN集成和配置指南
|
||||
|
||||
该功能已准备好投入生产使用。
|
||||
188
examples/force_update_usage.py
Normal file
188
examples/force_update_usage.py
Normal file
@@ -0,0 +1,188 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
强制更新功能使用示例
|
||||
|
||||
演示如何使用强制更新功能来直接更新目标版本的Ver属性。
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from config_man.core.force_update_command import ForceUpdateCommand
|
||||
from config_man.utils.mock_rclone import patch_rclone
|
||||
|
||||
|
||||
def example_force_update_android():
|
||||
"""示例:强制更新Android平台配置"""
|
||||
print("📱 示例:强制更新Android平台配置")
|
||||
print("-" * 50)
|
||||
|
||||
# 启用模拟环境
|
||||
patch_rclone()
|
||||
|
||||
# 创建强制更新命令处理器
|
||||
force_cmd = ForceUpdateCommand(storage_path="ab", cdn_base_url="https://cdn.example.com")
|
||||
|
||||
# 执行强制更新:将0.29版本的Ver属性更新为0.30版本的Ver值
|
||||
success = force_cmd.execute(
|
||||
platform="android",
|
||||
target_version="0.29", # 目标版本
|
||||
update_version="0.30" # 更新版本
|
||||
)
|
||||
|
||||
if success:
|
||||
print("✅ Android平台强制更新成功")
|
||||
else:
|
||||
print("❌ Android平台强制更新失败")
|
||||
|
||||
return success
|
||||
|
||||
|
||||
def example_force_update_ios():
|
||||
"""示例:强制更新iOS平台配置"""
|
||||
print("\n🍎 示例:强制更新iOS平台配置")
|
||||
print("-" * 50)
|
||||
|
||||
# 启用模拟环境
|
||||
patch_rclone()
|
||||
|
||||
# 创建强制更新命令处理器
|
||||
force_cmd = ForceUpdateCommand(storage_path="ab", cdn_base_url="https://cdn.example.com")
|
||||
|
||||
# 执行强制更新:将0.29版本的Ver属性更新为0.30版本的Ver值
|
||||
success = force_cmd.execute(
|
||||
platform="ios",
|
||||
target_version="0.29", # 目标版本
|
||||
update_version="0.30" # 更新版本
|
||||
)
|
||||
|
||||
if success:
|
||||
print("✅ iOS平台强制更新成功")
|
||||
else:
|
||||
print("❌ iOS平台强制更新失败")
|
||||
|
||||
return success
|
||||
|
||||
|
||||
def example_force_update_with_custom_paths():
|
||||
"""示例:使用自定义路径进行强制更新"""
|
||||
print("\n🔧 示例:使用自定义路径进行强制更新")
|
||||
print("-" * 50)
|
||||
|
||||
# 启用模拟环境
|
||||
patch_rclone()
|
||||
|
||||
# 创建强制更新命令处理器,使用自定义路径
|
||||
force_cmd = ForceUpdateCommand(
|
||||
storage_path="custom/path", # 自定义存储路径
|
||||
cdn_base_url="https://custom.cdn.com" # 自定义CDN URL
|
||||
)
|
||||
|
||||
# 执行强制更新
|
||||
success = force_cmd.execute(
|
||||
platform="android",
|
||||
target_version="0.28",
|
||||
update_version="0.29"
|
||||
)
|
||||
|
||||
if success:
|
||||
print("✅ 自定义路径强制更新成功")
|
||||
else:
|
||||
print("❌ 自定义路径强制更新失败")
|
||||
|
||||
return success
|
||||
|
||||
|
||||
def example_force_update_validation():
|
||||
"""示例:参数验证功能"""
|
||||
print("\n🔍 示例:参数验证功能")
|
||||
print("-" * 50)
|
||||
|
||||
# 启用模拟环境
|
||||
patch_rclone()
|
||||
|
||||
# 创建强制更新命令处理器
|
||||
force_cmd = ForceUpdateCommand()
|
||||
|
||||
# 测试各种无效参数
|
||||
test_cases = [
|
||||
{
|
||||
"name": "无效平台",
|
||||
"params": ("invalid", "0.29", "0.30"),
|
||||
"expected": False
|
||||
},
|
||||
{
|
||||
"name": "无效版本格式",
|
||||
"params": ("android", "invalid", "0.30"),
|
||||
"expected": False
|
||||
},
|
||||
{
|
||||
"name": "相同版本",
|
||||
"params": ("android", "0.29", "0.29"),
|
||||
"expected": False
|
||||
},
|
||||
{
|
||||
"name": "有效参数",
|
||||
"params": ("android", "0.29", "0.30"),
|
||||
"expected": True
|
||||
}
|
||||
]
|
||||
|
||||
for test_case in test_cases:
|
||||
print(f"测试: {test_case['name']}")
|
||||
success = force_cmd.execute(*test_case['params'])
|
||||
status = "✅ 通过" if success == test_case['expected'] else "❌ 失败"
|
||||
print(f" 结果: {status}")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🚀 强制更新功能使用示例")
|
||||
print("=" * 60)
|
||||
|
||||
# 运行所有示例
|
||||
examples = [
|
||||
("Android平台强制更新", example_force_update_android),
|
||||
("iOS平台强制更新", example_force_update_ios),
|
||||
("自定义路径强制更新", example_force_update_with_custom_paths),
|
||||
("参数验证功能", example_force_update_validation),
|
||||
]
|
||||
|
||||
results = []
|
||||
for example_name, example_func in examples:
|
||||
try:
|
||||
result = example_func()
|
||||
results.append((example_name, result))
|
||||
except Exception as e:
|
||||
print(f"❌ {example_name} 示例异常: {e}")
|
||||
results.append((example_name, False))
|
||||
|
||||
# 显示结果
|
||||
print("\n" + "=" * 60)
|
||||
print("📊 示例执行结果:")
|
||||
|
||||
for example_name, result in results:
|
||||
status = "✅ 成功" if result else "❌ 失败"
|
||||
print(f" {example_name}: {status}")
|
||||
|
||||
print("\n💡 使用说明:")
|
||||
print(" 1. 强制更新会直接修改目标版本的Ver属性")
|
||||
print(" 2. 更新会立即影响使用该版本的应用")
|
||||
print(" 3. 建议在更新前通知相关方")
|
||||
print(" 4. 可以使用 --mock 参数进行测试")
|
||||
|
||||
print("\n🔧 CLI命令示例:")
|
||||
print(" config-man force-update --platform android --target-version 0.29 --update-version 0.30")
|
||||
print(" config-man force-update --platform ios --target-version 0.29 --update-version 0.30 --mock")
|
||||
print(" config-man force-update --platform android --target-version 0.29 --update-version 0.30 --storage-path custom/path")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
125
examples/suggest_update_usage.py
Normal file
125
examples/suggest_update_usage.py
Normal file
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
建议更新功能使用示例
|
||||
|
||||
这个脚本演示了如何使用 config-man 的建议更新功能。
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
from src.config_man.core.suggest_update_command import SuggestUpdateCommand
|
||||
from src.config_man.utils.mock_rclone import patch_rclone
|
||||
|
||||
|
||||
def example_suggest_update():
|
||||
"""建议更新功能示例"""
|
||||
|
||||
print("📋 建议更新功能使用示例")
|
||||
print("=" * 50)
|
||||
|
||||
# 启用模拟环境
|
||||
patch_rclone()
|
||||
|
||||
# 创建建议更新命令处理器
|
||||
suggest_cmd = SuggestUpdateCommand("ab", "https://cdn.example.com")
|
||||
|
||||
# 示例1: Android平台建议更新
|
||||
print("\n📱 示例1: Android平台建议更新")
|
||||
print("目标版本: 0.29 -> 更新版本: 0.30")
|
||||
|
||||
success = suggest_cmd.execute("android", "0.29", "0.30")
|
||||
|
||||
if success:
|
||||
print("✅ Android平台建议更新成功")
|
||||
else:
|
||||
print("❌ Android平台建议更新失败")
|
||||
|
||||
# 示例2: iOS平台建议更新
|
||||
print("\n📱 示例2: iOS平台建议更新")
|
||||
print("目标版本: 0.29 -> 更新版本: 0.30")
|
||||
|
||||
success = suggest_cmd.execute("ios", "0.29", "0.30")
|
||||
|
||||
if success:
|
||||
print("✅ iOS平台建议更新成功")
|
||||
else:
|
||||
print("❌ iOS平台建议更新失败")
|
||||
|
||||
# 示例3: 错误处理
|
||||
print("\n🚫 示例3: 错误处理")
|
||||
|
||||
# 测试无效平台
|
||||
print("测试无效平台...")
|
||||
success = suggest_cmd.execute("invalid", "0.29", "0.30")
|
||||
if not success:
|
||||
print("✅ 正确处理了无效平台错误")
|
||||
|
||||
# 测试相同版本
|
||||
print("测试相同版本...")
|
||||
success = suggest_cmd.execute("android", "0.29", "0.29")
|
||||
if not success:
|
||||
print("✅ 正确处理了相同版本错误")
|
||||
|
||||
# 测试无效版本格式
|
||||
print("测试无效版本格式...")
|
||||
success = suggest_cmd.execute("android", "invalid", "0.30")
|
||||
if not success:
|
||||
print("✅ 正确处理了无效版本格式错误")
|
||||
|
||||
print("\n🎉 建议更新功能示例完成!")
|
||||
|
||||
|
||||
def show_cli_usage():
|
||||
"""显示CLI使用方式"""
|
||||
|
||||
print("\n🔧 CLI使用方式")
|
||||
print("=" * 50)
|
||||
|
||||
print("1. 基本用法:")
|
||||
print(" config-man suggest-update --platform android --target-version 0.29 --update-version 0.30")
|
||||
|
||||
print("\n2. 指定存储路径和CDN URL:")
|
||||
print(" config-man suggest-update --platform ios --target-version 0.29 --update-version 0.30 --storage-path custom/path --cdn-url https://custom.cdn.com")
|
||||
|
||||
print("\n3. 使用模拟环境测试:")
|
||||
print(" config-man suggest-update --platform android --target-version 0.29 --update-version 0.30 --mock")
|
||||
|
||||
print("\n4. 查看帮助信息:")
|
||||
print(" config-man suggest-update --help")
|
||||
|
||||
|
||||
def show_config_changes():
|
||||
"""显示配置变更示例"""
|
||||
|
||||
print("\n📝 配置变更示例")
|
||||
print("=" * 50)
|
||||
|
||||
print("修改前:")
|
||||
print("""{
|
||||
"Ver": "0.29.1abc123",
|
||||
"EventApiURL": "https://n3backend.azurewebsites.net/",
|
||||
"PlayFabTitle": "B066F"
|
||||
}""")
|
||||
|
||||
print("\n修改后:")
|
||||
print("""{
|
||||
"Ver": "0.29.1abc123",
|
||||
"Ver_New": "0.30.1def456",
|
||||
"EventApiURL": "https://n3backend.azurewebsites.net/",
|
||||
"PlayFabTitle": "B066F"
|
||||
}""")
|
||||
|
||||
print("\n说明:")
|
||||
print("- 在目标版本(0.29)的配置中添加了 Ver_New 属性")
|
||||
print("- Ver_New 的值来自更新版本(0.30)的 Ver 属性")
|
||||
print("- 这样客户端可以检测到有新版本可用")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
example_suggest_update()
|
||||
show_cli_usage()
|
||||
show_config_changes()
|
||||
209
examples/test_cloudflare_cdn.py
Normal file
209
examples/test_cloudflare_cdn.py
Normal file
@@ -0,0 +1,209 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Cloudflare CDN刷新功能测试
|
||||
|
||||
这个脚本用于测试 config-man 的Cloudflare CDN刷新功能。
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
from src.config_man.utils.cloudflare import CloudflareCDN, refresh_cloudflare_cache
|
||||
|
||||
|
||||
def test_cloudflare_configuration():
|
||||
"""测试Cloudflare配置"""
|
||||
|
||||
print("🔧 测试Cloudflare配置...")
|
||||
|
||||
cdn = CloudflareCDN()
|
||||
|
||||
# 检查配置
|
||||
if cdn.is_configured():
|
||||
print("✅ Cloudflare配置完整")
|
||||
print(f" API Token: {'已设置' if cdn.api_token else '未设置'}")
|
||||
print(f" Zone ID: {'已设置' if cdn.zone_id else '未设置'}")
|
||||
print(f" Timeout: {cdn.timeout}秒")
|
||||
print(f" Retry Count: {cdn.retry_count}次")
|
||||
|
||||
# 显示配置来源
|
||||
from src.config_man.utils.config import config
|
||||
config_api_token = config.get_cloudflare_api_token()
|
||||
config_zone_id = config.get_cloudflare_zone_id()
|
||||
|
||||
if config_api_token:
|
||||
print(" 📁 API Token来源: 配置文件")
|
||||
elif os.getenv('CLOUDFLARE_API_TOKEN'):
|
||||
print(" 🌍 API Token来源: 环境变量")
|
||||
else:
|
||||
print(" ❌ API Token来源: 未设置")
|
||||
|
||||
if config_zone_id:
|
||||
print(" 📁 Zone ID来源: 配置文件")
|
||||
elif os.getenv('CLOUDFLARE_ZONE_ID'):
|
||||
print(" 🌍 Zone ID来源: 环境变量")
|
||||
else:
|
||||
print(" ❌ Zone ID来源: 未设置")
|
||||
|
||||
# 获取域名信息
|
||||
zone_info = cdn.get_zone_info()
|
||||
if zone_info:
|
||||
print(f"✅ 域名信息获取成功: {zone_info.get('name', 'Unknown')}")
|
||||
else:
|
||||
print("❌ 域名信息获取失败")
|
||||
else:
|
||||
print("❌ Cloudflare配置不完整")
|
||||
print("请通过以下方式之一设置:")
|
||||
print("1. 配置文件:")
|
||||
print(" config-man setup-cloudflare --api-token your_token --zone-id your_zone_id")
|
||||
print("2. 环境变量:")
|
||||
print(" export CLOUDFLARE_API_TOKEN='your_token'")
|
||||
print(" export CLOUDFLARE_ZONE_ID='your_zone_id'")
|
||||
|
||||
|
||||
def test_cache_refresh():
|
||||
"""测试缓存刷新功能"""
|
||||
|
||||
print("\n🔄 测试缓存刷新功能...")
|
||||
|
||||
# 测试URL列表
|
||||
test_urls = [
|
||||
"https://ab.bamboogames.cc/ab/0_29/androidconfig.json",
|
||||
"https://ab.bamboogames.cc/ab/0_29/iosconfig.json"
|
||||
]
|
||||
|
||||
print(f"测试URL列表: {test_urls}")
|
||||
|
||||
# 测试缓存刷新
|
||||
success = refresh_cloudflare_cache(test_urls)
|
||||
|
||||
if success:
|
||||
print("✅ Cloudflare CDN缓存刷新成功")
|
||||
else:
|
||||
print("❌ Cloudflare CDN缓存刷新失败")
|
||||
|
||||
|
||||
def test_cache_refresh_by_tags():
|
||||
"""测试通过标签刷新缓存"""
|
||||
|
||||
print("\n🏷️ 测试通过标签刷新缓存...")
|
||||
|
||||
cdn = CloudflareCDN()
|
||||
|
||||
if not cdn.is_configured():
|
||||
print("❌ Cloudflare配置不完整,跳过标签测试")
|
||||
return
|
||||
|
||||
# 测试标签
|
||||
test_tags = ["config-man", "version-0.29"]
|
||||
|
||||
print(f"测试标签: {test_tags}")
|
||||
|
||||
success = cdn.purge_cache_by_tags(test_tags)
|
||||
|
||||
if success:
|
||||
print("✅ 通过标签刷新缓存成功")
|
||||
else:
|
||||
print("❌ 通过标签刷新缓存失败")
|
||||
|
||||
|
||||
def test_entire_cache_refresh():
|
||||
"""测试刷新整个缓存"""
|
||||
|
||||
print("\n🌐 测试刷新整个缓存...")
|
||||
|
||||
cdn = CloudflareCDN()
|
||||
|
||||
if not cdn.is_configured():
|
||||
print("❌ Cloudflare配置不完整,跳过全量刷新测试")
|
||||
return
|
||||
|
||||
print("警告: 这将刷新整个域名的缓存,请确认是否继续...")
|
||||
response = input("输入 'yes' 继续: ")
|
||||
|
||||
if response.lower() == 'yes':
|
||||
success = cdn.purge_entire_cache()
|
||||
|
||||
if success:
|
||||
print("✅ 整个缓存刷新成功")
|
||||
else:
|
||||
print("❌ 整个缓存刷新失败")
|
||||
else:
|
||||
print("⏭️ 跳过全量刷新测试")
|
||||
|
||||
|
||||
def test_suggest_update_with_cdn():
|
||||
"""测试建议更新功能中的CDN刷新"""
|
||||
|
||||
print("\n📝 测试建议更新功能中的CDN刷新...")
|
||||
|
||||
from src.config_man.core.suggest_update_command import SuggestUpdateCommand
|
||||
from src.config_man.utils.mock_rclone import patch_rclone
|
||||
|
||||
# 启用模拟环境
|
||||
patch_rclone()
|
||||
|
||||
# 创建建议更新命令处理器
|
||||
suggest_cmd = SuggestUpdateCommand("ab", "https://ab.bamboogames.cc")
|
||||
|
||||
# 测试Android平台
|
||||
print("测试Android平台建议更新...")
|
||||
success = suggest_cmd.execute("android", "0.29", "0.30")
|
||||
|
||||
if success:
|
||||
print("✅ Android平台建议更新成功,CDN刷新正常")
|
||||
else:
|
||||
print("❌ Android平台建议更新失败")
|
||||
|
||||
# 测试iOS平台
|
||||
print("测试iOS平台建议更新...")
|
||||
success = suggest_cmd.execute("ios", "0.29", "0.30")
|
||||
|
||||
if success:
|
||||
print("✅ iOS平台建议更新成功,CDN刷新正常")
|
||||
else:
|
||||
print("❌ iOS平台建议更新失败")
|
||||
|
||||
|
||||
def show_cloudflare_setup_guide():
|
||||
"""显示Cloudflare设置指南"""
|
||||
|
||||
print("\n📖 Cloudflare设置指南")
|
||||
print("=" * 50)
|
||||
|
||||
print("1. 获取Cloudflare API Token:")
|
||||
print(" - 登录Cloudflare控制台")
|
||||
print(" - 进入 'My Profile' -> 'API Tokens'")
|
||||
print(" - 创建新的API Token")
|
||||
print(" - 权限设置:")
|
||||
print(" * Zone:Zone:Read")
|
||||
print(" * Zone:Cache Purge:Edit")
|
||||
|
||||
print("\n2. 获取Zone ID:")
|
||||
print(" - 在Cloudflare控制台中选择域名")
|
||||
print(" - 在右侧边栏找到 'Zone ID'")
|
||||
|
||||
print("\n3. 设置环境变量:")
|
||||
print(" export CLOUDFLARE_API_TOKEN='your_api_token'")
|
||||
print(" export CLOUDFLARE_ZONE_ID='your_zone_id'")
|
||||
|
||||
print("\n4. 验证配置:")
|
||||
print(" python examples/test_cloudflare_cdn.py")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🧪 Cloudflare CDN刷新功能测试")
|
||||
print("=" * 50)
|
||||
|
||||
test_cloudflare_configuration()
|
||||
test_cache_refresh()
|
||||
test_cache_refresh_by_tags()
|
||||
test_entire_cache_refresh()
|
||||
test_suggest_update_with_cdn()
|
||||
show_cloudflare_setup_guide()
|
||||
|
||||
print("\n🎉 Cloudflare CDN刷新功能测试完成!")
|
||||
157
examples/test_force_update.py
Normal file
157
examples/test_force_update.py
Normal file
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
强制更新功能测试示例
|
||||
|
||||
测试强制更新命令的功能,包括参数验证、配置更新、CDN缓存刷新等。
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from config_man.core.force_update_command import ForceUpdateCommand
|
||||
from config_man.utils.mock_rclone import patch_rclone
|
||||
|
||||
|
||||
def test_force_update_basic():
|
||||
"""测试基本强制更新功能"""
|
||||
print("🧪 测试基本强制更新功能")
|
||||
|
||||
# 启用模拟环境
|
||||
patch_rclone()
|
||||
|
||||
# 创建强制更新命令处理器
|
||||
force_cmd = ForceUpdateCommand(storage_path="ab", cdn_base_url="https://cdn.example.com")
|
||||
|
||||
# 执行强制更新
|
||||
success = force_cmd.execute(
|
||||
platform="android",
|
||||
target_version="0.29",
|
||||
update_version="0.30"
|
||||
)
|
||||
|
||||
print(f"✅ 强制更新测试结果: {'成功' if success else '失败'}")
|
||||
return success
|
||||
|
||||
|
||||
def test_force_update_ios():
|
||||
"""测试iOS平台强制更新"""
|
||||
print("\n🧪 测试iOS平台强制更新")
|
||||
|
||||
# 启用模拟环境
|
||||
patch_rclone()
|
||||
|
||||
# 创建强制更新命令处理器
|
||||
force_cmd = ForceUpdateCommand(storage_path="ab", cdn_base_url="https://cdn.example.com")
|
||||
|
||||
# 执行强制更新
|
||||
success = force_cmd.execute(
|
||||
platform="ios",
|
||||
target_version="0.29",
|
||||
update_version="0.30"
|
||||
)
|
||||
|
||||
print(f"✅ iOS强制更新测试结果: {'成功' if success else '失败'}")
|
||||
return success
|
||||
|
||||
|
||||
def test_force_update_validation():
|
||||
"""测试参数验证功能"""
|
||||
print("\n🧪 测试参数验证功能")
|
||||
|
||||
# 启用模拟环境
|
||||
patch_rclone()
|
||||
|
||||
# 创建强制更新命令处理器
|
||||
force_cmd = ForceUpdateCommand()
|
||||
|
||||
# 测试无效平台
|
||||
print("测试无效平台...")
|
||||
success1 = force_cmd.execute("invalid", "0.29", "0.30")
|
||||
print(f"无效平台测试结果: {'失败(预期)' if not success1 else '成功(意外)'}")
|
||||
|
||||
# 测试无效版本格式
|
||||
print("测试无效版本格式...")
|
||||
success2 = force_cmd.execute("android", "invalid", "0.30")
|
||||
print(f"无效版本格式测试结果: {'失败(预期)' if not success2 else '成功(意外)'}")
|
||||
|
||||
# 测试相同版本
|
||||
print("测试相同版本...")
|
||||
success3 = force_cmd.execute("android", "0.29", "0.29")
|
||||
print(f"相同版本测试结果: {'失败(预期)' if not success3 else '成功(意外)'}")
|
||||
|
||||
return not success1 and not success2 and not success3
|
||||
|
||||
|
||||
def test_force_update_comparison():
|
||||
"""测试配置对比功能"""
|
||||
print("\n🧪 测试配置对比功能")
|
||||
|
||||
# 启用模拟环境
|
||||
patch_rclone()
|
||||
|
||||
# 创建强制更新命令处理器
|
||||
force_cmd = ForceUpdateCommand(storage_path="ab", cdn_base_url="https://cdn.example.com")
|
||||
|
||||
# 执行强制更新并观察对比输出
|
||||
success = force_cmd.execute(
|
||||
platform="android",
|
||||
target_version="0.29",
|
||||
update_version="0.30"
|
||||
)
|
||||
|
||||
print(f"✅ 配置对比测试结果: {'成功' if success else '失败'}")
|
||||
return success
|
||||
|
||||
|
||||
def main():
|
||||
"""主测试函数"""
|
||||
print("🚀 开始强制更新功能测试")
|
||||
print("=" * 50)
|
||||
|
||||
# 运行所有测试
|
||||
tests = [
|
||||
("基本强制更新功能", test_force_update_basic),
|
||||
("iOS平台强制更新", test_force_update_ios),
|
||||
("参数验证功能", test_force_update_validation),
|
||||
("配置对比功能", test_force_update_comparison),
|
||||
]
|
||||
|
||||
results = []
|
||||
for test_name, test_func in tests:
|
||||
try:
|
||||
result = test_func()
|
||||
results.append((test_name, result))
|
||||
except Exception as e:
|
||||
print(f"❌ {test_name} 测试异常: {e}")
|
||||
results.append((test_name, False))
|
||||
|
||||
# 显示测试结果
|
||||
print("\n" + "=" * 50)
|
||||
print("📊 测试结果汇总:")
|
||||
|
||||
passed = 0
|
||||
for test_name, result in results:
|
||||
status = "✅ 通过" if result else "❌ 失败"
|
||||
print(f" {test_name}: {status}")
|
||||
if result:
|
||||
passed += 1
|
||||
|
||||
print(f"\n总计: {len(results)} 个测试,通过: {passed} 个,失败: {len(results) - passed} 个")
|
||||
|
||||
if passed == len(results):
|
||||
print("🎉 所有测试通过!强制更新功能实现成功。")
|
||||
else:
|
||||
print("⚠️ 部分测试失败,请检查实现。")
|
||||
|
||||
return passed == len(results)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = main()
|
||||
sys.exit(0 if success else 1)
|
||||
209
examples/test_suggest_update.py
Normal file
209
examples/test_suggest_update.py
Normal file
@@ -0,0 +1,209 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试建议更新功能
|
||||
|
||||
这个脚本用于测试 config-man 的建议更新功能。
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import tempfile
|
||||
import subprocess
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
from src.config_man.utils.mock_rclone import patch_rclone
|
||||
from src.config_man.utils.crypto import encrypt
|
||||
from src.config_man.core.suggest_update_command import SuggestUpdateCommand
|
||||
|
||||
|
||||
def create_test_configs():
|
||||
"""创建测试配置文件"""
|
||||
|
||||
# 模拟目标版本配置 (0.29)
|
||||
target_config = {
|
||||
"Ver": "0.29.1abc123",
|
||||
"EventApiURL": "https://n3backend.azurewebsites.net/",
|
||||
"PlayFabTitle": "B066F",
|
||||
"Google_Play_URL": "https://play.google.com/store/apps/details?id=com.arkgame.ft",
|
||||
"APP_Store_URL": "https://apps.apple.com/us/app/id6505145935",
|
||||
"HeartBeat": "60",
|
||||
"RTMPid": "80000586",
|
||||
"RTMServerEndpoint": "rtm-intl-frontgate.ilivedata.com:13321",
|
||||
"RTMHmacSecret": "57047697437f4f2c97a835e8d9a53358",
|
||||
"FuncUrl": "https://leaderboardcreate.azurewebsites.net",
|
||||
"FuncKey": "R5kU45fNBRd52Eqp3tEqfpZqrbFw53uSSEo7wraUSqIfAzFuRmLm_w=="
|
||||
}
|
||||
|
||||
# 模拟更新版本配置 (0.30)
|
||||
update_config = {
|
||||
"Ver": "0.30.1def456",
|
||||
"EventApiURL": "https://n3backend.azurewebsites.net/",
|
||||
"PlayFabTitle": "B066F",
|
||||
"Google_Play_URL": "https://play.google.com/store/apps/details?id=com.arkgame.ft",
|
||||
"APP_Store_URL": "https://apps.apple.com/us/app/id6505145935",
|
||||
"HeartBeat": "60",
|
||||
"RTMPid": "80000586",
|
||||
"RTMServerEndpoint": "rtm-intl-frontgate.ilivedata.com:13321",
|
||||
"RTMHmacSecret": "57047697437f4f2c97a835e8d9a53358",
|
||||
"FuncUrl": "https://leaderboardcreate.azurewebsites.net",
|
||||
"FuncKey": "R5kU45fNBRd52Eqp3tEqfpZqrbFw53uSSEo7wraUSqIfAzFuRmLm_w=="
|
||||
}
|
||||
|
||||
return target_config, update_config
|
||||
|
||||
|
||||
def test_suggest_update():
|
||||
"""测试建议更新功能"""
|
||||
|
||||
print("🧪 开始测试建议更新功能...")
|
||||
|
||||
# 启用rclone模拟
|
||||
patch_rclone()
|
||||
|
||||
# 创建测试配置
|
||||
target_config, update_config = create_test_configs()
|
||||
|
||||
# 模拟存储测试配置
|
||||
# 使用MockRclone类来管理测试数据
|
||||
from src.config_man.utils.mock_rclone import MockRclone
|
||||
mock_rclone = MockRclone()
|
||||
|
||||
# 创建测试配置文件
|
||||
import os
|
||||
test_data_path = "test_data"
|
||||
storage_path = "ab"
|
||||
|
||||
# 创建目标版本配置 (0.29)
|
||||
target_version_path = os.path.join(test_data_path, storage_path, "0_29")
|
||||
os.makedirs(target_version_path, exist_ok=True)
|
||||
|
||||
# 保存Android配置
|
||||
android_config_path = os.path.join(target_version_path, "androidconfig.json")
|
||||
with open(android_config_path, 'w', encoding='utf-8') as f:
|
||||
f.write(encrypt(json.dumps(target_config, indent=2, ensure_ascii=False)))
|
||||
|
||||
# 保存iOS配置
|
||||
ios_config_path = os.path.join(target_version_path, "iosconfig.json")
|
||||
with open(ios_config_path, 'w', encoding='utf-8') as f:
|
||||
f.write(encrypt(json.dumps(target_config, indent=2, ensure_ascii=False)))
|
||||
|
||||
# 创建更新版本配置 (0.30)
|
||||
update_version_path = os.path.join(test_data_path, storage_path, "0_30")
|
||||
os.makedirs(update_version_path, exist_ok=True)
|
||||
|
||||
# 保存Android配置
|
||||
android_config_path = os.path.join(update_version_path, "androidconfig.json")
|
||||
with open(android_config_path, 'w', encoding='utf-8') as f:
|
||||
f.write(encrypt(json.dumps(update_config, indent=2, ensure_ascii=False)))
|
||||
|
||||
# 保存iOS配置
|
||||
ios_config_path = os.path.join(update_version_path, "iosconfig.json")
|
||||
with open(ios_config_path, 'w', encoding='utf-8') as f:
|
||||
f.write(encrypt(json.dumps(update_config, indent=2, ensure_ascii=False)))
|
||||
|
||||
print("📁 已创建模拟配置文件")
|
||||
|
||||
# 创建建议更新命令处理器
|
||||
suggest_cmd = SuggestUpdateCommand("ab", "")
|
||||
|
||||
# 测试Android平台
|
||||
print("\n📱 测试Android平台...")
|
||||
success = suggest_cmd.execute("android", "0.29", "0.30")
|
||||
|
||||
if success:
|
||||
print("✅ Android平台测试成功")
|
||||
|
||||
# 验证结果
|
||||
from src.config_man.utils.crypto import decrypt
|
||||
updated_config_path = os.path.join(test_data_path, storage_path, "0_29", "androidconfig.json")
|
||||
if os.path.exists(updated_config_path):
|
||||
with open(updated_config_path, 'r', encoding='utf-8') as f:
|
||||
encrypted_content = f.read().strip()
|
||||
decrypted_content = decrypt(encrypted_content)
|
||||
updated_config = json.loads(decrypted_content)
|
||||
|
||||
if "Ver_New" in updated_config:
|
||||
print(f"✅ 已添加 Ver_New: {updated_config['Ver_New']}")
|
||||
print("✅ 配置对比功能正常显示")
|
||||
else:
|
||||
print("❌ 未找到 Ver_New 属性")
|
||||
else:
|
||||
print("❌ 更新后的配置文件不存在")
|
||||
else:
|
||||
print("❌ Android平台测试失败")
|
||||
|
||||
# 测试iOS平台
|
||||
print("\n📱 测试iOS平台...")
|
||||
success = suggest_cmd.execute("ios", "0.29", "0.30")
|
||||
|
||||
if success:
|
||||
print("✅ iOS平台测试成功")
|
||||
|
||||
# 验证结果
|
||||
updated_config_path = os.path.join(test_data_path, storage_path, "0_29", "iosconfig.json")
|
||||
if os.path.exists(updated_config_path):
|
||||
with open(updated_config_path, 'r', encoding='utf-8') as f:
|
||||
encrypted_content = f.read().strip()
|
||||
decrypted_content = decrypt(encrypted_content)
|
||||
updated_config = json.loads(decrypted_content)
|
||||
|
||||
if "Ver_New" in updated_config:
|
||||
print(f"✅ 已添加 Ver_New: {updated_config['Ver_New']}")
|
||||
print("✅ 配置对比功能正常显示")
|
||||
else:
|
||||
print("❌ 未找到 Ver_New 属性")
|
||||
else:
|
||||
print("❌ 更新后的配置文件不存在")
|
||||
else:
|
||||
print("❌ iOS平台测试失败")
|
||||
|
||||
# 测试错误情况
|
||||
print("\n🚫 测试错误情况...")
|
||||
|
||||
# 测试无效平台
|
||||
success = suggest_cmd.execute("invalid", "0.29", "0.30")
|
||||
if not success:
|
||||
print("✅ 无效平台测试通过")
|
||||
|
||||
# 测试相同版本
|
||||
success = suggest_cmd.execute("android", "0.29", "0.29")
|
||||
if not success:
|
||||
print("✅ 相同版本测试通过")
|
||||
|
||||
# 测试无效版本格式
|
||||
success = suggest_cmd.execute("android", "invalid", "0.30")
|
||||
if not success:
|
||||
print("✅ 无效版本格式测试通过")
|
||||
|
||||
print("\n🎉 建议更新功能测试完成!")
|
||||
|
||||
|
||||
def test_cli_command():
|
||||
"""测试CLI命令"""
|
||||
|
||||
print("\n🔧 测试CLI命令...")
|
||||
|
||||
# 测试帮助信息
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["python", "-m", "src.config_man.cli.main", "suggest-update", "--help"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
print("✅ CLI帮助信息正常")
|
||||
else:
|
||||
print("❌ CLI帮助信息异常")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ CLI命令测试失败: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_suggest_update()
|
||||
test_cli_command()
|
||||
@@ -1,6 +1,8 @@
|
||||
import click
|
||||
import os
|
||||
from ..core.view_command import ViewCommand
|
||||
from ..core.suggest_update_command import SuggestUpdateCommand
|
||||
from ..core.force_update_command import ForceUpdateCommand
|
||||
from ..utils.mock_rclone import patch_rclone
|
||||
from ..utils.config import config
|
||||
|
||||
@@ -61,16 +63,40 @@ def view(version, json_format, storage_path, cdn_url, mock):
|
||||
help='平台类型')
|
||||
@click.option('--target-version', required=True, help='目标版本号')
|
||||
@click.option('--update-version', required=True, help='更新版本号')
|
||||
def suggest_update(platform, target_version, update_version):
|
||||
@click.option('--storage-path', help='对象存储路径前缀(覆盖配置文件)')
|
||||
@click.option('--cdn-url', help='CDN基础URL(覆盖配置文件)')
|
||||
@click.option('--mock', is_flag=True, help='使用模拟环境进行测试')
|
||||
def suggest_update(platform, target_version, update_version, storage_path, cdn_url, mock):
|
||||
"""建议更新
|
||||
|
||||
在目标版本的配置文件中添加 Ver_New 属性,提示用户有新版本可用。
|
||||
|
||||
示例:
|
||||
config-man suggest-update --platform android --target-version 0.29 --update-version 0.30
|
||||
config-man suggest-update --platform ios --target-version 0.29 --update-version 0.30 --mock
|
||||
"""
|
||||
click.echo("建议更新功能尚未实现")
|
||||
return 1
|
||||
try:
|
||||
# 如果使用模拟环境,启用rclone模拟
|
||||
if mock:
|
||||
patch_rclone()
|
||||
click.echo("使用模拟环境进行测试...")
|
||||
else:
|
||||
# 检查rclone是否可用
|
||||
if not _check_rclone():
|
||||
click.echo("错误: rclone 未安装或不可用。请先安装 rclone。")
|
||||
return 1
|
||||
|
||||
# 创建建议更新命令处理器
|
||||
suggest_cmd = SuggestUpdateCommand(storage_path, cdn_url)
|
||||
|
||||
# 执行建议更新命令
|
||||
success = suggest_cmd.execute(platform, target_version, update_version)
|
||||
|
||||
return 0 if success else 1
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"错误: {str(e)}")
|
||||
return 1
|
||||
|
||||
|
||||
@cli.command()
|
||||
@@ -78,16 +104,40 @@ def suggest_update(platform, target_version, update_version):
|
||||
help='平台类型')
|
||||
@click.option('--target-version', required=True, help='目标版本号')
|
||||
@click.option('--update-version', required=True, help='更新版本号')
|
||||
def force_update(platform, target_version, update_version):
|
||||
@click.option('--storage-path', help='对象存储路径前缀(覆盖配置文件)')
|
||||
@click.option('--cdn-url', help='CDN基础URL(覆盖配置文件)')
|
||||
@click.option('--mock', is_flag=True, help='使用模拟环境进行测试')
|
||||
def force_update(platform, target_version, update_version, storage_path, cdn_url, mock):
|
||||
"""强制更新
|
||||
|
||||
直接更新目标版本的 Ver 属性,强制用户升级到新版本。
|
||||
|
||||
示例:
|
||||
config-man force-update --platform ios --target-version 0.29 --update-version 0.30
|
||||
config-man force-update --platform android --target-version 0.29 --update-version 0.30 --mock
|
||||
"""
|
||||
click.echo("强制更新功能尚未实现")
|
||||
return 1
|
||||
try:
|
||||
# 如果使用模拟环境,启用rclone模拟
|
||||
if mock:
|
||||
patch_rclone()
|
||||
click.echo("使用模拟环境进行测试...")
|
||||
else:
|
||||
# 检查rclone是否可用
|
||||
if not _check_rclone():
|
||||
click.echo("错误: rclone 未安装或不可用。请先安装 rclone。")
|
||||
return 1
|
||||
|
||||
# 创建强制更新命令处理器
|
||||
force_cmd = ForceUpdateCommand(storage_path, cdn_url)
|
||||
|
||||
# 执行强制更新命令
|
||||
success = force_cmd.execute(platform, target_version, update_version)
|
||||
|
||||
return 0 if success else 1
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"错误: {str(e)}")
|
||||
return 1
|
||||
|
||||
|
||||
@cli.command()
|
||||
@@ -124,6 +174,33 @@ def show_config():
|
||||
console.print(f"\n配置文件路径: {config.config_file}")
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option('--api-token', required=True, help='Cloudflare API Token')
|
||||
@click.option('--zone-id', required=True, help='Cloudflare Zone ID')
|
||||
def setup_cloudflare(api_token, zone_id):
|
||||
"""设置Cloudflare CDN配置
|
||||
|
||||
设置Cloudflare API Token和Zone ID。
|
||||
|
||||
示例:
|
||||
config-man setup-cloudflare --api-token your_api_token --zone-id your_zone_id
|
||||
"""
|
||||
try:
|
||||
# 设置Cloudflare配置
|
||||
config.set('cdn.cloudflare.api_token', api_token)
|
||||
config.set('cdn.cloudflare.zone_id', zone_id)
|
||||
config.save()
|
||||
|
||||
click.echo("✅ Cloudflare配置已更新")
|
||||
click.echo(f" API Token: {api_token[:10]}...")
|
||||
click.echo(f" Zone ID: {zone_id}")
|
||||
click.echo("\n💡 提示: 配置已保存到配置文件,无需重复设置环境变量")
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"❌ 设置Cloudflare配置失败: {e}")
|
||||
return 1
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option('--key', required=True, help='配置键,如 storage.path')
|
||||
@click.option('--value', required=True, help='配置值')
|
||||
@@ -135,6 +212,8 @@ def set_config(key, value):
|
||||
示例:
|
||||
config-man set-config --key storage.path --value custom_path
|
||||
config-man set-config --key cdn.base_url --value https://cdn.example.com
|
||||
config-man set-config --key cdn.cloudflare.api_token --value your_api_token
|
||||
config-man set-config --key cdn.cloudflare.zone_id --value your_zone_id
|
||||
"""
|
||||
try:
|
||||
# 尝试转换数值类型
|
||||
|
||||
@@ -7,5 +7,7 @@
|
||||
from .config_manager import ConfigManager
|
||||
from .display_manager import DisplayManager
|
||||
from .view_command import ViewCommand
|
||||
from .suggest_update_command import SuggestUpdateCommand
|
||||
from .force_update_command import ForceUpdateCommand
|
||||
|
||||
__all__ = ["ConfigManager", "DisplayManager", "ViewCommand"]
|
||||
__all__ = ["ConfigManager", "DisplayManager", "ViewCommand", "SuggestUpdateCommand", "ForceUpdateCommand"]
|
||||
@@ -109,4 +109,8 @@ class DisplayManager:
|
||||
|
||||
def display_info(self, message: str):
|
||||
"""显示信息"""
|
||||
self.console.print(f"[bold blue]信息: {message}[/bold blue]")
|
||||
self.console.print(f"[bold blue]信息: {message}[/bold blue]")
|
||||
|
||||
def display_success(self, message: str):
|
||||
"""显示成功信息"""
|
||||
self.console.print(f"[bold green]✅ 成功: {message}[/bold green]")
|
||||
268
src/config_man/core/force_update_command.py
Normal file
268
src/config_man/core/force_update_command.py
Normal file
@@ -0,0 +1,268 @@
|
||||
import json
|
||||
import subprocess
|
||||
import requests
|
||||
from typing import Dict, Optional, List
|
||||
from .config_manager import ConfigManager
|
||||
from .display_manager import DisplayManager
|
||||
from ..utils.crypto import encrypt
|
||||
from ..utils.cloudflare import refresh_cloudflare_cache
|
||||
|
||||
|
||||
class ForceUpdateCommand:
|
||||
"""强制更新命令处理器"""
|
||||
|
||||
def __init__(self, storage_path: str = "ab", cdn_base_url: str = ""):
|
||||
self.config_manager = ConfigManager(storage_path, cdn_base_url)
|
||||
self.display_manager = DisplayManager()
|
||||
|
||||
def execute(self, platform: str, target_version: str, update_version: str) -> bool:
|
||||
"""执行强制更新命令"""
|
||||
|
||||
# 验证参数
|
||||
if not self._validate_parameters(platform, target_version, update_version):
|
||||
return False
|
||||
|
||||
self.display_manager.display_info(
|
||||
f"正在为 {platform} 平台执行强制更新: "
|
||||
f"目标版本 {target_version} -> 更新版本 {update_version}"
|
||||
)
|
||||
|
||||
try:
|
||||
# 1. 获取更新版本的配置,提取Ver值
|
||||
update_ver_value = self._get_update_version_ver(platform, update_version)
|
||||
if not update_ver_value:
|
||||
self.display_manager.display_error(
|
||||
f"无法获取更新版本 {update_version} 的Ver值"
|
||||
)
|
||||
return False
|
||||
|
||||
# 2. 获取目标版本的配置(保存原始配置用于对比)
|
||||
original_config = self._get_target_config(platform, target_version)
|
||||
if not original_config:
|
||||
self.display_manager.display_error(
|
||||
f"无法获取目标版本 {target_version} 的配置"
|
||||
)
|
||||
return False
|
||||
|
||||
# 3. 创建更新后的配置(复制原始配置并更新Ver)
|
||||
updated_config = original_config.copy()
|
||||
updated_config["Ver"] = update_ver_value
|
||||
|
||||
# 4. 加密并上传到对象存储
|
||||
success = self._upload_config(platform, target_version, updated_config)
|
||||
if not success:
|
||||
return False
|
||||
|
||||
# 5. 刷新CDN缓存
|
||||
success = self._refresh_cdn_cache(platform, target_version)
|
||||
if not success:
|
||||
self.display_manager.display_warning("配置已更新,但CDN缓存刷新失败")
|
||||
|
||||
# 6. 显示结果和对比
|
||||
self._display_success_result(platform, target_version, update_version, update_ver_value)
|
||||
self._display_config_comparison(platform, target_version, original_config, updated_config)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.display_manager.display_error(f"强制更新失败: {str(e)}")
|
||||
return False
|
||||
|
||||
def _validate_parameters(self, platform: str, target_version: str, update_version: str) -> bool:
|
||||
"""验证输入参数"""
|
||||
|
||||
# 验证平台
|
||||
if platform not in ["android", "ios"]:
|
||||
self.display_manager.display_error(f"无效的平台: {platform},支持 android 或 ios")
|
||||
return False
|
||||
|
||||
# 验证版本号格式
|
||||
for version in [target_version, update_version]:
|
||||
if not self._validate_version_format(version):
|
||||
self.display_manager.display_error(f"无效的版本号格式: {version}")
|
||||
return False
|
||||
|
||||
# 验证版本号不能相同
|
||||
if target_version == update_version:
|
||||
self.display_manager.display_error("目标版本和更新版本不能相同")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _validate_version_format(self, version: str) -> bool:
|
||||
"""验证版本号格式"""
|
||||
try:
|
||||
parts = version.split('.')
|
||||
if len(parts) != 2:
|
||||
return False
|
||||
|
||||
major, minor = parts
|
||||
if not major.isdigit() or not minor.isdigit():
|
||||
return False
|
||||
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def _get_update_version_ver(self, platform: str, update_version: str) -> Optional[str]:
|
||||
"""获取更新版本的Ver值"""
|
||||
try:
|
||||
# 从对象存储获取更新版本的配置
|
||||
content = self.config_manager.download_from_storage(update_version, platform)
|
||||
if not content:
|
||||
return None
|
||||
|
||||
config = self.config_manager.parse_config(content)
|
||||
if not config:
|
||||
return None
|
||||
|
||||
# 提取Ver值
|
||||
ver_value = config.get("Ver")
|
||||
if not ver_value:
|
||||
self.display_manager.display_error(f"更新版本 {update_version} 的配置中没有Ver属性")
|
||||
return None
|
||||
|
||||
return ver_value
|
||||
|
||||
except Exception as e:
|
||||
self.display_manager.display_error(f"获取更新版本Ver值失败: {str(e)}")
|
||||
return None
|
||||
|
||||
def _get_target_config(self, platform: str, target_version: str) -> Optional[Dict]:
|
||||
"""获取目标版本的配置"""
|
||||
try:
|
||||
# 从对象存储获取目标版本的配置
|
||||
content = self.config_manager.download_from_storage(target_version, platform)
|
||||
if not content:
|
||||
self.display_manager.display_error(f"目标版本 {target_version} 的配置不存在")
|
||||
return None
|
||||
|
||||
config = self.config_manager.parse_config(content)
|
||||
if not config:
|
||||
self.display_manager.display_error(f"目标版本 {target_version} 的配置解析失败")
|
||||
return None
|
||||
|
||||
return config
|
||||
|
||||
except Exception as e:
|
||||
self.display_manager.display_error(f"获取目标版本配置失败: {str(e)}")
|
||||
return None
|
||||
|
||||
def _upload_config(self, platform: str, version: str, config: Dict) -> bool:
|
||||
"""加密并上传配置到对象存储"""
|
||||
try:
|
||||
# 将配置转换为JSON字符串
|
||||
config_json = json.dumps(config, indent=2, ensure_ascii=False)
|
||||
|
||||
# 加密配置
|
||||
encrypted_content = encrypt(config_json)
|
||||
|
||||
# 获取rclone路径
|
||||
rclone_path = self.config_manager.get_rclone_path(version, platform)
|
||||
|
||||
# 使用rclone上传文件
|
||||
timeout = self.config_manager.storage_config.get('timeout', 30)
|
||||
result = subprocess.run(
|
||||
["rclone", "rcat", rclone_path],
|
||||
input=encrypted_content,
|
||||
text=True,
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
self.display_manager.display_error(
|
||||
f"上传配置到对象存储失败: {result.stderr}"
|
||||
)
|
||||
return False
|
||||
|
||||
self.display_manager.display_info(f"配置已成功上传到对象存储: {rclone_path}")
|
||||
return True
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
self.display_manager.display_error("上传配置超时")
|
||||
return False
|
||||
except Exception as e:
|
||||
self.display_manager.display_error(f"上传配置失败: {str(e)}")
|
||||
return False
|
||||
|
||||
def _refresh_cdn_cache(self, platform: str, version: str) -> bool:
|
||||
"""刷新CDN缓存"""
|
||||
try:
|
||||
cdn_url = self.config_manager.get_cdn_url(version, platform)
|
||||
if not cdn_url:
|
||||
self.display_manager.display_warning("CDN URL未配置,跳过缓存刷新")
|
||||
return True
|
||||
|
||||
# 使用Cloudflare CDN刷新功能
|
||||
urls_to_refresh = [cdn_url]
|
||||
self.display_manager.display_info(f"正在刷新Cloudflare CDN缓存: {cdn_url}")
|
||||
|
||||
success = refresh_cloudflare_cache(urls_to_refresh)
|
||||
|
||||
if success:
|
||||
self.display_manager.display_success(f"Cloudflare CDN缓存刷新成功: {cdn_url}")
|
||||
else:
|
||||
self.display_manager.display_warning(f"Cloudflare CDN缓存刷新失败: {cdn_url}")
|
||||
|
||||
return success
|
||||
|
||||
except Exception as e:
|
||||
self.display_manager.display_error(f"刷新CDN缓存失败: {str(e)}")
|
||||
return False
|
||||
|
||||
def _display_success_result(self, platform: str, target_version: str,
|
||||
update_version: str, update_ver_value: str):
|
||||
"""显示成功结果"""
|
||||
self.display_manager.display_success(
|
||||
f"✅ 强制更新成功完成!\n"
|
||||
f" 平台: {platform}\n"
|
||||
f" 目标版本: {target_version}\n"
|
||||
f" 更新版本: {update_version}\n"
|
||||
f" 新的Ver值: {update_ver_value}"
|
||||
)
|
||||
|
||||
def _display_config_comparison(self, platform: str, target_version: str,
|
||||
original_config: Dict, updated_config: Dict):
|
||||
"""显示配置对比"""
|
||||
self.display_manager.display_info(f"\n📋 配置变更对比 (平台: {platform}, 版本: {target_version})")
|
||||
|
||||
# 创建对比表格
|
||||
from rich.table import Table
|
||||
from rich.console import Console
|
||||
|
||||
console = Console()
|
||||
table = Table(title="配置变更详情")
|
||||
table.add_column("配置项", style="cyan")
|
||||
table.add_column("原始值", style="red")
|
||||
table.add_column("新值", style="green")
|
||||
|
||||
# 只显示有变化的配置项
|
||||
changed_items = []
|
||||
for key in original_config:
|
||||
original_value = original_config.get(key, "")
|
||||
updated_value = updated_config.get(key, "")
|
||||
|
||||
if original_value != updated_value:
|
||||
changed_items.append((key, str(original_value), str(updated_value)))
|
||||
|
||||
# 添加新配置项
|
||||
for key in updated_config:
|
||||
if key not in original_config:
|
||||
changed_items.append((key, "", str(updated_config[key])))
|
||||
|
||||
# 按配置项名称排序
|
||||
changed_items.sort(key=lambda x: x[0])
|
||||
|
||||
for key, original_value, updated_value in changed_items:
|
||||
table.add_row(key, original_value, updated_value)
|
||||
|
||||
if changed_items:
|
||||
console.print(table)
|
||||
else:
|
||||
console.print("没有配置项发生变化")
|
||||
|
||||
# 显示警告信息
|
||||
self.display_manager.display_warning(
|
||||
"⚠️ 注意: 强制更新会立即影响使用该版本的应用,"
|
||||
"请确保所有相关方已了解此变更。"
|
||||
)
|
||||
274
src/config_man/core/suggest_update_command.py
Normal file
274
src/config_man/core/suggest_update_command.py
Normal file
@@ -0,0 +1,274 @@
|
||||
import json
|
||||
import subprocess
|
||||
import requests
|
||||
from typing import Dict, Optional, List
|
||||
from .config_manager import ConfigManager
|
||||
from .display_manager import DisplayManager
|
||||
from ..utils.crypto import encrypt
|
||||
from ..utils.cloudflare import refresh_cloudflare_cache
|
||||
|
||||
|
||||
class SuggestUpdateCommand:
|
||||
"""建议更新命令处理器"""
|
||||
|
||||
def __init__(self, storage_path: str = "ab", cdn_base_url: str = ""):
|
||||
self.config_manager = ConfigManager(storage_path, cdn_base_url)
|
||||
self.display_manager = DisplayManager()
|
||||
|
||||
def execute(self, platform: str, target_version: str, update_version: str) -> bool:
|
||||
"""执行建议更新命令"""
|
||||
|
||||
# 验证参数
|
||||
if not self._validate_parameters(platform, target_version, update_version):
|
||||
return False
|
||||
|
||||
self.display_manager.display_info(
|
||||
f"正在为 {platform} 平台执行建议更新: "
|
||||
f"目标版本 {target_version} -> 更新版本 {update_version}"
|
||||
)
|
||||
|
||||
try:
|
||||
# 1. 获取更新版本的配置,提取Ver值
|
||||
update_ver_value = self._get_update_version_ver(platform, update_version)
|
||||
if not update_ver_value:
|
||||
self.display_manager.display_error(
|
||||
f"无法获取更新版本 {update_version} 的Ver值"
|
||||
)
|
||||
return False
|
||||
|
||||
# 2. 获取目标版本的配置(保存原始配置用于对比)
|
||||
original_config = self._get_target_config(platform, target_version)
|
||||
if not original_config:
|
||||
self.display_manager.display_error(
|
||||
f"无法获取目标版本 {target_version} 的配置"
|
||||
)
|
||||
return False
|
||||
|
||||
# 3. 创建更新后的配置(复制原始配置并添加Ver_New)
|
||||
updated_config = original_config.copy()
|
||||
updated_config["Ver_New"] = update_ver_value
|
||||
|
||||
# 4. 加密并上传到对象存储
|
||||
success = self._upload_config(platform, target_version, updated_config)
|
||||
if not success:
|
||||
return False
|
||||
|
||||
# 5. 刷新CDN缓存
|
||||
success = self._refresh_cdn_cache(platform, target_version)
|
||||
if not success:
|
||||
self.display_manager.display_warning("配置已更新,但CDN缓存刷新失败")
|
||||
|
||||
# 6. 显示结果和对比
|
||||
self._display_success_result(platform, target_version, update_version, update_ver_value)
|
||||
self._display_config_comparison(platform, target_version, original_config, updated_config)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.display_manager.display_error(f"建议更新失败: {str(e)}")
|
||||
return False
|
||||
|
||||
def _validate_parameters(self, platform: str, target_version: str, update_version: str) -> bool:
|
||||
"""验证输入参数"""
|
||||
|
||||
# 验证平台
|
||||
if platform not in ["android", "ios"]:
|
||||
self.display_manager.display_error(f"无效的平台: {platform},支持 android 或 ios")
|
||||
return False
|
||||
|
||||
# 验证版本号格式
|
||||
for version in [target_version, update_version]:
|
||||
if not self._validate_version_format(version):
|
||||
self.display_manager.display_error(f"无效的版本号格式: {version}")
|
||||
return False
|
||||
|
||||
# 验证版本号不能相同
|
||||
if target_version == update_version:
|
||||
self.display_manager.display_error("目标版本和更新版本不能相同")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _validate_version_format(self, version: str) -> bool:
|
||||
"""验证版本号格式"""
|
||||
try:
|
||||
parts = version.split('.')
|
||||
if len(parts) != 2:
|
||||
return False
|
||||
|
||||
major, minor = parts
|
||||
if not major.isdigit() or not minor.isdigit():
|
||||
return False
|
||||
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def _get_update_version_ver(self, platform: str, update_version: str) -> Optional[str]:
|
||||
"""获取更新版本的Ver值"""
|
||||
try:
|
||||
# 从对象存储获取更新版本的配置
|
||||
content = self.config_manager.download_from_storage(update_version, platform)
|
||||
if not content:
|
||||
return None
|
||||
|
||||
config = self.config_manager.parse_config(content)
|
||||
if not config:
|
||||
return None
|
||||
|
||||
return config.get("Ver")
|
||||
|
||||
except Exception as e:
|
||||
self.display_manager.display_error(f"获取更新版本Ver值失败: {str(e)}")
|
||||
return None
|
||||
|
||||
def _get_target_config(self, platform: str, target_version: str) -> Optional[Dict]:
|
||||
"""获取目标版本的配置"""
|
||||
try:
|
||||
# 从对象存储获取目标版本的配置
|
||||
content = self.config_manager.download_from_storage(target_version, platform)
|
||||
if not content:
|
||||
return None
|
||||
|
||||
config = self.config_manager.parse_config(content)
|
||||
if not config:
|
||||
return None
|
||||
|
||||
return config
|
||||
|
||||
except Exception as e:
|
||||
self.display_manager.display_error(f"获取目标版本配置失败: {str(e)}")
|
||||
return None
|
||||
|
||||
def _upload_config(self, platform: str, version: str, config: Dict) -> bool:
|
||||
"""加密并上传配置到对象存储"""
|
||||
try:
|
||||
# 将配置转换为JSON字符串
|
||||
config_json = json.dumps(config, indent=2, ensure_ascii=False)
|
||||
|
||||
# 加密配置
|
||||
encrypted_content = encrypt(config_json)
|
||||
|
||||
# 获取rclone路径
|
||||
rclone_path = self.config_manager.get_rclone_path(version, platform)
|
||||
|
||||
# 使用rclone上传文件
|
||||
timeout = self.config_manager.storage_config.get('timeout', 30)
|
||||
result = subprocess.run(
|
||||
["rclone", "rcat", rclone_path],
|
||||
input=encrypted_content,
|
||||
text=True,
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
self.display_manager.display_error(
|
||||
f"上传配置到对象存储失败: {result.stderr}"
|
||||
)
|
||||
return False
|
||||
|
||||
self.display_manager.display_info(f"配置已成功上传到对象存储: {rclone_path}")
|
||||
return True
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
self.display_manager.display_error("上传配置超时")
|
||||
return False
|
||||
except Exception as e:
|
||||
self.display_manager.display_error(f"上传配置失败: {str(e)}")
|
||||
return False
|
||||
|
||||
def _refresh_cdn_cache(self, platform: str, version: str) -> bool:
|
||||
"""刷新CDN缓存"""
|
||||
try:
|
||||
cdn_url = self.config_manager.get_cdn_url(version, platform)
|
||||
if not cdn_url:
|
||||
self.display_manager.display_warning("CDN URL未配置,跳过缓存刷新")
|
||||
return True
|
||||
|
||||
# 使用Cloudflare CDN刷新功能
|
||||
urls_to_refresh = [cdn_url]
|
||||
self.display_manager.display_info(f"正在刷新Cloudflare CDN缓存: {cdn_url}")
|
||||
|
||||
success = refresh_cloudflare_cache(urls_to_refresh)
|
||||
|
||||
if success:
|
||||
self.display_manager.display_success(f"Cloudflare CDN缓存刷新成功: {cdn_url}")
|
||||
else:
|
||||
self.display_manager.display_warning(f"Cloudflare CDN缓存刷新失败: {cdn_url}")
|
||||
|
||||
return success
|
||||
|
||||
except Exception as e:
|
||||
self.display_manager.display_error(f"刷新CDN缓存失败: {str(e)}")
|
||||
return False
|
||||
|
||||
def _display_success_result(self, platform: str, target_version: str,
|
||||
update_version: str, update_ver_value: str):
|
||||
"""显示成功结果"""
|
||||
self.display_manager.display_success(
|
||||
f"建议更新成功完成!\n"
|
||||
f"平台: {platform}\n"
|
||||
f"目标版本: {target_version}\n"
|
||||
f"更新版本: {update_version}\n"
|
||||
f"已添加 Ver_New: {update_ver_value}"
|
||||
)
|
||||
|
||||
def _display_config_comparison(self, platform: str, target_version: str,
|
||||
original_config: Dict, updated_config: Dict):
|
||||
"""显示配置对比"""
|
||||
self.display_manager.display_info(f"\n📊 配置对比 - {platform.title()} 平台 (版本 {target_version})")
|
||||
|
||||
# 创建对比表格
|
||||
from rich.table import Table
|
||||
from rich.console import Console
|
||||
|
||||
console = Console()
|
||||
table = Table(show_header=True, header_style="bold magenta")
|
||||
table.add_column("配置项", style="cyan", width=20)
|
||||
table.add_column("更新前", style="red", width=30)
|
||||
table.add_column("更新后", style="green", width=30)
|
||||
table.add_column("状态", style="yellow", width=10)
|
||||
|
||||
# 获取所有配置项
|
||||
all_keys = set(original_config.keys()) | set(updated_config.keys())
|
||||
|
||||
# 添加数据行
|
||||
for key in sorted(all_keys):
|
||||
original_value = original_config.get(key, "N/A")
|
||||
updated_value = updated_config.get(key, "N/A")
|
||||
|
||||
if key == "Ver_New":
|
||||
status = "🆕 新增"
|
||||
elif original_value != updated_value:
|
||||
status = "🔄 修改"
|
||||
else:
|
||||
status = "✅ 不变"
|
||||
|
||||
# 格式化显示值
|
||||
original_display = str(original_value)
|
||||
updated_display = str(updated_value)
|
||||
|
||||
# 截断过长的值
|
||||
max_length = 25
|
||||
if len(original_display) > max_length:
|
||||
original_display = original_display[:max_length-3] + "..."
|
||||
if len(updated_display) > max_length:
|
||||
updated_display = updated_display[:max_length-3] + "..."
|
||||
|
||||
table.add_row(key, original_display, updated_display, status)
|
||||
|
||||
console.print(table)
|
||||
|
||||
# 显示统计信息
|
||||
total_items = len(all_keys)
|
||||
new_items = len([k for k in all_keys if k not in original_config])
|
||||
modified_items = len([k for k in all_keys if k in original_config and k in updated_config and original_config[k] != updated_config[k]])
|
||||
unchanged_items = total_items - new_items - modified_items
|
||||
|
||||
self.display_manager.display_info(
|
||||
f"\n📈 统计信息:\n"
|
||||
f" 总配置项: {total_items}\n"
|
||||
f" 新增项: {new_items}\n"
|
||||
f" 修改项: {modified_items}\n"
|
||||
f" 不变项: {unchanged_items}"
|
||||
)
|
||||
275
src/config_man/utils/cloudflare.py
Normal file
275
src/config_man/utils/cloudflare.py
Normal file
@@ -0,0 +1,275 @@
|
||||
"""
|
||||
Cloudflare CDN刷新模块
|
||||
|
||||
提供Cloudflare CDN缓存刷新功能。
|
||||
"""
|
||||
|
||||
import os
|
||||
import requests
|
||||
from typing import List, Optional, Dict
|
||||
from .config import config
|
||||
|
||||
|
||||
class CloudflareCDN:
|
||||
"""Cloudflare CDN管理器"""
|
||||
|
||||
def __init__(self):
|
||||
# 优先从配置文件读取,然后从环境变量读取
|
||||
self.api_token = config.get_cloudflare_api_token() or os.getenv('CLOUDFLARE_API_TOKEN')
|
||||
self.zone_id = config.get_cloudflare_zone_id() or os.getenv('CLOUDFLARE_ZONE_ID')
|
||||
self.api_base_url = "https://api.cloudflare.com/client/v4"
|
||||
|
||||
# 从配置文件获取CDN配置
|
||||
cdn_config = config.get_cdn_config()
|
||||
self.timeout = cdn_config.get('timeout', 10)
|
||||
self.retry_count = cdn_config.get('retry_count', 3)
|
||||
|
||||
def is_configured(self) -> bool:
|
||||
"""检查Cloudflare配置是否完整"""
|
||||
return bool(self.api_token and self.zone_id)
|
||||
|
||||
def purge_cache_by_urls(self, urls: List[str]) -> bool:
|
||||
"""
|
||||
通过URL列表清除缓存
|
||||
|
||||
Args:
|
||||
urls: 需要清除缓存的URL列表
|
||||
|
||||
Returns:
|
||||
是否成功
|
||||
"""
|
||||
if not self.is_configured():
|
||||
return False
|
||||
|
||||
try:
|
||||
headers = {
|
||||
'Authorization': f'Bearer {self.api_token}',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
data = {
|
||||
'files': urls
|
||||
}
|
||||
|
||||
url = f"{self.api_base_url}/zones/{self.zone_id}/purge_cache"
|
||||
|
||||
response = requests.post(
|
||||
url,
|
||||
headers=headers,
|
||||
json=data,
|
||||
timeout=self.timeout
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
if result.get('success'):
|
||||
return True
|
||||
else:
|
||||
print(f"Cloudflare API错误: {result.get('errors', [])}")
|
||||
return False
|
||||
else:
|
||||
print(f"Cloudflare API请求失败: {response.status_code} - {response.text}")
|
||||
return False
|
||||
|
||||
except requests.RequestException as e:
|
||||
print(f"Cloudflare API请求异常: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"Cloudflare缓存清除失败: {e}")
|
||||
return False
|
||||
|
||||
def purge_cache_by_tags(self, tags: List[str]) -> bool:
|
||||
"""
|
||||
通过标签清除缓存
|
||||
|
||||
Args:
|
||||
tags: 需要清除缓存的标签列表
|
||||
|
||||
Returns:
|
||||
是否成功
|
||||
"""
|
||||
if not self.is_configured():
|
||||
return False
|
||||
|
||||
try:
|
||||
headers = {
|
||||
'Authorization': f'Bearer {self.api_token}',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
data = {
|
||||
'tags': tags
|
||||
}
|
||||
|
||||
url = f"{self.api_base_url}/zones/{self.zone_id}/purge_cache"
|
||||
|
||||
response = requests.post(
|
||||
url,
|
||||
headers=headers,
|
||||
json=data,
|
||||
timeout=self.timeout
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
if result.get('success'):
|
||||
return True
|
||||
else:
|
||||
print(f"Cloudflare API错误: {result.get('errors', [])}")
|
||||
return False
|
||||
else:
|
||||
print(f"Cloudflare API请求失败: {response.status_code} - {response.text}")
|
||||
return False
|
||||
|
||||
except requests.RequestException as e:
|
||||
print(f"Cloudflare API请求异常: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"Cloudflare缓存清除失败: {e}")
|
||||
return False
|
||||
|
||||
def purge_entire_cache(self) -> bool:
|
||||
"""
|
||||
清除整个域名的缓存
|
||||
|
||||
Returns:
|
||||
是否成功
|
||||
"""
|
||||
if not self.is_configured():
|
||||
return False
|
||||
|
||||
try:
|
||||
headers = {
|
||||
'Authorization': f'Bearer {self.api_token}',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
data = {
|
||||
'purge_everything': True
|
||||
}
|
||||
|
||||
url = f"{self.api_base_url}/zones/{self.zone_id}/purge_cache"
|
||||
|
||||
response = requests.post(
|
||||
url,
|
||||
headers=headers,
|
||||
json=data,
|
||||
timeout=self.timeout
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
if result.get('success'):
|
||||
return True
|
||||
else:
|
||||
print(f"Cloudflare API错误: {result.get('errors', [])}")
|
||||
return False
|
||||
else:
|
||||
print(f"Cloudflare API请求失败: {response.status_code} - {response.text}")
|
||||
return False
|
||||
|
||||
except requests.RequestException as e:
|
||||
print(f"Cloudflare API请求异常: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"Cloudflare缓存清除失败: {e}")
|
||||
return False
|
||||
|
||||
def get_zone_info(self) -> Optional[Dict]:
|
||||
"""
|
||||
获取域名信息
|
||||
|
||||
Returns:
|
||||
域名信息字典,如果失败返回None
|
||||
"""
|
||||
if not self.is_configured():
|
||||
return None
|
||||
|
||||
try:
|
||||
headers = {
|
||||
'Authorization': f'Bearer {self.api_token}',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
url = f"{self.api_base_url}/zones/{self.zone_id}"
|
||||
|
||||
response = requests.get(
|
||||
url,
|
||||
headers=headers,
|
||||
timeout=self.timeout
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
if result.get('success'):
|
||||
return result.get('result', {})
|
||||
else:
|
||||
print(f"Cloudflare API错误: {result.get('errors', [])}")
|
||||
return None
|
||||
else:
|
||||
print(f"Cloudflare API请求失败: {response.status_code} - {response.text}")
|
||||
return None
|
||||
|
||||
except requests.RequestException as e:
|
||||
print(f"Cloudflare API请求异常: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"获取Cloudflare域名信息失败: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def refresh_cloudflare_cache(urls: List[str]) -> bool:
|
||||
"""
|
||||
刷新Cloudflare CDN缓存
|
||||
|
||||
Args:
|
||||
urls: 需要刷新的URL列表
|
||||
|
||||
Returns:
|
||||
是否成功
|
||||
"""
|
||||
cdn = CloudflareCDN()
|
||||
|
||||
if not cdn.is_configured():
|
||||
print("警告: Cloudflare配置不完整,跳过CDN缓存刷新")
|
||||
print("请设置以下环境变量:")
|
||||
print(" CLOUDFLARE_API_TOKEN: Cloudflare API Token")
|
||||
print(" CLOUDFLARE_ZONE_ID: Cloudflare Zone ID")
|
||||
return False
|
||||
|
||||
return cdn.purge_cache_by_urls(urls)
|
||||
|
||||
|
||||
def refresh_cloudflare_cache_by_tags(tags: List[str]) -> bool:
|
||||
"""
|
||||
通过标签刷新Cloudflare CDN缓存
|
||||
|
||||
Args:
|
||||
tags: 需要刷新的标签列表
|
||||
|
||||
Returns:
|
||||
是否成功
|
||||
"""
|
||||
cdn = CloudflareCDN()
|
||||
|
||||
if not cdn.is_configured():
|
||||
print("警告: Cloudflare配置不完整,跳过CDN缓存刷新")
|
||||
return False
|
||||
|
||||
return cdn.purge_cache_by_tags(tags)
|
||||
|
||||
|
||||
def refresh_entire_cloudflare_cache() -> bool:
|
||||
"""
|
||||
刷新整个Cloudflare CDN缓存
|
||||
|
||||
Returns:
|
||||
是否成功
|
||||
"""
|
||||
cdn = CloudflareCDN()
|
||||
|
||||
if not cdn.is_configured():
|
||||
print("警告: Cloudflare配置不完整,跳过CDN缓存刷新")
|
||||
return False
|
||||
|
||||
return cdn.purge_entire_cache()
|
||||
@@ -49,7 +49,11 @@ class Config:
|
||||
"cdn": {
|
||||
"base_url": "",
|
||||
"timeout": 10,
|
||||
"retry_count": 3
|
||||
"retry_count": 3,
|
||||
"cloudflare": {
|
||||
"api_token": "",
|
||||
"zone_id": ""
|
||||
}
|
||||
},
|
||||
"crypto": {
|
||||
"algorithm": "DES",
|
||||
@@ -98,6 +102,8 @@ class Config:
|
||||
"CONFIG_MAN_CDN_BASE_URL": ("cdn", "base_url"),
|
||||
"CONFIG_MAN_CDN_TIMEOUT": ("cdn", "timeout"),
|
||||
"CONFIG_MAN_CDN_RETRY_COUNT": ("cdn", "retry_count"),
|
||||
"CONFIG_MAN_CDN_CLOUDFLARE_API_TOKEN": ("cdn", "cloudflare", "api_token"),
|
||||
"CONFIG_MAN_CDN_CLOUDFLARE_ZONE_ID": ("cdn", "cloudflare", "zone_id"),
|
||||
"CONFIG_MAN_CRYPTO_KEY": ("crypto", "key"),
|
||||
"CONFIG_MAN_CRYPTO_ALGORITHM": ("crypto", "algorithm"),
|
||||
"CONFIG_MAN_LOG_LEVEL": ("logging", "level"),
|
||||
@@ -216,6 +222,18 @@ class Config:
|
||||
def get_logging_config(self) -> Dict[str, Any]:
|
||||
"""获取日志配置"""
|
||||
return self.get('logging', {})
|
||||
|
||||
def get_cloudflare_config(self) -> Dict[str, Any]:
|
||||
"""获取Cloudflare配置"""
|
||||
return self.get('cdn.cloudflare', {})
|
||||
|
||||
def get_cloudflare_api_token(self) -> str:
|
||||
"""获取Cloudflare API Token"""
|
||||
return self.get('cdn.cloudflare.api_token', '')
|
||||
|
||||
def get_cloudflare_zone_id(self) -> str:
|
||||
"""获取Cloudflare Zone ID"""
|
||||
return self.get('cdn.cloudflare.zone_id', '')
|
||||
|
||||
|
||||
# 全局配置实例
|
||||
|
||||
@@ -123,6 +123,28 @@ class MockRclone:
|
||||
|
||||
with open(local_path, 'r', encoding='utf-8') as f:
|
||||
return f.read().strip()
|
||||
|
||||
def mock_rclone_rcat(self, path: str, content: str):
|
||||
"""模拟rclone rcat命令(上传文件)"""
|
||||
# 处理rclone路径格式:remote:path 或 path
|
||||
if ':' in path:
|
||||
# 如果包含远程名称,提取路径部分
|
||||
_, actual_path = path.split(':', 1)
|
||||
else:
|
||||
# 如果没有远程名称,直接使用路径
|
||||
actual_path = path
|
||||
|
||||
# 将路径转换为本地文件路径
|
||||
local_path = os.path.join(self.test_data_path, actual_path)
|
||||
|
||||
# 确保目录存在
|
||||
os.makedirs(os.path.dirname(local_path), exist_ok=True)
|
||||
|
||||
# 保存内容到文件
|
||||
with open(local_path, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
print(f"模拟上传文件: {path} -> {local_path}")
|
||||
|
||||
|
||||
def patch_rclone():
|
||||
@@ -132,26 +154,56 @@ def patch_rclone():
|
||||
original_run = subprocess.run
|
||||
|
||||
def mock_run(cmd, *args, **kwargs):
|
||||
if len(cmd) >= 2 and cmd[0] == 'rclone' and cmd[1] == 'cat':
|
||||
# 模拟rclone cat命令
|
||||
mock_rclone = MockRclone()
|
||||
try:
|
||||
content = mock_rclone.mock_rclone_cat(cmd[2])
|
||||
# 返回模拟的subprocess结果
|
||||
from types import SimpleNamespace
|
||||
result = SimpleNamespace()
|
||||
result.returncode = 0
|
||||
result.stdout = content
|
||||
result.stderr = ""
|
||||
return result
|
||||
except FileNotFoundError as e:
|
||||
# 返回错误结果
|
||||
from types import SimpleNamespace
|
||||
result = SimpleNamespace()
|
||||
result.returncode = 1
|
||||
result.stdout = ""
|
||||
result.stderr = str(e)
|
||||
return result
|
||||
if len(cmd) >= 2 and cmd[0] == 'rclone':
|
||||
if cmd[1] == 'cat':
|
||||
# 模拟rclone cat命令
|
||||
mock_rclone = MockRclone()
|
||||
try:
|
||||
content = mock_rclone.mock_rclone_cat(cmd[2])
|
||||
# 返回模拟的subprocess结果
|
||||
from types import SimpleNamespace
|
||||
result = SimpleNamespace()
|
||||
result.returncode = 0
|
||||
result.stdout = content
|
||||
result.stderr = ""
|
||||
return result
|
||||
except FileNotFoundError as e:
|
||||
# 返回错误结果
|
||||
from types import SimpleNamespace
|
||||
result = SimpleNamespace()
|
||||
result.returncode = 1
|
||||
result.stdout = ""
|
||||
result.stderr = str(e)
|
||||
return result
|
||||
elif cmd[1] == 'rcat':
|
||||
# 模拟rclone rcat命令(上传文件)
|
||||
mock_rclone = MockRclone()
|
||||
try:
|
||||
# 获取文件路径
|
||||
file_path = cmd[2]
|
||||
# 从stdin读取内容
|
||||
content = kwargs.get('input', '')
|
||||
if not content and 'input' in kwargs:
|
||||
content = kwargs['input']
|
||||
|
||||
# 保存到本地文件
|
||||
mock_rclone.mock_rclone_rcat(file_path, content)
|
||||
|
||||
# 返回成功结果
|
||||
from types import SimpleNamespace
|
||||
result = SimpleNamespace()
|
||||
result.returncode = 0
|
||||
result.stdout = ""
|
||||
result.stderr = ""
|
||||
return result
|
||||
except Exception as e:
|
||||
# 返回错误结果
|
||||
from types import SimpleNamespace
|
||||
result = SimpleNamespace()
|
||||
result.returncode = 1
|
||||
result.stdout = ""
|
||||
result.stderr = str(e)
|
||||
return result
|
||||
else:
|
||||
# 对于其他命令,使用原始实现
|
||||
return original_run(cmd, *args, **kwargs)
|
||||
|
||||
Reference in New Issue
Block a user