Skip to content

Commit 8c26cc1

Browse files
committed
Merge branch 'main_upstream'
# Conflicts: # README.md # api.py # biz/cmd/review.py # biz/event/event_manager.py # biz/llm/factory.py # biz/queue/worker.py # biz/utils/code_reviewer.py # biz/utils/im/notifier.py # doc/faq.md # docker-compose.prod.yml # docker-compose.rq.yml # docker-compose.yml
2 parents be05b3b + 28ad557 commit 8c26cc1

33 files changed

+1275
-373
lines changed

.github/workflows/build_images.yml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ jobs:
3333
username: ${{ github.repository_owner }}
3434
password: ${{ secrets.GITHUB_TOKEN }}
3535

36-
# Extract tags with suffixes for prod and worker
37-
- name: Extract metadata (tags, labels) for Docker (prod)
38-
id: meta_prod
36+
# Extract tags with suffixes for app and worker
37+
- name: Extract metadata (tags, labels) for Docker (app)
38+
id: meta_app
3939
uses: docker/metadata-action@v5
4040
with:
4141
images: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}
@@ -56,17 +56,17 @@ jobs:
5656
type=semver,pattern={{major}},suffix=-worker
5757
type=edge,branch=main,suffix=-worker
5858
59-
# Build and push multi-arch prod image
60-
- name: Build and push prod Docker image (Multi-Arch)
59+
# Build and push multi-arch app image
60+
- name: Build and push app Docker image (Multi-Arch)
6161
uses: docker/build-push-action@v6
6262
with:
6363
context: .
6464
file: ./Dockerfile
6565
push: true
6666
platforms: linux/amd64,linux/arm64/v8
67-
tags: ${{ steps.meta_prod.outputs.tags }}
68-
labels: ${{ steps.meta_prod.outputs.labels }}
69-
target: prod
67+
tags: ${{ steps.meta_app.outputs.tags }}
68+
labels: ${{ steps.meta_app.outputs.labels }}
69+
target: app
7070

7171
# Build and push multi-arch worker image
7272
- name: Build and push worker Docker image (Multi-Arch)
@@ -78,4 +78,4 @@ jobs:
7878
platforms: linux/amd64,linux/arm64/v8
7979
tags: ${{ steps.meta_worker.outputs.tags }}
8080
labels: ${{ steps.meta_worker.outputs.labels }}
81-
target: worker
81+
target: worker

Dockerfile

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,8 @@ COPY ui.py ./ui.py
2222
# 使用 supervisord 作为启动命令
2323
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
2424

25-
FROM base AS dev
26-
COPY ./conf/supervisord.dev.conf /etc/supervisor/conf.d/supervisord.conf
27-
# 暴露 Flask 和 Streamlit 的端口
28-
EXPOSE 5001 5002
29-
30-
FROM base AS prod
31-
COPY ./conf/supervisord.prod.conf /etc/supervisor/conf.d/supervisord.conf
25+
FROM base AS app
26+
COPY conf/supervisord.app.conf /etc/supervisor/conf.d/supervisord.conf
3227
# 暴露 Flask 和 Streamlit 的端口
3328
EXPOSE 5001 5002
3429

README.md

Lines changed: 53 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,22 @@
66

77
## 功能
88

9-
- 多大模型支持:兼容 DeepSeek、ZhipuAI、OpenAI 和 Ollama。
10-
- 消息推送:审查结果可自动推送至钉钉、企业微信和飞书;
11-
- 自动化日报生成:基于 GitLab Commit 记录,自动整理开发日报;
12-
- 可视化 Dashboard:集中展示 Code Review 记录。
9+
- 🚀 多模型支持
10+
- 兼容 DeepSeek、ZhipuAI、OpenAI 和 Ollama,想用哪个就用哪个。
11+
- 📢 消息即时推送
12+
- 审查结果一键直达 钉钉、企业微信 或 飞书,代码问题无处可藏!
13+
- 📅 自动化日报生成
14+
- 基于 GitLab & GitHub Commit 记录,自动整理每日开发进展,谁在摸鱼、谁在卷,一目了然 😼。
15+
- 📊 可视化 Dashboard
16+
- 集中展示所有 Code Review 记录,项目统计、开发者统计,数据说话,甩锅无门!
17+
- 🎭 Review Style 任你选
18+
- 专业型 🤵:严谨细致,正式专业。
19+
- 讽刺型 😈:毒舌吐槽,专治不服("这代码是用脚写的吗?")
20+
- 绅士型 🌸:温柔建议,如沐春风("或许这里可以再优化一下呢~")
21+
- 幽默型 🤪:搞笑点评,快乐改码("这段 if-else 比我的相亲经历还曲折!")
1322

