修复CDN URL和路径构建逻辑

- 修复CDN URL构建,正确处理路径重复问题
- 修复rclone路径构建,使用正确的格式: {rclone_remote}:{storage_path}/{version_path}/{filename}
- 修复CDN路径构建,使用正确的格式: {cdn_base_url}/{version_path}/{filename}
- 修复CDN内容解密处理,支持加密和解密后的内容
- 添加路径构建测试示例
- 完善错误处理和空内容处理
This commit is contained in:
2025-08-07 18:03:16 +08:00
parent 4cb6c4eab7
commit 9eddb3133c
13 changed files with 675 additions and 32 deletions

View File

@@ -43,6 +43,7 @@ config-man/
│ └── basic_usage.py # 基本使用示例 │ └── basic_usage.py # 基本使用示例
├── main.py # 主入口文件 ├── main.py # 主入口文件
├── pyproject.toml # 项目配置文件 ├── pyproject.toml # 项目配置文件
├── config-man.json # 配置文件
├── README.md # 项目主文档 ├── README.md # 项目主文档
├── .gitignore # Git忽略文件 ├── .gitignore # Git忽略文件
└── PROJECT_STRUCTURE.md # 项目结构说明(本文件) └── PROJECT_STRUCTURE.md # 项目结构说明(本文件)

25
config-man.json Normal file
View File

@@ -0,0 +1,25 @@
{
"storage": {
"path": "n3ft/ab",
"rclone_remote": "ft",
"timeout": 30
},
"cdn": {
"base_url": "https://ab.bamboogames.cc/ab",
"timeout": 10,
"retry_count": 3
},
"crypto": {
"algorithm": "DES",
"key": "tbambooz"
},
"logging": {
"level": "INFO",
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
},
"display": {
"table_format": "grid",
"max_width": 100,
"truncate_length": 25
}
}

View File

