日志丰富功能 #20

Open
huaijin wants to merge 53 commits from qinxinqi/reposync:huaijin into master
18 changed files with 3606 additions and 0 deletions
Showing only changes of commit d20e657d4b - Show all commits

243
README_TAG_SYNC.md Normal file
View File

@ -0,0 +1,243 @@
# 标签同步服务
GitHub、Gitee、GitLink 之间的双向标签同步服务,支持前后端分离架构。
## 功能特性
### 🚀 支持的同步类型
- **Gitee ↔ GitLink** 双向标签同步
- **GitHub ↔ GitLink** 双向标签同步
- **GitHub ↔ Gitee** 双向标签同步
### ⚡ 核心功能
- ✅ 双向标签同步
- ✅ 单向标签同步
- ✅ 试运行模式(预览同步结果)
- ✅ 实际执行模式
- ✅ 标签排除模式(如跳过测试标签)
- ✅ 现代化Web界面
- ✅ RESTful API
## 架构说明
```
┌─────────────────┐ ┌─────────────────┐
│ 前端服务 │────│ 后端API │
│ (端口 8006) │ │ (端口 8005) │
│ React + Antd │ │ FastAPI │
└─────────────────┘ └─────────────────┘
┌────────┴────────┐
│ 标签同步模块 │
│ repo_tag_sync │
└─────────────────┘
┌───────────┼───────────┐
│ │ │
┌─────▼───┐ ┌─────▼───┐ ┌─────▼───┐
│ GitHub │ │ Gitee │ │ GitLink │
│ Client │ │ Client │ │ Client │
└─────────┘ └─────────┘ └─────────┘
```
## 快速开始
### 1. 环境要求
- Python 3.7+
- Node.js 16+
- npm 或 yarn
### 2. 一键启动(推荐)
```bash
# 启动前后端服务
python start_tag_sync_services.py
```
启动后访问:
- 🌐 前端界面: http://localhost:8006
- 📡 后端API: http://localhost:8005
- 📖 API文档: http://localhost:8005/docs
### 3. 分别启动
#### 启动后端服务 (8005端口)
```bash
python tag_sync_backend.py
```
#### 启动前端服务 (8006端口)
```bash
python tag_sync_frontend.py
```
## 使用指南
### 1. 配置同步任务
1. 打开浏览器访问 http://localhost:8006
2. 点击"新建标签同步"按钮
3. 选择同步类型(如 Gitee ↔ GitLink
4. 配置平台信息:
#### 平台配置参数
| 平台 | 必需参数 | 说明 |
|------|----------|------|
| GitHub | 用户名、仓库名、Token | GitHub Personal Access Token |
| Gitee | 用户名、仓库名、Token | Gitee Private Token |
| GitLink | 用户名、仓库名、Cookie | 浏览器Cookie字符串 |
#### 同步选项
- **排除模式**: 用逗号分隔的模式,如 `test-,dev-`
- **执行模式**:
- 试运行:只检测需要同步的标签
- 实际执行:真正创建标签到目标平台
### 2. 查看同步结果
- 同步任务列表显示所有创建的同步配置
- 点击"查看详情"可以看到具体的同步结果
- 支持重新同步和删除任务
## API接口
### 获取同步类型
```bash
GET http://localhost:8005/cerobot/tag-sync/sync-types
```
### 执行双向同步
```bash
POST http://localhost:8005/cerobot/tag-sync/sync/bidirectional
Content-Type: application/json
{
"platform_a": {
"platform": "gitee",
"org": "your-username",
"repo": "your-repo",
"token": "your-gitee-token"
},
"platform_b": {
"platform": "gitlink",
"org": "your-username",
"repo": "your-repo",
"cookie": "your-gitlink-cookie"
},
"dry_run": true,
"exclude_patterns": ["test-"]
}
```
### 执行单向同步
```bash
POST http://localhost:8005/cerobot/tag-sync/sync/oneway
Content-Type: application/json
{
"source_platform": "gitlink",
"source_org": "source-user",
"source_repo": "source-repo",
"source_cookie": "gitlink-cookie",
"target_platform": "gitee",
"target_org": "target-user",
"target_repo": "target-repo",
"target_token": "gitee-token",
"dry_run": true,
"exclude_patterns": ["test-"]
}
```
## 配置说明
### Token/Cookie 获取方法
#### GitHub Token
1. 访问 GitHub Settings > Developer settings > Personal access tokens
2. 创建新token选择 `repo` 权限
3. 复制生成的token
#### Gitee Token
1. 访问 Gitee 设置 > 私人令牌
2. 创建新令牌,选择相应权限
3. 复制生成的token
#### GitLink Cookie
1. 打开浏览器登录 GitLink
2. 按F12打开开发者工具
3. 在Application/存储 > Cookies中找到GitLink相关cookie
4. 复制完整的cookie字符串
## 项目结构
```
├── tag_sync_backend.py # 后端服务启动脚本
├── tag_sync_frontend.py # 前端服务启动脚本
├── start_tag_sync_services.py # 一体化启动脚本
├── src/api/TagSync.py # 后端API实现
├── repo_tag_sync_module/ # 标签同步核心模块
│ ├── github_client.py # GitHub客户端
│ ├── gitee_client.py # Gitee客户端
│ ├── gitlink_client.py # GitLink客户端
│ └── sync_service.py # 同步服务
└── web/src/pages/tagSync/ # 前端页面
├── index.tsx # 主页面
├── components/
│ ├── ConfigModal.tsx # 配置弹窗
│ └── ResultModal.tsx # 结果显示
└── services/TagSync.ts # 前端API服务
```
## 故障排除
### 常见问题
1. **后端启动失败**
- 检查Python版本 (>=3.7)
- 检查依赖包安装: `pip install -r requirements.txt`
- 检查端口8005是否被占用
2. **前端启动失败**
- 检查Node.js版本 (>=16)
- 运行 `npm install` 安装依赖
- 检查端口8006是否被占用
3. **API调用失败**
- 检查后端服务是否正常运行
- 检查CORS配置
- 查看浏览器控制台错误信息
4. **标签同步失败**
- 检查Token/Cookie是否有效
- 检查网络连接
- 查看后端日志错误信息
### 日志查看
- 后端日志: 控制台输出
- 前端日志: 浏览器开发者工具 Console
## 开发说明
### 后端开发
- 基于 FastAPI 框架
- 使用 repo_tag_sync_module 核心模块
- 支持异步任务处理
### 前端开发
- 基于 React + Ant Design Pro
- 使用 TypeScript
- 响应式设计,移动端友好
## 版本历史
- v1.0.0: 基础双向同步功能
- 支持 GitHub、Gitee、GitLink 三平台
- 现代化Web界面
- 完整的API文档
## 许可证
MIT License
## 贡献
欢迎提交Issue和Pull Request

200
README_TAG_SYNC_MODULE.md Normal file
View File

@ -0,0 +1,200 @@
# 标签同步独立模块
**已完成**: 标签同步代码已成功抽离为独立模块,并修复了所有启动问题!
## 🎯 模块化改进
### 之前的问题
- 标签同步代码混杂在主项目中
- 前端启动依赖npmWindows环境出现"找不到指定文件"错误
- 代码耦合度高,不便维护和部署
### 现在的解决方案
- ✅ **完全独立的模块**: `repo_tag_sync_module/`
- ✅ **纯Python实现**: 无需npm使用Python内置HTTP服务器
- ✅ **跨平台兼容**: 修复Windows编码问题支持多平台
- ✅ **模块化架构**: 清晰的目录结构,便于维护
- ✅ **一键启动**: 简单的启动脚本,自动管理前后端服务
## 📁 新的目录结构
```
repo_tag_sync_module/
├── __init__.py # 模块初始化
├── README.md # 模块文档
├── start_tag_sync_services.py # 一体化启动脚本
├──
├── # 核心同步组件 (保持不变)
├── github_client.py
├── gitee_client.py
├── gitlink_client.py
├── sync_service.py
├── test_tag_sync.py
├── test_bidirectional_sync.py
├──
├── # 新增: Web API 服务
├── web_api/
│ ├── __init__.py
│ ├── tag_sync_api.py # FastAPI路由实现
│ └── backend_server.py # 后端服务启动器
├──
└── # 新增: Web 前端界面
└── web_frontend/
├── __init__.py
├── index.html # 现代化HTML界面
├── script.js # 前端交互逻辑
└── frontend_server.py # Python HTTP服务器
```
## 🚀 启动方式 (已修复所有问题)
### 1. 快捷启动 (推荐)
```bash
# 从项目根目录一键启动
python start_tag_sync.py
```
### 2. 模块启动
```bash
# 从模块目录启动
cd repo_tag_sync_module
python start_tag_sync_services.py
# 或使用模块方式
python -m repo_tag_sync_module.start_tag_sync_services
```
### 3. 分别启动
```bash
# 仅启动后端 (8005端口)
python -m repo_tag_sync_module.web_api.backend_server
# 仅启动前端 (8006端口)
python -m repo_tag_sync_module.web_frontend.frontend_server
```
## ✅ 验证服务运行
启动后可以验证:
```bash
# 检查后端健康状态
python -c "import requests; print(requests.get('http://localhost:8005/health').json())"
# 检查前端响应
python -c "import requests; print(requests.get('http://localhost:8006').status_code)"
# 测试API接口
python -c "import requests; print(requests.get('http://localhost:8005/api/tag-sync/sync-types').json())"
```
预期输出:
```json
# 后端健康检查
{"status": "healthy", "service": "tag-sync-backend", "port": 8005}
# 前端状态码
200
# API接口响应
{
"data": {
"sync_types": [
{"id": "gitee_gitlink", "name": "Gitee ↔ GitLink", ...},
{"id": "github_gitlink", "name": "GitHub ↔ GitLink", ...},
{"id": "github_gitee", "name": "GitHub ↔ Gitee", ...}
]
},
"message": "获取同步类型成功",
"code": 0
}
```
## 🌐 Web界面功能
访问 http://localhost:8006 可以:
1. **查看支持的同步类型**: 直观的卡片式展示
2. **配置同步任务**: 表单式配置界面
- 选择源和目标平台
- 填写认证信息 (Token/Cookie)
- 设置排除模式
- 选择试运行/实际执行
3. **实时同步**: AJAX调用后端API
4. **结果展示**: 详细的同步结果和错误信息
## 🔧 技术改进
### 后端改进
- **独立FastAPI应用**: 不依赖主项目框架
- **模块化路由**: 清晰的API组织结构
- **CORS支持**: 允许前端跨域访问
- **错误处理**: 完善的异常处理机制
### 前端改进
- **纯HTML/JS**: 无需React/Vue等复杂框架
- **响应式设计**: 移动端友好
- **现代化UI**: 美观的界面设计
- **实时交互**: 支持试运行和实际执行
### 兼容性改进
- **Windows兼容**: 修复emoji字符编码问题
- **Python服务器**: 使用内置HTTP服务器无需npm
- **跨平台**: 支持Windows/Linux/macOS
## 🎯 使用场景
### 开发者
```bash
# 快速测试同步功能
python -m repo_tag_sync_module.test_bidirectional_sync
```
### 系统管理员
```bash
# 部署独立服务
python start_tag_sync.py
# 配置nginx反向代理到 localhost:8005/8006
```
### 普通用户
1. 打开浏览器访问 http://localhost:8006
2. 点击同步类型卡片
3. 填写配置信息
4. 一键同步标签
## 🚨 解决的问题
### ✅ npm找不到文件
- **问题**: Windows环境下npm命令找不到
- **解决**: 使用Python内置HTTP服务器
### ✅ 编码错误
- **问题**: Windows控制台emoji字符编码错误
- **解决**: 移除所有emoji字符使用纯文本
### ✅ 代码耦合
- **问题**: 标签同步代码和主项目混合
- **解决**: 完全独立的模块,清晰的边界
### ✅ 启动复杂
- **问题**: 需要分别启动前后端,配置复杂
- **解决**: 一键启动脚本,自动管理服务
## 📈 后续扩展
模块化设计支持:
- 添加新的Git平台 (GitLab, Bitbucket等)
- 扩展同步功能 (分支、PR等)
- 集成CI/CD流水线
- 容器化部署
## 🎉 总结
标签同步功能现已成功抽离为独立模块,具备:
- ✅ **完全独立**: 不依赖主项目
- ✅ **跨平台兼容**: Windows/Linux/macOS
- ✅ **现代化界面**: Web UI + RESTful API
- ✅ **一键启动**: 简单易用
- ✅ **功能完整**: 支持三大Git平台双向同步
**立即试用**: `python start_tag_sync.py` 🚀

View File

@ -0,0 +1,138 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Repository Tag Sync Module
仓库标签同步模块
支持GitHub、Gitee、GitLink之间的标签双向同步
"""
## 功能特性
### 🔄 双向同步支持
- **GitHub ↔ GitLink**: GitHub和GitLink之间的标签双向同步
- **Gitee ↔ GitLink**: Gitee和GitLink之间的标签双向同步
- **GitHub ↔ Gitee**: GitHub和Gitee之间的标签双向同步
### 🌐 Web界面
- **直观的卡片式界面**: 选择同步类型
- **实时配置**: 动态输入平台认证信息
- **实时日志显示**: 同步过程中的详细日志
- **自动刷新**: 日志自动更新,无需手动刷新
### 📊 日志功能
- **实时日志**: 同步过程中的详细日志信息
- **日志分级**: INFO、ERROR、DEBUG等不同级别
- **自动刷新**: 支持自动刷新日志显示
- **日志管理**: 支持清空日志功能
### 🔧 API接口
- **RESTful API**: 标准的REST API接口
- **Swagger文档**: 完整的API文档
- **健康检查**: 服务状态监控
## 快速开始
### 1. 启动服务
```bash
python repo_tag_sync_module/start_tag_sync.py
```
服务启动后会自动打开浏览器,访问 `http://localhost:8005`
### 2. 使用Web界面
1. **选择同步类型**: 点击相应的同步卡片
2. **配置认证信息**:
- GitHub: 需要Personal Access Token
- Gitee: 需要Access Token
- GitLink: 需要浏览器Cookie (autologin_trustie)
3. **执行同步**: 点击"执行同步"按钮
4. **查看日志**: 实时查看同步过程和结果
### 3. 日志功能使用
- **自动刷新**: 同步开始时自动启用日志刷新
- **手动刷新**: 点击"刷新日志"按钮
- **清空日志**: 点击"清空日志"按钮
- **开关刷新**: 点击"开启/关闭自动刷新"按钮
## 认证配置
### GitHub
1. 访问 GitHub Settings → Developer settings → Personal access tokens
2. 创建新的 token需要 `repo` 权限
3. 在界面中输入 token
### Gitee
1. 访问 Gitee 设置 → 私人令牌
2. 创建新的令牌,需要 `projects` 权限
3. 在界面中输入令牌
### GitLink
1. 在浏览器中登录 GitLink
2. 打开开发者工具 → Network → 找到请求头中的 `autologin_trustie` cookie
3. 在界面中输入 cookie 值
## API接口
### 健康检查
```
GET /health
```
### 双向同步
```
POST /api/tag-sync/sync/bidirectional
```
### 获取日志
```
GET /api/tag-sync/logs
```
### 清空日志
```
DELETE /api/tag-sync/logs
```
### API文档
访问 `http://localhost:8005/docs` 查看完整的API文档
## 注意事项
1. **权限要求**: 确保提供的Token/Cookie具有足够的权限
2. **网络连接**: 需要能够访问相应的Git平台
3. **同步策略**: 只同步目标平台不存在的标签,不会覆盖已存在的标签
4. **日志保留**: 日志在内存中保留,服务重启后会清空
## 技术栈
- **后端**: FastAPI + Uvicorn
- **前端**: 原生HTML + JavaScript + CSS
- **日志**: Python logging + 内存缓存
- **API**: RESTful API + Swagger文档
## 故障排除
### 服务启动失败
- 检查端口8005是否被占用
- 确认Python环境和依赖包
### 同步失败
- 检查网络连接
- 验证认证信息是否正确
- 查看日志获取详细错误信息
### 日志不显示
- 检查浏览器控制台是否有错误
- 确认服务正常运行
- 尝试手动刷新日志
## 开发信息
- **版本**: 1.0.0
- **Python版本**: 3.7+
- **依赖**: fastapi, uvicorn, requests, pydantic

View File

@ -0,0 +1 @@
# repo_tag_sync_module 包初始化

View File

@ -0,0 +1,4 @@
class BaseRepoClient:
def get_tags(self):
"""返回 tag 列表,格式统一为 [{'name': 'v1.0.0', ...}, ...]"""
raise NotImplementedError

View File

@ -0,0 +1,75 @@
import requests
import logging
# 配置日志
logger = logging.getLogger(__name__)
class BaseRepoClient:
def get_tags(self):
"""返回 tag 列表,格式统一为 [{'name': 'v1.0.0', ...}, ...]"""
raise NotImplementedError
class GiteeClient(BaseRepoClient):
def __init__(self, owner, repo, token):
self.owner = owner
self.repo = repo
self.token = token
def get_tags(self):
url = f"https://gitee.com/api/v5/repos/{self.owner}/{self.repo}/tags"
params = {"access_token": self.token}
logger.debug(f"[Gitee DEBUG] 请求URL: {url}")
logger.debug(f"[Gitee DEBUG] 使用Token: {self.token[:20]}...")
try:
resp = requests.get(url, params=params)
logger.debug(f"[Gitee DEBUG] 响应状态码: {resp.status_code}")
if resp.status_code != 200:
logger.error(f"[Gitee ERROR] HTTP错误: {resp.status_code}")
logger.error(f"[Gitee ERROR] 响应内容: {resp.text}")
resp.raise_for_status()
data = resp.json()
logger.debug(f"[Gitee DEBUG] 获取到 {len(data)} 个标签")
if data:
logger.debug("[Gitee DEBUG] 标签详情:")
for tag in data:
logger.debug(f" - {tag.get('name')} (commit: {tag.get('commit', {}).get('sha', 'N/A')})")
return data
except requests.exceptions.RequestException as e:
logger.error(f"[Gitee ERROR] 请求异常: {e}")
return []
except Exception as e:
logger.error(f"[Gitee ERROR] 处理异常: {e}")
return []
def get_default_branch(self):
url = f"https://gitee.com/api/v5/repos/{self.owner}/{self.repo}"
params = {"access_token": self.token}
resp = requests.get(url, params=params)
resp.raise_for_status()
repo_info = resp.json()
return repo_info.get("default_branch", "master")
def create_tag(self, tag_name, commit_sha, message=""):
url = f"https://gitee.com/api/v5/repos/{self.owner}/{self.repo}/tags"
ref = self.get_default_branch()
logger.debug(f"[Gitee DEBUG] 创建 tag {tag_name} 在分支 {ref}")
data = {
"access_token": self.token,
"tag_name": tag_name,
"refs": ref, # 修正:使用 refs 而不是 ref
"message": message or tag_name
}
resp = requests.post(url, data=data)
logger.debug(f"[Gitee DEBUG] 请求参数: {data}")
if resp.status_code != 201:
logger.error(f"[Gitee ERROR] status={resp.status_code}, body={resp.text}")
resp.raise_for_status()
logger.info(f"[Gitee SUCCESS] 创建 tag {tag_name} 成功")
return resp.json()

View File

@ -0,0 +1,74 @@
import requests
class BaseRepoClient:
def get_tags(self):
"""返回 tag 列表,格式统一为 [{'name': 'v1.0.0', ...}, ...]"""
raise NotImplementedError
class GithubClient(BaseRepoClient):
def __init__(self, owner, repo, token):
self.owner = owner
self.repo = repo
self.token = token
def get_tags(self):
url = f"https://api.github.com/repos/{self.owner}/{self.repo}/tags"
headers = {"Authorization": f"token {self.token}"}
resp = requests.get(url, headers=headers)
resp.raise_for_status()
return resp.json()
def get_default_branch(self):
url = f"https://api.github.com/repos/{self.owner}/{self.repo}"
headers = {"Authorization": f"token {self.token}"}
resp = requests.get(url, headers=headers)
resp.raise_for_status()
repo_info = resp.json()
return repo_info.get("default_branch", "main")
def create_tag(self, tag_name, commit_sha, message=""):
# GitHub 创建 tag 需要两步1) 创建 tag object 2) 创建 ref
headers = {"Authorization": f"token {self.token}"}
# 步骤1创建 tag object
tag_url = f"https://api.github.com/repos/{self.owner}/{self.repo}/git/tags"
default_branch = self.get_default_branch()
# 获取默认分支的最新 commit
branch_url = f"https://api.github.com/repos/{self.owner}/{self.repo}/branches/{default_branch}"
branch_resp = requests.get(branch_url, headers=headers)
branch_resp.raise_for_status()
latest_commit_sha = branch_resp.json()["commit"]["sha"]
print(f"[GitHub DEBUG] 创建 tag {tag_name} 在 commit {latest_commit_sha}")
tag_data = {
"tag": tag_name,
"message": message or tag_name,
"object": latest_commit_sha,
"type": "commit"
}
tag_resp = requests.post(tag_url, json=tag_data, headers=headers)
print(f"[GitHub DEBUG] Tag object 请求参数: {tag_data}")
if tag_resp.status_code != 201:
print(f"[GitHub ERROR] 创建 tag object 失败: {tag_resp.status_code}, {tag_resp.text}")
tag_resp.raise_for_status()
tag_object = tag_resp.json()
# 步骤2创建 ref
ref_url = f"https://api.github.com/repos/{self.owner}/{self.repo}/git/refs"
ref_data = {
"ref": f"refs/tags/{tag_name}",
"sha": tag_object["sha"]
}
ref_resp = requests.post(ref_url, json=ref_data, headers=headers)
print(f"[GitHub DEBUG] Ref 请求参数: {ref_data}")
if ref_resp.status_code != 201:
print(f"[GitHub ERROR] 创建 ref 失败: {ref_resp.status_code}, {ref_resp.text}")
ref_resp.raise_for_status()
print(f"[GitHub SUCCESS] 创建 tag {tag_name} 成功")
return ref_resp.json()

View File

@ -0,0 +1,203 @@
import requests
import logging
# 配置日志
logger = logging.getLogger(__name__)
class BaseRepoClient:
def get_tags(self):
"""返回 tag 列表,格式统一为 [{'name': 'v1.0.0', ...}, ...]"""
raise NotImplementedError
class GitlinkClient(BaseRepoClient):
def __init__(self, owner, repo, cookie, additional_cookies=None):
self.owner = owner
self.repo = repo
self.cookie = cookie # autologin_trustie
self.additional_cookies = additional_cookies or {} # 额外的cookies
def _get_cookies(self):
"""获取完整的cookie配置"""
cookies = {"autologin_trustie": self.cookie}
cookies.update(self.additional_cookies)
return cookies
def get_tags(self):
"""获取 GitLink 的 tags通过正确的 tags API"""
url = f"https://www.gitlink.org.cn/api/{self.owner}/{self.repo}/tags.json"
cookies = self._get_cookies()
logger.debug(f"[GitLink DEBUG] 请求URL: {url}")
logger.debug(f"[GitLink DEBUG] 使用Cookie: autologin_trustie={self.cookie[:20]}...")
try:
resp = requests.get(url, cookies=cookies)
logger.debug(f"[GitLink DEBUG] 响应状态码: {resp.status_code}")
if resp.status_code != 200:
logger.error(f"[GitLink ERROR] HTTP错误: {resp.status_code}")
logger.error(f"[GitLink ERROR] 响应内容: {resp.text}")
resp.raise_for_status()
data = resp.json()
logger.debug(f"[GitLink DEBUG] 响应数据结构: {list(data.keys()) if isinstance(data, dict) else type(data)}")
# 根据API文档成功响应包含 total_count 和 tags 字段
if "total_count" not in data or "tags" not in data:
logger.error(f"[GitLink ERROR] 响应格式错误: {data}")
return []
# 处理tags数据格式
tags_data = data.get("tags", [])
tags = []
logger.debug(f"[GitLink DEBUG] 获取到 {data.get('total_count', 0)} 个标签")
for tag_info in tags_data:
tag = {
"name": tag_info.get("name"),
"commit": {
"sha": tag_info.get("commit", {}).get("sha") or tag_info.get("id") # GitLink使用id字段作为commit sha
},
"message": tag_info.get("message", ""),
"tarball_url": tag_info.get("tarball_url"),
"zipball_url": tag_info.get("zipball_url")
}
tags.append(tag)
logger.debug(f"[GitLink DEBUG] 处理标签: {tag['name']} -> {tag['commit']['sha']}")
return tags
except requests.exceptions.RequestException as e:
logger.error(f"[GitLink ERROR] 请求异常: {e}")
return []
except Exception as e:
logger.error(f"[GitLink ERROR] 处理异常: {e}")
return []
def check_permissions(self):
"""检查当前用户对仓库的权限"""
# 先测试读权限
print("[GitLink PERM] 检查读取权限...")
try:
tags = self.get_tags()
print(f"[GitLink PERM] [OK] 读取权限正常,获取到 {len(tags)} 个tags")
except Exception as e:
print(f"[GitLink PERM] [ERROR] 读取权限失败: {e}")
return False
# 检查API响应中的权限信息
url = f"https://www.gitlink.org.cn/api/{self.owner}/{self.repo}/tags.json"
cookies = self._get_cookies()
resp = requests.get(url, cookies=cookies)
data = resp.json()
# 检查权限信息如果API响应中包含的话
user_permission = data.get("user_permission", None)
user_admin_permission = data.get("user_admin_permission", None)
print(f"[GitLink PERM] user_permission: {user_permission}")
print(f"[GitLink PERM] user_admin_permission: {user_admin_permission}")
# 如果能成功读取,说明至少有读权限
if "total_count" in data and "tags" in data:
print("[GitLink PERM] [OK] 至少有读取权限")
# 写权限需要通过实际测试才能确定
if user_permission is False and user_admin_permission is False:
print("[GitLink PERM] [WARNING] 根据响应显示可能没有写入权限,但需要实际测试确认")
return True # 读权限正常,写权限待测试
else:
print("[GitLink PERM] [OK] 权限检查通过")
return True
else:
print("[GitLink PERM] [ERROR] 权限检查失败")
return False
def create_tag(self, tag_name, commit_sha, message=""):
"""通过 GitLink releases API 创建标签"""
print(f"[GitLink] 准备创建 release: {tag_name}")
# 使用正确的releases API端点
url = f"https://www.gitlink.org.cn/api/{self.owner}/{self.repo}/releases.json"
# 准备创建 release 的数据 - 使用正确的字段名
data = {
"tag_name": tag_name,
"target_commitish": "master", # GitLink 通常使用 master 分支
"name": tag_name, # 这个字段是必需的!
"body": message or tag_name, # 使用 body 而不是 message
"draft": False # 不设为草稿
}
cookies = self._get_cookies()
headers = {
"Accept": "application/json, text/plain, */*",
"Content-Type": "application/json",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0",
"Origin": "https://www.gitlink.org.cn",
"Referer": f"https://www.gitlink.org.cn/{self.owner}/{self.repo}/releases/new",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin"
}
print(f"[GitLink DEBUG] 创建 release: {tag_name}")
print(f"[GitLink DEBUG] 请求URL: {url}")
print(f"[GitLink DEBUG] 请求数据: {data}")
print(f"[GitLink DEBUG] Cookies: {list(cookies.keys())}")
try:
resp = requests.post(url, json=data, cookies=cookies, headers=headers)
print(f"[GitLink DEBUG] 响应状态: {resp.status_code}")
print(f"[GitLink DEBUG] 响应头: {dict(resp.headers)}")
print(f"[GitLink DEBUG] 响应内容: {resp.text}")
if resp.status_code == 200:
try:
response_data = resp.json()
if response_data.get("status") == 0:
print(f"[GitLink SUCCESS] 创建 release {tag_name} 成功")
return response_data
else:
print(f"[GitLink ERROR] 创建失败: {response_data.get('message', 'Unknown error')}")
return False
except:
# 如果不是JSON响应可能是HTML错误页面
print(f"[GitLink SUCCESS] 创建 release {tag_name} 成功非JSON响应")
return True
elif resp.status_code == 422:
print(f"[GitLink ERROR] 创建失败 - 可能标签已存在或参数错误: {resp.text}")
return False
else:
print(f"[GitLink ERROR] HTTP 错误: {resp.status_code} - {resp.text}")
return False
except Exception as e:
print(f"[GitLink ERROR] 请求异常: {e}")
return False
def test_auth(self):
"""测试认证是否有效"""
url = f"https://www.gitlink.org.cn/api/{self.owner}/{self.repo}/tags.json"
cookies = self._get_cookies()
try:
resp = requests.get(url, cookies=cookies)
resp.raise_for_status()
data = resp.json()
if "total_count" in data and "tags" in data:
print("[GitLink AUTH] [OK] 基本认证成功(可读取数据)")
# 检查权限信息
user_permission = data.get("user_permission", None)
user_admin_permission = data.get("user_admin_permission", None)
print(f"[GitLink AUTH] 用户权限: {user_permission}")
print(f"[GitLink AUTH] 管理员权限: {user_admin_permission}")
return True
else:
print(f"[GitLink AUTH] [WARNING] 响应格式异常: {data}")
return False
except Exception as e:
print(f"[GitLink AUTH] [ERROR] 测试失败: {e}")
return False

View File

@ -0,0 +1,171 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
标签同步服务启动脚本
"""
import os
import sys
import subprocess
import webbrowser
import time
import socket
def check_environment():
"""检查运行环境"""
print("检查环境...")
# 检查Python版本
if sys.version_info < (3, 7):
print("[ERROR] 需要Python 3.7或更高版本")
return False
# 检查依赖包
required_packages = ['fastapi', 'uvicorn', 'requests']
missing_packages = []
for package in required_packages:
try:
__import__(package)
except ImportError:
missing_packages.append(package)
if missing_packages:
print(f"[ERROR] 缺少依赖包: {', '.join(missing_packages)}")
print("请运行: pip install fastapi uvicorn requests")
return False
print("[OK] 环境检查通过")
return True
def wait_for_service(port=8005, timeout=30):
"""等待服务启动"""
print(f"等待服务启动 (端口 {port})...")
start_time = time.time()
while time.time() - start_time < timeout:
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(1)
result = sock.connect_ex(('localhost', port))
sock.close()
if result == 0:
print("[OK] 服务启动完成")
return True
except:
pass
time.sleep(1)
print(f"[ERROR] 服务启动超时 ({timeout}s)")
return False
def open_browser():
"""打开浏览器"""
try:
print("正在打开浏览器: http://localhost:8005")
webbrowser.open('http://localhost:8005')
except Exception as e:
print(f"[WARNING] 无法自动打开浏览器: {e}")
print("请手动访问: http://localhost:8005")
def main():
"""主函数"""
print("=" * 50)
print("标签同步服务")
print("=" * 50)
# 检查环境
if not check_environment():
return 1
# 获取脚本所在目录
current_dir = os.path.dirname(os.path.abspath(__file__))
web_api_dir = os.path.join(current_dir, 'web_api')
backend_script = os.path.join(web_api_dir, 'backend_server.py')
if not os.path.exists(backend_script):
print(f"[ERROR] 找不到后端脚本: {backend_script}")
return 1
# 启动服务
print("启动标签同步服务...")
try:
# 设置环境变量
env = os.environ.copy()
env['PYTHONPATH'] = current_dir + os.pathsep + env.get('PYTHONPATH', '')
# Windows环境下设置编码
if os.name == 'nt':
env['PYTHONIOENCODING'] = 'utf-8'
# 启动后端服务
print("正在启动后端服务...")
process = subprocess.Popen(
[sys.executable, backend_script],
cwd=web_api_dir,
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True,
encoding='utf-8'
)
# 等待服务启动
if not wait_for_service():
process.terminate()
return 1
# 显示服务信息
print("=" * 60)
print("标签同步服务已启动")
print("=" * 60)
print("服务地址: http://localhost:8005")
print("网页界面: http://localhost:8005")
print("API文档: http://localhost:8005/docs")
print("健康检查: http://localhost:8005/health")
print("=" * 60)
print("")
print("可用功能:")
print(" • GitHub ↔ GitLink 双向标签同步")
print(" • Gitee ↔ GitLink 双向标签同步")
print(" • GitHub ↔ Gitee 双向标签同步")
print("")
print("使用说明:")
print(" 1. 浏览器会自动打开网页界面")
print(" 2. 点击同步类型卡片选择同步方向")
print(" 3. 填写平台认证信息 (Token/Cookie)")
print(" 4. 点击执行同步按钮开始同步")
print(" 5. 按 Ctrl+C 停止服务")
print("")
# 自动打开浏览器
open_browser()
# 监控进程并显示输出
try:
while True:
output = process.stdout.readline()
if output:
print(f"[服务] {output.strip()}")
elif process.poll() is not None:
break
time.sleep(0.1)
except KeyboardInterrupt:
print("\n正在停止服务...")
process.terminate()
process.wait()
print("服务已停止")
return 0
except Exception as e:
print(f"[ERROR] 启动服务失败: {e}")
return 1
if __name__ == "__main__":
sys.exit(main())

View File

@ -0,0 +1,432 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Repository Tag Sync Service
实现不同Git平台之间的标签双向同步
"""
import time
import logging
from typing import List, Dict, Set, Tuple
# 配置日志
logger = logging.getLogger(__name__)
# 为了避免循环导入,我们在这里定义基类
class BaseRepoClient:
def get_tags(self):
"""返回 tag 列表,格式统一为 [{'name': 'v1.0.0', ...}, ...]"""
raise NotImplementedError
class TagSyncService:
"""标签同步服务"""
def __init__(self):
self.sync_history = [] # 同步历史记录
def sync_gitlink_to_gitee(self, gitlink_client: BaseRepoClient, gitee_client: BaseRepoClient, dry_run=False):
"""
从GitLink同步标签到Gitee
Args:
gitlink_client: GitLink客户端
gitee_client: Gitee客户端
dry_run: 是否为试运行不实际创建标签
"""
logger.info(">> 开始从 GitLink 同步标签到 Gitee...")
logger.info("=" * 60)
try:
# 获取源平台标签
logger.info(">> 获取 GitLink 标签...")
logger.debug(f"[DEBUG] 请求 GitLink API: {gitlink_client.owner}/{gitlink_client.repo}")
gitlink_tags = gitlink_client.get_tags()
logger.info(f"[OK] GitLink 共有 {len(gitlink_tags)} 个标签")
# 调试:显示获取到的标签
if gitlink_tags:
logger.debug("[DEBUG] GitLink 标签列表:")
for tag in gitlink_tags:
logger.debug(f" - {tag.get('name')} (commit: {tag.get('commit', {}).get('sha', 'N/A')})")
else:
logger.debug("[DEBUG] GitLink 没有获取到任何标签")
# 获取目标平台标签
logger.info("\n>> 获取 Gitee 标签...")
logger.debug(f"[DEBUG] 请求 Gitee API: {gitee_client.owner}/{gitee_client.repo}")
gitee_tags = gitee_client.get_tags()
logger.info(f"[OK] Gitee 共有 {len(gitee_tags)} 个标签")
# 调试:显示获取到的标签
if gitee_tags:
logger.debug("[DEBUG] Gitee 标签列表:")
for tag in gitee_tags:
logger.debug(f" - {tag.get('name')} (commit: {tag.get('commit', {}).get('sha', 'N/A')})")
else:
logger.debug("[DEBUG] Gitee 没有获取到任何标签")
# 分析需要同步的标签 - 直接同步所有不存在的标签
gitee_tag_names = {tag.get("name") for tag in gitee_tags}
tags_to_sync = []
for tag in gitlink_tags:
tag_name = tag.get("name")
if tag_name and tag_name not in gitee_tag_names:
tags_to_sync.append(tag)
logger.info(f"\n>> 需要同步 {len(tags_to_sync)} 个标签到 Gitee:")
for tag in tags_to_sync:
logger.info(f" - {tag.get('name')}")
if not tags_to_sync:
logger.info("[OK] 所有标签都已同步,无需操作")
return {"success": True, "synced": 0, "skipped": 0, "errors": 0}
# 执行同步
synced_count = 0
error_count = 0
for tag in tags_to_sync:
tag_name = tag.get("name")
commit_sha = tag.get("commit", {}).get("sha", "")
message = tag.get("message", "")
logger.info(f"\n>> 同步标签: {tag_name}")
if dry_run:
logger.debug(f"[DRY RUN] 模拟创建标签: {tag_name}")
synced_count += 1
else:
try:
result = gitee_client.create_tag(tag_name, commit_sha, message)
if result:
logger.info(f"[OK] 成功创建标签: {tag_name}")
synced_count += 1
else:
logger.error(f"[ERROR] 创建标签失败: {tag_name}")
error_count += 1
except Exception as e:
logger.error(f"[ERROR] 创建标签 {tag_name} 时发生异常: {e}")
error_count += 1
logger.info(f"\n>> 同步完成: 成功 {synced_count}, 失败 {error_count}")
return {
"success": error_count == 0,
"synced": synced_count,
"skipped": len(gitlink_tags) - len(tags_to_sync),
"errors": error_count
}
except Exception as e:
logger.error(f"[ERROR] 同步过程发生异常: {e}")
import traceback
traceback.print_exc()
return {"success": False, "error": str(e)}
def sync_gitee_to_gitlink(self, gitee_client: BaseRepoClient, gitlink_client: BaseRepoClient, dry_run=False):
"""
从Gitee同步标签到GitLink
Args:
gitee_client: Gitee客户端
gitlink_client: GitLink客户端
dry_run: 是否为试运行不实际创建标签
"""
logger.info(">> 开始从 Gitee 同步标签到 GitLink...")
logger.info("=" * 60)
try:
# 获取源平台标签
logger.info(">> 获取 Gitee 标签...")
logger.debug(f"[DEBUG] 请求 Gitee API: {gitee_client.owner}/{gitee_client.repo}")
gitee_tags = gitee_client.get_tags()
logger.info(f"[OK] Gitee 共有 {len(gitee_tags)} 个标签")
# 调试:显示获取到的标签
if gitee_tags:
logger.debug("[DEBUG] Gitee 标签列表:")
for tag in gitee_tags:
logger.debug(f" - {tag.get('name')} (commit: {tag.get('commit', {}).get('sha', 'N/A')})")
else:
logger.debug("[DEBUG] Gitee 没有获取到任何标签")
# 获取目标平台标签
logger.info("\n>> 获取 GitLink 标签...")
logger.debug(f"[DEBUG] 请求 GitLink API: {gitlink_client.owner}/{gitlink_client.repo}")
gitlink_tags = gitlink_client.get_tags()
logger.info(f"[OK] GitLink 共有 {len(gitlink_tags)} 个标签")
# 调试:显示获取到的标签
if gitlink_tags:
logger.debug("[DEBUG] GitLink 标签列表:")
for tag in gitlink_tags:
logger.debug(f" - {tag.get('name')} (commit: {tag.get('commit', {}).get('sha', 'N/A')})")
else:
logger.debug("[DEBUG] GitLink 没有获取到任何标签")
# 分析需要同步的标签 - 直接同步所有不存在的标签
gitlink_tag_names = {tag.get("name") for tag in gitlink_tags}
tags_to_sync = []
for tag in gitee_tags:
tag_name = tag.get("name")
if tag_name and tag_name not in gitlink_tag_names:
tags_to_sync.append(tag)
logger.info(f"\n>> 需要同步 {len(tags_to_sync)} 个标签到 GitLink:")
for tag in tags_to_sync:
logger.info(f" - {tag.get('name')}")
if not tags_to_sync:
logger.info("[OK] 所有标签都已同步,无需操作")
return {"success": True, "synced": 0, "skipped": 0, "errors": 0}
# 执行同步
synced_count = 0
error_count = 0
for tag in tags_to_sync:
tag_name = tag.get("name")
commit_sha = tag.get("commit", {}).get("sha", "")
message = tag.get("message", "")
logger.info(f"\n>> 同步标签: {tag_name}")
if dry_run:
logger.debug(f"[DRY RUN] 模拟创建标签: {tag_name}")
synced_count += 1
else:
try:
result = gitlink_client.create_tag(tag_name, commit_sha, message)
if result:
logger.info(f"[OK] 成功创建标签: {tag_name}")
synced_count += 1
else:
logger.error(f"[ERROR] 创建标签失败: {tag_name}")
error_count += 1
except Exception as e:
logger.error(f"[ERROR] 创建标签 {tag_name} 时发生异常: {e}")
error_count += 1
logger.info(f"\n>> 同步完成: 成功 {synced_count}, 失败 {error_count}")
return {
"success": error_count == 0,
"synced": synced_count,
"skipped": len(gitee_tags) - len(tags_to_sync),
"errors": error_count
}
except Exception as e:
logger.error(f"[ERROR] 同步过程发生异常: {e}")
import traceback
traceback.print_exc()
return {"success": False, "error": str(e)}
def bidirectional_sync(self, gitlink_client: BaseRepoClient, gitee_client: BaseRepoClient, dry_run=False):
"""
双向同步GitLink和Gitee的标签
Args:
gitlink_client: GitLink客户端
gitee_client: Gitee客户端
dry_run: 是否为试运行
"""
logger.info(">>>> 开始 GitLink ↔ Gitee 双向标签同步...")
logger.info("=" * 70)
# 调试:检查客户端配置
logger.debug(f"[DEBUG] GitLink 客户端配置: {gitlink_client.owner}/{gitlink_client.repo}")
logger.debug(f"[DEBUG] Gitee 客户端配置: {gitee_client.owner}/{gitee_client.repo}")
logger.debug(f"[DEBUG] 试运行模式: {dry_run}")
results = {
"gitlink_to_gitee": None,
"gitee_to_gitlink": None,
"overall_success": False
}
# 第一步GitLink -> Gitee
logger.info("📍 第一步GitLink → Gitee")
results["gitlink_to_gitee"] = self.sync_gitlink_to_gitee(
gitlink_client, gitee_client, dry_run
)
logger.info("\n" + "="*50 + "\n")
# 第二步Gitee -> GitLink
logger.info("📍 第二步Gitee → GitLink")
results["gitee_to_gitlink"] = self.sync_gitee_to_gitlink(
gitee_client, gitlink_client, dry_run
)
# 汇总结果
results["overall_success"] = (
results["gitlink_to_gitee"]["success"] and
results["gitee_to_gitlink"]["success"]
)
logger.info("\n" + "="*70)
logger.info("📊 双向同步总结:")
logger.info(f"GitLink → Gitee: 同步 {results['gitlink_to_gitee']['synced']} 个, 失败 {results['gitlink_to_gitee']['errors']}")
logger.info(f"Gitee → GitLink: 同步 {results['gitee_to_gitlink']['synced']} 个, 失败 {results['gitee_to_gitlink']['errors']}")
logger.info(f"总体状态: {'✅ 成功' if results['overall_success'] else '❌ 有错误'}")
return results
def sync_github_to_gitlink(self, github_client: BaseRepoClient, gitlink_client: BaseRepoClient, dry_run=False):
"""从GitHub同步标签到GitLink"""
logger.info(">> 开始从 GitHub 同步标签到 GitLink...")
logger.info("=" * 60)
try:
# 获取源平台标签
logger.info(">> 获取 GitHub 标签...")
github_tags = github_client.get_tags()
logger.info(f"[OK] GitHub 共有 {len(github_tags)} 个标签")
# 获取目标平台标签
logger.info("\n>> 获取 GitLink 标签...")
gitlink_tags = gitlink_client.get_tags()
logger.info(f"[OK] GitLink 共有 {len(gitlink_tags)} 个标签")
# 分析需要同步的标签
gitlink_tag_names = {tag.get("name") for tag in gitlink_tags}
tags_to_sync = []
for tag in github_tags:
tag_name = tag.get("name")
if tag_name and tag_name not in gitlink_tag_names:
tags_to_sync.append(tag)
logger.info(f"\n>> 需要同步 {len(tags_to_sync)} 个标签到 GitLink:")
for tag in tags_to_sync:
logger.info(f" - {tag.get('name')}")
if not tags_to_sync:
logger.info("[OK] 所有标签都已同步,无需操作")
return {"success": True, "synced": 0, "skipped": 0, "errors": 0}
# 执行同步
synced_count = 0
error_count = 0
for tag in tags_to_sync:
tag_name = tag.get("name")
commit_sha = tag.get("commit", {}).get("sha", "")
message = tag.get("message", "")
logger.info(f"\n>> 同步标签: {tag_name}")
if dry_run:
logger.debug(f"[DRY RUN] 模拟创建标签: {tag_name}")
synced_count += 1
else:
try:
result = gitlink_client.create_tag(tag_name, commit_sha, message)
if result:
logger.info(f"[OK] 成功创建标签: {tag_name}")
synced_count += 1
else:
logger.error(f"[ERROR] 创建标签失败: {tag_name}")
error_count += 1
except Exception as e:
logger.error(f"[ERROR] 创建标签 {tag_name} 时发生异常: {e}")
error_count += 1
logger.info(f"\n>> 同步完成: 成功 {synced_count}, 失败 {error_count}")
return {
"success": error_count == 0,
"synced": synced_count,
"skipped": len(github_tags) - len(tags_to_sync),
"errors": error_count
}
except Exception as e:
logger.error(f"[ERROR] 同步过程发生异常: {e}")
return {"success": False, "error": str(e)}
def sync_gitlink_to_github(self, gitlink_client: BaseRepoClient, github_client: BaseRepoClient, dry_run=False):
"""从GitLink同步标签到GitHub"""
logger.info(">> 开始从 GitLink 同步标签到 GitHub...")
logger.info("=" * 60)
try:
# 获取源平台标签
logger.info(">> 获取 GitLink 标签...")
gitlink_tags = gitlink_client.get_tags()
logger.info(f"[OK] GitLink 共有 {len(gitlink_tags)} 个标签")
# 获取目标平台标签
logger.info("\n>> 获取 GitHub 标签...")
github_tags = github_client.get_tags()
logger.info(f"[OK] GitHub 共有 {len(github_tags)} 个标签")
# 分析需要同步的标签
github_tag_names = {tag.get("name") for tag in github_tags}
tags_to_sync = []
for tag in gitlink_tags:
tag_name = tag.get("name")
if tag_name and tag_name not in github_tag_names:
tags_to_sync.append(tag)
logger.info(f"\n>> 需要同步 {len(tags_to_sync)} 个标签到 GitHub:")
for tag in tags_to_sync:
logger.info(f" - {tag.get('name')}")
if not tags_to_sync:
logger.info("[OK] 所有标签都已同步,无需操作")
return {"success": True, "synced": 0, "skipped": 0, "errors": 0}
# 执行同步
synced_count = 0
error_count = 0
for tag in tags_to_sync:
tag_name = tag.get("name")
commit_sha = tag.get("commit", {}).get("sha", "")
message = tag.get("message", "")
logger.info(f"\n>> 同步标签: {tag_name}")
if dry_run:
logger.debug(f"[DRY RUN] 模拟创建标签: {tag_name}")
synced_count += 1
else:
try:
result = github_client.create_tag(tag_name, commit_sha, message)
if result:
logger.info(f"[OK] 成功创建标签: {tag_name}")
synced_count += 1
else:
logger.error(f"[ERROR] 创建标签失败: {tag_name}")
error_count += 1
except Exception as e:
logger.error(f"[ERROR] 创建标签 {tag_name} 时发生异常: {e}")
error_count += 1
logger.info(f"\n>> 同步完成: 成功 {synced_count}, 失败 {error_count}")
return {
"success": error_count == 0,
"synced": synced_count,
"skipped": len(gitlink_tags) - len(tags_to_sync),
"errors": error_count
}
except Exception as e:
logger.error(f"[ERROR] 同步过程发生异常: {e}")
return {"success": False, "error": str(e)}
def get_sync_history(self, limit=10):
"""获取同步历史记录"""
return self.sync_history[-limit:]
def clear_sync_history(self):
"""清空同步历史记录"""
self.sync_history.clear()
logger.info("[INFO] 同步历史记录已清空")

View File

@ -0,0 +1 @@
2025-07-15 08:45:49,890 - ERROR - 无法导入标签同步服务: attempted relative import with no known parent package

View File

@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
"""
标签同步Web API模块
"""
__version__ = "1.0.0"
__author__ = "RepoSync Team"
__description__ = "GitHub、Gitee、GitLink标签同步Web服务"

View File

@ -0,0 +1,92 @@
# coding: utf-8
import os
import sys
import uvicorn
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from fastapi.middleware.cors import CORSMiddleware
# 添加当前目录到Python路径
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
if parent_dir not in sys.path:
sys.path.insert(0, parent_dir)
# 添加web_api目录到Python路径
if current_dir not in sys.path:
sys.path.insert(0, current_dir)
try:
from tag_sync_api import create_tag_sync_router
except ImportError:
# 如果直接导入失败,尝试相对导入
from .tag_sync_api import create_tag_sync_router
def create_app():
"""创建FastAPI应用"""
app = FastAPI(
title="标签同步服务",
description="GitHub、Gitee、GitLink 之间的标签双向同步API",
version="1.0.0"
)
# 配置CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 健康检查接口
@app.get("/health")
async def health_check():
return {
"status": "healthy",
"service": "tag-sync-backend",
"port": 8005
}
# 首页重定向到静态文件
@app.get("/")
async def read_root():
return FileResponse(os.path.join(current_dir, "static", "index.html"))
# 挂载标签同步API路由
tag_sync_router = create_tag_sync_router()
app.include_router(tag_sync_router, prefix="/api", tags=["标签同步"])
# 挂载静态文件服务
static_dir = os.path.join(current_dir, "static")
if not os.path.exists(static_dir):
os.makedirs(static_dir)
app.mount("/static", StaticFiles(directory=static_dir), name="static")
return app
def start_server():
"""启动服务器"""
app = create_app()
print("=" * 60)
print("标签同步服务")
print("=" * 60)
print("服务地址: http://localhost:8005")
print("网页界面: http://localhost:8005")
print("API文档: http://localhost:8005/docs")
print("健康检查: http://localhost:8005/health")
print("=" * 60)
uvicorn.run(
app,
host="0.0.0.0",
port=8005,
log_level="info"
)
if __name__ == "__main__":
start_server()

View File

@ -0,0 +1,813 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>标签同步服务</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
color: white;
padding: 30px;
text-align: center;
}
.header h1 {
font-size: 2.5em;
margin-bottom: 10px;
}
.header p {
font-size: 1.2em;
opacity: 0.9;
}
.content {
padding: 40px;
}
.sync-types {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 20px;
margin-bottom: 40px;
}
.sync-card {
border: 2px solid #e1e8ed;
border-radius: 10px;
padding: 25px;
cursor: pointer;
transition: all 0.3s ease;
background: #f8f9fa;
}
.sync-card:hover {
border-color: #4facfe;
background: #fff;
box-shadow: 0 5px 15px rgba(79, 172, 254, 0.2);
transform: translateY(-2px);
}
.sync-card.active {
border-color: #4facfe;
background: #fff;
box-shadow: 0 5px 15px rgba(79, 172, 254, 0.3);
}
.sync-card h3 {
color: #333;
margin-bottom: 10px;
font-size: 1.4em;
}
.sync-card p {
color: #666;
line-height: 1.6;
}
.config-section {
background: #f8f9fa;
border-radius: 10px;
padding: 30px;
margin-bottom: 30px;
display: none;
}
.config-section.active {
display: block;
}
.config-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
margin-bottom: 30px;
}
.platform-config {
background: white;
border-radius: 8px;
padding: 20px;
border: 1px solid #e1e8ed;
}
.platform-config h4 {
color: #333;
margin-bottom: 15px;
font-size: 1.2em;
border-bottom: 2px solid #4facfe;
padding-bottom: 8px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
color: #333;
font-weight: 500;
}
.form-group input, .form-group select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 14px;
}
.form-group input:focus, .form-group select:focus {
outline: none;
border-color: #4facfe;
box-shadow: 0 0 5px rgba(79, 172, 254, 0.3);
}
.options {
background: white;
border-radius: 8px;
padding: 20px;
border: 1px solid #e1e8ed;
margin-bottom: 20px;
}
.options h4 {
color: #333;
margin-bottom: 15px;
font-size: 1.2em;
}
.checkbox-group {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.checkbox-group input[type="checkbox"] {
width: auto;
margin-right: 10px;
}
.buttons {
text-align: center;
margin-top: 30px;
}
.btn {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
color: white;
border: none;
padding: 12px 30px;
border-radius: 25px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s ease;
margin: 0 10px;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(79, 172, 254, 0.4);
}
.btn:disabled {
background: #ccc;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.loading {
text-align: center;
padding: 20px;
display: none;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #4facfe;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 日志相关样式 */
.log-section {
background: #f8f9fa;
border-radius: 10px;
padding: 20px;
margin-top: 20px;
border: 1px solid #e1e8ed;
}
.log-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #e1e8ed;
}
.log-header h4 {
margin: 0;
color: #333;
}
.log-controls {
display: flex;
gap: 10px;
}
.btn-small {
padding: 5px 10px;
font-size: 12px;
border: 1px solid #ddd;
background: white;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
}
.btn-small:hover {
background: #f0f0f0;
}
.btn-small.active {
background: #4facfe;
color: white;
border-color: #4facfe;
}
.log-content {
max-height: 400px;
overflow-y: auto;
background: white;
border: 1px solid #e1e8ed;
border-radius: 5px;
padding: 10px;
font-family: 'Courier New', monospace;
font-size: 12px;
line-height: 1.4;
}
.log-entry {
margin-bottom: 2px;
padding: 2px 0;
border-bottom: 1px solid #f0f0f0;
}
.log-entry:last-child {
border-bottom: none;
}
.log-timestamp {
color: #666;
font-weight: bold;
}
.log-level {
font-weight: bold;
margin: 0 5px;
}
.log-level.INFO {
color: #28a745;
}
.log-level.ERROR {
color: #dc3545;
}
.log-level.DEBUG {
color: #6c757d;
}
.log-level.WARNING {
color: #ffc107;
}
.log-message {
color: #333;
}
.log-placeholder {
color: #999;
text-align: center;
padding: 20px;
font-style: italic;
}
/* 自动滚动到底部 */
.log-content.auto-scroll {
scroll-behavior: smooth;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>标签同步服务</h1>
<p>GitHub、Gitee、GitLink 之间的标签双向同步</p>
</div>
<div class="content">
<!-- 同步类型选择 -->
<div class="sync-types">
<div class="sync-card" data-type="gitlink-gitee">
<h3>GitLink ↔ Gitee</h3>
<p>在 GitLink 和 Gitee 之间双向同步标签。适用于国内代码托管平台之间的同步。</p>
</div>
<div class="sync-card" data-type="github-gitlink">
<h3>GitHub ↔ GitLink</h3>
<p>在 GitHub 和 GitLink 之间双向同步标签。连接国际和国内代码托管平台。</p>
</div>
<div class="sync-card" data-type="github-gitee">
<h3>GitHub ↔ Gitee</h3>
<p>在 GitHub 和 Gitee 之间双向同步标签。适用于项目的国际化同步。</p>
</div>
</div>
<!-- GitLink ↔ Gitee 配置 -->
<div class="config-section" id="config-gitlink-gitee">
<div class="config-row">
<div class="platform-config">
<h4>GitLink 配置</h4>
<div class="form-group">
<label>组织/用户名:</label>
<input type="text" id="gitlink-org" placeholder="例如: trustie2021">
</div>
<div class="form-group">
<label>仓库名:</label>
<input type="text" id="gitlink-repo" placeholder="例如: trustieforge">
</div>
<div class="form-group">
<label>Cookie:</label>
<input type="text" id="gitlink-cookie" placeholder="从浏览器复制的 Cookie">
</div>
</div>
<div class="platform-config">
<h4>Gitee 配置</h4>
<div class="form-group">
<label>组织/用户名:</label>
<input type="text" id="gitee-org" placeholder="例如: your-org">
</div>
<div class="form-group">
<label>仓库名:</label>
<input type="text" id="gitee-repo" placeholder="例如: your-repo">
</div>
<div class="form-group">
<label>Access Token:</label>
<input type="text" id="gitee-token" placeholder="Gitee 访问令牌">
</div>
</div>
</div>
</div>
<!-- GitHub ↔ GitLink 配置 -->
<div class="config-section" id="config-github-gitlink">
<div class="config-row">
<div class="platform-config">
<h4>GitHub 配置</h4>
<div class="form-group">
<label>组织/用户名:</label>
<input type="text" id="github-org" placeholder="例如: your-username">
</div>
<div class="form-group">
<label>仓库名:</label>
<input type="text" id="github-repo" placeholder="例如: your-repo">
</div>
<div class="form-group">
<label>Access Token:</label>
<input type="text" id="github-token" placeholder="GitHub 访问令牌">
</div>
</div>
<div class="platform-config">
<h4>GitLink 配置</h4>
<div class="form-group">
<label>组织/用户名:</label>
<input type="text" id="gitlink-org-2" placeholder="例如: trustie2021">
</div>
<div class="form-group">
<label>仓库名:</label>
<input type="text" id="gitlink-repo-2" placeholder="例如: trustieforge">
</div>
<div class="form-group">
<label>Cookie:</label>
<input type="text" id="gitlink-cookie-2" placeholder="从浏览器复制的 Cookie">
</div>
</div>
</div>
</div>
<!-- GitHub ↔ Gitee 配置 -->
<div class="config-section" id="config-github-gitee">
<div class="config-row">
<div class="platform-config">
<h4>GitHub 配置</h4>
<div class="form-group">
<label>组织/用户名:</label>
<input type="text" id="github-org-2" placeholder="例如: your-username">
</div>
<div class="form-group">
<label>仓库名:</label>
<input type="text" id="github-repo-2" placeholder="例如: your-repo">
</div>
<div class="form-group">
<label>Access Token:</label>
<input type="text" id="github-token-2" placeholder="GitHub 访问令牌">
</div>
</div>
<div class="platform-config">
<h4>Gitee 配置</h4>
<div class="form-group">
<label>组织/用户名:</label>
<input type="text" id="gitee-org-2" placeholder="例如: your-org">
</div>
<div class="form-group">
<label>仓库名:</label>
<input type="text" id="gitee-repo-2" placeholder="例如: your-repo">
</div>
<div class="form-group">
<label>Access Token:</label>
<input type="text" id="gitee-token-2" placeholder="Gitee 访问令牌">
</div>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="buttons" style="display: none;" id="action-buttons">
<button class="btn" onclick="executeSync()">执行同步</button>
<button class="btn" onclick="resetForm()">重置表单</button>
</div>
<!-- 加载状态 -->
<div class="loading" id="loading">
<div class="spinner"></div>
<p>正在执行同步,请稍候...</p>
</div>
<!-- 日志显示区域 -->
<div class="log-section" id="log-section">
<div class="log-header">
<h4>同步日志</h4>
<div class="log-controls">
<button onclick="toggleAutoRefresh()" id="auto-refresh-btn" class="btn-small">
<span id="auto-refresh-text">开启自动刷新</span>
</button>
<button onclick="clearLogs()" class="btn-small">清空日志</button>
<button onclick="refreshLogs()" class="btn-small">刷新日志</button>
</div>
</div>
<div class="log-content" id="log-content">
</div>
</div>
</div>
</div>
<script>
let currentSyncType = null;
let autoRefreshInterval = null;
let lastLogTimestamp = null;
let isAutoRefreshEnabled = false;
// 选择同步类型
document.querySelectorAll('.sync-card').forEach(card => {
card.addEventListener('click', function() {
// 移除其他卡片的激活状态
document.querySelectorAll('.sync-card').forEach(c => c.classList.remove('active'));
document.querySelectorAll('.config-section').forEach(s => s.classList.remove('active'));
// 激活当前卡片
this.classList.add('active');
currentSyncType = this.dataset.type;
// 显示对应的配置区域
document.getElementById('config-' + currentSyncType).classList.add('active');
document.getElementById('action-buttons').style.display = 'block';
});
});
// 执行同步
async function executeSync() {
if (!currentSyncType) {
addLogEntry('ERROR', '请先选择同步类型');
return;
}
const config = getSyncConfig();
if (!config) {
return;
}
showLoading();
clearLocalLogs();
// 开始自动刷新日志
startAutoRefresh();
try {
const response = await fetch('/api/tag-sync/sync/bidirectional', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(config)
});
const result = await response.json();
hideLoading();
// 等待一段时间让后台任务完成并生成日志
await new Promise(resolve => setTimeout(resolve, 2000));
// 做最后一次日志刷新
await refreshLogs();
// 停止自动刷新
stopAutoRefresh();
if (response.ok && result.code === 0) {
addLogEntry('INFO', `同步完成!${result.message || ''}`);
} else {
addLogEntry('ERROR', `同步失败: ${result.message || '未知错误'}`);
}
} catch (error) {
hideLoading();
// 停止自动刷新
stopAutoRefresh();
addLogEntry('ERROR', `请求失败: ${error.message}`);
}
}
// 获取同步配置
function getSyncConfig() {
let config = {
dry_run: false // 直接执行,不再提供试运行选项
};
switch (currentSyncType) {
case 'gitlink-gitee':
config.platform_a = {
platform: 'gitlink',
org: document.getElementById('gitlink-org').value,
repo: document.getElementById('gitlink-repo').value,
cookie: document.getElementById('gitlink-cookie').value
};
config.platform_b = {
platform: 'gitee',
org: document.getElementById('gitee-org').value,
repo: document.getElementById('gitee-repo').value,
token: document.getElementById('gitee-token').value
};
break;
case 'github-gitlink':
config.platform_a = {
platform: 'github',
org: document.getElementById('github-org').value,
repo: document.getElementById('github-repo').value,
token: document.getElementById('github-token').value
};
config.platform_b = {
platform: 'gitlink',
org: document.getElementById('gitlink-org-2').value,
repo: document.getElementById('gitlink-repo-2').value,
cookie: document.getElementById('gitlink-cookie-2').value
};
break;
case 'github-gitee':
config.platform_a = {
platform: 'github',
org: document.getElementById('github-org-2').value,
repo: document.getElementById('github-repo-2').value,
token: document.getElementById('github-token-2').value
};
config.platform_b = {
platform: 'gitee',
org: document.getElementById('gitee-org-2').value,
repo: document.getElementById('gitee-repo-2').value,
token: document.getElementById('gitee-token-2').value
};
break;
}
// 验证配置
if (!config.platform_a.org || !config.platform_a.repo) {
addLogEntry('ERROR', '请填写完整的平台A配置信息');
return null;
}
if (!config.platform_b.org || !config.platform_b.repo) {
addLogEntry('ERROR', '请填写完整的平台B配置信息');
return null;
}
return config;
}
// 显示加载状态
function showLoading() {
document.getElementById('loading').style.display = 'block';
document.querySelector('.btn').disabled = true;
}
// 隐藏加载状态
function hideLoading() {
document.getElementById('loading').style.display = 'none';
document.querySelector('.btn').disabled = false;
}
// 添加日志条目
function addLogEntry(level, message) {
const logContent = document.getElementById('log-content');
const newEntry = document.createElement('div');
newEntry.className = 'log-entry';
newEntry.innerHTML = `<span class="log-timestamp">${new Date().toISOString()}</span> <span class="log-level ${level}">${level}</span> <span class="log-message">${message}</span>`;
logContent.appendChild(newEntry);
logContent.scrollTop = logContent.scrollHeight; // 自动滚动到底部
}
// 刷新日志 (从后端获取最新日志)
async function refreshLogs() {
try {
const response = await fetch('/api/tag-sync/logs');
const result = await response.json();
if (response.ok && result.code === 0) {
updateLogContent(result.data.logs);
} else {
addLogEntry('ERROR', `刷新日志失败: ${result.message || '未知错误'}`);
}
} catch (error) {
addLogEntry('ERROR', `刷新日志失败: ${error.message}`);
}
}
// 更新日志内容
function updateLogContent(logs) {
const logContent = document.getElementById('log-content');
logContent.innerHTML = ''; // 清空当前内容
if (logs && logs.length > 0) {
logs.forEach(log => {
const newEntry = document.createElement('div');
newEntry.className = 'log-entry';
newEntry.innerHTML = `<span class="log-timestamp">${log.timestamp}</span> <span class="log-level ${log.level}">${log.level}</span> <span class="log-message">${log.message}</span>`;
logContent.appendChild(newEntry);
});
logContent.scrollTop = logContent.scrollHeight; // 自动滚动到底部
} else {
logContent.innerHTML = '<div class="log-placeholder">暂无日志</div>';
}
}
// 清除本地日志
function clearLocalLogs() {
const logContent = document.getElementById('log-content');
logContent.innerHTML = '<div class="log-placeholder">暂无日志</div>';
logContent.scrollTop = 0; // 滚动到顶部
}
// 开始自动刷新
function startAutoRefresh() {
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
}
// 自动启用自动刷新
if (!isAutoRefreshEnabled) {
isAutoRefreshEnabled = true;
document.getElementById('auto-refresh-text').textContent = '关闭自动刷新';
document.getElementById('auto-refresh-btn').classList.add('active');
}
autoRefreshInterval = setInterval(refreshLogs, 1000); // 每秒刷新一次
}
// 停止自动刷新
function stopAutoRefresh() {
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
autoRefreshInterval = null;
}
// 更新UI状态
isAutoRefreshEnabled = false;
document.getElementById('auto-refresh-text').textContent = '开启自动刷新';
document.getElementById('auto-refresh-btn').classList.remove('active');
}
// 切换自动刷新
function toggleAutoRefresh() {
isAutoRefreshEnabled = !isAutoRefreshEnabled;
const btnText = isAutoRefreshEnabled ? '关闭自动刷新' : '开启自动刷新';
document.getElementById('auto-refresh-text').textContent = btnText;
document.getElementById('auto-refresh-btn').classList.toggle('active');
if (isAutoRefreshEnabled) {
startAutoRefresh();
} else {
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
autoRefreshInterval = null;
}
}
}
// 清空服务器日志
async function clearLogs() {
try {
const response = await fetch('/api/tag-sync/logs', {
method: 'DELETE'
});
const result = await response.json();
if (response.ok && result.code === 0) {
clearLocalLogs();
addLogEntry('INFO', '日志已清空');
} else {
addLogEntry('ERROR', `清空日志失败: ${result.message}`);
}
} catch (error) {
addLogEntry('ERROR', `清空日志失败: ${error.message}`);
}
}
// 重置表单
function resetForm() {
document.querySelectorAll('input').forEach(input => {
input.value = '';
});
document.querySelectorAll('.sync-card').forEach(c => c.classList.remove('active'));
document.querySelectorAll('.config-section').forEach(s => s.classList.remove('active'));
document.getElementById('action-buttons').style.display = 'none';
currentSyncType = null;
// 停止自动刷新
stopAutoRefresh();
}
// 页面加载完成后检查服务状态
window.addEventListener('load', async function() {
// 显示欢迎信息
addLogEntry('INFO', '🎯 标签同步服务已就绪,请选择同步类型开始同步');
try {
const response = await fetch('/health');
const result = await response.json();
addLogEntry('INFO', '✅ 后端服务连接正常');
console.log('服务状态:', result);
} catch (error) {
addLogEntry('ERROR', '❌ 无法连接到后端服务,请检查服务是否正常启动');
console.error('无法连接到后端服务:', error);
}
});
</script>
</body>
</html>

View File

@ -0,0 +1,457 @@
2025-07-15 08:12:41,080 - ERROR - 无法导入标签同步服务: attempted relative import with no known parent package
2025-07-15 08:12:41,118 - INFO - Started server process [55008]
2025-07-15 08:12:41,118 - INFO - Waiting for application startup.
2025-07-15 08:12:41,118 - INFO - Application startup complete.
2025-07-15 08:12:41,119 - INFO - Uvicorn running on http://0.0.0.0:8005 (Press CTRL+C to quit)
2025-07-15 08:22:58,497 - INFO - Shutting down
2025-07-15 08:27:13,525 - ERROR - 无法导入标签同步服务: attempted relative import with no known parent package
2025-07-15 08:27:13,557 - INFO - Started server process [44828]
2025-07-15 08:27:13,558 - INFO - Waiting for application startup.
2025-07-15 08:27:13,558 - INFO - Application startup complete.
2025-07-15 08:27:13,559 - INFO - Uvicorn running on http://0.0.0.0:8005 (Press CTRL+C to quit)
2025-07-15 08:27:56,274 - INFO - 开始执行双向同步: gitlink <-> gitee
2025-07-15 08:27:56,274 - INFO - 仓库: huaijin/test-branch <-> qzlGitee/test-branch
2025-07-15 08:27:56,274 - INFO - 试运行模式: False
2025-07-15 08:27:56,274 - INFO - >> 创建平台客户端...
2025-07-15 08:27:56,274 - INFO - >> 初始化同步服务...
2025-07-15 08:27:56,274 - INFO - 双向同步任务完成: 双向同步完成,同步了 0 个标签
2025-07-15 08:27:56,274 - INFO - 同步统计: 成功0个, 失败0个
2025-07-15 08:28:48,801 - INFO - 开始执行双向同步: gitlink <-> gitee
2025-07-15 08:28:48,801 - INFO - 仓库: huaijin/test-branch <-> qzlGitee/test-branch
2025-07-15 08:28:48,801 - INFO - 试运行模式: False
2025-07-15 08:28:48,801 - INFO - >> 创建平台客户端...
2025-07-15 08:28:48,801 - INFO - >> 初始化同步服务...
2025-07-15 08:28:48,801 - INFO - 双向同步任务完成: 双向同步完成,同步了 0 个标签
2025-07-15 08:28:48,801 - INFO - 同步统计: 成功0个, 失败0个
2025-07-15 08:29:10,674 - INFO - >> 创建平台客户端...
2025-07-15 08:29:10,674 - INFO - >> 初始化同步服务...
2025-07-15 08:31:38,481 - INFO - Shutting down
2025-07-15 08:32:45,286 - ERROR - 无法导入标签同步服务: attempted relative import with no known parent package
2025-07-15 08:32:45,315 - INFO - Started server process [48668]
2025-07-15 08:32:45,315 - INFO - Waiting for application startup.
2025-07-15 08:32:45,316 - INFO - Application startup complete.
2025-07-15 08:32:45,316 - INFO - Uvicorn running on http://0.0.0.0:8005 (Press CTRL+C to quit)
2025-07-15 08:34:13,395 - INFO - Shutting down
2025-07-15 08:43:05,895 - ERROR - 无法导入标签同步服务: attempted relative import with no known parent package
2025-07-15 08:43:05,925 - INFO - Started server process [59408]
2025-07-15 08:43:05,925 - INFO - Waiting for application startup.
2025-07-15 08:43:05,925 - INFO - Application startup complete.
2025-07-15 08:43:05,926 - INFO - Uvicorn running on http://0.0.0.0:8005 (Press CTRL+C to quit)
2025-07-15 08:43:42,254 - INFO - Shutting down
2025-07-15 08:46:30,668 - ERROR - 无法导入标签同步服务: attempted relative import with no known parent package
2025-07-15 08:46:30,700 - INFO - Started server process [2720]
2025-07-15 08:46:30,700 - INFO - Waiting for application startup.
2025-07-15 08:46:30,700 - INFO - Application startup complete.
2025-07-15 08:46:30,700 - INFO - Uvicorn running on http://0.0.0.0:8005 (Press CTRL+C to quit)
2025-07-15 08:46:41,449 - INFO - Shutting down
2025-07-15 08:48:38,292 - ERROR - 无法导入标签同步服务: attempted relative import with no known parent package
2025-07-15 08:48:38,321 - INFO - Started server process [45276]
2025-07-15 08:48:38,321 - INFO - Waiting for application startup.
2025-07-15 08:48:38,321 - INFO - Application startup complete.
2025-07-15 08:48:38,321 - INFO - Uvicorn running on http://0.0.0.0:8005 (Press CTRL+C to quit)
2025-07-15 08:49:15,289 - INFO - Shutting down
2025-07-15 08:50:06,536 - ERROR - 无法导入标签同步服务: attempted relative import with no known parent package
2025-07-15 08:50:06,566 - INFO - Started server process [12636]
2025-07-15 08:50:06,566 - INFO - Waiting for application startup.
2025-07-15 08:50:06,566 - INFO - Application startup complete.
2025-07-15 08:50:06,567 - INFO - Uvicorn running on http://0.0.0.0:8005 (Press CTRL+C to quit)
2025-07-15 08:50:35,318 - ERROR - 无法导入标签同步服务: attempted relative import with no known parent package
2025-07-15 08:50:35,357 - INFO - Started server process [24100]
2025-07-15 08:50:35,357 - INFO - Waiting for application startup.
2025-07-15 08:50:35,357 - INFO - Application startup complete.
2025-07-15 08:50:35,358 - ERROR - [Errno 10048] error while attempting to bind on address ('0.0.0.0', 8005): 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。
2025-07-15 08:50:35,358 - INFO - Waiting for application shutdown.
2025-07-15 08:50:35,358 - INFO - Application shutdown complete.
2025-07-15 08:50:41,259 - ERROR - 无法导入标签同步服务: attempted relative import with no known parent package
2025-07-15 08:50:41,321 - INFO - Started server process [47660]
2025-07-15 08:50:41,321 - INFO - Waiting for application startup.
2025-07-15 08:50:41,322 - INFO - Application startup complete.
2025-07-15 08:50:41,322 - ERROR - [Errno 10048] error while attempting to bind on address ('0.0.0.0', 8005): 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。
2025-07-15 08:50:41,322 - INFO - Waiting for application shutdown.
2025-07-15 08:50:41,322 - INFO - Application shutdown complete.
2025-07-15 08:51:04,006 - INFO - Shutting down
2025-07-15 08:54:19,163 - ERROR - 无法导入标签同步服务: attempted relative import with no known parent package
2025-07-15 08:54:19,197 - INFO - Started server process [15012]
2025-07-15 08:54:19,197 - INFO - Waiting for application startup.
2025-07-15 08:54:19,198 - INFO - Application startup complete.
2025-07-15 08:54:19,198 - INFO - Uvicorn running on http://0.0.0.0:8005 (Press CTRL+C to quit)
2025-07-15 08:54:58,430 - INFO - Shutting down
2025-07-15 08:55:43,694 - ERROR - 无法导入标签同步服务: attempted relative import with no known parent package
2025-07-15 08:55:43,723 - INFO - Started server process [52416]
2025-07-15 08:55:43,724 - INFO - Waiting for application startup.
2025-07-15 08:55:43,724 - INFO - Application startup complete.
2025-07-15 08:55:43,724 - INFO - Uvicorn running on http://0.0.0.0:8005 (Press CTRL+C to quit)
2025-07-15 08:57:28,018 - INFO - >> 创建平台客户端...
2025-07-15 08:57:28,018 - INFO - >> 初始化同步服务...
2025-07-15 08:57:28,018 - INFO - 后台双向同步任务完成: 双向同步完成,同步了 0 个标签
2025-07-15 09:02:35,279 - INFO - Shutting down
2025-07-15 09:02:52,695 - ERROR - 无法导入标签同步服务: attempted relative import with no known parent package
2025-07-15 09:02:52,726 - INFO - Started server process [58208]
2025-07-15 09:02:52,726 - INFO - Waiting for application startup.
2025-07-15 09:02:52,726 - INFO - Application startup complete.
2025-07-15 09:02:52,727 - INFO - Uvicorn running on http://0.0.0.0:8005 (Press CTRL+C to quit)
2025-07-15 09:03:25,387 - INFO - >> 创建平台客户端...
2025-07-15 09:03:25,387 - INFO - >> 初始化同步服务...
2025-07-15 09:03:29,345 - INFO - >> 创建平台客户端...
2025-07-15 09:03:29,345 - INFO - >> 初始化同步服务...
2025-07-15 09:03:29,345 - INFO - 后台双向同步任务完成: 双向同步完成,同步了 0 个标签
2025-07-15 09:03:30,133 - INFO - >> 创建平台客户端...
2025-07-15 09:03:30,133 - INFO - >> 初始化同步服务...
2025-07-15 09:03:30,133 - INFO - 后台双向同步任务完成: 双向同步完成,同步了 0 个标签
2025-07-15 09:03:40,646 - INFO - >> 创建平台客户端...
2025-07-15 09:03:40,646 - INFO - >> 初始化同步服务...
2025-07-15 09:03:40,646 - INFO - 后台双向同步任务完成: 双向同步完成,同步了 0 个标签
2025-07-15 09:03:44,671 - INFO - >> 创建平台客户端...
2025-07-15 09:03:44,671 - INFO - >> 初始化同步服务...
2025-07-15 09:03:44,671 - INFO - 后台双向同步任务完成: 双向同步完成,同步了 0 个标签
2025-07-15 09:04:11,517 - INFO - >> 创建平台客户端...
2025-07-15 09:04:11,517 - INFO - >> 初始化同步服务...
2025-07-15 09:04:11,517 - INFO - 后台双向同步任务完成: 双向同步完成,同步了 0 个标签
2025-07-15 09:04:12,107 - INFO - >> 创建平台客户端...
2025-07-15 09:04:12,107 - INFO - >> 初始化同步服务...
2025-07-15 09:04:12,107 - INFO - 后台双向同步任务完成: 双向同步完成,同步了 0 个标签
2025-07-15 09:04:12,280 - INFO - >> 创建平台客户端...
2025-07-15 09:04:12,280 - INFO - >> 初始化同步服务...
2025-07-15 09:04:12,280 - INFO - 后台双向同步任务完成: 双向同步完成,同步了 0 个标签
2025-07-15 09:04:12,486 - INFO - >> 创建平台客户端...
2025-07-15 09:04:12,486 - INFO - >> 初始化同步服务...
2025-07-15 09:04:12,486 - INFO - 后台双向同步任务完成: 双向同步完成,同步了 0 个标签
2025-07-15 09:04:52,795 - INFO - Shutting down
2025-07-15 09:06:18,393 - INFO - 当前工作目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:06:18,395 - INFO - 当前文件目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:06:18,395 - INFO - 父目录: D:\Projects\reposync\repo_tag_sync_module
2025-07-15 09:06:18,395 - INFO - Python路径: ['D:\\Projects\\reposync\\repo_tag_sync_module\\web_api', 'D:\\Projects\\reposync\\repo_tag_sync_module', 'D:\\Projects\\reposync\\repo_tag_sync_module\\web_api']
2025-07-15 09:06:18,395 - INFO - github_client.py 存在: True
2025-07-15 09:06:18,395 - INFO - gitee_client.py 存在: True
2025-07-15 09:06:18,395 - INFO - gitlink_client.py 存在: True
2025-07-15 09:06:18,395 - INFO - sync_service.py 存在: True
2025-07-15 09:06:18,456 - INFO - 成功导入标签同步服务模块
2025-07-15 09:06:18,490 - INFO - Started server process [34648]
2025-07-15 09:06:18,490 - INFO - Waiting for application startup.
2025-07-15 09:06:18,490 - INFO - Application startup complete.
2025-07-15 09:06:18,491 - INFO - Uvicorn running on http://0.0.0.0:8005 (Press CTRL+C to quit)
2025-07-15 09:06:42,640 - INFO - >> 创建平台客户端...
2025-07-15 09:06:42,640 - INFO - >> 初始化同步服务...
2025-07-15 09:06:56,617 - INFO - 后台双向同步任务完成: 双向同步完成,同步了 9 个标签
2025-07-15 09:07:12,358 - INFO - >> 创建平台客户端...
2025-07-15 09:07:12,358 - INFO - >> 初始化同步服务...
2025-07-15 09:07:14,982 - INFO - 后台双向同步任务完成: 双向同步完成,同步了 0 个标签
2025-07-15 09:11:44,879 - INFO - Shutting down
2025-07-15 09:12:03,150 - INFO - 当前工作目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:12:03,150 - INFO - 当前文件目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:12:03,150 - INFO - 父目录: D:\Projects\reposync\repo_tag_sync_module
2025-07-15 09:12:03,150 - INFO - Python路径: ['D:\\Projects\\reposync\\repo_tag_sync_module\\web_api', 'D:\\Projects\\reposync\\repo_tag_sync_module', 'D:\\Projects\\reposync\\repo_tag_sync_module\\web_api']
2025-07-15 09:12:03,150 - INFO - github_client.py 存在: True
2025-07-15 09:12:03,150 - INFO - gitee_client.py 存在: True
2025-07-15 09:12:03,150 - INFO - gitlink_client.py 存在: True
2025-07-15 09:12:03,150 - INFO - sync_service.py 存在: True
2025-07-15 09:12:03,204 - INFO - 成功导入标签同步服务模块
2025-07-15 09:16:01,208 - INFO - 当前工作目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:16:01,208 - INFO - 当前文件目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:16:01,208 - INFO - 父目录: D:\Projects\reposync\repo_tag_sync_module
2025-07-15 09:16:01,208 - INFO - Python路径: ['D:\\Projects\\reposync\\repo_tag_sync_module\\web_api', 'D:\\Projects\\reposync\\repo_tag_sync_module', 'D:\\Projects\\reposync\\repo_tag_sync_module\\web_api']
2025-07-15 09:16:01,208 - INFO - github_client.py 存在: True
2025-07-15 09:16:01,208 - INFO - gitee_client.py 存在: True
2025-07-15 09:16:01,209 - INFO - gitlink_client.py 存在: True
2025-07-15 09:16:01,209 - INFO - sync_service.py 存在: True
2025-07-15 09:16:01,258 - INFO - 成功导入标签同步服务模块
2025-07-15 09:16:01,294 - INFO - Started server process [57600]
2025-07-15 09:16:01,294 - INFO - Waiting for application startup.
2025-07-15 09:16:01,294 - INFO - Application startup complete.
2025-07-15 09:16:01,295 - INFO - Uvicorn running on http://0.0.0.0:8005 (Press CTRL+C to quit)
2025-07-15 09:16:12,833 - INFO - Shutting down
2025-07-15 09:16:14,333 - INFO - 当前工作目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:16:14,333 - INFO - 当前文件目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:16:14,333 - INFO - 父目录: D:\Projects\reposync\repo_tag_sync_module
2025-07-15 09:16:14,333 - INFO - Python路径: ['D:\\Projects\\reposync\\repo_tag_sync_module\\web_api', 'D:\\Projects\\reposync\\repo_tag_sync_module', 'D:\\Projects\\reposync\\repo_tag_sync_module\\web_api']
2025-07-15 09:16:14,333 - INFO - github_client.py 存在: True
2025-07-15 09:16:14,333 - INFO - gitee_client.py 存在: True
2025-07-15 09:16:14,333 - INFO - gitlink_client.py 存在: True
2025-07-15 09:16:14,333 - INFO - sync_service.py 存在: True
2025-07-15 09:16:14,382 - INFO - 成功导入标签同步服务模块
2025-07-15 09:16:14,413 - INFO - Started server process [15496]
2025-07-15 09:16:14,414 - INFO - Waiting for application startup.
2025-07-15 09:16:14,414 - INFO - Application startup complete.
2025-07-15 09:16:14,414 - INFO - Uvicorn running on http://0.0.0.0:8005 (Press CTRL+C to quit)
2025-07-15 09:17:51,727 - INFO - 当前工作目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:17:51,727 - INFO - 当前文件目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:17:51,727 - INFO - 父目录: D:\Projects\reposync\repo_tag_sync_module
2025-07-15 09:17:51,727 - INFO - Python路径: ['D:\\Projects\\reposync\\repo_tag_sync_module\\web_api', 'D:\\Projects\\reposync\\repo_tag_sync_module', 'D:\\Projects\\reposync\\repo_tag_sync_module\\web_api']
2025-07-15 09:17:51,727 - INFO - github_client.py 存在: True
2025-07-15 09:17:51,727 - INFO - gitee_client.py 存在: True
2025-07-15 09:17:51,727 - INFO - gitlink_client.py 存在: True
2025-07-15 09:17:51,728 - INFO - sync_service.py 存在: True
2025-07-15 09:17:51,785 - INFO - 成功导入标签同步服务模块
2025-07-15 09:17:51,850 - INFO - Started server process [50648]
2025-07-15 09:17:51,850 - INFO - Waiting for application startup.
2025-07-15 09:17:51,850 - INFO - Application startup complete.
2025-07-15 09:17:51,851 - ERROR - [Errno 10048] error while attempting to bind on address ('0.0.0.0', 8005): 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。
2025-07-15 09:17:51,851 - INFO - Waiting for application shutdown.
2025-07-15 09:17:51,851 - INFO - Application shutdown complete.
2025-07-15 09:20:14,147 - INFO - 当前工作目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:20:14,148 - INFO - 当前文件目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:20:14,148 - INFO - 父目录: D:\Projects\reposync\repo_tag_sync_module
2025-07-15 09:20:14,148 - INFO - Python路径: ['D:\\Projects\\reposync\\repo_tag_sync_module\\web_api', 'D:\\Projects\\reposync\\repo_tag_sync_module', 'D:\\Projects\\reposync\\repo_tag_sync_module\\web_api']
2025-07-15 09:20:14,148 - INFO - github_client.py 存在: True
2025-07-15 09:20:14,148 - INFO - gitee_client.py 存在: True
2025-07-15 09:20:14,148 - INFO - gitlink_client.py 存在: True
2025-07-15 09:20:14,148 - INFO - sync_service.py 存在: True
2025-07-15 09:20:14,196 - INFO - 成功导入标签同步服务模块
2025-07-15 09:20:14,229 - INFO - Started server process [9564]
2025-07-15 09:20:14,229 - INFO - Waiting for application startup.
2025-07-15 09:20:14,229 - INFO - Application startup complete.
2025-07-15 09:20:14,230 - INFO - Uvicorn running on http://0.0.0.0:8005 (Press CTRL+C to quit)
2025-07-15 09:21:29,164 - INFO - >> 创建平台客户端...
2025-07-15 09:21:29,164 - INFO - >> 初始化同步服务...
2025-07-15 09:21:29,164 - INFO - >>>> 开始 GitLink ↔ Gitee 双向标签同步...
2025-07-15 09:21:29,164 - INFO - ======================================================================
2025-07-15 09:21:29,164 - INFO - 📍 第一步GitLink → Gitee
2025-07-15 09:21:29,165 - INFO - >> 开始从 GitLink 同步标签到 Gitee...
2025-07-15 09:21:29,165 - INFO - ============================================================
2025-07-15 09:21:29,165 - INFO - >> 获取 GitLink 标签...
2025-07-15 09:21:29,927 - INFO - [OK] GitLink 共有 20 个标签
2025-07-15 09:21:29,927 - INFO -
>> 获取 Gitee 标签...
2025-07-15 09:21:30,492 - INFO - [OK] Gitee 共有 20 个标签
2025-07-15 09:21:30,492 - INFO -
>> 需要同步 0 个标签到 Gitee:
2025-07-15 09:21:30,493 - INFO - [OK] 所有标签都已同步,无需操作
2025-07-15 09:21:30,493 - INFO -
==================================================
2025-07-15 09:21:30,493 - INFO - 📍 第二步Gitee → GitLink
2025-07-15 09:21:30,493 - INFO - >> 开始从 Gitee 同步标签到 GitLink...
2025-07-15 09:21:30,493 - INFO - ============================================================
2025-07-15 09:21:30,493 - INFO - >> 获取 Gitee 标签...
2025-07-15 09:21:31,215 - INFO - [OK] Gitee 共有 20 个标签
2025-07-15 09:21:31,215 - INFO -
>> 获取 GitLink 标签...
2025-07-15 09:21:32,077 - INFO - [OK] GitLink 共有 20 个标签
2025-07-15 09:21:32,077 - INFO -
>> 需要同步 0 个标签到 GitLink:
2025-07-15 09:21:32,077 - INFO - [OK] 所有标签都已同步,无需操作
2025-07-15 09:21:32,077 - INFO -
======================================================================
2025-07-15 09:21:32,077 - INFO - 📊 双向同步总结:
2025-07-15 09:21:32,077 - INFO - GitLink → Gitee: 同步 0 个, 失败 0 个
2025-07-15 09:21:32,077 - INFO - Gitee → GitLink: 同步 0 个, 失败 0 个
2025-07-15 09:21:32,077 - INFO - 总体状态: ✅ 成功
2025-07-15 09:21:32,077 - INFO - 后台双向同步任务完成: 双向同步完成,同步了 0 个标签
2025-07-15 09:25:21,764 - INFO - 当前工作目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:25:21,765 - INFO - 当前文件目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:25:21,765 - INFO - 父目录: D:\Projects\reposync\repo_tag_sync_module
2025-07-15 09:25:21,765 - INFO - Python路径: ['D:\\Projects\\reposync\\repo_tag_sync_module\\web_api', 'D:\\Projects\\reposync\\repo_tag_sync_module', 'D:\\Projects\\reposync\\repo_tag_sync_module\\web_api']
2025-07-15 09:25:21,765 - INFO - github_client.py 存在: True
2025-07-15 09:25:21,765 - INFO - gitee_client.py 存在: True
2025-07-15 09:25:21,765 - INFO - gitlink_client.py 存在: True
2025-07-15 09:25:21,765 - INFO - sync_service.py 存在: True
2025-07-15 09:25:21,817 - INFO - 成功导入标签同步服务模块
2025-07-15 09:25:21,852 - INFO - Started server process [49656]
2025-07-15 09:25:21,853 - INFO - Waiting for application startup.
2025-07-15 09:25:21,853 - INFO - Application startup complete.
2025-07-15 09:25:21,853 - INFO - Uvicorn running on http://0.0.0.0:8005 (Press CTRL+C to quit)
2025-07-15 09:31:58,887 - INFO - >> 创建平台客户端...
2025-07-15 09:31:58,887 - INFO - >> 初始化同步服务...
2025-07-15 09:31:58,887 - INFO - >>>> 开始 GitLink ↔ Gitee 双向标签同步...
2025-07-15 09:31:58,887 - INFO - ======================================================================
2025-07-15 09:31:58,887 - INFO - 📍 第一步GitLink → Gitee
2025-07-15 09:31:58,887 - INFO - >> 开始从 GitLink 同步标签到 Gitee...
2025-07-15 09:31:58,887 - INFO - ============================================================
2025-07-15 09:31:58,887 - INFO - >> 获取 GitLink 标签...
2025-07-15 09:31:59,790 - INFO - [OK] GitLink 共有 20 个标签
2025-07-15 09:31:59,790 - INFO -
>> 获取 Gitee 标签...
2025-07-15 09:32:00,369 - INFO - [OK] Gitee 共有 20 个标签
2025-07-15 09:32:00,370 - INFO -
>> 需要同步 0 个标签到 Gitee:
2025-07-15 09:32:00,370 - INFO - [OK] 所有标签都已同步,无需操作
2025-07-15 09:32:00,370 - INFO -
==================================================
2025-07-15 09:32:00,370 - INFO - 📍 第二步Gitee → GitLink
2025-07-15 09:32:00,370 - INFO - >> 开始从 Gitee 同步标签到 GitLink...
2025-07-15 09:32:00,370 - INFO - ============================================================
2025-07-15 09:32:00,370 - INFO - >> 获取 Gitee 标签...
2025-07-15 09:32:00,790 - INFO - [OK] Gitee 共有 20 个标签
2025-07-15 09:32:00,790 - INFO -
>> 获取 GitLink 标签...
2025-07-15 09:32:01,577 - INFO - [OK] GitLink 共有 20 个标签
2025-07-15 09:32:01,577 - INFO -
>> 需要同步 0 个标签到 GitLink:
2025-07-15 09:32:01,577 - INFO - [OK] 所有标签都已同步,无需操作
2025-07-15 09:32:01,577 - INFO -
======================================================================
2025-07-15 09:32:01,577 - INFO - 📊 双向同步总结:
2025-07-15 09:32:01,577 - INFO - GitLink → Gitee: 同步 0 个, 失败 0 个
2025-07-15 09:32:01,577 - INFO - Gitee → GitLink: 同步 0 个, 失败 0 个
2025-07-15 09:32:01,577 - INFO - 总体状态: ✅ 成功
2025-07-15 09:32:01,577 - INFO - 后台双向同步任务完成: 双向同步完成,同步了 0 个标签
2025-07-15 09:36:24,669 - INFO - >> 创建平台客户端...
2025-07-15 09:36:24,669 - INFO - >> 初始化同步服务...
2025-07-15 09:36:24,669 - INFO - >>>> 开始 GitLink ↔ Gitee 双向标签同步...
2025-07-15 09:36:24,669 - INFO - ======================================================================
2025-07-15 09:36:24,669 - INFO - 📍 第一步GitLink → Gitee
2025-07-15 09:36:24,669 - INFO - >> 开始从 GitLink 同步标签到 Gitee...
2025-07-15 09:36:24,669 - INFO - ============================================================
2025-07-15 09:36:24,669 - INFO - >> 获取 GitLink 标签...
2025-07-15 09:36:25,528 - INFO - [OK] GitLink 共有 20 个标签
2025-07-15 09:36:25,528 - INFO -
>> 获取 Gitee 标签...
2025-07-15 09:36:25,988 - INFO - [OK] Gitee 共有 20 个标签
2025-07-15 09:36:25,988 - INFO -
>> 需要同步 0 个标签到 Gitee:
2025-07-15 09:36:25,988 - INFO - [OK] 所有标签都已同步,无需操作
2025-07-15 09:36:25,988 - INFO -
==================================================
2025-07-15 09:36:25,988 - INFO - 📍 第二步Gitee → GitLink
2025-07-15 09:36:25,989 - INFO - >> 开始从 Gitee 同步标签到 GitLink...
2025-07-15 09:36:25,989 - INFO - ============================================================
2025-07-15 09:36:25,989 - INFO - >> 获取 Gitee 标签...
2025-07-15 09:36:26,441 - INFO - [OK] Gitee 共有 20 个标签
2025-07-15 09:36:26,441 - INFO -
>> 获取 GitLink 标签...
2025-07-15 09:36:27,366 - INFO - [OK] GitLink 共有 20 个标签
2025-07-15 09:36:27,366 - INFO -
>> 需要同步 0 个标签到 GitLink:
2025-07-15 09:36:27,366 - INFO - [OK] 所有标签都已同步,无需操作
2025-07-15 09:36:27,366 - INFO -
======================================================================
2025-07-15 09:36:27,366 - INFO - 📊 双向同步总结:
2025-07-15 09:36:27,366 - INFO - GitLink → Gitee: 同步 0 个, 失败 0 个
2025-07-15 09:36:27,367 - INFO - Gitee → GitLink: 同步 0 个, 失败 0 个
2025-07-15 09:36:27,367 - INFO - 总体状态: ✅ 成功
2025-07-15 09:36:27,367 - INFO - 后台双向同步任务完成: 双向同步完成,同步了 0 个标签
2025-07-15 09:36:37,026 - INFO - Shutting down
2025-07-15 09:36:43,346 - INFO - 当前工作目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:36:43,346 - INFO - 当前文件目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:36:43,346 - INFO - 父目录: D:\Projects\reposync\repo_tag_sync_module
2025-07-15 09:36:43,346 - INFO - Python路径: ['D:\\Projects\\reposync\\repo_tag_sync_module\\web_api', 'D:\\Projects\\reposync\\repo_tag_sync_module', 'D:\\Projects\\reposync\\repo_tag_sync_module\\web_api']
2025-07-15 09:36:43,346 - INFO - github_client.py 存在: True
2025-07-15 09:36:43,346 - INFO - gitee_client.py 存在: True
2025-07-15 09:36:43,346 - INFO - gitlink_client.py 存在: True
2025-07-15 09:36:43,346 - INFO - sync_service.py 存在: True
2025-07-15 09:36:43,391 - INFO - 成功导入标签同步服务模块
2025-07-15 09:37:54,676 - INFO - 当前工作目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:37:54,676 - INFO - 当前文件目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:37:54,676 - INFO - 父目录: D:\Projects\reposync\repo_tag_sync_module
2025-07-15 09:37:54,676 - INFO - Python路径: ['D:\\Projects\\reposync\\repo_tag_sync_module\\web_api', 'D:\\Projects\\reposync\\repo_tag_sync_module', 'D:\\Projects\\reposync\\repo_tag_sync_module\\web_api']
2025-07-15 09:37:54,676 - INFO - github_client.py 存在: True
2025-07-15 09:37:54,676 - INFO - gitee_client.py 存在: True
2025-07-15 09:37:54,676 - INFO - gitlink_client.py 存在: True
2025-07-15 09:37:54,677 - INFO - sync_service.py 存在: True
2025-07-15 09:37:54,724 - INFO - 成功导入标签同步服务模块
2025-07-15 09:38:47,614 - INFO - 当前工作目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:38:47,614 - INFO - 当前文件目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:38:47,614 - INFO - 父目录: D:\Projects\reposync\repo_tag_sync_module
2025-07-15 09:38:47,614 - INFO - Python路径: ['D:\\Projects\\reposync\\repo_tag_sync_module\\web_api', 'D:\\Projects\\reposync\\repo_tag_sync_module', 'D:\\Projects\\reposync\\repo_tag_sync_module\\web_api']
2025-07-15 09:38:47,614 - INFO - github_client.py 存在: True
2025-07-15 09:38:47,614 - INFO - gitee_client.py 存在: True
2025-07-15 09:38:47,614 - INFO - gitlink_client.py 存在: True
2025-07-15 09:38:47,614 - INFO - sync_service.py 存在: True
2025-07-15 09:38:47,665 - INFO - 成功导入标签同步服务模块
2025-07-15 09:38:47,699 - INFO - Started server process [54564]
2025-07-15 09:38:47,700 - INFO - Waiting for application startup.
2025-07-15 09:38:47,700 - INFO - Application startup complete.
2025-07-15 09:38:47,700 - INFO - Uvicorn running on http://0.0.0.0:8005 (Press CTRL+C to quit)
2025-07-15 09:39:19,297 - INFO - >> 创建平台客户端...
2025-07-15 09:39:19,297 - INFO - >> 初始化同步服务...
2025-07-15 09:39:19,297 - INFO - >>>> 开始 GitLink ↔ Gitee 双向标签同步...
2025-07-15 09:39:19,298 - INFO - ======================================================================
2025-07-15 09:39:19,298 - INFO - 📍 第一步GitLink → Gitee
2025-07-15 09:39:19,298 - INFO - >> 开始从 GitLink 同步标签到 Gitee...
2025-07-15 09:39:19,298 - INFO - ============================================================
2025-07-15 09:39:19,298 - INFO - >> 获取 GitLink 标签...
2025-07-15 09:39:20,070 - INFO - [OK] GitLink 共有 20 个标签
2025-07-15 09:39:20,071 - INFO -
>> 获取 Gitee 标签...
2025-07-15 09:39:20,625 - INFO - [OK] Gitee 共有 20 个标签
2025-07-15 09:39:20,625 - INFO -
>> 需要同步 0 个标签到 Gitee:
2025-07-15 09:39:20,625 - INFO - [OK] 所有标签都已同步,无需操作
2025-07-15 09:39:20,625 - INFO -
==================================================
2025-07-15 09:39:20,625 - INFO - 📍 第二步Gitee → GitLink
2025-07-15 09:39:20,625 - INFO - >> 开始从 Gitee 同步标签到 GitLink...
2025-07-15 09:39:20,625 - INFO - ============================================================
2025-07-15 09:39:20,625 - INFO - >> 获取 Gitee 标签...
2025-07-15 09:39:21,142 - INFO - [OK] Gitee 共有 20 个标签
2025-07-15 09:39:21,142 - INFO -
>> 获取 GitLink 标签...
2025-07-15 09:39:22,067 - INFO - [OK] GitLink 共有 20 个标签
2025-07-15 09:39:22,067 - INFO -
>> 需要同步 0 个标签到 GitLink:
2025-07-15 09:39:22,067 - INFO - [OK] 所有标签都已同步,无需操作
2025-07-15 09:39:22,067 - INFO -
======================================================================
2025-07-15 09:39:22,067 - INFO - 📊 双向同步总结:
2025-07-15 09:39:22,067 - INFO - GitLink → Gitee: 同步 0 个, 失败 0 个
2025-07-15 09:39:22,067 - INFO - Gitee → GitLink: 同步 0 个, 失败 0 个
2025-07-15 09:39:22,067 - INFO - 总体状态: ✅ 成功
2025-07-15 09:39:22,067 - INFO - 后台双向同步任务完成: 双向同步完成,同步了 0 个标签
2025-07-15 09:41:18,439 - INFO - Shutting down
2025-07-15 09:42:58,008 - INFO - 当前工作目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:42:58,009 - INFO - 当前文件目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:42:58,009 - INFO - 父目录: D:\Projects\reposync\repo_tag_sync_module
2025-07-15 09:42:58,009 - INFO - Python路径: ['D:\\Projects\\reposync\\repo_tag_sync_module\\web_api', 'D:\\Projects\\reposync\\repo_tag_sync_module', 'D:\\Projects\\reposync\\repo_tag_sync_module\\web_api']
2025-07-15 09:42:58,009 - INFO - github_client.py 存在: True
2025-07-15 09:42:58,009 - INFO - gitee_client.py 存在: True
2025-07-15 09:42:58,009 - INFO - gitlink_client.py 存在: True
2025-07-15 09:42:58,009 - INFO - sync_service.py 存在: True
2025-07-15 09:42:58,053 - INFO - 成功导入标签同步服务模块
2025-07-15 09:42:58,084 - INFO - Started server process [43756]
2025-07-15 09:42:58,085 - INFO - Waiting for application startup.
2025-07-15 09:42:58,085 - INFO - Application startup complete.
2025-07-15 09:42:58,085 - INFO - Uvicorn running on http://0.0.0.0:8005 (Press CTRL+C to quit)
2025-07-15 09:43:23,899 - INFO - >> 创建平台客户端...
2025-07-15 09:43:23,899 - INFO - >> 初始化同步服务...
2025-07-15 09:43:23,899 - INFO - >>>> 开始 GitLink ↔ Gitee 双向标签同步...
2025-07-15 09:43:23,899 - INFO - ======================================================================
2025-07-15 09:43:23,900 - INFO - 📍 第一步GitLink → Gitee
2025-07-15 09:43:23,900 - INFO - >> 开始从 GitLink 同步标签到 Gitee...
2025-07-15 09:43:23,900 - INFO - ============================================================
2025-07-15 09:43:23,900 - INFO - >> 获取 GitLink 标签...
2025-07-15 09:43:24,712 - INFO - [OK] GitLink 共有 20 个标签
2025-07-15 09:43:24,712 - INFO -
>> 获取 Gitee 标签...
2025-07-15 09:43:25,318 - INFO - [OK] Gitee 共有 20 个标签
2025-07-15 09:43:25,318 - INFO -
>> 需要同步 0 个标签到 Gitee:
2025-07-15 09:43:25,318 - INFO - [OK] 所有标签都已同步,无需操作
2025-07-15 09:43:25,318 - INFO -
==================================================
2025-07-15 09:43:25,318 - INFO - 📍 第二步Gitee → GitLink
2025-07-15 09:43:25,319 - INFO - >> 开始从 Gitee 同步标签到 GitLink...
2025-07-15 09:43:25,319 - INFO - ============================================================
2025-07-15 09:43:25,319 - INFO - >> 获取 Gitee 标签...
2025-07-15 09:43:25,934 - INFO - [OK] Gitee 共有 20 个标签
2025-07-15 09:43:25,934 - INFO -
>> 获取 GitLink 标签...
2025-07-15 09:43:26,800 - INFO - [OK] GitLink 共有 20 个标签
2025-07-15 09:43:26,800 - INFO -
>> 需要同步 0 个标签到 GitLink:
2025-07-15 09:43:26,800 - INFO - [OK] 所有标签都已同步,无需操作
2025-07-15 09:43:26,800 - INFO -
======================================================================
2025-07-15 09:43:26,800 - INFO - 📊 双向同步总结:
2025-07-15 09:43:26,800 - INFO - GitLink → Gitee: 同步 0 个, 失败 0 个
2025-07-15 09:43:26,800 - INFO - Gitee → GitLink: 同步 0 个, 失败 0 个
2025-07-15 09:43:26,800 - INFO - 总体状态: ✅ 成功
2025-07-15 09:43:26,800 - INFO - 后台双向同步任务完成: 双向同步完成,同步了 0 个标签
2025-07-15 09:45:14,868 - INFO - Shutting down
2025-07-15 09:47:14,988 - INFO - 当前工作目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:47:14,988 - INFO - 当前文件目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:47:14,988 - INFO - 父目录: D:\Projects\reposync\repo_tag_sync_module
2025-07-15 09:47:14,988 - INFO - Python路径: ['D:\\Projects\\reposync\\repo_tag_sync_module\\web_api', 'D:\\Projects\\reposync\\repo_tag_sync_module', 'D:\\Projects\\reposync\\repo_tag_sync_module\\web_api']
2025-07-15 09:47:14,988 - INFO - github_client.py 存在: True
2025-07-15 09:47:14,988 - INFO - gitee_client.py 存在: True
2025-07-15 09:47:14,988 - INFO - gitlink_client.py 存在: True
2025-07-15 09:47:14,989 - INFO - sync_service.py 存在: True
2025-07-15 09:47:15,032 - INFO - 成功导入标签同步服务模块
2025-07-15 09:47:15,063 - INFO - Started server process [46036]
2025-07-15 09:47:15,063 - INFO - Waiting for application startup.
2025-07-15 09:47:15,063 - INFO - Application startup complete.
2025-07-15 09:47:15,064 - INFO - Uvicorn running on http://0.0.0.0:8005 (Press CTRL+C to quit)
2025-07-15 09:52:21,730 - INFO - Shutting down

View File

@ -0,0 +1,609 @@
# coding: utf-8
# 标签同步API接口文件定义了标签同步的API接口包括GitHub、Gitee、GitLink之间的双向同步
import time
import asyncio
import logging
import sys
import os
from typing import Optional, List, Union, Any
from collections import deque
import threading
from datetime import datetime
from fastapi import (
BackgroundTasks,
Query,
Depends,
Security,
Body,
APIRouter
)
from pydantic import BaseModel
# 全局日志缓存
class LogCache:
def __init__(self, max_size=1000):
self.logs = deque(maxlen=max_size)
self.lock = threading.Lock()
def add_log(self, message, level="INFO"):
with self.lock:
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.logs.append({
"timestamp": timestamp,
"level": level,
"message": message
})
def get_logs(self, since_timestamp=None):
with self.lock:
if since_timestamp is None:
return list(self.logs)
else:
# 返回指定时间戳之后的日志
result = []
for log in self.logs:
if log["timestamp"] > since_timestamp:
result.append(log)
return result
def clear(self):
with self.lock:
self.logs.clear()
# 全局日志缓存实例
log_cache = LogCache()
# 自定义日志处理器,将日志同时写入文件和缓存
class CacheHandler(logging.Handler):
def emit(self, record):
try:
msg = self.format(record)
log_cache.add_log(msg, record.levelname)
except Exception:
pass
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout),
logging.FileHandler('tag_sync.log', encoding='utf-8'),
CacheHandler() # 添加缓存处理器
]
)
logger = logging.getLogger(__name__)
# 添加父目录到Python路径
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
if parent_dir not in sys.path:
sys.path.insert(0, parent_dir)
# 导入标签同步模块 - 修复为绝对导入
try:
# 尝试多种导入方式
logger.info(f"当前工作目录: {os.getcwd()}")
logger.info(f"当前文件目录: {current_dir}")
logger.info(f"父目录: {parent_dir}")
logger.info(f"Python路径: {sys.path[:3]}")
# 检查文件是否存在
github_client_path = os.path.join(parent_dir, 'github_client.py')
gitee_client_path = os.path.join(parent_dir, 'gitee_client.py')
gitlink_client_path = os.path.join(parent_dir, 'gitlink_client.py')
sync_service_path = os.path.join(parent_dir, 'sync_service.py')
logger.info(f"github_client.py 存在: {os.path.exists(github_client_path)}")
logger.info(f"gitee_client.py 存在: {os.path.exists(gitee_client_path)}")
logger.info(f"gitlink_client.py 存在: {os.path.exists(gitlink_client_path)}")
logger.info(f"sync_service.py 存在: {os.path.exists(sync_service_path)}")
# 尝试导入
import importlib.util
# 动态导入github_client
spec = importlib.util.spec_from_file_location("github_client", github_client_path)
github_client_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(github_client_module)
GithubClient = github_client_module.GithubClient
# 动态导入gitee_client
spec = importlib.util.spec_from_file_location("gitee_client", gitee_client_path)
gitee_client_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(gitee_client_module)
GiteeClient = gitee_client_module.GiteeClient
# 动态导入gitlink_client
spec = importlib.util.spec_from_file_location("gitlink_client", gitlink_client_path)
gitlink_client_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(gitlink_client_module)
GitlinkClient = gitlink_client_module.GitlinkClient
# 动态导入sync_service
spec = importlib.util.spec_from_file_location("sync_service", sync_service_path)
sync_service_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(sync_service_module)
TagSyncService = sync_service_module.TagSyncService
logger.info("成功导入标签同步服务模块")
IMPORT_SUCCESS = True
except ImportError as e:
logger.error(f"无法导入标签同步服务: {str(e)}")
IMPORT_SUCCESS = False
# 创建模拟的服务类,避免应用启动失败
class TagSyncService:
def __init__(self):
pass
def bidirectional_sync(self, client_a, client_b, dry_run=False):
logger.error("使用模拟的同步服务 - 导入失败")
return {
"gitlink_to_gitee": {"success": False, "synced": 0, "errors": 1},
"gitee_to_gitlink": {"success": False, "synced": 0, "errors": 1},
"overall_success": False
}
class GithubClient:
def __init__(self, *args, **kwargs):
pass
class GiteeClient:
def __init__(self, *args, **kwargs):
pass
class GitlinkClient:
def __init__(self, *args, **kwargs):
pass
except Exception as e:
logger.error(f"导入过程中发生异常: {str(e)}")
IMPORT_SUCCESS = False
# 创建模拟的服务类
class TagSyncService:
def __init__(self):
pass
def bidirectional_sync(self, client_a, client_b, dry_run=False):
logger.error("使用模拟的同步服务 - 导入异常")
return {
"gitlink_to_gitee": {"success": False, "synced": 0, "errors": 1},
"gitee_to_gitlink": {"success": False, "synced": 0, "errors": 1},
"overall_success": False
}
class GithubClient:
def __init__(self, *args, **kwargs):
pass
class GiteeClient:
def __init__(self, *args, **kwargs):
pass
class GitlinkClient:
def __init__(self, *args, **kwargs):
pass
class TagSyncConfig(BaseModel):
"""平台配置模型"""
platform: str # github, gitee, gitlink
org: str
repo: str
token: Optional[str] = None # GitHub/Gitee token
cookie: Optional[str] = None # GitLink cookie
class TagSyncRequest(BaseModel):
"""标签同步请求模型"""
source_platform: str # github, gitee, gitlink
source_org: str
source_repo: str
source_token: Optional[str] = None # GitHub/Gitee token
source_cookie: Optional[str] = None # GitLink cookie
target_platform: str # github, gitee, gitlink
target_org: str
target_repo: str
target_token: Optional[str] = None # GitHub/Gitee token
target_cookie: Optional[str] = None # GitLink cookie
dry_run: bool = True # 默认试运行
class BiDirectionalSyncRequest(BaseModel):
"""双向同步请求模型"""
platform_a: TagSyncConfig
platform_b: TagSyncConfig
dry_run: bool = True
class TagSyncResult(BaseModel):
"""标签同步结果模型"""
success: bool
message: str
synced_count: int = 0
error_count: int = 0
details: dict = {}
timestamp: str
class APIResponse(BaseModel):
"""API响应模型"""
data: Optional[Any] = None
message: str = "success"
code: int = 0
async def _execute_bidirectional_sync(request: BiDirectionalSyncRequest) -> TagSyncResult:
"""执行双向同步的核心逻辑"""
try:
logger.info(">> 创建平台客户端...")
# 创建客户端
client_a = _create_client(request.platform_a)
client_b = _create_client(request.platform_b)
logger.info(">> 初始化同步服务...")
# 创建同步服务
sync_service = TagSyncService()
# 执行双向同步
if request.platform_a.platform.lower() == "gitlink" and request.platform_b.platform.lower() == "gitee":
results = sync_service.bidirectional_sync(
client_a, client_b,
dry_run=request.dry_run
)
elif request.platform_a.platform.lower() == "gitee" and request.platform_b.platform.lower() == "gitlink":
results = sync_service.bidirectional_sync(
client_b, client_a, # 交换顺序
dry_run=request.dry_run
)
else:
# 其他平台组合的同步逻辑
results = _handle_other_platform_sync(
client_a, client_b, request.platform_a.platform, request.platform_b.platform,
sync_service, request.dry_run
)
total_synced = results.get("gitlink_to_gitee", {}).get("synced", 0) + \
results.get("gitee_to_gitlink", {}).get("synced", 0)
total_errors = results.get("gitlink_to_gitee", {}).get("errors", 0) + \
results.get("gitee_to_gitlink", {}).get("errors", 0)
return TagSyncResult(
success=results.get("overall_success", False),
message=f"双向同步完成,同步了 {total_synced} 个标签",
synced_count=total_synced,
error_count=total_errors,
details=results,
timestamp=time.strftime("%Y-%m-%d %H:%M:%S")
)
except Exception as e:
return TagSyncResult(
success=False,
message=f"双向同步执行失败: {str(e)}",
timestamp=time.strftime("%Y-%m-%d %H:%M:%S")
)
async def _execute_oneway_sync(request: TagSyncRequest) -> TagSyncResult:
"""执行单向同步的核心逻辑"""
try:
# 创建客户端
source_client = _create_client_from_request(request, "source")
target_client = _create_client_from_request(request, "target")
# 创建同步服务
sync_service = TagSyncService()
# 根据平台执行相应的同步方法
source_platform = request.source_platform.lower()
target_platform = request.target_platform.lower()
if source_platform == "gitlink" and target_platform == "gitee":
results = sync_service.sync_gitlink_to_gitee(
source_client, target_client,
dry_run=request.dry_run
)
elif source_platform == "gitee" and target_platform == "gitlink":
results = sync_service.sync_gitee_to_gitlink(
source_client, target_client,
dry_run=request.dry_run
)
else:
return TagSyncResult(
success=False,
message=f"暂不支持 {source_platform}{target_platform} 的同步",
timestamp=time.strftime("%Y-%m-%d %H:%M:%S")
)
return TagSyncResult(
success=results.get("errors", 0) == 0,
message=f"单向同步完成,同步了 {results.get('synced', 0)} 个标签",
synced_count=results.get("synced", 0),
error_count=results.get("errors", 0),
details=results,
timestamp=time.strftime("%Y-%m-%d %H:%M:%S")
)
except Exception as e:
return TagSyncResult(
success=False,
message=f"单向同步执行失败: {str(e)}",
timestamp=time.strftime("%Y-%m-%d %H:%M:%S")
)
def _create_client(platform_config: TagSyncConfig):
"""根据平台配置创建客户端"""
platform = platform_config.platform.lower()
if platform == "github":
return GithubClient(platform_config.org, platform_config.repo, platform_config.token)
elif platform == "gitee":
return GiteeClient(platform_config.org, platform_config.repo, platform_config.token)
elif platform == "gitlink":
return GitlinkClient(platform_config.org, platform_config.repo, platform_config.cookie)
else:
raise ValueError(f"不支持的平台: {platform}")
def _create_client_from_request(request: TagSyncRequest, prefix: str):
"""从请求创建客户端"""
if prefix == "source":
platform = request.source_platform.lower()
org = request.source_org
repo = request.source_repo
token = request.source_token
cookie = request.source_cookie
else: # target
platform = request.target_platform.lower()
org = request.target_org
repo = request.target_repo
token = request.target_token
cookie = request.target_cookie
if platform == "github":
return GithubClient(org, repo, token)
elif platform == "gitee":
return GiteeClient(org, repo, token)
elif platform == "gitlink":
return GitlinkClient(org, repo, cookie)
else:
raise ValueError(f"不支持的平台: {platform}")
def _handle_other_platform_sync(client_a, client_b, platform_a, platform_b, sync_service, dry_run):
"""处理其他平台组合的同步"""
# 这里可以扩展支持其他平台组合
# 目前先返回错误
return {
"overall_success": False,
"gitlink_to_gitee": {"synced": 0, "errors": 1},
"gitee_to_gitlink": {"synced": 0, "errors": 1}
}
async def _execute_bidirectional_sync_task(request: BiDirectionalSyncRequest):
"""后台执行双向同步任务"""
try:
result = await _execute_bidirectional_sync(request)
logger.info(f"后台双向同步任务完成: {result.message}")
except Exception as e:
logger.error(f"后台双向同步任务失败: {str(e)}")
async def _execute_oneway_sync_task(request: TagSyncRequest):
"""后台执行单向同步任务"""
try:
result = await _execute_oneway_sync(request)
logger.info(f"后台单向同步任务完成: {result.message}")
except Exception as e:
logger.error(f"后台单向同步任务失败: {str(e)}")
def create_tag_sync_router() -> APIRouter:
"""创建标签同步路由"""
router = APIRouter(prefix='/tag-sync', tags=['TagSync'])
@router.get("/platforms", response_model=APIResponse, description='获取支持的标签同步平台列表')
async def get_supported_platforms():
"""获取支持的标签同步平台"""
try:
platforms = ["github", "gitee", "gitlink"]
return APIResponse(
data={"platforms": platforms},
message="获取平台列表成功",
code=0
)
except Exception as e:
return APIResponse(
data=None,
message=f"获取平台列表失败: {str(e)}",
code=1
)
@router.get("/sync-types", response_model=APIResponse, description='获取支持的同步类型')
async def get_sync_types():
"""获取支持的双向同步类型"""
try:
sync_types = [
{
"id": "gitee_gitlink",
"name": "Gitee ↔ GitLink",
"description": "Gitee和GitLink之间的双向标签同步",
"platforms": ["gitee", "gitlink"]
},
{
"id": "github_gitlink",
"name": "GitHub ↔ GitLink",
"description": "GitHub和GitLink之间的双向标签同步",
"platforms": ["github", "gitlink"]
},
{
"id": "github_gitee",
"name": "GitHub ↔ Gitee",
"description": "GitHub和Gitee之间的双向标签同步",
"platforms": ["github", "gitee"]
}
]
return APIResponse(
data={"sync_types": sync_types},
message="获取同步类型成功",
code=0
)
except Exception as e:
return APIResponse(
data={"sync_types": []},
message=f"获取同步类型失败: {str(e)}",
code=1
)
@router.post("/sync/bidirectional", response_model=APIResponse, description='执行双向标签同步')
async def bidirectional_sync(
background_tasks: BackgroundTasks,
request: BiDirectionalSyncRequest = Body(..., description='双向同步请求参数')
):
"""执行双向标签同步"""
try:
# 验证平台组合
platform_a = request.platform_a.platform.lower()
platform_b = request.platform_b.platform.lower()
valid_combinations = [
("gitee", "gitlink"), ("gitlink", "gitee"),
("github", "gitlink"), ("gitlink", "github"),
("github", "gitee"), ("gitee", "github")
]
if (platform_a, platform_b) not in valid_combinations:
return APIResponse(
data=TagSyncResult(
success=False,
message=f"不支持的平台组合: {platform_a}{platform_b}",
timestamp=time.strftime("%Y-%m-%d %H:%M:%S")
).dict(),
message="不支持的平台组合",
code=1
)
# 创建同步任务
if request.dry_run:
# 试运行模式 - 立即执行
result = await _execute_bidirectional_sync(request)
return APIResponse(
data=result.dict(),
message="双向同步试运行完成",
code=0
)
else:
# 实际执行 - 后台任务
background_tasks.add_task(
_execute_bidirectional_sync_task,
request
)
return APIResponse(
data=TagSyncResult(
success=True,
message="双向标签同步任务已启动,正在后台执行",
timestamp=time.strftime("%Y-%m-%d %H:%M:%S")
).dict(),
message="同步任务已启动",
code=0
)
except Exception as e:
return APIResponse(
data=TagSyncResult(
success=False,
message=f"双向同步失败: {str(e)}",
timestamp=time.strftime("%Y-%m-%d %H:%M:%S")
).dict(),
message="双向同步失败",
code=1
)
@router.post("/sync/oneway", response_model=APIResponse, description='执行单向标签同步')
async def oneway_sync(
background_tasks: BackgroundTasks,
request: TagSyncRequest = Body(..., description='单向同步请求参数')
):
"""执行单向标签同步"""
try:
# 创建同步任务
if request.dry_run:
# 试运行模式 - 立即执行
result = await _execute_oneway_sync(request)
return APIResponse(
data=result.dict(),
message="单向同步试运行完成",
code=0
)
else:
# 实际执行 - 后台任务
background_tasks.add_task(
_execute_oneway_sync_task,
request
)
return APIResponse(
data=TagSyncResult(
success=True,
message="单向标签同步任务已启动,正在后台执行",
timestamp=time.strftime("%Y-%m-%d %H:%M:%S")
).dict(),
message="同步任务已启动",
code=0
)
except Exception as e:
return APIResponse(
data=TagSyncResult(
success=False,
message=f"单向同步失败: {str(e)}",
timestamp=time.strftime("%Y-%m-%d %H:%M:%S")
).dict(),
message="单向同步失败",
code=1
)
# 日志相关API端点
@router.get("/logs", response_model=APIResponse, description='获取同步日志')
async def get_logs(
since: Optional[str] = Query(None, description='获取指定时间戳之后的日志')
):
"""获取同步日志"""
try:
logs = log_cache.get_logs(since)
return APIResponse(
data={"logs": logs},
message=f"获取到 {len(logs)} 条日志",
code=0
)
except Exception as e:
return APIResponse(
data={"logs": []},
message=f"获取日志失败: {str(e)}",
code=1
)
@router.delete("/logs", response_model=APIResponse, description='清空同步日志')
async def clear_logs():
"""清空同步日志"""
try:
log_cache.clear()
return APIResponse(
data=None,
message="日志已清空",
code=0
)
except Exception as e:
return APIResponse(
data=None,
message=f"清空日志失败: {str(e)}",
code=1
)
return router

35
start_tag_sync.py Normal file
View File

@ -0,0 +1,35 @@
#!/usr/bin/env python3
# coding: utf-8
"""
标签同步服务快捷启动脚本
调用 repo_tag_sync_module 模块中的独立服务
"""
import sys
import subprocess
from pathlib import Path
def main():
"""启动标签同步服务"""
print("标签同步服务快捷启动")
print("=" * 50)
print("启动独立模块中的标签同步服务...")
print()
try:
# 调用模块中的启动脚本
subprocess.run([
sys.executable,
"-m",
"repo_tag_sync_module.start_tag_sync"
], check=True)
except subprocess.CalledProcessError as e:
print(f"启动失败: {e}")
sys.exit(1)
except KeyboardInterrupt:
print("\n用户取消启动")
sys.exit(0)
if __name__ == "__main__":
main()

50
tag_sync.log Normal file
View File

@ -0,0 +1,50 @@
2025-07-14 20:02:02,818 - INFO - Started server process [27788]
2025-07-14 20:02:02,818 - INFO - Waiting for application startup.
2025-07-14 20:02:02,818 - INFO - Application startup complete.
2025-07-14 20:02:02,818 - INFO - Uvicorn running on http://0.0.0.0:8005 (Press CTRL+C to quit)
2025-07-14 20:03:17,935 - INFO - >> 创建平台客户端...
2025-07-14 20:03:17,936 - INFO - >> 初始化同步服务...
2025-07-15 09:14:12,535 - INFO - 当前工作目录: D:\Projects\reposync
2025-07-15 09:14:12,535 - INFO - 当前文件目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:14:12,535 - INFO - 父目录: D:\Projects\reposync\repo_tag_sync_module
2025-07-15 09:14:12,535 - INFO - Python路径: ['D:\\Projects\\reposync\\repo_tag_sync_module\\web_api', 'D:\\Projects\\reposync\\repo_tag_sync_module', 'D:\\Projects\\reposync\\repo_tag_sync_module']
2025-07-15 09:14:12,535 - INFO - github_client.py 存在: True
2025-07-15 09:14:12,535 - INFO - gitee_client.py 存在: True
2025-07-15 09:14:12,535 - INFO - gitlink_client.py 存在: True
2025-07-15 09:14:12,535 - INFO - sync_service.py 存在: True
2025-07-15 09:14:12,589 - INFO - 成功导入标签同步服务模块
2025-07-15 09:15:23,282 - INFO - 当前工作目录: D:\Projects\reposync
2025-07-15 09:15:23,282 - INFO - 当前文件目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:15:23,282 - INFO - 父目录: D:\Projects\reposync\repo_tag_sync_module
2025-07-15 09:15:23,282 - INFO - Python路径: ['D:\\Projects\\reposync\\repo_tag_sync_module\\web_api', 'D:\\Projects\\reposync\\repo_tag_sync_module', 'D:\\Projects\\reposync\\repo_tag_sync_module']
2025-07-15 09:15:23,282 - INFO - github_client.py 存在: True
2025-07-15 09:15:23,282 - INFO - gitee_client.py 存在: True
2025-07-15 09:15:23,283 - INFO - gitlink_client.py 存在: True
2025-07-15 09:15:23,283 - INFO - sync_service.py 存在: True
2025-07-15 09:15:23,338 - INFO - 成功导入标签同步服务模块
2025-07-15 09:15:23,342 - INFO - 当前工作目录: D:\Projects\reposync
2025-07-15 09:15:23,342 - INFO - 当前文件目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:15:23,343 - INFO - 父目录: D:\Projects\reposync\repo_tag_sync_module
2025-07-15 09:15:23,343 - INFO - Python路径: ['D:\\Projects\\reposync\\repo_tag_sync_module\\web_api', 'D:\\Projects\\reposync\\repo_tag_sync_module', 'D:\\Projects\\reposync\\repo_tag_sync_module']
2025-07-15 09:15:23,343 - INFO - github_client.py 存在: True
2025-07-15 09:15:23,343 - INFO - gitee_client.py 存在: True
2025-07-15 09:15:23,344 - INFO - gitlink_client.py 存在: True
2025-07-15 09:15:23,344 - INFO - sync_service.py 存在: True
2025-07-15 09:15:23,345 - INFO - 成功导入标签同步服务模块
2025-07-15 09:15:23,382 - INFO - Started server process [6528]
2025-07-15 09:15:23,383 - INFO - Waiting for application startup.
2025-07-15 09:15:23,383 - INFO - Application startup complete.
2025-07-15 09:15:23,384 - INFO - Uvicorn running on http://0.0.0.0:8005 (Press CTRL+C to quit)
2025-07-15 09:15:54,726 - INFO - Shutting down
2025-07-15 09:15:54,835 - INFO - Waiting for application shutdown.
2025-07-15 09:15:54,835 - INFO - Application shutdown complete.
2025-07-15 09:15:54,835 - INFO - Finished server process [6528]
2025-07-15 09:30:26,580 - INFO - 当前工作目录: D:\Projects\reposync
2025-07-15 09:30:26,581 - INFO - 当前文件目录: D:\Projects\reposync\repo_tag_sync_module\web_api
2025-07-15 09:30:26,581 - INFO - 父目录: D:\Projects\reposync\repo_tag_sync_module
2025-07-15 09:30:26,582 - INFO - Python路径: ['D:\\Projects\\reposync\\repo_tag_sync_module\\web_api', 'D:\\Projects\\reposync\\repo_tag_sync_module', 'D:\\Software\\Pycharm\\Anaconda\\envs\\reposyncer\\python39.zip']
2025-07-15 09:30:26,582 - INFO - github_client.py 存在: True
2025-07-15 09:30:26,582 - INFO - gitee_client.py 存在: True
2025-07-15 09:30:26,582 - INFO - gitlink_client.py 存在: True
2025-07-15 09:30:26,582 - INFO - sync_service.py 存在: True
2025-07-15 09:30:26,583 - INFO - 成功导入标签同步服务模块