1423
**效果图:**
1524

16-
![Push图片](./doc/img/push.jpeg)
17-
1825
![MR图片](./doc/img/mr.png)
1926

2027
![Note图片](./doc/img/note.jpg)
@@ -33,24 +40,30 @@ Note 中,便于团队查看和处理。
3340

3441
### 方案一:Docker 部署
3542

36-
**1. 创建.env文件**
43+
**1. 准备环境文件**
3744

38-
复制本项目 .env.dist 文件内容到本地 .env 文件,并根据实际情况修改, 部分内容如下:
45+
- 克隆项目仓库:
46+
```aiignore
47+
git clone https://github.com/sunmh207/AI-Codereview-Gitlab.git
48+
cd AI-Codereview-Gitlab
49+
```
3950

40-
```bash
41-
#服务端口
42-
SERVER_PORT=5001
51+
- 创建配置文件:
52+
```aiignore
53+
cp conf/.env.dist conf/.env
54+
```
55+
56+
- 编辑 conf/.env 文件,配置以下关键参数:
4357

44-
#大模型供应商配置,支持 zhipuai , openai , deepseek or ollama
58+
```bash
59+
#大模型供应商配置,支持 zhipuai , openai , deepseek 和 ollama
4560
LLM_PROVIDER=deepseek
4661

4762
#DeepSeek
4863
DEEPSEEK_API_KEY={YOUR_DEEPSEEK_API_KEY}
4964

5065
#支持review的文件类型(未配置的文件类型不会被审查)
51-
SUPPORTED_EXTENSIONS=.java,.py,.php,.yml
52-
#提交给大模型的最长字符数,超出的部分会截断,防止大模型处理内容过长或Token消耗过多
53-
REVIEW_MAX_LENGTH=20000
66+
SUPPORTED_EXTENSIONS=.java,.py,.php,.yml,.vue,.go,.c,.cpp,.h,.js,.css,.md,.sql
5467