@@ -43,10 +43,72 @@ python main.py view --help
- `--version`: 版本号,格式为 0.29, 0.30, 0.31 等(必需) - `--version`: 版本号,格式为 0.29, 0.30, 0.31 等(必需)
- `--json`: 以JSON格式输出可选 - `--json`: 以JSON格式输出可选
- `--storage-path`: 对象存储路径前缀,默认为 'ab'(可选 - `--storage-path`: 对象存储路径前缀(覆盖配置文件
- `--cdn-url`: CDN基础URL可选 - `--cdn-url`: CDN基础URL覆盖配置文件
- `--mock`: 使用模拟环境进行测试(可选) - `--mock`: 使用模拟环境进行测试(可选)
### 配置管理命令
- `show-config`: 显示当前配置
- `set-config`: 设置配置值
## 配置文件
Config-Man 支持多种配置方式:
### 1. 配置文件
默认配置文件位置:
- 用户配置: `~/.config/config-man/config-man.json`
- 项目配置: `./config-man.json`
配置文件格式:
```json
{
"storage": {
"path": "ab",
"rclone_remote": "remote",
"timeout": 30
},
"cdn": {
"base_url": "",
"timeout": 10,
"retry_count": 3
},
"crypto": {
"algorithm": "DES",
"key": "tbambooz"
},
"display": {
"table_format": "grid",
"max_width": 80,
"truncate_length": 25
}
}
```
### 2. 环境变量
支持以下环境变量:
| 环境变量 | 配置项 | 说明 |
|---------|--------|------|
| `CONFIG_MAN_STORAGE_PATH` | `storage.path` | 存储路径 |
| `CONFIG_MAN_STORAGE_RCLONE_REMOTE` | `storage.rclone_remote` | rclone远程名称 |
| `CONFIG_MAN_STORAGE_TIMEOUT` | `storage.timeout` | 存储操作超时时间 |
| `CONFIG_MAN_CDN_BASE_URL` | `cdn.base_url` | CDN基础URL |
| `CONFIG_MAN_CDN_TIMEOUT` | `cdn.timeout` | CDN请求超时时间 |
| `CONFIG_MAN_CDN_RETRY_COUNT` | `cdn.retry_count` | CDN重试次数 |
| `CONFIG_MAN_CRYPTO_KEY` | `crypto.key` | 加密密钥 |
| `CONFIG_MAN_CRYPTO_ALGORITHM` | `crypto.algorithm` | 加密算法 |
| `CONFIG_MAN_DISPLAY_TABLE_FORMAT` | `display.table_format` | 表格格式 |
| `CONFIG_MAN_DISPLAY_MAX_WIDTH` | `display.max_width` | 最大宽度 |
| `CONFIG_MAN_DISPLAY_TRUNCATE_LENGTH` | `display.truncate_length` | 截断长度 |
### 3. 命令行参数
命令行参数会覆盖配置文件和环境变量的设置。
## 输出格式 ## 输出格式
### 表格格式(默认) ### 表格格式(默认)
@@ -146,16 +208,35 @@ python main.py view --version 0.30 --mock
## 高级用法 ## 高级用法
### 自定义存储路径 ### 配置管理
```bash ```bash
python main.py view --version 0.30 --storage-path custom_path # 显示当前配置
python main.py show-config
# 设置配置值
python main.py set-config --key storage.path --value custom_path
python main.py set-config --key cdn.base_url --value https://cdn.example.com
python main.py set-config --key display.max_width --value 100
``` ```
### 指定CDN URL ### 环境变量配置
```bash ```bash
python main.py view --version 0.30 --cdn-url https://cdn.example.com # 使用环境变量设置配置
export CONFIG_MAN_STORAGE_PATH=custom_path
export CONFIG_MAN_CDN_BASE_URL=https://cdn.example.com
export CONFIG_MAN_DISPLAY_MAX_WIDTH=120
# 运行命令
python main.py view --version 0.30
```
### 命令行参数覆盖
```bash
# 命令行参数会覆盖配置文件设置
python main.py view --version 0.30 --storage-path custom_path --cdn-url https://cdn.example.com
``` ```
### 组合使用 ### 组合使用

58
examples/config_usage.py Normal file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""
Config-Man 配置使用示例
演示如何使用配置文件和环境变量。
"""
import sys
import os
# 添加项目根目录到路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from config_man.utils import config
def main():
"""配置使用示例"""
print("Config-Man 配置使用示例")
print("=" * 50)
# 1. 显示当前配置
print("\n1. 当前配置:")
print(f"存储路径: {config.get_storage_path()}")
print(f"CDN基础URL: {config.get_cdn_base_url()}")
print(f"加密密钥: {config.get_crypto_key()}")
# 2. 设置配置值
print("\n2. 设置配置值:")
config.set('storage.path', 'custom_storage')
config.set('cdn.base_url', 'https://cdn.example.com')
config.set('display.max_width', 100)
print("配置已更新")
# 3. 显示更新后的配置
print("\n3. 更新后的配置:")
print(f"存储路径: {config.get_storage_path()}")
print(f"CDN基础URL: {config.get_cdn_base_url()}")
print(f"显示最大宽度: {config.get('display.max_width')}")
# 4. 保存配置到文件
print("\n4. 保存配置:")
config.save()
print(f"配置已保存到: {config.config_file}")
print(f"配置文件名称: config-man.json")
# 5. 环境变量示例
print("\n5. 环境变量使用示例:")
print("可以通过以下环境变量覆盖配置:")
print(" CONFIG_MAN_STORAGE_PATH=custom_path")
print(" CONFIG_MAN_CDN_BASE_URL=https://cdn.example.com")
print(" CONFIG_MAN_CRYPTO_KEY=custom_key")
print(" CONFIG_MAN_DISPLAY_MAX_WIDTH=120")
if __name__ == "__main__":
main()

50
examples/test_cdn_url.py Normal file
View File

@@ -0,0 +1,50 @@
#!/usr/bin/env python3
"""
测试CDN URL构建
验证CDN URL的正确构建。
"""
import sys
import os
# 添加项目根目录到路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from config_man.core import ConfigManager
def main():
"""测试CDN URL构建"""
print("测试CDN URL构建")
print("=" * 50)
# 创建配置管理器
config_manager = ConfigManager()
# 测试不同版本的CDN URL构建
versions = ["0.29", "0.30"]
platforms = ["android", "ios"]
print(f"CDN基础URL: {config_manager.cdn_base_url}")
print(f"存储路径: {config_manager.storage_path}")
print()
for version in versions:
for platform in platforms:
# 获取CDN URL
cdn_url = config_manager.get_cdn_url(version, platform)
print(f"版本: {version}, 平台: {platform}")
print(f"CDN URL: {cdn_url}")
print()
# 显示当前配置
print(f"当前CDN配置:")
print(f"CDN基础URL: {config_manager.cdn_base_url}")
print(f"CDN超时: {config_manager.cdn_config.get('timeout', 10)}")
print(f"CDN重试次数: {config_manager.cdn_config.get('retry_count', 3)}")
if __name__ == "__main__":
main()

53
examples/test_paths.py Normal file
View File

@@ -0,0 +1,53 @@
#!/usr/bin/env python3
"""
测试路径构建
验证rclone路径和CDN路径的正确构建。
"""
import sys
import os
# 添加项目根目录到路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from config_man.core import ConfigManager
def main():
"""测试路径构建"""
print("测试路径构建")
print("=" * 50)
# 创建配置管理器
config_manager = ConfigManager()
# 测试不同版本的路径构建
versions = ["0.29", "0.30"]
platforms = ["android", "ios"]
print(f"存储配置:")
print(f" rclone_remote: {config_manager.storage_config.get('rclone_remote', 'remote')}")
print(f" storage_path: {config_manager.storage_path}")
print()
print(f"CDN配置:")
print(f" base_url: {config_manager.cdn_base_url}")
print()
for version in versions:
for platform in platforms:
# 获取rclone路径
rclone_path = config_manager.get_rclone_path(version, platform)
# 获取CDN URL
cdn_url = config_manager.get_cdn_url(version, platform)
print(f"版本: {version}, 平台: {platform}")
print(f"Rclone路径: {rclone_path}")
print(f"CDN URL: {cdn_url}")
print()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,48 @@
#!/usr/bin/env python3
"""
测试rclone路径构建
验证rclone_remote配置项的正确使用。
"""
import sys
import os
# 添加项目根目录到路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from config_man.core import ConfigManager
def main():
"""测试rclone路径构建"""
print("测试rclone路径构建")
print("=" * 50)
# 创建配置管理器
config_manager = ConfigManager()
# 测试不同版本的路径构建
versions = ["0.29", "0.30"]
platforms = ["android", "ios"]
for version in versions:
for platform in platforms:
# 获取配置路径
config_path = config_manager.get_config_path(version, platform)
# 获取rclone路径
rclone_path = config_manager.get_rclone_path(version, platform)
print(f"\n版本: {version}, 平台: {platform}")
print(f"配置路径: {config_path}")
print(f"rclone路径: {rclone_path}")
# 显示当前配置
print(f"\n当前存储配置:")
print(f"存储路径: {config_manager.storage_path}")
print(f"rclone远程: {config_manager.storage_config.get('rclone_remote', 'remote')}")
if __name__ == "__main__":
main()

View File

@@ -2,6 +2,7 @@ import click
import os import os
from ..core.view_command import ViewCommand from ..core.view_command import ViewCommand
from ..utils.mock_rclone import patch_rclone from ..utils.mock_rclone import patch_rclone
from ..utils.config import config
@click.group() @click.group()
@@ -18,8 +19,8 @@ def cli():
@cli.command() @cli.command()
@click.option('--version', required=True, help='版本号,格式为 0.29, 0.30, 0.31 等') @click.option('--version', required=True, help='版本号,格式为 0.29, 0.30, 0.31 等')
@click.option('--json', 'json_format', is_flag=True, help='以JSON格式输出') @click.option('--json', 'json_format', is_flag=True, help='以JSON格式输出')
@click.option('--storage-path', default='ab', help='对象存储路径前缀') @click.option('--storage-path', help='对象存储路径前缀(覆盖配置文件)')
@click.option('--cdn-url', default='', help='CDN基础URL') @click.option('--cdn-url', help='CDN基础URL(覆盖配置文件)')
@click.option('--mock', is_flag=True, help='使用模拟环境进行测试') @click.option('--mock', is_flag=True, help='使用模拟环境进行测试')
def view(version, json_format, storage_path, cdn_url, mock): def view(version, json_format, storage_path, cdn_url, mock):
"""查看配置文件 """查看配置文件
@@ -89,6 +90,67 @@ def force_update(platform, target_version, update_version):
return 1 return 1
@cli.command()
def show_config():
"""显示当前配置
显示当前加载的配置信息。
"""
import json
from rich.console import Console
from rich.panel import Panel
console = Console()
# 获取所有配置
all_config = {
"storage": config.get_storage_config(),
"cdn": config.get_cdn_config(),
"crypto": config.get_crypto_config(),
"display": config.get_display_config(),
"logging": config.get_logging_config()
}
# 格式化输出
config_json = json.dumps(all_config, indent=2, ensure_ascii=False)
panel = Panel(
config_json,
title="当前配置",
border_style="blue"
)
console.print(panel)
# 显示配置文件路径
console.print(f"\n配置文件路径: {config.config_file}")
@cli.command()
@click.option('--key', required=True, help='配置键,如 storage.path')
@click.option('--value', required=True, help='配置值')
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
"""
try:
# 尝试转换数值类型
if value.isdigit():
value = int(value)
elif value.lower() in ('true', 'false'):
value = value.lower() == 'true'
config.set(key, value)
config.save()
click.echo(f"✅ 配置已更新: {key} = {value}")
except Exception as e:
click.echo(f"❌ 设置配置失败: {e}")
return 1
def _check_rclone(): def _check_rclone():
"""检查rclone是否可用""" """检查rclone是否可用"""
import subprocess import subprocess

View File

@@ -4,14 +4,18 @@ import subprocess
import requests import requests
from typing import Dict, Optional, Tuple from typing import Dict, Optional, Tuple
from ..utils.crypto import encrypt, decrypt from ..utils.crypto import encrypt, decrypt
from ..utils.config import config
class ConfigManager: class ConfigManager:
"""配置文件管理器""" """配置文件管理器"""
def __init__(self, storage_path: str = "ab", cdn_base_url: str = ""): def __init__(self, storage_path: str = None, cdn_base_url: str = None):
self.storage_path = storage_path # 使用配置文件中的值如果参数为None
self.cdn_base_url = cdn_base_url self.storage_path = storage_path or config.get_storage_path()
self.cdn_base_url = cdn_base_url or config.get_cdn_base_url()
self.storage_config = config.get_storage_config()
self.cdn_config = config.get_cdn_config()
def version_to_path(self, version: str) -> str: def version_to_path(self, version: str) -> str:
"""将版本号转换为存储路径格式""" """将版本号转换为存储路径格式"""
@@ -24,45 +28,54 @@ class ConfigManager:
filename = f"{platform}config.json" filename = f"{platform}config.json"
return f"{self.storage_path}/{version_path}/{filename}" return f"{self.storage_path}/{version_path}/{filename}"
def get_rclone_path(self, version: str, platform: str) -> str:
"""获取rclone完整路径包含远程名称"""
version_path = self.version_to_path(version)
filename = f"{platform}config.json"
rclone_remote = self.storage_config.get('rclone_remote', 'remote')
return f"{rclone_remote}:{self.storage_path}/{version_path}/{filename}"
def get_cdn_url(self, version: str, platform: str) -> str: def get_cdn_url(self, version: str, platform: str) -> str:
"""获取配置文件在CDN中的URL""" """获取配置文件在CDN中的URL"""
if not self.cdn_base_url: if not self.cdn_base_url:
return "" return ""
version_path = self.version_to_path(version) version_path = self.version_to_path(version)
filename = f"{platform}config.json" filename = f"{platform}config.json"
return f"{self.cdn_base_url}/{self.storage_path}/{version_path}/{filename}" return f"{self.cdn_base_url}/{version_path}/{filename}"
def download_from_storage(self, version: str, platform: str) -> Optional[str]: def download_from_storage(self, version: str, platform: str) -> Optional[str]:
"""从对象存储下载并解密配置文件""" """从对象存储下载并解密配置文件"""
try: try:
config_path = self.get_config_path(version, platform) # 获取rclone完整路径
rclone_path = self.get_rclone_path(version, platform)
# 使用rclone下载文件 # 使用rclone下载文件
timeout = self.storage_config.get('timeout', 30)
result = subprocess.run( result = subprocess.run(
["rclone", "cat", config_path], ["rclone", "cat", rclone_path],
capture_output=True, capture_output=True,
text=True, text=True,
timeout=30 timeout=timeout
) )
if result.returncode != 0: if result.returncode != 0:
print(f"警告: 无法从对象存储下载 {config_path}: {result.stderr}") print(f"警告: 无法从对象存储下载 {rclone_path}: {result.stderr}")
return None return None
# 解密文件内容 # 解密文件内容
encrypted_content = result.stdout.strip() encrypted_content = result.stdout.strip()
if not encrypted_content: if not encrypted_content:
print(f"警告: 文件 {config_path} 为空") print(f"警告: 文件 {rclone_path} 为空")
return None return None
decrypted_content = decrypt(encrypted_content) decrypted_content = decrypt(encrypted_content)
return decrypted_content return decrypted_content
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
print(f"错误: 下载 {config_path} 超时") print(f"错误: 下载 {rclone_path} 超时")
return None return None
except Exception as e: except Exception as e:
print(f"错误: 下载 {config_path} 失败: {str(e)}") print(f"错误: 下载 {rclone_path} 失败: {str(e)}")
return None return None
def get_from_cdn(self, version: str, platform: str) -> Optional[str]: def get_from_cdn(self, version: str, platform: str) -> Optional[str]:
@@ -73,12 +86,25 @@ class ConfigManager:
print(f"警告: CDN URL未配置跳过CDN内容获取") print(f"警告: CDN URL未配置跳过CDN内容获取")
return None return None
response = requests.get(cdn_url, timeout=10) timeout = self.cdn_config.get('timeout', 10)
response = requests.get(cdn_url, timeout=timeout)
response.raise_for_status() response.raise_for_status()
# CDN中的内容应该是解密后的JSON # CDN中的内容可能是加密的,需要解密
return response.text encrypted_content = response.text.strip()
if not encrypted_content:
print(f"警告: CDN返回空内容: {cdn_url}")
return None
# 尝试解密内容
try:
decrypted_content = decrypt(encrypted_content)
return decrypted_content
except Exception as e:
# 如果解密失败可能CDN中的内容已经是解密后的JSON
print(f"警告: CDN内容解密失败尝试直接解析: {str(e)}")
return encrypted_content
except requests.RequestException as e: except requests.RequestException as e:
print(f"警告: 无法从CDN获取 {cdn_url}: {str(e)}") print(f"警告: 无法从CDN获取 {cdn_url}: {str(e)}")
return None return None

View File

@@ -4,6 +4,7 @@ from rich.table import Table
from rich.panel import Panel from rich.panel import Panel
from rich.text import Text from rich.text import Text
import json import json
from ..utils.config import config
class DisplayManager: class DisplayManager:
@@ -11,6 +12,7 @@ class DisplayManager:
def __init__(self): def __init__(self):
self.console = Console() self.console = Console()
self.display_config = config.get_display_config()
def display_config_comparison(self, version: str, platform: str, def display_config_comparison(self, version: str, platform: str,
storage_config: Dict, cdn_config: Dict, storage_config: Dict, cdn_config: Dict,
@@ -29,16 +31,17 @@ class DisplayManager:
table.add_column("状态", style="red", width=10) table.add_column("状态", style="red", width=10)
# 添加数据行 # 添加数据行
truncate_length = self.display_config.get('truncate_length', 25)
for key, data in differences.items(): for key, data in differences.items():
storage_value = str(data["storage"]) storage_value = str(data["storage"])
cdn_value = str(data["cdn"]) cdn_value = str(data["cdn"])
status = "❌ 不同" if data["different"] else "✅ 一致" status = "❌ 不同" if data["different"] else "✅ 一致"
# 截断过长的值 # 截断过长的值
if len(storage_value) > 25: if len(storage_value) > truncate_length:
storage_value = storage_value[:22] + "..." storage_value = storage_value[:truncate_length-3] + "..."
if len(cdn_value) > 25: if len(cdn_value) > truncate_length:
cdn_value = cdn_value[:22] + "..." cdn_value = cdn_value[:truncate_length-3] + "..."
table.add_row(key, storage_value, cdn_value, status) table.add_row(key, storage_value, cdn_value, status)

View File

@@ -1,10 +1,11 @@
""" """
工具模块 工具模块
包含加密解密、模拟环境等工具功能。 包含加密解密、模拟环境、配置管理等工具功能。
""" """
from .crypto import encrypt, decrypt from .crypto import encrypt, decrypt
from .mock_rclone import MockRclone, patch_rclone from .mock_rclone import MockRclone, patch_rclone
from .config import config, Config
__all__ = ["encrypt", "decrypt", "MockRclone", "patch_rclone"] __all__ = ["encrypt", "decrypt", "MockRclone", "patch_rclone", "config", "Config"]

View File

@@ -0,0 +1,222 @@
"""
配置管理模块
支持从配置文件和环境变量读取配置信息。
"""
import os
import json
from pathlib import Path
from typing import Dict, Any, Optional
class Config:
"""配置管理类"""
def __init__(self, config_file: Optional[str] = None):
"""
初始化配置管理器
Args:
config_file: 配置文件路径如果为None则使用默认路径
"""
self.config_file = config_file or self._get_default_config_path()
self._config = self._load_config()
def _get_default_config_path(self) -> str:
"""获取默认配置文件路径"""
# 优先使用用户主目录下的配置文件
home_config = Path.home() / ".config" / "config-man" / "config-man.json"
if home_config.exists():
return str(home_config)
# 其次使用项目根目录下的配置文件
project_config = Path(__file__).parent.parent.parent.parent / "config-man.json"
if project_config.exists():
return str(project_config)
# 最后使用默认配置文件
return str(Path(__file__).parent.parent.parent.parent / "config-man.json")
def _load_config(self) -> Dict[str, Any]:
"""加载配置文件"""
default_config = {
"storage": {
"path": "ab",
"rclone_remote": "remote",
"timeout": 30
},
"cdn": {
"base_url": "",
"timeout": 10,
"retry_count": 3
},
"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
}
}
# 如果配置文件存在,则加载并合并
if os.path.exists(self.config_file):
try:
with open(self.config_file, 'r', encoding='utf-8') as f:
file_config = json.load(f)
# 深度合并配置
self._merge_config(default_config, file_config)
except (json.JSONDecodeError, IOError) as e:
print(f"警告: 无法加载配置文件 {self.config_file}: {e}")
# 从环境变量覆盖配置
self._load_from_env(default_config)
return default_config
def _merge_config(self, base: Dict[str, Any], override: Dict[str, Any]) -> None:
"""深度合并配置"""
for key, value in override.items():
if key in base and isinstance(base[key], dict) and isinstance(value, dict):
self._merge_config(base[key], value)
else:
base[key] = value
def _load_from_env(self, config: Dict[str, Any]) -> None:
"""从环境变量加载配置"""
env_mappings = {
"CONFIG_MAN_STORAGE_PATH": ("storage", "path"),
"CONFIG_MAN_STORAGE_RCLONE_REMOTE": ("storage", "rclone_remote"),
"CONFIG_MAN_STORAGE_TIMEOUT": ("storage", "timeout"),
"CONFIG_MAN_CDN_BASE_URL": ("cdn", "base_url"),
"CONFIG_MAN_CDN_TIMEOUT": ("cdn", "timeout"),
"CONFIG_MAN_CDN_RETRY_COUNT": ("cdn", "retry_count"),
"CONFIG_MAN_CRYPTO_KEY": ("crypto", "key"),
"CONFIG_MAN_CRYPTO_ALGORITHM": ("crypto", "algorithm"),
"CONFIG_MAN_LOG_LEVEL": ("logging", "level"),
"CONFIG_MAN_DISPLAY_TABLE_FORMAT": ("display", "table_format"),
"CONFIG_MAN_DISPLAY_MAX_WIDTH": ("display", "max_width"),
"CONFIG_MAN_DISPLAY_TRUNCATE_LENGTH": ("display", "truncate_length"),
}
for env_var, config_path in env_mappings.items():
env_value = os.getenv(env_var)
if env_value is not None:
# 获取配置路径的父级
current = config
for key in config_path[:-1]:
if key not in current:
current[key] = {}
current = current[key]
# 设置值,尝试转换为适当类型
key = config_path[-1]
try:
# 尝试转换为整数
if isinstance(current.get(key), int):
current[key] = int(env_value)
# 尝试转换为布尔值
elif isinstance(current.get(key), bool):
current[key] = env_value.lower() in ('true', '1', 'yes', 'on')
else:
current[key] = env_value
except ValueError:
current[key] = env_value
def get(self, key: str, default: Any = None) -> Any:
"""
获取配置值
Args:
key: 配置键,支持点号分隔的嵌套键,如 'storage.path'
default: 默认值
Returns:
配置值
"""
keys = key.split('.')
value = self._config
for k in keys:
if isinstance(value, dict) and k in value:
value = value[k]
else:
return default
return value
def set(self, key: str, value: Any) -> None:
"""
设置配置值
Args:
key: 配置键,支持点号分隔的嵌套键
value: 配置值
"""
keys = key.split('.')
current = self._config
# 导航到父级
for k in keys[:-1]:
if k not in current:
current[k] = {}
current = current[k]
# 设置值
current[keys[-1]] = value
def save(self) -> None:
"""保存配置到文件"""
try:
# 确保配置目录存在
config_dir = os.path.dirname(self.config_file)
if config_dir:
os.makedirs(config_dir, exist_ok=True)
with open(self.config_file, 'w', encoding='utf-8') as f:
json.dump(self._config, f, indent=2, ensure_ascii=False)
except IOError as e:
print(f"警告: 无法保存配置文件 {self.config_file}: {e}")
def get_storage_path(self) -> str:
"""获取存储路径"""
return self.get('storage.path', 'ab')
def get_cdn_base_url(self) -> str:
"""获取CDN基础URL"""
return self.get('cdn.base_url', '')
def get_crypto_key(self) -> str:
"""获取加密密钥"""
return self.get('crypto.key', 'tbambooz')
def get_display_config(self) -> Dict[str, Any]:
"""获取显示配置"""
return self.get('display', {})
def get_storage_config(self) -> Dict[str, Any]:
"""获取存储配置"""
return self.get('storage', {})
def get_cdn_config(self) -> Dict[str, Any]:
"""获取CDN配置"""
return self.get('cdn', {})
def get_crypto_config(self) -> Dict[str, Any]:
"""获取加密配置"""
return self.get('crypto', {})
def get_logging_config(self) -> Dict[str, Any]:
"""获取日志配置"""
return self.get('logging', {})
# 全局配置实例
config = Config()

View File

@@ -21,6 +21,8 @@ class MockRclone:
def _create_test_configs(self): def _create_test_configs(self):
"""创建测试配置文件""" """创建测试配置文件"""
from ..utils.config import config
test_configs = { test_configs = {
"0.29": { "0.29": {
"android": { "android": {
@@ -82,17 +84,20 @@ class MockRclone:
} }
} }
# 获取配置的存储路径
storage_path = config.get_storage_path()
# 创建测试目录结构 # 创建测试目录结构
for version, platforms in test_configs.items(): for version, platforms in test_configs.items():
version_path = f"{self.test_data_path}/ab/{version.replace('.', '_')}" version_path = f"{self.test_data_path}/{storage_path}/{version.replace('.', '_')}"
os.makedirs(version_path, exist_ok=True) os.makedirs(version_path, exist_ok=True)
for platform, config in platforms.items(): for platform, config_data in platforms.items():
filename = f"{platform}config.json" filename = f"{platform}config.json"
filepath = os.path.join(version_path, filename) filepath = os.path.join(version_path, filename)
# 加密配置并保存 # 加密配置并保存
config_json = json.dumps(config, indent=2, ensure_ascii=False) config_json = json.dumps(config_data, indent=2, ensure_ascii=False)
encrypted_config = encrypt(config_json) encrypted_config = encrypt(config_json)
with open(filepath, 'w', encoding='utf-8') as f: with open(filepath, 'w', encoding='utf-8') as f:
@@ -102,8 +107,16 @@ class MockRclone:
def mock_rclone_cat(self, path: str) -> str: def mock_rclone_cat(self, path: str) -> str:
"""模拟rclone cat命令""" """模拟rclone cat命令"""
# 处理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, path) local_path = os.path.join(self.test_data_path, actual_path)
if not os.path.exists(local_path): if not os.path.exists(local_path):
raise FileNotFoundError(f"文件不存在: {path}") raise FileNotFoundError(f"文件不存在: {path}")