Merge remote-tracking branch 'origin/main'

This commit is contained in:
高大 2026-01-28 09:22:05 +08:00
commit 616838a9a8
5 changed files with 196 additions and 28 deletions

View File

@ -12,6 +12,8 @@ main_repository:
monitor: monitor:
poll_interval: 10 # 轮询间隔(秒) poll_interval: 10 # 轮询间隔(秒)
enabled_repos: [] # 空数组表示监听所有仓库,或指定具体仓库名称列表 enabled_repos: [] # 空数组表示监听所有仓库,或指定具体仓库名称列表
watch_tags: true # 是否监听 tag 变化(打 tag 时触发部署)
tag_pattern: "v*" # 监听的 tag 模式,支持通配符(如 v*、release-*
# 部署配置 # 部署配置
deploy: deploy:

View File

@ -32,10 +32,13 @@ class GitMonitor:
self.config_path = config_path self.config_path = config_path
self.config = None self.config = None
self.last_commits = {} self.last_commits = {}
self.last_tags = {} # 记录每个仓库的最新 tag
self.global_branch = 'main' self.global_branch = 'main'
self.project_root = None self.project_root = None
self.runtime_path = None self.runtime_path = None
self.dingtalk_notifier = None self.dingtalk_notifier = None
self.watch_tags = False
self.tag_pattern = "v*"
# 初始化 # 初始化
self._print_startup_banner() self._print_startup_banner()
@ -62,6 +65,11 @@ class GitMonitor:
self.global_branch = self.config.get('global_branch', 'main') self.global_branch = self.config.get('global_branch', 'main')
# 加载 tag 监听配置
monitor_config = self.config.get('monitor', {})
self.watch_tags = monitor_config.get('watch_tags', False)
self.tag_pattern = monitor_config.get('tag_pattern', 'v*')
# 初始化日志配置 # 初始化日志配置
log_config = self.config.get('logging', {}) log_config = self.config.get('logging', {})
log_file = log_config.get('file', '.devops/logs/devops.log') log_file = log_config.get('file', '.devops/logs/devops.log')
@ -69,6 +77,7 @@ class GitMonitor:
Logger.init(log_file=log_file, max_size=max_size) Logger.init(log_file=log_file, max_size=max_size)
Logger.info(f"✓ 配置加载成功 - 全局分支: {self.global_branch}") Logger.info(f"✓ 配置加载成功 - 全局分支: {self.global_branch}")
Logger.info(f"✓ Tag 监听: {'已启用' if self.watch_tags else '未启用'} (模式: {self.tag_pattern})")
Logger.info(f"✓ 日志配置 - 文件: {log_file}, 最大大小: {max_size} 字节") Logger.info(f"✓ 日志配置 - 文件: {log_file}, 最大大小: {max_size} 字节")
except Exception as e: except Exception as e:
Logger.error(f"配置加载失败: {e}") Logger.error(f"配置加载失败: {e}")
@ -154,6 +163,32 @@ class GitMonitor:
Logger.error(f"获取提交消息失败: {e}") Logger.error(f"获取提交消息失败: {e}")
return f"提交 {commit_hash[:8]}" return f"提交 {commit_hash[:8]}"
def get_remote_tags(self, repo_url):
"""获取远程仓库的所有 tags"""
try:
cmd = f"git ls-remote --tags {repo_url}"
result = subprocess.run(
cmd, shell=True, capture_output=True, text=True, timeout=30
)
if result.returncode == 0 and result.stdout:
tags = {}
for line in result.stdout.strip().split('\n'):
if line:
parts = line.split()
if len(parts) >= 2:
commit_hash = parts[0]
ref = parts[1]
# 提取 tag 名称,去掉 refs/tags/ 前缀和 ^{} 后缀
if ref.startswith('refs/tags/'):
tag_name = ref.replace('refs/tags/', '')
if not tag_name.endswith('^{}'):
tags[tag_name] = commit_hash
return tags
return {}
except Exception as e:
Logger.error(f"获取远程 tags 失败 {repo_url}: {e}")
return {}
def check_repository(self, repo_config): def check_repository(self, repo_config):
"""检查单个仓库是否有新提交""" """检查单个仓库是否有新提交"""
repo_name = repo_config['name'] repo_name = repo_config['name']
@ -176,6 +211,51 @@ class GitMonitor:
return False return False
def check_repository_tags(self, repo_config):
"""检查单个仓库是否有新 tag"""
repo_name = repo_config['name']
repo_url = repo_config['url']
# 获取远程所有 tags
current_tags = self.get_remote_tags(repo_url)
if current_tags is None or (not current_tags and repo_name not in self.last_tags):
# 首次获取失败或获取失败时发送通知
error_msg = f"获取 {repo_name} 远程 tags 失败"
Logger.error(error_msg)
if self.dingtalk_notifier and repo_name in self.last_tags:
# 只有在之前成功获取过 tags 的情况下才发送通知,避免首次初始化时发送
self.dingtalk_notifier.send_build_failure(
repo_name=repo_name,
branch=self.global_branch,
commit_hash='unknown',
error_msg=error_msg
)
return False, None
# 获取上次记录的 tags
last_tags = self.last_tags.get(repo_name, {})
# 找出新增的 tags
new_tags = []
for tag_name, commit_hash in current_tags.items():
# 检查 tag 是否匹配模式
import fnmatch
if fnmatch.fnmatch(tag_name, self.tag_pattern):
if tag_name not in last_tags:
new_tags.append((tag_name, commit_hash))
# 更新记录
self.last_tags[repo_name] = current_tags
if new_tags:
# 返回最新的 tag
new_tags.sort(reverse=True) # 按名称排序,最新的在前
latest_tag = new_tags[0]
Logger.info(f"检测到 {repo_name} 新 tag: {latest_tag[0]} ({latest_tag[1][:8]})")
return True, latest_tag
return False, None
def update_main_repo(self): def update_main_repo(self):
"""更新主仓库和所有子模块""" """更新主仓库和所有子模块"""
repo_path = self.runtime_path / 'a-cloud-all' repo_path = self.runtime_path / 'a-cloud-all'
@ -311,8 +391,13 @@ class GitMonitor:
return True return True
def deploy(self, repo_config): def deploy(self, repo_config, tag_name=None):
"""执行部署流程""" """执行部署流程
参数
repo_config: 仓库配置
tag_name: 可选的 tag 名称如果提供则表示这是由 tag 触发的部署
"""
repo_path = self.runtime_path / 'a-cloud-all' repo_path = self.runtime_path / 'a-cloud-all'
repo_name = repo_config['name'] repo_name = repo_config['name']
commit_hash = self.last_commits.get(repo_name, 'unknown') commit_hash = self.last_commits.get(repo_name, 'unknown')
@ -320,11 +405,24 @@ class GitMonitor:
Logger.separator() Logger.separator()
Logger.info(f"开始部署: {repo_name}") Logger.info(f"开始部署: {repo_name}")
if tag_name:
Logger.info(f"触发方式: Tag ({tag_name})")
else:
Logger.info(f"触发方式: 分支提交")
Logger.separator() Logger.separator()
try: try:
# 1. 更新主仓库和子模块 # 1. 更新主仓库和子模块
if not self.update_main_repo(): if not self.update_main_repo():
# 发送 Git 更新失败通知
if self.dingtalk_notifier:
duration = time.time() - start_time
self.dingtalk_notifier.send_build_failure(
repo_name=repo_name,
branch=self.global_branch,
commit_hash=commit_hash,
error_msg="Git 仓库更新失败(主仓库或子模块)"
)
return False return False
# 获取子仓库的 commit message # 获取子仓库的 commit message
@ -348,16 +446,30 @@ class GitMonitor:
# 发送构建开始通知(包含 commit message 和服务器 IP # 发送构建开始通知(包含 commit message 和服务器 IP
if self.dingtalk_notifier: if self.dingtalk_notifier:
# 如果是 tag 触发,在 commit_message 中添加 tag 信息
display_message = commit_message
if tag_name:
display_message = f"Tag: {tag_name}" + (f" - {commit_message}" if commit_message else "")
self.dingtalk_notifier.send_build_start( self.dingtalk_notifier.send_build_start(
repo_name=repo_name, repo_name=repo_name,
branch=self.global_branch, branch=self.global_branch if not tag_name else f"tag/{tag_name}",
commit_hash=commit_hash, commit_hash=commit_hash,
commit_message=commit_message, commit_message=display_message,
server_ip=server_ip server_ip=server_ip
) )
# 2. 初始化基础设施 # 2. 初始化基础设施
if not self.init_infrastructure(): if not self.init_infrastructure():
# 发送基础设施初始化失败通知
if self.dingtalk_notifier:
duration = time.time() - start_time
self.dingtalk_notifier.send_build_failure(
repo_name=repo_name,
branch=self.global_branch,
commit_hash=commit_hash,
error_msg="基础设施初始化失败MySQL/Redis/Nacos等"
)
return False return False
# 3. 根据项目类型执行打包 # 3. 根据项目类型执行打包
@ -368,7 +480,17 @@ class GitMonitor:
source_path = repo_config['path'] + '/' + repo_config['artifact_path'] source_path = repo_config['path'] + '/' + repo_config['artifact_path']
target_dir = repo_path / repo_config['docker_path'] target_dir = repo_path / repo_config['docker_path']
if not maven.run_maven(work_dir, commands, source_path, target_dir): success, error_msg = maven.run_maven(work_dir, commands, source_path, target_dir)
if not success:
# 发送 Maven 构建失败通知
if self.dingtalk_notifier:
duration = time.time() - start_time
self.dingtalk_notifier.send_build_failure(
repo_name=repo_name,
branch=self.global_branch,
commit_hash=commit_hash,
error_msg=f"Maven 打包失败: {error_msg}"
)
return False return False
elif repo_config['type'] == 'nodejs': elif repo_config['type'] == 'nodejs':
@ -378,7 +500,17 @@ class GitMonitor:
source_dir = repo_config['artifact_path'] source_dir = repo_config['artifact_path']
target_dir = repo_path / repo_config['docker_path'] target_dir = repo_path / repo_config['docker_path']
if not npm.run_npm(work_dir, commands, source_dir, target_dir): success, error_msg = npm.run_npm(work_dir, commands, source_dir, target_dir)
if not success:
# 发送 NPM/PNPM 构建失败通知
if self.dingtalk_notifier:
duration = time.time() - start_time
self.dingtalk_notifier.send_build_failure(
repo_name=repo_name,
branch=self.global_branch,
commit_hash=commit_hash,
error_msg=f"NPM/PNPM 打包失败: {error_msg}"
)
return False return False
# 4. Docker 部署 # 4. Docker 部署
@ -436,14 +568,40 @@ class GitMonitor:
for repo_config in repos: for repo_config in repos:
try: try:
# 检查分支提交
if self.check_repository(repo_config): if self.check_repository(repo_config):
Logger.info(f"触发部署: {repo_config['name']}") Logger.info(f"触发部署: {repo_config['name']} (分支提交)")
if self.deploy(repo_config): if self.deploy(repo_config):
Logger.info(f"✓ 部署成功: {repo_config['name']}") Logger.info(f"✓ 部署成功: {repo_config['name']}")
else: else:
Logger.error(f"✗ 部署失败: {repo_config['name']}") Logger.error(f"✗ 部署失败: {repo_config['name']}")
continue # 已经部署,跳过 tag 检查
# 检查 tag如果启用
if self.watch_tags:
has_new_tag, tag_info = self.check_repository_tags(repo_config)
if has_new_tag and tag_info:
tag_name, commit_hash = tag_info
Logger.info(f"触发部署: {repo_config['name']} (新 tag: {tag_name})")
# 更新 last_commits 以便 deploy 方法使用
self.last_commits[repo_config['name']] = commit_hash
if self.deploy(repo_config, tag_name=tag_name):
Logger.info(f"✓ 部署成功: {repo_config['name']}")
else:
Logger.error(f"✗ 部署失败: {repo_config['name']}")
except Exception as e: except Exception as e:
Logger.error(f"处理仓库异常 {repo_config['name']}: {e}") Logger.error(f"处理仓库异常 {repo_config['name']}: {e}")
# 发送异常通知
if self.dingtalk_notifier:
commit_hash = self.last_commits.get(repo_config['name'], 'unknown')
self.dingtalk_notifier.send_build_failure(
repo_name=repo_config['name'],
branch=self.global_branch,
commit_hash=commit_hash,
error_msg=f"处理仓库时发生异常: {str(e)}",
at_all=True
)
def run(self): def run(self):
"""持续监听运行""" """持续监听运行"""

