Skip to content

Commit b994113

Browse files
committed
Merge branch 'main_upstream' into feature/i18n
# Conflicts: # api.py # biz/event/event_manager.py # biz/utils/im/im_notifier.py # biz/utils/im/wecom.py # doc/faq.md
2 parents 4e2af2f + fd27e50 commit b994113

File tree

12 files changed

+336
-248
lines changed

12 files changed

+336
-248
lines changed

.github/workflows/build_image.yml

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: Docker image build
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
tags:
8+
- v*
9+
10+
jobs:
11+
build-and-push-images:
12+
runs-on: ubuntu-latest
13+
14+
permissions:
15+
contents: read
16+
packages: write
17+
18+
steps:
19+
- name: Checkout repository
20+
uses: actions/checkout@v4
21+
22+
- name: Set up QEMU for multi-arch builds
23+
uses: docker/setup-qemu-action@v3
24+
25+
- name: Set up Docker Buildx
26+
id: buildx
27+
uses: docker/setup-buildx-action@v3
28+
29+
- name: Log in to the Container registry
30+
uses: docker/login-action@v3
31+
with:
32+
registry: ghcr.io
33+
username: ${{ github.repository_owner }}
34+
password: ${{ secrets.GITHUB_TOKEN }}
35+
36+
# Extract tags with suffixes for prod and worker
37+
- name: Extract metadata (tags, labels) for Docker (prod)
38+
id: meta_prod
39+
uses: docker/metadata-action@v5
40+
with:
41+
images: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}
42+
tags: |
43+
type=semver,pattern={{version}}
44+
type=semver,pattern={{major}}.{{minor}}
45+
type=semver,pattern={{major}}
46+
type=edge,branch=main
47+
48+
# Build and push multi-arch prod image
49+
- name: Build and push prod Docker image (Multi-Arch)
50+
uses: docker/build-push-action@v6
51+
with:
52+
context: .
53+
file: ./Dockerfile
54+
push: true
55+
platforms: linux/amd64,linux/arm64/v8
56+
tags: ${{ steps.meta_prod.outputs.tags }}
57+
labels: ${{ steps.meta_prod.outputs.labels }}
58+
target: prod

api.py

+25-21
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import atexit
22
import json
33
import os
4+
import re
45
import traceback
56
from datetime import datetime
67
from multiprocessing import Process
@@ -120,16 +121,16 @@ def handle_webhook():
120121
if not gitlab_url:
121122
repository = data.get('repository')
122123
if not repository:
123-
return jsonify({'message': 'Missing GitLab URL'}), 400
124+
return jsonify({'message': 'Missing GitLab URL'}), 400
124125
homepage = repository.get("homepage")
125126
if not homepage:
126-
return jsonify({'message': 'Missing GitLab URL'}), 400
127+
return jsonify({'message': 'Missing GitLab URL'}), 400
127128
try:
128129
parsed_url = urlparse(homepage)
129130
gitlab_url = f"{parsed_url.scheme}://{parsed_url.netloc}/"
130131
except Exception as e:
131132
return jsonify({"error": f"Failed to parse homepage URL: {str(e)}"}), 400
132-
133+
133134
# 优先从环境变量获取,如果没有,则从请求头获取
134135
gitlab_token = os.getenv('GITLAB_ACCESS_TOKEN') or request.headers.get('X-Gitlab-Token')
135136
# 如果gitlab_token为空,返回错误
@@ -160,20 +161,24 @@ def handle_webhook():
160161
else:
161162
return jsonify({'message': _('Invalid data format')}), 400
162163

163-
def transform_gitlab_url(url):
164-
# 去掉 http:// 或 https://
165-
if url.startswith("http://"):
166-
url = url[len("http://"):]
167-
elif url.startswith("https://"):
168-
url = url[len("https://"):]
169-
170-
if url.endswith('/'):
171-
url = url[:-1]
172-
173-
# 替换 . 和 / 为 _
174-
transformed_url = url.replace('.', '_').replace('/', '_')
175-
176-
return transformed_url
164+
165+
def slugify_url(original_url: str) -> str:
166+
"""
167+
将原始URL转换为适合作为文件名的字符串,其中非字母或数字的字符会被替换为下划线,举例:
168+
slugify_url("http://example.com/path/to/repo/") => example_com_path_to_repo
169+
slugify_url("https://gitlab.com/user/repo.git") => gitlab_com_user_repo_git
170+
"""
171+
# Remove URL scheme (http, https, etc.) if present
172+
original_url = re.sub(r'^https?://', '', original_url)
173+
174+
# Replace non-alphanumeric characters (except underscore) with underscores
175+
target = re.sub(r'[^a-zA-Z0-9]', '_', original_url)
176+
177+
# Remove trailing underscore if present
178+
target = target.rstrip('_')
179+
180+
return target
181+
177182