5568
#钉钉消息推送: 0不发送钉钉消息,1发送钉钉消息
5669
DINGTALK_ENABLED=0
@@ -60,19 +73,20 @@ DINGTALK_WEBHOOK_URL={YOUR_WDINGTALK_WEBHOOK_URL}
6073
GITLAB_ACCESS_TOKEN={YOUR_GITLAB_ACCESS_TOKEN}
6174
```
6275

63-
**2. 启动docker容器**
76+
**2. 启动服务**
6477

6578
```bash
66-
git clone https://github.com/mashb1t/ai-codereview-gitlab.git
67-
cd ai-codereview-gitlab
6879
docker-compose up -d
6980
```
7081

71-
**3. 验证服务**
72-
73-
访问 http://your-server-ip:5001 显示 "The code review server is running." 说明服务启动成功。
82+
**3. 验证部署**
7483

75-
访问 http://your-server-ip:5002 看到一个审查日志页面,说明 Dashboard 启动成功。
84+
- 主服务验证:
85+
- 访问 http://your-server-ip:5001
86+
- 显示 "The code review server is running." 说明服务启动成功。
87+
- Dashboard 验证:
88+
- 访问 http://your-server-ip:5002
89+
- 看到一个审查日志页面,说明 Dashboard 启动成功。
7690

7791
### 方案二:本地Python环境部署
7892

@@ -93,7 +107,7 @@ pip install -r requirements.txt
93107

94108
**3. 配置环境变量**
95109

96-
同 Docker 部署方案中的 【创建.env文件】
110+
同 Docker 部署方案中的.env 文件配置。
97111

98112
**4. 启动服务**
99113

@@ -111,21 +125,28 @@ streamlit run ui.py --server.port=5002 --server.address=0.0.0.0
111125

112126
### 配置 GitLab Webhook
113127

114-
#### 1. 创建Access Token**
128+
#### 1. 创建Access Token
115129

116130
方法一:在 GitLab 个人设置中,创建一个 Personal Access Token。
117131

118132
方法二:在 GitLab 项目设置中,创建Project Access Token
119133

120-
#### 2. 配置 Webhook**
134+
#### 2. 配置 Webhook
121135

122136
在 GitLab 项目设置中,配置 Webhook:
123137

124138
- URL:http://your-server-ip:5001/review/webhook
125139
- Trigger Events:勾选 Push Events 和 Merge Request Events (不要勾选其它Event)
126140
- Secret Token:上面配置的 Access Token(可选)
127141

128-
备注:系统会优先使用.env中的GITLAB_ACCESS_TOKEN,如果未找到,则使用Webhook 传递的Secret Token
142+
**备注**
143+
144+
1. Token使用优先级
145+
- 系统优先使用 .env 文件中的 GITLAB_ACCESS_TOKEN。
146+
- 如果 .env 文件中没有配置 GITLAB_ACCESS_TOKEN,则使用 Webhook 传递的Secret Token。
147+
2. 网络访问要求
148+
- 请确保 GitLab 能够访问本系统。
149+
- 若内网环境受限,建议将系统部署在外网服务器上。
129150

130151
### 配置消息推送
131152

@@ -143,15 +164,15 @@ streamlit run ui.py --server.port=5002 --server.address=0.0.0.0
143164

144165
## 其它
145166

146-
**1.如何review代码结构?**
167+
**1.如何对整个代码库进行Review?**
147168

148-
可通过命令工具对代码结构进行检查,命令如下
169+
可以通过命令行工具对整个代码库进行审查。当前功能仍在不断完善中,欢迎试用并反馈宝贵意见!具体操作如下
149170

150171
```bash
151172
python -m biz.cmd.review
152173
```
153174

154-
然后按照提示进行操作即可
175+
运行后,请按照命令行中的提示进行操作即可
155176

156177
**2.其它问题**
157178

@@ -168,3 +189,6 @@ python -m biz.cmd.review
168189
<img src="doc/img/wechat_group.jpg" width="400" />
169190
</p>
170191

192+
## Star History
193+
194+
[![Star History Chart](https://api.star-history.com/svg?repos=sunmh207/AI-Codereview-Gitlab&type=Timeline)](https://www.star-history.com/#sunmh207/AI-Codereview-Gitlab&Timeline)

api.py

Lines changed: 90 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from flask import Flask, request, jsonify
1212

1313
from biz.gitlab.webhook_handler import slugify_url
14-
from biz.queue.worker import handle_merge_request_event, handle_push_event
14+
from biz.queue.worker import handle_merge_request_event, handle_push_event, handle_github_pull_request_event, handle_github_push_event
1515
from biz.service.review_service import ReviewService
1616
from biz.utils.im import notifier
1717
from biz.utils.log import logger
@@ -114,58 +114,98 @@ def handle_webhook():
114114
if not data:
115115
return jsonify({"error": _("Invalid JSON")}), 400
116116

117-
object_kind = data.get("object_kind")
118-
119-
# 优先从请求头获取,如果没有,则从环境变量获取,如果没有,则从推送事件中获取
120-
gitlab_url = os.getenv('GITLAB_URL') or request.headers.get('X-Gitlab-Instance')
121-
if not gitlab_url:
122-
repository = data.get('repository')
123-
if not repository:
124-
return jsonify({'message': _('Missing GitLab URL')}), 400
125-
homepage = repository.get("homepage")
126-
if not homepage:
127-
return jsonify({'message': _('Missing GitLab URL')}), 400
128-
try:
129-
parsed_url = urlparse(homepage)
130-
gitlab_url = f"{parsed_url.scheme}://{parsed_url.netloc}/"
131-
except Exception as e:
132-
return jsonify({"error": f"Failed to parse homepage URL: {str(e)}"}), 400
133-
134-
# 优先从环境变量获取,如果没有,则从请求头获取
135-
gitlab_token = os.getenv('GITLAB_ACCESS_TOKEN') or request.headers.get('X-Gitlab-Token')
136-
# 如果gitlab_token为空,返回错误
137-
if not gitlab_token:
138-
return jsonify({'message': _('Missing GitLab access token')}), 400
139-
140-
gitlab_url_slug = slugify_url(gitlab_url)
141-
142-
# 打印整个payload数据,或根据需求进行处理
143-
logger.info(_('Received event: {}').format(object_kind))
144-
logger.info(_('Payload: {}').format(json.dumps(data)))
145-
146-
# 处理Merge Request Hook
147-
if object_kind == "merge_request":
148-
# 创建一个新进程进行异步处理
149-
handle_queue(handle_merge_request_event, data, gitlab_token, gitlab_url, gitlab_url_slug)
150-
# 立马返回响应
151-
return jsonify({'message': _('Request received(object_kind={}), will process asynchronously.').format(
152-
object_kind)}), 200
153-
elif object_kind == "push":
154-
# 创建一个新进程进行异步处理
155-
# TODO check if PUSH_REVIEW_ENABLED is needed here
156-
handle_queue(handle_push_event, data, gitlab_token, gitlab_url, gitlab_url_slug)
157-
# 立马返回响应
158-
return jsonify({'message': _('Request received(object_kind={}), will process asynchronously.').format(
159-
object_kind)}), 200
160-
else:
161-
error_message = _(
162-
'Only merge_request and push events are supported (both Webhook and System Hook), but received: {}.').format(
163-
object_kind)
164-
logger.error(error_message)
165-
return jsonify(error_message), 400
117+
# 判断是GitLab还是GitHub的webhook
118+
webhook_source = request.headers.get('X-GitHub-Event')
119+
120+
if webhook_source: # GitHub webhook
121+
return handle_github_webhook(webhook_source, data)
122+
else: # GitLab webhook
123+
return handle_gitlab_webhook(data)
166124
else:
167125
return jsonify({'message': _('Invalid data format')}), 400
168126

127+
def handle_github_webhook(event_type, data):
128+
# 获取GitHub配置
129+
github_token = os.getenv('GITHUB_ACCESS_TOKEN') or request.headers.get('X-GitHub-Token')
130+
if not github_token:
131+
return jsonify({'message': _('Missing GitLab URL')}), 400
132+
133+
github_url = os.getenv('GITHUB_URL') or 'https://github.com'
134+
github_url_slug = slugify_url(github_url)
135+
136+
# 打印整个payload数据
137+
logger.info(_('Received event: {}').format(event_type))
138+
logger.info(_('Payload: {}').format(json.dumps(data)))
139+
140+
if event_type == "pull_request":
141+
# 使用handle_queue进行异步处理
142+
handle_queue(handle_github_pull_request_event, data, github_token, github_url, github_url_slug)
143+
# 立马返回响应
144+
return jsonify({'message': _('GitHub request received(event_type={}), will process asynchronously.').format(
145+
event_type)}), 200
146+
elif event_type == "push":
147+
# 使用handle_queue进行异步处理
148+
handle_queue(handle_github_push_event, data, github_token, github_url, github_url_slug)
149+
# 立马返回响应
150+
return jsonify({'message': _('GitHub request received(event_type={}), will process asynchronously.').format(
151+
event_type)}), 200
152+
else:
153+
error_message = _(
154+
'Only pull_request and push events are supported for GitHub webhook, but received:: {}.').format(
155+
event_type)
156+
logger.error(error_message)
157+
return jsonify(error_message), 400
158+
159+
def handle_gitlab_webhook(data):
160+
object_kind = data.get("object_kind")
161+
162+
# 优先从请求头获取,如果没有,则从环境变量获取,如果没有,则从推送事件中获取
163+
gitlab_url = os.getenv('GITLAB_URL') or request.headers.get('X-Gitlab-Instance')
164+
if not gitlab_url:
165+
repository = data.get('repository')
166+
if not repository:
167+
return jsonify({'message': _('Missing GitLab URL')}), 400
168+
homepage = repository.get("homepage")
169+
if not homepage:
170+
return jsonify({'message': _('Missing GitLab URL')}), 400
171+
try:
172+
parsed_url = urlparse(homepage)
173+
gitlab_url = f"{parsed_url.scheme}://{parsed_url.netloc}/"
174+
except Exception as e:
175+
return jsonify({"error": f"Failed to parse homepage URL: {str(e)}"}), 400
176+
177+
# 优先从环境变量获取,如果没有,则从请求头获取
178+
gitlab_token = os.getenv('GITLAB_ACCESS_TOKEN') or request.headers.get('X-Gitlab-Token')
179+
# 如果gitlab_token为空,返回错误
180+
if not gitlab_token:
181+
return jsonify({'message': _('Missing GitLab access token')}), 400
182+
183+
gitlab_url_slug = slugify_url(gitlab_url)
184+
185+
# 打印整个payload数据,或根据需求进行处理
186+
logger.info(_('Received event: {}').format(object_kind))
187+
logger.info(_('Payload: {}').format(json.dumps(data)))
188+
189+
# 处理Merge Request Hook
190+
if object_kind == "merge_request":
191+
# 创建一个新进程进行异步处理
192+
handle_queue(handle_merge_request_event, data, gitlab_token, gitlab_url, gitlab_url_slug)
193+
# 立马返回响应
194+
return jsonify({'message': _('Request received(object_kind={}), will process asynchronously.').format(
195+
object_kind)}), 200
196+
elif object_kind == "push":
197+
# 创建一个新进程进行异步处理
198+
# TODO check if PUSH_REVIEW_ENABLED is needed here
199+
handle_queue(handle_push_event, data, gitlab_token, gitlab_url, gitlab_url_slug)
200+
# 立马返回响应
201+
return jsonify({'message': _('Request received(object_kind={}), will process asynchronously.').format(
202+
object_kind)}), 200
203+
else:
204+
error_message = _(
205+
'Only merge_request and push events are supported (both Webhook and System Hook), but received: {}.').format(
206+
object_kind)
207+
logger.error(error_message)
208+
return jsonify(error_message), 400
169209

170210
if __name__ == '__main__':
171211
# 启动定时任务调度器

biz/cmd/func/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)