View File

@ -22,7 +22,7 @@ def run_maven(work_dir, maven_commands, source_path, target_dir):
target_dir: 复制的目标目录 target_dir: 复制的目标目录
返回 返回
bool: 成功返回 True失败返回 False tuple: (success: bool, error_message: str) 成功返回 (True, "")失败返回 (False, "错误信息")
""" """
try: try:
# 转换为绝对路径 # 转换为绝对路径
@ -36,13 +36,15 @@ def run_maven(work_dir, maven_commands, source_path, target_dir):
# 检查目录是否存在 # 检查目录是否存在
if not work_dir.exists(): if not work_dir.exists():
Logger.error(f"目录不存在: {work_dir}") error_msg = f"目录不存在: {work_dir}"
return False Logger.error(error_msg)
return False, error_msg
# 执行 Maven 命令 # 执行 Maven 命令
if not Logger.run_command(maven_commands, work_dir): if not Logger.run_command(maven_commands, work_dir):
Logger.error("Maven 打包失败") error_msg = "Maven 编译失败,请查看日志获取详细错误信息"
return False Logger.error(error_msg)
return False, error_msg
Logger.info("Maven 打包成功") Logger.info("Maven 打包成功")
@ -61,8 +63,9 @@ def run_maven(work_dir, maven_commands, source_path, target_dir):
# 复制文件 # 复制文件
files = glob.glob(str(source_full_path)) files = glob.glob(str(source_full_path))
if not files: if not files:
Logger.error(f"未找到构建产物: {source_full_path}") error_msg = f"未找到构建产物: {source_full_path}"
return False Logger.error(error_msg)
return False, error_msg
for file in files: for file in files:
file_path = Path(file) file_path = Path(file)
@ -74,8 +77,9 @@ def run_maven(work_dir, maven_commands, source_path, target_dir):
Logger.info("构建产物复制成功") Logger.info("构建产物复制成功")
Logger.info("Maven 打包和复制完成") Logger.info("Maven 打包和复制完成")
return True return True, ""
except Exception as e: except Exception as e:
Logger.error(f"Maven 打包异常: {e}") error_msg = f"Maven 打包异常: {str(e)}"
return False Logger.error(error_msg)
return False, error_msg

