mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-09-28 16:20:56 +08:00
✨ feat(http_utils): 添加可选的 accept_status_codes 参数以支持自定义状态码处理 (#2096)
- 在 AsyncHttpx 类中,新增 accept_status_codes 参数,允许用户指定哪些 HTTP 状态码不视为错误。 - 更新了单次请求的逻辑,以便在响应状态码符合 accept_status_codes 时不调用 raise_for_status 方法。 ✨ feat(message): 增加随机失败提示消息功能 - 在 MessageUtils 类中,添加可爱风格的失败提示消息列表和获取随机消息的方法。 ✨ feat(github_utils): 引入动态查询外部插件分组的功能 - 在 AliyunFileInfo 类中,新增 list_group_repositories 方法以列出分组下的所有仓库,并实现通过仓库名称获取仓库ID的逻辑。 ✨ feat(virtual_env_package_manager): 增加清理 requirements 文件的功能 - 新增 _clean_requirements_file 方法,清理 requirements 文件中的非ASCII注释,避免 Windows 上 pip 使用 GBK 编码读取 UTF-8 文件时出错。 ✨ feat(repo_utils): 优化阿里云仓库 URL 处理逻辑 - 更新 prepare_aliyun_url 方法以支持指定分组名称,并新增 get_aliyun_group_for_repo 方法以获取仓库所属的阿里云分组名。
This commit is contained in:
@@ -74,3 +74,6 @@ ALIYUN_REPO_MAPPING = {
|
||||
"zhenxun_bot": "4957428",
|
||||
}
|
||||
"""阿里云仓库ID映射"""
|
||||
|
||||
ALIYUN_EXTERNAL_PLUGIN_GROUPS: list[str] = ["zhenxun_plugins"]
|
||||
"""阿里云外部插件分组列表,用于动态查询第三方插件仓库"""
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import base64
|
||||
import contextlib
|
||||
import sys
|
||||
from typing import Protocol
|
||||
from typing import ClassVar, Protocol
|
||||
|
||||
from aiocache import cached
|
||||
from alibabacloud_devops20210625 import models as devops_20210625_models
|
||||
@@ -20,6 +20,7 @@ else:
|
||||
|
||||
from .const import (
|
||||
ALIYUN_ENDPOINT,
|
||||
ALIYUN_EXTERNAL_PLUGIN_GROUPS,
|
||||
ALIYUN_ORG_ID,
|
||||
ALIYUN_REGION,
|
||||
ALIYUN_REPO_MAPPING,
|
||||
@@ -317,6 +318,9 @@ class AliyunFileInfo:
|
||||
repository_id: str
|
||||
"""仓库ID"""
|
||||
|
||||
# 动态仓库ID缓存: {repo_name: repository_id}
|
||||
_dynamic_repo_cache: ClassVar[dict[str, str]] = {}
|
||||
|
||||
@classmethod
|
||||
async def get_client(cls) -> devops20210625Client:
|
||||
"""获取阿里云客户端"""
|
||||
@@ -331,6 +335,179 @@ class AliyunFileInfo:
|
||||
|
||||
return devops20210625Client(config)
|
||||
|
||||
@classmethod
|
||||
@cached(ttl=CACHED_API_TTL)
|
||||
async def list_group_repositories(cls, group_path: str) -> list[dict]:
|
||||
"""列出分组下的所有仓库
|
||||
|
||||
参数:
|
||||
group_path: 分组路径,如 "zhenxun_plugins"
|
||||
|
||||
返回:
|
||||
list[dict]: 仓库信息列表,每个元素包含 id, name, path 等
|
||||
"""
|
||||
try:
|
||||
client = await cls.get_client()
|
||||
result = []
|
||||
page = 1
|
||||
per_page = 100
|
||||
|
||||
while True:
|
||||
request = devops_20210625_models.ListRepositoriesRequest(
|
||||
organization_id=ALIYUN_ORG_ID,
|
||||
access_token=base64.b64decode(
|
||||
RDC_access_token_encrypted.encode()
|
||||
).decode(),
|
||||
page=page,
|
||||
per_page=per_page,
|
||||
)
|
||||
|
||||
runtime = util_models.RuntimeOptions()
|
||||
headers = {}
|
||||
|
||||
response = await client.list_repositories_with_options_async(
|
||||
request, headers, runtime
|
||||
)
|
||||
|
||||
if response and response.body:
|
||||
if not response.body.success:
|
||||
raise ValueError(
|
||||
f"阿里云请求失败: {response.body.error_code} - "
|
||||
f"{response.body.error_message}"
|
||||
)
|
||||
|
||||
repos = response.body.result or []
|
||||
if not repos:
|
||||
break
|
||||
|
||||
for repo in repos:
|
||||
repo_dict = repo.to_map()
|
||||
# 尝试多种可能的字段名
|
||||
path_with_ns = (
|
||||
repo_dict.get("pathWithNamespace")
|
||||
or repo_dict.get("path_with_namespace")
|
||||
or repo_dict.get("path")
|
||||
or ""
|
||||
)
|
||||
# pathWithNamespace 格式: {org_id}/{group_path}/{repo_name}
|
||||
# 只保留属于该分组的仓库
|
||||
if f"/{group_path}/" in path_with_ns:
|
||||
result.append(repo_dict)
|
||||
# 同时更新缓存
|
||||
repo_name = repo_dict.get("name", "")
|
||||
# 注意:阿里云API返回的ID字段是 "Id" (大写I)
|
||||
repo_id = str(
|
||||
repo_dict.get("Id") or repo_dict.get("id") or ""
|
||||
)
|
||||
if repo_name and repo_id:
|
||||
cls._dynamic_repo_cache[repo_name] = repo_id
|
||||
|
||||
# 如果返回的数量小于请求的数量,说明已经没有更多数据
|
||||
if len(repos) < per_page:
|
||||
break
|
||||
page += 1
|
||||
else:
|
||||
break
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
raise ValueError(f"获取仓库列表失败: {e}")
|
||||
|
||||
@classmethod
|
||||
async def get_repository_id(cls, repo_name: str) -> str | None:
|
||||
"""通过仓库名称获取仓库ID
|
||||
|
||||
优先从静态映射中查找,然后从缓存中查找,最后从API动态查询
|
||||
|
||||
参数:
|
||||
repo_name: 仓库名称
|
||||
|
||||
返回:
|
||||
str | None: 仓库ID,未找到返回 None
|
||||
"""
|
||||
# 1. 先从静态映射中查找
|
||||
if repo_id := ALIYUN_REPO_MAPPING.get(repo_name):
|
||||
return repo_id
|
||||
|
||||
# 2. 从动态缓存中查找
|
||||
if repo_id := cls._dynamic_repo_cache.get(repo_name):
|
||||
return repo_id
|
||||
|
||||
# 3. 从外部插件分组中动态查询
|
||||
for group_path in ALIYUN_EXTERNAL_PLUGIN_GROUPS:
|
||||
try:
|
||||
repos = await cls.list_group_repositories(group_path)
|
||||
for repo in repos:
|
||||
if repo.get("name") == repo_name:
|
||||
# 注意:阿里云API返回的ID字段是 "Id" (大写I)
|
||||
repo_id = str(repo.get("Id") or repo.get("id") or "")
|
||||
if repo_id:
|
||||
cls._dynamic_repo_cache[repo_name] = repo_id
|
||||
return repo_id
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
async def debug_list_all_repositories(cls) -> list[dict]:
|
||||
"""调试方法:列出组织下的所有仓库信息
|
||||
|
||||
返回:
|
||||
list[dict]: 所有仓库信息列表
|
||||
"""
|
||||
try:
|
||||
client = await cls.get_client()
|
||||
result = []
|
||||
|
||||
request = devops_20210625_models.ListRepositoriesRequest(
|
||||
organization_id=ALIYUN_ORG_ID,
|
||||
access_token=base64.b64decode(
|
||||
RDC_access_token_encrypted.encode()
|
||||
).decode(),
|
||||
page=1,
|
||||
per_page=100,
|
||||
)
|
||||
|
||||
runtime = util_models.RuntimeOptions()
|
||||
headers = {}
|
||||
|
||||
response = await client.list_repositories_with_options_async(
|
||||
request, headers, runtime
|
||||
)
|
||||
|
||||
if response and response.body and response.body.result:
|
||||
for repo in response.body.result:
|
||||
repo_dict = repo.to_map()
|
||||
result.append(repo_dict)
|
||||
# 同时填充缓存
|
||||
repo_name = repo_dict.get("name", "")
|
||||
repo_id = str(repo_dict.get("Id") or repo_dict.get("id") or "")
|
||||
if repo_name and repo_id:
|
||||
cls._dynamic_repo_cache[repo_name] = repo_id
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
raise ValueError(f"列出仓库失败: {e}")
|
||||
|
||||
@classmethod
|
||||
async def _get_repository_id_or_raise(cls, repo: str) -> str:
|
||||
"""获取仓库ID,如果未找到则抛出异常
|
||||
|
||||
参数:
|
||||
repo: 仓库名称
|
||||
|
||||
返回:
|
||||
str: 仓库ID
|
||||
|
||||
异常:
|
||||
ValueError: 未找到仓库
|
||||
"""
|
||||
repository_id = await cls.get_repository_id(repo)
|
||||
if not repository_id:
|
||||
raise ValueError(f"未找到仓库 {repo} 对应的阿里云仓库ID")
|
||||
return repository_id
|
||||
|
||||
@classmethod
|
||||
async def get_file_content(
|
||||
cls, file_path: str, repo: str, ref: str = "main"
|
||||
@@ -346,9 +523,7 @@ class AliyunFileInfo:
|
||||
str: 文件内容
|
||||
"""
|
||||
try:
|
||||
repository_id = ALIYUN_REPO_MAPPING.get(repo)
|
||||
if not repository_id:
|
||||
raise ValueError(f"未找到仓库 {repo} 对应的阿里云仓库ID")
|
||||
repository_id = await cls._get_repository_id_or_raise(repo)
|
||||
|
||||
client = await cls.get_client()
|
||||
|
||||
@@ -406,9 +581,7 @@ class AliyunFileInfo:
|
||||
list[AliyunTree]: 仓库树信息列表
|
||||
"""
|
||||
try:
|
||||
repository_id = ALIYUN_REPO_MAPPING.get(repo)
|
||||
if not repository_id:
|
||||
raise ValueError(f"未找到仓库 {repo} 对应的阿里云仓库ID")
|
||||
repository_id = await cls._get_repository_id_or_raise(repo)
|
||||
|
||||
client = await cls.get_client()
|
||||
|
||||
@@ -452,9 +625,7 @@ class AliyunFileInfo:
|
||||
commit: 最新提交信息
|
||||
"""
|
||||
try:
|
||||
repository_id = ALIYUN_REPO_MAPPING.get(repo)
|
||||
if not repository_id:
|
||||
raise ValueError(f"未找到仓库 {repo} 对应的阿里云仓库ID")
|
||||
repository_id = await cls._get_repository_id_or_raise(repo)
|
||||
|
||||
client = await cls.get_client()
|
||||
|
||||
@@ -498,9 +669,8 @@ class AliyunFileInfo:
|
||||
@classmethod
|
||||
async def parse_repo_info(cls, repo: str) -> list[str]:
|
||||
"""解析仓库信息获取仓库树"""
|
||||
repository_id = ALIYUN_REPO_MAPPING.get(repo)
|
||||
if not repository_id:
|
||||
raise ValueError(f"未找到仓库 {repo} 对应的阿里云仓库ID")
|
||||
# 验证仓库存在
|
||||
await cls._get_repository_id_or_raise(repo)
|
||||
|
||||
tree_list = await cls.get_repository_tree(
|
||||
repo=repo,
|
||||
|
||||
@@ -219,8 +219,12 @@ class AsyncHttpx:
|
||||
) -> Response:
|
||||
"""
|
||||
执行单次HTTP请求的私有方法,内置了默认的重试逻辑。
|
||||
accept_status_codes: 若提供,这些状态码不视为错误,不调用 raise_for_status。
|
||||
"""
|
||||
client_kwargs, request_kwargs = cls._split_kwargs(kwargs)
|
||||
accept_status_codes: Sequence[int] | None = request_kwargs.pop(
|
||||
"accept_status_codes", None
|
||||
)
|
||||
|
||||
async with cls._get_active_client_context(
|
||||
client=client, **client_kwargs
|
||||
@@ -228,7 +232,11 @@ class AsyncHttpx:
|
||||
response = await cls()._execute_request_inner(
|
||||
active_client, method, url, **request_kwargs
|
||||
)
|
||||
response.raise_for_status()
|
||||
if (
|
||||
accept_status_codes is None
|
||||
or response.status_code not in accept_status_codes
|
||||
):
|
||||
response.raise_for_status()
|
||||
return response
|
||||
|
||||
@classmethod
|
||||
@@ -292,6 +300,7 @@ class AsyncHttpx:
|
||||
*,
|
||||
follow_redirects: bool = True,
|
||||
check_status_code: int | None = None,
|
||||
accept_status_codes: Sequence[int] | None = None,
|
||||
client: AsyncClient | None = None,
|
||||
**kwargs,
|
||||
) -> Response:
|
||||
@@ -301,6 +310,8 @@ class AsyncHttpx:
|
||||
url: 单个请求 URL 或一个 URL 列表。
|
||||
follow_redirects: 是否跟随重定向。
|
||||
check_status_code: (可选) 若提供,将检查响应状态码是否匹配,否则抛出异常。
|
||||
accept_status_codes: (可选) 这些状态码不视为错误,
|
||||
如 (302,) 用于接受重定向响应。
|
||||
client: (可选) 指定一个活动的HTTP客户端实例。若提供,则忽略
|
||||
`**kwargs`中的客户端配置。
|
||||
**kwargs: 其他所有传递给 httpx.get 的参数 (如 `params`, `headers`,
|
||||
@@ -316,6 +327,8 @@ class AsyncHttpx:
|
||||
|
||||
async def worker(current_url: str, **worker_kwargs) -> Response:
|
||||
logger.info(f"开始获取 {current_url}..", "AsyncHttpx:get")
|
||||
if accept_status_codes is not None:
|
||||
worker_kwargs["accept_status_codes"] = accept_status_codes
|
||||
response = await cls._single_request(
|
||||
"GET", current_url, follow_redirects=follow_redirects, **worker_kwargs
|
||||
)
|
||||
@@ -342,11 +355,20 @@ class AsyncHttpx:
|
||||
|
||||
@classmethod
|
||||
async def post(
|
||||
cls, url: str | list[str], *, client: AsyncClient | None = None, **kwargs
|
||||
cls,
|
||||
url: str | list[str],
|
||||
*,
|
||||
accept_status_codes: Sequence[int] | None = None,
|
||||
client: AsyncClient | None = None,
|
||||
**kwargs,
|
||||
) -> Response:
|
||||
"""发送 POST 请求,并返回第一个成功的响应。"""
|
||||
"""发送 POST 请求,并返回第一个成功的响应。
|
||||
accept_status_codes: (可选) 这些状态码不视为错误,如 (302,) 用于接受重定向响应。
|
||||
"""
|
||||
|
||||
async def worker(current_url: str, **worker_kwargs) -> Response:
|
||||
if accept_status_codes is not None:
|
||||
worker_kwargs["accept_status_codes"] = accept_status_codes
|
||||
return await cls._single_request("POST", current_url, **worker_kwargs)
|
||||
|
||||
return await cls._execute_with_fallbacks(url, worker, client=client, **kwargs)
|
||||
|
||||
@@ -125,6 +125,40 @@ class VirtualEnvPackageManager:
|
||||
logger.error(f"更新虚拟环境包指令执行失败: {e.stderr}.", LOG_COMMAND)
|
||||
return e.stderr
|
||||
|
||||
@staticmethod
|
||||
def _clean_requirements_file(file_path: Path) -> None:
|
||||
"""清理 requirements 文件中的非ASCII注释
|
||||
|
||||
防止 Windows 上 pip 使用 GBK 编码读取 UTF-8 文件时出错
|
||||
|
||||
参数:
|
||||
file_path: requirements 文件路径
|
||||
"""
|
||||
try:
|
||||
content = file_path.read_text(encoding="utf-8")
|
||||
lines = content.splitlines()
|
||||
cleaned_lines = []
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
# 跳过空行
|
||||
if not stripped:
|
||||
continue
|
||||
# 如果是注释行且包含非ASCII字符,跳过
|
||||
if stripped.startswith("#"):
|
||||
try:
|
||||
stripped.encode("ascii")
|
||||
except UnicodeEncodeError:
|
||||
continue
|
||||
cleaned_lines.append(line)
|
||||
# 写回文件
|
||||
file_path.write_text(
|
||||
"\n".join(cleaned_lines) + "\n" if cleaned_lines else "",
|
||||
encoding="utf-8",
|
||||
)
|
||||
except Exception:
|
||||
# 如果清理失败,忽略错误继续安装
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
async def install_requirement(cls, requirement_file: Path):
|
||||
"""安装依赖文件
|
||||
@@ -137,6 +171,8 @@ class VirtualEnvPackageManager:
|
||||
"""
|
||||
if not requirement_file.exists():
|
||||
raise FileNotFoundError(f"依赖文件 {requirement_file} 不存在", LOG_COMMAND)
|
||||
# 清理 requirements 文件中的非ASCII注释,防止 Windows GBK 编码问题
|
||||
cls._clean_requirements_file(requirement_file)
|
||||
try:
|
||||
command = cls.__get_command()
|
||||
command.append("install")
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import base64
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
import random
|
||||
from typing import ClassVar
|
||||
|
||||
import nonebot
|
||||
from nonebot.adapters.onebot.v11 import Message, MessageSegment
|
||||
@@ -48,6 +50,43 @@ class Config(BaseModel):
|
||||
|
||||
|
||||
class MessageUtils:
|
||||
# 可爱风格的失败提示消息列表
|
||||
FAILURE_MESSAGES: ClassVar[list[str]] = [
|
||||
"出了点小问题,待会再试试吧~ (´・ω・`)",
|
||||
"哎呀,失败了呢 QAQ",
|
||||
"好像哪里不对劲... (・∀・;)",
|
||||
"emm...出错了 (;′⌒`)",
|
||||
"失败了,但问题不大!╮(╯▽╰)╭",
|
||||
"搞砸了...下次一定行 (๑•̀ㅂ•́)و✧",
|
||||
"这次没成功,再来一次?",
|
||||
"出错啦,让我缓缓... (´-ω-`)",
|
||||
"翻车了,容我想想 ( ˘•ω•˘ )",
|
||||
"不太顺利呢,稍后再试吧 (´;ω;`)",
|
||||
"失败了...不过没关系啦 (・ω<)☆",
|
||||
"呃,出了点状况 Σ(っ°Д°;)っ",
|
||||
"没搞定,但别灰心~",
|
||||
"坏掉了...等等再试试看 (>_<)",
|
||||
"这次不太行,下次加油!(ง •_•)ง",
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def get_failure_message(cls) -> str:
|
||||
"""获取随机失败提示消息
|
||||
|
||||
返回:
|
||||
str: 随机的可爱失败提示消息
|
||||
"""
|
||||
return random.choice(cls.FAILURE_MESSAGES)
|
||||
|
||||
@classmethod
|
||||
def build_failure_message(cls) -> UniMessage:
|
||||
"""构造随机失败提示消息
|
||||
|
||||
返回:
|
||||
UniMessage: 构造完成的失败提示消息
|
||||
"""
|
||||
return cls.build_message(cls.get_failure_message())
|
||||
|
||||
@classmethod
|
||||
def __build_message(
|
||||
cls, msg_list: list[MESSAGE_TYPE], format_args: dict | None = None
|
||||
|
||||
@@ -23,7 +23,7 @@ from .exceptions import (
|
||||
RepoManagerError,
|
||||
)
|
||||
from .models import FileDownloadResult, RepoFileInfo, RepoType
|
||||
from .utils import prepare_aliyun_url, sparse_checkout_clone
|
||||
from .utils import get_aliyun_group_for_repo, prepare_aliyun_url, sparse_checkout_clone
|
||||
|
||||
|
||||
class RepoFileManager:
|
||||
@@ -469,6 +469,35 @@ class RepoFileManager:
|
||||
if all(f.path != file.path for f in file_list if f != file)
|
||||
]
|
||||
|
||||
def _clean_requirements_content(self, content: str) -> str:
|
||||
"""
|
||||
清理 requirements.txt 内容,移除包含非ASCII字符的注释行
|
||||
|
||||
这是为了防止 Windows 上 pip 使用 GBK 编码读取 UTF-8 文件时出错
|
||||
|
||||
参数:
|
||||
content: requirements.txt 文件内容
|
||||
|
||||
返回:
|
||||
str: 清理后的内容
|
||||
"""
|
||||
lines = content.splitlines()
|
||||
cleaned_lines = []
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
# 跳过空行
|
||||
if not stripped:
|
||||
continue
|
||||
# 如果是注释行且包含非ASCII字符,跳过
|
||||
if stripped.startswith("#"):
|
||||
try:
|
||||
stripped.encode("ascii")
|
||||
except UnicodeEncodeError:
|
||||
# 包含非ASCII字符的注释行,跳过
|
||||
continue
|
||||
cleaned_lines.append(line)
|
||||
return "\n".join(cleaned_lines) + "\n" if cleaned_lines else ""
|
||||
|
||||
async def download_files(
|
||||
self,
|
||||
repo_url: str,
|
||||
@@ -576,6 +605,10 @@ class RepoFileManager:
|
||||
local_path = file_path_mapping[repo_file_path]
|
||||
local_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
if isinstance(content, str):
|
||||
# 对 requirements 文件特殊处理:移除包含非ASCII字符的注释行
|
||||
# 防止 Windows GBK 编码问题
|
||||
if repo_file_path.endswith(("requirements.txt", "requirement.txt")):
|
||||
content = self._clean_requirements_content(content)
|
||||
content_bytes = content.encode("utf-8")
|
||||
else:
|
||||
content_bytes = content
|
||||
@@ -604,8 +637,16 @@ class RepoFileManager:
|
||||
result: FileDownloadResult,
|
||||
) -> FileDownloadResult:
|
||||
try:
|
||||
# 获取仓库所属的分组名(外部插件仓库可能在不同分组下)
|
||||
repo_name = (
|
||||
repo_url.split("/tree/")[0].split("/")[-1].replace(".git", "").strip()
|
||||
)
|
||||
group_name = await get_aliyun_group_for_repo(repo_name)
|
||||
|
||||
aliyun_repo_url = prepare_aliyun_url(repo_url, group_name)
|
||||
|
||||
await sparse_checkout_clone(
|
||||
repo_url=prepare_aliyun_url(repo_url),
|
||||
repo_url=aliyun_repo_url,
|
||||
branch=branch,
|
||||
sparse_path=sparse_path,
|
||||
target_dir=target_dir,
|
||||
@@ -618,7 +659,7 @@ class RepoFileManager:
|
||||
total_size += f.stat().st_size
|
||||
result.success = True
|
||||
result.file_size = total_size
|
||||
logger.info(f"sparse-checkout 克隆成功: {target_dir}")
|
||||
logger.info(f"sparse-checkout 克隆成功: {target_dir}:{aliyun_repo_url}")
|
||||
return result
|
||||
except GitUnavailableError as e:
|
||||
logger.error(f"Git不可用: {e}")
|
||||
|
||||
@@ -227,11 +227,12 @@ async def sparse_checkout_clone(
|
||||
shutil.move(str(source_path), str(target_path))
|
||||
|
||||
|
||||
def prepare_aliyun_url(repo_url: str) -> str:
|
||||
def prepare_aliyun_url(repo_url: str, group_name: str | None = None) -> str:
|
||||
"""解析阿里云CodeUp的仓库URL
|
||||
|
||||
参数:
|
||||
repo_url: 仓库URL
|
||||
group_name: 分组名称,如果为None则使用默认组织名称
|
||||
|
||||
返回:
|
||||
str: 解析后的仓库URL
|
||||
@@ -239,10 +240,12 @@ def prepare_aliyun_url(repo_url: str) -> str:
|
||||
config = RepoConfig.get_instance()
|
||||
|
||||
repo_name = repo_url.split("/tree/")[0].split("/")[-1].replace(".git", "")
|
||||
# 使用指定的分组名或默认组织名称
|
||||
group = group_name or config.aliyun_codeup.organization_name
|
||||
# 构建仓库URL
|
||||
# 阿里云CodeUp的仓库URL格式通常为:
|
||||
# https://codeup.aliyun.com/{organization_id}/{organization_name}/{repo_name}.git
|
||||
url = f"https://codeup.aliyun.com/{config.aliyun_codeup.organization_id}/{config.aliyun_codeup.organization_name}/{repo_name}.git"
|
||||
# https://codeup.aliyun.com/{organization_id}/{group_name}/{repo_name}.git
|
||||
url = f"https://codeup.aliyun.com/{config.aliyun_codeup.organization_id}/{group}/{repo_name}.git"
|
||||
|
||||
# 添加访问令牌 - 使用base64解码后的令牌
|
||||
if config.aliyun_codeup.rdc_access_token_encrypted:
|
||||
@@ -258,3 +261,35 @@ def prepare_aliyun_url(repo_url: str) -> str:
|
||||
logger.error(f"解码RDC令牌失败: {e}")
|
||||
|
||||
return url
|
||||
|
||||
|
||||
async def get_aliyun_group_for_repo(repo_name: str) -> str | None:
|
||||
"""获取仓库所属的阿里云分组名
|
||||
|
||||
参数:
|
||||
repo_name: 仓库名称
|
||||
|
||||
返回:
|
||||
str | None: 分组名称,如果在核心映射中则返回None(使用默认组织名)
|
||||
"""
|
||||
from zhenxun.utils.github_utils.const import (
|
||||
ALIYUN_EXTERNAL_PLUGIN_GROUPS,
|
||||
ALIYUN_REPO_MAPPING,
|
||||
)
|
||||
from zhenxun.utils.github_utils.models import AliyunFileInfo
|
||||
|
||||
# 如果在核心映射中,使用默认组织名
|
||||
if repo_name in ALIYUN_REPO_MAPPING:
|
||||
return None
|
||||
|
||||
# 尝试从外部插件分组中查找
|
||||
for group_path in ALIYUN_EXTERNAL_PLUGIN_GROUPS:
|
||||
try:
|
||||
repos = await AliyunFileInfo.list_group_repositories(group_path)
|
||||
for repo in repos:
|
||||
if repo.get("name") == repo_name:
|
||||
return group_path
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user