178183
def __handle_push_event(webhook_data: dict, gitlab_token: str, gitlab_url: str):
179184
try:
@@ -203,7 +208,6 @@ def __handle_push_event(webhook_data: dict, gitlab_token: str, gitlab_url: str):
203208
# 将review结果提交到Gitlab的 notes
204209
handler.add_push_notes(_('Auto Review Result: \n{}').format(review_result))
205210

206-
url_base = transform_gitlab_url(gitlab_url)
207211

208212
event_manager['push_reviewed'].send(PushReviewEntity(
209213
project_name=webhook_data['project']['name'],
@@ -213,7 +217,7 @@ def __handle_push_event(webhook_data: dict, gitlab_token: str, gitlab_url: str):
213217
commits=commits,
214218
score=score,
215219
review_result=review_result,
216-
gitlab_url = url_base,
220+
gitlab_url_slug = slugify_url(gitlab_url),
217221
))
218222

219223
except Exception as e:
@@ -261,7 +265,6 @@ def __handle_merge_request_event(webhook_data: dict, gitlab_token: str, gitlab_u
261265
# 将review结果提交到Gitlab的 notes
262266
handler.add_merge_request_notes(_('Auto Review Result: \n{}').format(review_result))
263267

264-
url_base = transform_gitlab_url(gitlab_url)
265268
# dispatch merge_request_reviewed event
266269
event_manager['merge_request_reviewed'].send(
267270
MergeRequestReviewEntity(
@@ -274,7 +277,7 @@ def __handle_merge_request_event(webhook_data: dict, gitlab_token: str, gitlab_u
274277
score=CodeReviewer.parse_review_score(review_text=review_result),
275278
url=webhook_data['object_attributes']['url'],
276279
review_result=review_result,
277-
gitlab_url = url_base,
280+
gitlab_url_slug = slugify_url(gitlab_url),
278281
)
279282
)
280283

@@ -287,6 +290,7 @@ def __handle_merge_request_event(webhook_data: dict, gitlab_token: str, gitlab_u
287290
logger.error(_('出现未知错误: {}').format(error_message))
288291

289292

293+
290294
def filter_changes(changes: list):
291295
'''
292296
过滤数据,只保留支持的文件类型以及必要的字段信息

biz/entity/review_entity.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
class MergeRequestReviewEntity:
22
def __init__(self, project_name: str, author: str, source_branch: str, target_branch: str, updated_at: int,
3-
commits: list, score: float, url: str, review_result: str, gitlab_url: str):
3+
commits: list, score: float, url: str, review_result: str, gitlab_url_slug: str):
44
self.project_name = project_name
55
self.author = author
66
self.source_branch = source_branch
@@ -10,7 +10,7 @@ def __init__(self, project_name: str, author: str, source_branch: str, target_br
1010
self.score = score
1111
self.url = url
1212
self.review_result = review_result
13-
self.gitlab_url = gitlab_url
13+
self.gitlab_url_slug = gitlab_url_slug
1414

1515
@property
1616
def commit_messages(self):
@@ -20,15 +20,15 @@ def commit_messages(self):
2020

2121
class PushReviewEntity:
2222
def __init__(self, project_name: str, author: str, branch: str, updated_at: int, commits: list, score: float,
23-
review_result: str, gitlab_url: str):
23+
review_result: str, gitlab_url_slug: str):
2424
self.project_name = project_name
2525
self.author = author
2626
self.branch = branch
2727
self.updated_at = updated_at
2828
self.commits = commits
2929
self.score = score
3030
self.review_result = review_result
31-
self.gitlab_url = gitlab_url
31+
self.gitlab_url_slug = gitlab_url_slug
3232

3333
@property
3434
def commit_messages(self):

biz/event/event_manager.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ def on_merge_request_reviewed(mr_review_entity: MergeRequestReviewEntity):
4444
review_result=mr_review_entity.review_result
4545
)
4646
im_notifier.send_notification(content=im_msg, msg_type='markdown', title=_('Merge Request Review'),
47-
project_name=mr_review_entity.project_name, url_base=mr_review_entity.gitlab_url)
47+
project_name=mr_review_entity.project_name,
48+
gitlab_url_slug=mr_review_entity.gitlab_url_slug)
4849

4950
# 记录到数据库
5051
ReviewService().insert_mr_review_log(mr_review_entity)
@@ -77,7 +78,7 @@ def on_push_reviewed(entity: PushReviewEntity):
7778
im_notifier.send_notification(content=im_msg, msg_type='markdown',
7879
title=_("{project_name} Push Event").format(project_name=entity.project_name),
7980
project_name=entity.project_name,
80-
url_base=entity.gitlab_url)
81+
gitlab_url_slug=entity.gitlab_url_slug)
8182

8283
# 记录到数据库
8384
ReviewService().insert_push_review_log(entity)

biz/utils/im/dingtalk.py

+14-13
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ def __init__(self, webhook_url=None):
1313
self.enabled = os.environ.get('DINGTALK_ENABLED', '0') == '1'
1414
self.default_webhook_url = webhook_url or os.environ.get('DINGTALK_WEBHOOK_URL')
1515

16-
def _get_webhook_url(self, project_name=None, url_base=None):
16+
def _get_webhook_url(self, project_name=None, gitlab_url_slug=None):
1717
"""
1818
获取项目对应的 Webhook URL
1919
:param project_name: 项目名称
20+
:param gitlab_url_slug: 由 gitlab 项目的 url 转换而来的 slug
2021
:return: Webhook URL
2122
:raises ValueError: 如果未找到 Webhook URL
2223
"""
@@ -27,17 +28,17 @@ def _get_webhook_url(self, project_name=None, url_base=None):
2728
else:
2829
raise ValueError(_("未提供项目名称,且未设置默认的钉钉 Webhook URL。"))
2930

30-
# 遍历所有环境变量(忽略大小写),找到项目对应的 Webhook URL
31-
target_key = f"DINGTALK_WEBHOOK_URL_{project_name.upper()}"
32-
for env_key, env_value in os.environ.items():
33-
if env_key.upper() == target_key:
34-
return env_value # 找到匹配项,直接返回
35-
36-
# url_base 优先级次之
37-
target_key_url_base = f"WECOM_WEBHOOK_URL_{url_base.upper()}"
31+
# 构造目标键
32+
target_key_project = f"DINGTALK_WEBHOOK_URL_{project_name.upper()}"
33+
target_key_url_slug = f"DINGTALK_WEBHOOK_URL_{gitlab_url_slug.upper()}"
34+
35+
# 遍历环境变量
3836
for env_key, env_value in os.environ.items():
39-
if target_key_url_base !=None and env_key.upper() == target_key_url_base:
40-
return env_value # 找到匹配项,直接返回
37+
env_key_upper = env_key.upper()
38+
if env_key_upper == target_key_project:
39+
return env_value # 找到项目名称对应的 Webhook URL,直接返回
40+
if env_key_upper == target_key_url_slug:
41+
return env_value # 找到 GitLab URL 对应的 Webhook URL,直接返回
4142

4243
# 如果未找到匹配的环境变量,降级使用全局的 Webhook URL
4344
if self.default_webhook_url:
@@ -46,13 +47,13 @@ def _get_webhook_url(self, project_name=None, url_base=None):
4647
# 如果既未找到匹配项,也没有默认值,抛出异常
4748
raise ValueError(_("未找到项目 '{}' 对应的钉钉Webhook URL,且未设置默认的 Webhook URL。").format(project_name))
4849

49-
def send_message(self, content: str, msg_type='text', title='通知', is_at_all=False, project_name=None, url_base = None):
50+
def send_message(self, content: str, msg_type='text', title='通知', is_at_all=False, project_name=None, gitlab_url_slug = None):
5051
if not self.enabled:
5152
logger.info(_("钉钉推送未启用"))
5253
return
5354

5455
try:
55-
post_url = self._get_webhook_url(project_name=project_name, url_base=url_base)
56+
post_url = self._get_webhook_url(project_name=project_name, gitlab_url_slug=gitlab_url_slug)
5657
headers = {
5758
"Content-Type": "application/json",
5859
"Charset": "UTF-8"

biz/utils/im/feishu.py

+13-13
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def __init__(self, webhook_url=None):
1313
self.default_webhook_url = webhook_url or os.environ.get('FEISHU_WEBHOOK_URL', '')
1414
self.enabled = os.environ.get('FEISHU_ENABLED', '0') == '1'
1515

16-
def _get_webhook_url(self, project_name=None, url_base=None):
16+
def _get_webhook_url(self, project_name=None, gitlab_url_slug=None):
1717
"""
1818
获取项目对应的 Webhook URL
1919
:param project_name: 项目名称
@@ -27,17 +27,17 @@ def _get_webhook_url(self, project_name=None, url_base=None):
2727
else:
2828
raise ValueError(_("未提供项目名称,且未设置默认的 飞书 Webhook URL。"))
2929

30-
# 遍历所有环境变量(忽略大小写),找到项目对应的 Webhook URL
31-
target_key = f"FEISHU_WEBHOOK_URL_{project_name.upper()}"
32-
for env_key, env_value in os.environ.items():
33-
if env_key.upper() == target_key:
34-
return env_value # 找到匹配项,直接返回
35-
36-
# url_base 优先级次之
37-
target_key_url_base = f"WECOM_WEBHOOK_URL_{url_base.upper()}"
30+
# 构造目标键
31+
target_key_project = f"FEISHU_WEBHOOK_URL_{project_name.upper()}"
32+
target_key_url_slug = f"FEISHU_WEBHOOK_URL_{gitlab_url_slug.upper()}"
33+
34+
# 遍历环境变量
3835
for env_key, env_value in os.environ.items():
39-
if target_key_url_base !=None and env_key.upper() == target_key_url_base:
40-
return env_value # 找到匹配项,直接返回
36+
env_key_upper = env_key.upper()
37+
if env_key_upper == target_key_project:
38+
return env_value # 找到项目名称对应的 Webhook URL,直接返回
39+
if env_key_upper == target_key_url_slug:
40+
return env_value # 找到 GitLab URL 对应的 Webhook URL,直接返回
4141

4242
# 如果未找到匹配的环境变量,降级使用全局的 Webhook URL
4343
if self.default_webhook_url:
@@ -46,7 +46,7 @@ def _get_webhook_url(self, project_name=None, url_base=None):
4646
# 如果既未找到匹配项,也没有默认值,抛出异常
4747
raise ValueError(_("未找到项目 '{project_name}' 对应的 Feishu Webhook URL,且未设置默认的 Webhook URL。"))
4848

49-
def send_message(self, content, msg_type='text', title=None, is_at_all=False, project_name=None, url_base=None):
49+
def send_message(self, content, msg_type='text', title=None, is_at_all=False, project_name=None, gitlab_url_slug=None):
5050
"""
5151
发送飞书消息
5252
:param content: 消息内容
@@ -60,7 +60,7 @@ def send_message(self, content, msg_type='text', title=None, is_at_all=False, pr
6060
return
6161

6262
try:
63-
post_url = self._get_webhook_url(project_name=project_name, url_base=url_base)
63+
post_url = self._get_webhook_url(project_name=project_name, gitlab_url_slug=gitlab_url_slug)
6464
if msg_type == 'markdown':
6565
data = {
6666
"msg_type": "interactive",

biz/utils/im/im_notifier.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,26 @@
66
_ = get_translator()
77

88

9-
def send_notification(content, msg_type='text', title=_("通知"), is_at_all=False, project_name=None, url_base=None):
9+
def send_notification(content, msg_type='text', title=_("通知"), is_at_all=False, project_name=None, gitlab_url_slug=None):
1010
"""
1111
发送通知消息到配置的平台(钉钉和企业微信)
1212
:param content: 消息内容
1313
:param msg_type: 消息类型,支持text和markdown
1414
:param title: 消息标题(markdown类型时使用)
1515
:param is_at_all: 是否@所有人
16-
:param url_base: gitlab服务器的url地址 http://www.gitlab.com 传递进来自动移除http和https,转换成 www_gitlab_com
16+
:param gitlab_url_slug: 由gitlab服务器的url地址(如:http://www.gitlab.com)转换成的slug格式,如: www_gitlab_com
1717
"""
1818
# 钉钉推送
1919
notifier = DingTalkNotifier()
2020
notifier.send_message(content=content, msg_type=msg_type, title=title, is_at_all=is_at_all,
21-
project_name=project_name, url_base=url_base)
21+
project_name=project_name, gitlab_url_slug=gitlab_url_slug)
2222

2323
# 企业微信推送
2424
wecom_notifier = WeComNotifier()
2525
wecom_notifier.send_message(content=content, msg_type=msg_type, title=title, is_at_all=is_at_all,
26-
project_name=project_name, url_base=url_base)
26+
project_name=project_name, gitlab_url_slug=gitlab_url_slug)
2727

2828
# 飞书推送
2929
feishu_notifier = FeishuNotifier()
3030
feishu_notifier.send_message(content=content, msg_type=msg_type, title=title, is_at_all=is_at_all,
31-
project_name=project_name, url_base=url_base)
31+
project_name=project_name, gitlab_url_slug=gitlab_url_slug)

0 commit comments

Comments
 (0)