View File

@ -22,7 +22,7 @@ def run_npm(work_dir, npm_commands, source_dir, target_dir):
target_dir: 复制的目标目录 target_dir: 复制的目标目录
返回 返回
bool: 成功返回 True失败返回 False tuple: (success: bool, error_message: str) 成功返回 (True, "")失败返回 (False, "错误信息")
""" """
try: try:
# 转换为绝对路径 # 转换为绝对路径
@ -36,13 +36,15 @@ def run_npm(work_dir, npm_commands, source_dir, target_dir):
# 检查目录是否存在 # 检查目录是否存在
if not work_dir.exists(): if not work_dir.exists():
Logger.error(f"目录不存在: {work_dir}") error_msg = f"目录不存在: {work_dir}"
return False Logger.error(error_msg)
return False, error_msg
# 执行 NPM 命令 # 执行 NPM 命令
if not Logger.run_command(npm_commands, work_dir): if not Logger.run_command(npm_commands, work_dir):
Logger.error("NPM 打包失败") error_msg = "NPM/PNPM 编译失败,请查看日志获取详细错误信息"
return False Logger.error(error_msg)
return False, error_msg
Logger.info("NPM 打包成功") Logger.info("NPM 打包成功")
@ -57,8 +59,9 @@ def run_npm(work_dir, npm_commands, source_dir, target_dir):
# 检查源目录是否存在 # 检查源目录是否存在
if not source_full_path.exists(): if not source_full_path.exists():
Logger.error(f"源目录不存在: {source_full_path}") error_msg = f"源目录不存在: {source_full_path}"
return False Logger.error(error_msg)
return False, error_msg
# 清空目标目录(保留 .gitkeep # 清空目标目录(保留 .gitkeep
target_path = Path(target_dir) target_path = Path(target_dir)
@ -84,8 +87,9 @@ def run_npm(work_dir, npm_commands, source_dir, target_dir):
Logger.info("构建产物复制成功") Logger.info("构建产物复制成功")
Logger.info("NPM 打包和复制完成") Logger.info("NPM 打包和复制完成")
return True return True, ""
except Exception as e: except Exception as e:
Logger.error(f"NPM 打包异常: {e}") error_msg = f"NPM 打包异常: {str(e)}"
return False Logger.error(error_msg)
return False, error_msg

View File

@ -69,7 +69,7 @@ public class AircraftDetailVO extends AircraftVO {
/** RTK信号 */ /** RTK信号 */
@Schema(description = "RTK信号") @Schema(description = "RTK信号")
@Excel(name = "RTK信号") @Excel(name = "RTK信号")
private Double rtkSignal; private Integer rtkSignal;
/** 限高 */ /** 限高 */
@Schema(description = "限高") @Schema(description = "限高")