mirror of
https://github.com/jeffusion/gitea-ai-assistant.git
synced 2026-06-14 23:16:45 +00:00
feat: 项目更名并调整接口
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.env
|
||||
kubernetes.yaml
|
||||
|
||||
39
Makefile
Normal file
39
Makefile
Normal file
@@ -0,0 +1,39 @@
|
||||
NAME = ai-code-review
|
||||
REGISTRY = docker-hosted.nexus.satfabric.com
|
||||
SHA1 = $(shell git rev-parse HEAD)
|
||||
REVISION = $(shell git rev-list --count HEAD)
|
||||
LABELS = --label SHA1=${SHA1} --label REVISION=${REVISION}
|
||||
VERSION = $(shell ./auto-ver.sh).${REVISION}
|
||||
|
||||
OS ?= linux
|
||||
ARCH ?= amd64
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
npm run build
|
||||
|
||||
.PHONY: container.build
|
||||
container.build:
|
||||
make build
|
||||
docker buildx build --platform=${OS}/${ARCH} -t ${REGISTRY}/${NAME}:v${VERSION} ${LABELS} -f Dockerfile .
|
||||
|
||||
.PHONY: container.push
|
||||
container.push:
|
||||
make build
|
||||
docker buildx build --platform=${OS}/${ARCH} -t ${REGISTRY}/${NAME}:v${VERSION} ${LABELS} -f Dockerfile . --push
|
||||
|
||||
.PHONY: k8s.yaml
|
||||
k8s.yaml:
|
||||
cp ./kubernetes.yaml.template ./kubernetes.yaml
|
||||
sed -i.bak 's@<%= IMAGE_FROM %>@registry.kuiper.com/${NAME}:v${VERSION}@g' ./kubernetes.yaml
|
||||
sed -i.bak 's@<%= APP_NAME %>@${NAME}@g' ./kubernetes.yaml
|
||||
rm -f ./kubernetes.yaml.bak
|
||||
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo 'Usage: make [target]'
|
||||
|
||||
@echo 'Available targets:'
|
||||
@printf " %-25s %s\n" "container.<build|push>" "本地构建(或构建加推送)容器镜像,若不执行TAG参数,则自动生成VERSION字段"
|
||||
@printf " %-25s %s\n" "k8s.yaml" "生成kubernetes.yaml文件"
|
||||
@printf " %-25s %s\n" "help" "显示此帮助信息"
|
||||
23
README.md
23
README.md
@@ -1,6 +1,6 @@
|
||||
# AI Code Review for Gitea
|
||||
# Gitea Assistant
|
||||
|
||||
基于Bun和TypeScript的Gitea代码审查助手,自动为Pull Request和单个提交提供AI驱动的代码审查。
|
||||
Gitea功能增强助手,基于Bun和TypeScript开发,提供AI驱动的代码审查等增强功能。
|
||||
|
||||
## 功能特点
|
||||
|
||||
@@ -68,21 +68,15 @@
|
||||
|
||||
2. 在Gitea仓库中配置Webhook
|
||||
|
||||
在Gitea仓库设置中添加两个Webhook:
|
||||
在Gitea仓库设置中添加Webhook:
|
||||
|
||||
**Pull Request审查webhook**:
|
||||
- URL: `http://your-server:3000/webhook/gitea/pull_request`
|
||||
**统一Webhook端点**:
|
||||
- URL: `http://your-server:3000/webhook/gitea`
|
||||
- 内容类型: `application/json`
|
||||
- 秘钥: 设置为与`WEBHOOK_SECRET`环境变量相同的值
|
||||
- 触发事件: 选择"Pull Request"
|
||||
- 触发事件: 选择"Pull Request"和"Status"事件
|
||||
|
||||
**提交状态审查webhook**:
|
||||
- URL: `http://your-server:3000/webhook/gitea/status`
|
||||
- 内容类型: `application/json`
|
||||
- 秘钥: 设置为与`WEBHOOK_SECRET`环境变量相同的值
|
||||
- 触发事件: 选择"Status"
|
||||
|
||||
> 注意: 老端点 `/webhook/gitea` 仍然支持Pull Request审查,但仅作向后兼容使用。
|
||||
> 注意: 系统使用统一的webhook端点处理所有事件类型,包括Pull Request和Commit Status事件。
|
||||
|
||||
### Webhook签名验证
|
||||
|
||||
@@ -92,6 +86,7 @@
|
||||
2. 在Gitea的Webhook配置中,使用相同的字符串作为"秘钥"
|
||||
3. 每次请求时,系统会验证请求头中的`X-Gitea-Signature`
|
||||
4. 如果签名验证失败,请求会被拒绝处理
|
||||
5. 在开发环境下(`NODE_ENV=development`),如果没有提供签名,系统会跳过验证
|
||||
|
||||
验证方法使用SHA-256哈希算法,在处理高负载的情况下这能防止恶意请求并保证请求来源的真实性。
|
||||
|
||||
@@ -139,6 +134,6 @@ MIT
|
||||
|
||||
- `${file.path}` - 当前文件路径
|
||||
- `${fileContent}` - 文件的完整内容
|
||||
- `${file.changes.map(c => `${c.lineNumber}: ${c.content} (${c.type === 'add' ? '新增' : '上下文'})`).join('\n')}` - 变更行的上下文
|
||||
- ```${file.changes.map(c => `${c.lineNumber}: ${c.content} (${c.type === 'add' ? '新增' : '上下文'})`).join('\n')}``` - 变更行的上下文
|
||||
|
||||
请确保你的自定义提示返回正确的格式,特别是对于行评论,必须返回有效的JSON数组。
|
||||
|
||||
46
auto-ver.sh
Executable file
46
auto-ver.sh
Executable file
@@ -0,0 +1,46 @@
|
||||
#!/bin/sh
|
||||
|
||||
# 生成基于日期的版本号函数
|
||||
generate_date_version() {
|
||||
date +"%Y.%-m.%-d"
|
||||
}
|
||||
|
||||
# 由于可能允许在detached状态下执行脚本,因此我们允许外部指定HEAD的REF
|
||||
HEAD_REF_NAME=$1
|
||||
if [ -z "$HEAD_REF_NAME" ]; then
|
||||
HEAD_REF_NAME=$(git rev-parse --abbrev-ref HEAD)
|
||||
fi
|
||||
if [ "$HEAD_REF_NAME" = "main" ]; then
|
||||
# 对于main分支,使用日期格式的版本号
|
||||
generate_date_version
|
||||
exit 0
|
||||
fi
|
||||
TAG_RECENT=$(git describe --tags --abbrev=0 --match "v*" 2>/dev/null)
|
||||
if [ -z "$TAG_RECENT" ]; then
|
||||
# 无法获取到符合semver格式的tag时,使用日期格式的版本号
|
||||
generate_date_version
|
||||
exit 0
|
||||
fi
|
||||
TAG_COMMIT=$(git log $TAG_RECENT --oneline -1 | awk '{print $1}')
|
||||
TAG_VERSION=$(echo $TAG_RECENT | tr -d "v")
|
||||
|
||||
sub_version_by_type()
|
||||
{
|
||||
type=$1
|
||||
field=3
|
||||
if [ "$type" = "major" ]; then
|
||||
field=1
|
||||
elif [ "$type" = "minor" ]; then
|
||||
field=2
|
||||
else
|
||||
field=3
|
||||
fi
|
||||
echo $TAG_VERSION | awk -F '.' -v field=$field '{print $field}'
|
||||
}
|
||||
|
||||
MAJOR=$(sub_version_by_type major)
|
||||
MINOR=$(sub_version_by_type minor)
|
||||
PATCH=$(sub_version_by_type patch)
|
||||
REVISION_TO_HEAD=$(git rev-list --count $TAG_COMMIT...HEAD)
|
||||
|
||||
echo "$MAJOR.$MINOR.$(($PATCH+$REVISION_TO_HEAD))"
|
||||
@@ -1,12 +1,12 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
ai-code-review:
|
||||
gitea-assistant:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: registry.kuiper.com/ai-code-review:latest
|
||||
container_name: ai-code-review
|
||||
image: registry.kuiper.com/gitea-assistant:latest
|
||||
container_name: gitea-assistant
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3000:3000"
|
||||
|
||||
97
kubernetes.yaml.template
Normal file
97
kubernetes.yaml.template
Normal file
@@ -0,0 +1,97 @@
|
||||
# ConfigMap 用于存储非敏感配置
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: gitea-assistant-config
|
||||
data:
|
||||
GITEA_API_URL: "http://gitea.kuiper.com/api/v1"
|
||||
OPENAI_BASE_URL: "https://aihubmix.com/v1"
|
||||
OPENAI_MODEL: "gpt-4o-mini"
|
||||
PORT: "3000"
|
||||
|
||||
---
|
||||
# Secret 用于存储敏感信息
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: gitea-assistant-secrets
|
||||
type: Opaque
|
||||
data:
|
||||
# base64 编码的敏感数据
|
||||
GITEA_ACCESS_TOKEN: "{{GITEA_ACCESS_TOKEN}}"
|
||||
OPENAI_API_KEY: "{{OPENAI_API_KEY}}"
|
||||
WEBHOOK_SECRET: "{{WEBHOOK_SECRET}}"
|
||||
|
||||
---
|
||||
# Deployment 定义应用程序部署
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: gitea-assistant
|
||||
labels:
|
||||
app: gitea-assistant
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: gitea-assistant
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: gitea-assistant
|
||||
spec:
|
||||
containers:
|
||||
- name: gitea-assistant
|
||||
image: registry.kuiper.com/gitea-assistant:{{VERSION}}
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
name: http
|
||||
resources:
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 3000
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 3000
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
securityContext:
|
||||
runAsUser: 1001
|
||||
runAsGroup: 1001
|
||||
allowPrivilegeEscalation: false
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: gitea-assistant-config
|
||||
- secretRef:
|
||||
name: gitea-assistant-secrets
|
||||
|
||||
---
|
||||
# Service 暴露应用程序
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: gitea-assistant
|
||||
labels:
|
||||
app: gitea-assistant
|
||||
spec:
|
||||
selector:
|
||||
app: gitea-assistant
|
||||
ports:
|
||||
- port: 3000
|
||||
targetPort: 3000
|
||||
nodePort: 30300
|
||||
name: http
|
||||
type: NodePort
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "gitea-ai-reviewer",
|
||||
"name": "gitea-assistant",
|
||||
"version": "1.0.0",
|
||||
"description": "AI-driven code review for Gitea",
|
||||
"description": "Gitea功能增强助手,包含AI代码审核功能",
|
||||
"engines": {
|
||||
"bun": ">=1.2.5"
|
||||
},
|
||||
|
||||
@@ -8,6 +8,13 @@ import { logger } from '../utils/logger';
|
||||
// 判断是否为开发环境
|
||||
const isDev = process.env.NODE_ENV === 'development' || !process.env.NODE_ENV;
|
||||
|
||||
// Gitea webhook事件类型
|
||||
enum GiteaEventType {
|
||||
PullRequest = 'pull_request',
|
||||
Status = 'status',
|
||||
Unknown = 'unknown'
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证Webhook请求签名
|
||||
*/
|
||||
@@ -48,9 +55,9 @@ function verifyWebhookSignature(body: string, signature: string): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理Pull Request事件
|
||||
* 统一处理Gitea Webhook事件
|
||||
*/
|
||||
export async function handlePullRequestEvent(c: Context): Promise<Response> {
|
||||
export async function handleGiteaWebhook(c: Context): Promise<Response> {
|
||||
try {
|
||||
// 验证Webhook签名
|
||||
const signature = c.req.header('X-Gitea-Signature') || '';
|
||||
@@ -64,137 +71,160 @@ export async function handlePullRequestEvent(c: Context): Promise<Response> {
|
||||
// 解析请求体
|
||||
const body = JSON.parse(rawBody);
|
||||
|
||||
// 仅处理PR打开或更新事件
|
||||
if (
|
||||
body.action !== 'opened' &&
|
||||
body.action !== 'reopened' &&
|
||||
body.action !== 'synchronize' &&
|
||||
body.action !== 'edited'
|
||||
) {
|
||||
return c.json({ status: 'ignored', message: '无需处理的事件类型' }, 200);
|
||||
// 确定事件类型
|
||||
const eventType = determineEventType(c, body);
|
||||
logger.info(`收到Gitea Webhook事件: ${eventType}`);
|
||||
|
||||
// 根据事件类型路由到相应的处理逻辑
|
||||
switch (eventType) {
|
||||
case GiteaEventType.PullRequest:
|
||||
return await handlePullRequestEvent(c, body);
|
||||
case GiteaEventType.Status:
|
||||
return await handleCommitStatusEvent(c, body);
|
||||
default:
|
||||
logger.warn(`未支持的Webhook事件类型: ${eventType}`);
|
||||
return c.json({ status: 'ignored', message: '未支持的Webhook事件类型' }, 200);
|
||||
}
|
||||
|
||||
// 从事件中提取必要信息
|
||||
const {
|
||||
pull_request: pullRequest,
|
||||
repository: repo
|
||||
} = body;
|
||||
|
||||
if (!pullRequest || !repo) {
|
||||
return c.json({ error: '无效的Webhook数据' }, 400);
|
||||
}
|
||||
|
||||
const prNumber = pullRequest.number;
|
||||
const owner = repo.owner.login;
|
||||
const repoName = repo.name;
|
||||
|
||||
logger.info(`收到PR事件`, { owner, repo: repoName, prNumber, action: body.action });
|
||||
|
||||
// 开始异步审查流程
|
||||
reviewPullRequest(owner, repoName, prNumber).catch(error => {
|
||||
logger.error(`审查PR ${owner}/${repoName}#${prNumber} 失败:`, error);
|
||||
});
|
||||
|
||||
// 立即返回以不阻塞Webhook
|
||||
return c.json({ status: 'accepted', message: '代码审查请求已接受' }, 202);
|
||||
} catch (error) {
|
||||
logger.error('处理Webhook事件失败:', error);
|
||||
return c.json({ error: '处理Webhook事件失败' }, 500);
|
||||
logger.error('处理Gitea Webhook事件失败:', error);
|
||||
return c.json({ error: '处理Gitea Webhook事件失败' }, 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定Gitea Webhook事件类型
|
||||
*/
|
||||
function determineEventType(c: Context, body: any): GiteaEventType {
|
||||
// 优先从请求头获取事件类型
|
||||
const eventHeader = c.req.header('X-Gitea-Event');
|
||||
if (eventHeader) {
|
||||
if (eventHeader === 'pull_request') return GiteaEventType.PullRequest;
|
||||
if (eventHeader === 'status') return GiteaEventType.Status;
|
||||
}
|
||||
|
||||
// 如果没有事件头,尝试从请求体判断
|
||||
if (body.pull_request) return GiteaEventType.PullRequest;
|
||||
if (body.state && (body.sha || body.commit)) return GiteaEventType.Status;
|
||||
|
||||
// 无法确定事件类型
|
||||
return GiteaEventType.Unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理Pull Request事件
|
||||
*/
|
||||
async function handlePullRequestEvent(c: Context, body: any): Promise<Response> {
|
||||
// 仅处理PR打开或更新事件
|
||||
if (
|
||||
body.action !== 'opened' &&
|
||||
body.action !== 'reopened' &&
|
||||
body.action !== 'synchronize' &&
|
||||
body.action !== 'edited'
|
||||
) {
|
||||
return c.json({ status: 'ignored', message: '无需处理的事件类型' }, 200);
|
||||
}
|
||||
|
||||
// 从事件中提取必要信息
|
||||
const {
|
||||
pull_request: pullRequest,
|
||||
repository: repo
|
||||
} = body;
|
||||
|
||||
if (!pullRequest || !repo) {
|
||||
return c.json({ error: '无效的Webhook数据' }, 400);
|
||||
}
|
||||
|
||||
const prNumber = pullRequest.number;
|
||||
const owner = repo.owner.login;
|
||||
const repoName = repo.name;
|
||||
|
||||
logger.info(`收到PR事件`, { owner, repo: repoName, prNumber, action: body.action });
|
||||
|
||||
// 开始异步审查流程
|
||||
reviewPullRequest(owner, repoName, prNumber).catch(error => {
|
||||
logger.error(`审查PR ${owner}/${repoName}#${prNumber} 失败:`, error);
|
||||
});
|
||||
|
||||
// 立即返回以不阻塞Webhook
|
||||
return c.json({ status: 'accepted', message: '代码审查请求已接受' }, 202);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理提交状态更新事件
|
||||
*/
|
||||
export async function handleCommitStatusEvent(c: Context): Promise<Response> {
|
||||
try {
|
||||
// 验证Webhook签名
|
||||
const signature = c.req.header('X-Gitea-Signature') || '';
|
||||
const rawBody = await c.req.text();
|
||||
async function handleCommitStatusEvent(c: Context, body: any): Promise<Response> {
|
||||
// 记录收到的数据,方便调试
|
||||
logger.debug('收到提交状态webhook数据', {
|
||||
state: body.state,
|
||||
sha: body.sha,
|
||||
commit_id: body.commit?.id,
|
||||
context: body.context,
|
||||
repo: body.repository?.full_name
|
||||
});
|
||||
|
||||
if (!verifyWebhookSignature(rawBody, signature)) {
|
||||
logger.error('Webhook签名验证失败');
|
||||
return c.json({ error: 'Webhook签名验证失败' }, 401);
|
||||
}
|
||||
|
||||
const body = JSON.parse(rawBody);
|
||||
|
||||
// 记录收到的数据,方便调试
|
||||
logger.debug('收到提交状态webhook数据', {
|
||||
state: body.state,
|
||||
sha: body.sha,
|
||||
commit_id: body.commit?.id,
|
||||
context: body.context,
|
||||
repo: body.repository?.full_name
|
||||
});
|
||||
|
||||
// 验证请求体中是否包含必要信息
|
||||
if (!body.commit || !body.repository || !body.state) {
|
||||
logger.error('无效的Webhook数据', { body: JSON.stringify(body).substring(0, 500) });
|
||||
return c.json({ error: '无效的Webhook数据' }, 400);
|
||||
}
|
||||
|
||||
// 只处理成功状态的提交
|
||||
if (body.state !== 'success') {
|
||||
return c.json({ status: 'ignored', message: `忽略非成功状态的提交: ${body.state}` }, 200);
|
||||
}
|
||||
|
||||
// 获取关键信息
|
||||
const commitSha = body.sha || body.commit.id; // 兼容不同版本的Gitea
|
||||
const owner = body.repository.owner.login;
|
||||
const repoName = body.repository.name;
|
||||
|
||||
// 检查提交是否与PR相关
|
||||
let relatedPR: PullRequestDetails | null = null;
|
||||
try {
|
||||
relatedPR = await giteaService.getRelatedPullRequest(owner, repoName, commitSha);
|
||||
if (!relatedPR) {
|
||||
logger.info(`提交 ${commitSha} 不与任何PR关联,跳过审查`);
|
||||
return c.json({ status: 'ignored', message: '提交不与任何PR关联' }, 200);
|
||||
}
|
||||
logger.info(`提交 ${commitSha} 关联到PR #${relatedPR.number}`);
|
||||
} catch (error) {
|
||||
logger.warn(`检查提交 ${commitSha} 是否与PR关联时出错`, error);
|
||||
// 继续处理,因为有可能API临时错误,但提交仍需审查
|
||||
}
|
||||
|
||||
// 提取commit信息
|
||||
const commitInfo = {
|
||||
sha: commitSha,
|
||||
message: body.commit.message || '',
|
||||
added: body.commit.added || [],
|
||||
removed: body.commit.removed || [],
|
||||
modified: body.commit.modified || []
|
||||
};
|
||||
|
||||
logger.info(`收到提交状态更新事件`, {
|
||||
owner,
|
||||
repo: repoName,
|
||||
commitSha,
|
||||
state: body.state,
|
||||
relatedPR: relatedPR?.number || 'unknown',
|
||||
added: commitInfo.added.length,
|
||||
modified: commitInfo.modified.length,
|
||||
removed: commitInfo.removed.length
|
||||
});
|
||||
|
||||
// 如果没有文件变更信息,则忽略
|
||||
if (commitInfo.added.length === 0 && commitInfo.modified.length === 0 && commitInfo.removed.length === 0) {
|
||||
logger.warn('提交没有文件变更信息,忽略审查', { commitSha });
|
||||
return c.json({ status: 'ignored', message: '提交没有文件变更信息' }, 200);
|
||||
}
|
||||
|
||||
// 开始异步审查流程,传入关联的PR信息
|
||||
reviewCommit(owner, repoName, commitSha, commitInfo, relatedPR).catch(error => {
|
||||
logger.error(`审查提交 ${owner}/${repoName}@${commitSha} 失败:`, error);
|
||||
});
|
||||
|
||||
// 立即返回以不阻塞Webhook
|
||||
return c.json({ status: 'accepted', message: '提交代码审查请求已接受' }, 202);
|
||||
} catch (error) {
|
||||
logger.error('处理提交状态Webhook事件失败:', error);
|
||||
return c.json({ error: '处理提交状态Webhook事件失败' }, 500);
|
||||
// 验证请求体中是否包含必要信息
|
||||
if (!body.commit || !body.repository || !body.state) {
|
||||
logger.error('无效的Webhook数据', { body: JSON.stringify(body).substring(0, 500) });
|
||||
return c.json({ error: '无效的Webhook数据' }, 400);
|
||||
}
|
||||
|
||||
// 只处理成功状态的提交
|
||||
if (body.state !== 'success') {
|
||||
return c.json({ status: 'ignored', message: `忽略非成功状态的提交: ${body.state}` }, 200);
|
||||
}
|
||||
|
||||
// 获取关键信息
|
||||
const commitSha = body.sha || body.commit.id; // 兼容不同版本的Gitea
|
||||
const owner = body.repository.owner.login;
|
||||
const repoName = body.repository.name;
|
||||
|
||||
// 检查提交是否与PR相关
|
||||
let relatedPR: PullRequestDetails | null = null;
|
||||
try {
|
||||
relatedPR = await giteaService.getRelatedPullRequest(owner, repoName, commitSha);
|
||||
if (!relatedPR) {
|
||||
logger.info(`提交 ${commitSha} 不与任何PR关联,跳过审查`);
|
||||
return c.json({ status: 'ignored', message: '提交不与任何PR关联' }, 200);
|
||||
}
|
||||
logger.info(`提交 ${commitSha} 关联到PR #${relatedPR.number}`);
|
||||
} catch (error) {
|
||||
logger.warn(`检查提交 ${commitSha} 是否与PR关联时出错`, error);
|
||||
// 继续处理,因为有可能API临时错误,但提交仍需审查
|
||||
}
|
||||
|
||||
// 提取commit信息
|
||||
const commitInfo = {
|
||||
sha: commitSha,
|
||||
message: body.commit.message || '',
|
||||
added: body.commit.added || [],
|
||||
removed: body.commit.removed || [],
|
||||
modified: body.commit.modified || []
|
||||
};
|
||||
|
||||
logger.info(`收到提交状态更新事件`, {
|
||||
owner,
|
||||
repo: repoName,
|
||||
commitSha,
|
||||
state: body.state,
|
||||
relatedPR: relatedPR?.number || 'unknown',
|
||||
added: commitInfo.added.length,
|
||||
modified: commitInfo.modified.length,
|
||||
removed: commitInfo.removed.length
|
||||
});
|
||||
|
||||
// 如果没有文件变更信息,则忽略
|
||||
if (commitInfo.added.length === 0 && commitInfo.modified.length === 0 && commitInfo.removed.length === 0) {
|
||||
logger.warn('提交没有文件变更信息,忽略审查', { commitSha });
|
||||
return c.json({ status: 'ignored', message: '提交没有文件变更信息' }, 200);
|
||||
}
|
||||
|
||||
// 开始异步审查流程,传入关联的PR信息
|
||||
reviewCommit(owner, repoName, commitSha, commitInfo, relatedPR).catch(error => {
|
||||
logger.error(`审查提交 ${owner}/${repoName}@${commitSha} 失败:`, error);
|
||||
});
|
||||
|
||||
// 立即返回以不阻塞Webhook
|
||||
return c.json({ status: 'accepted', message: '提交代码审查请求已接受' }, 202);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
18
src/index.ts
18
src/index.ts
@@ -1,5 +1,5 @@
|
||||
import { Hono } from 'hono';
|
||||
import { handlePullRequestEvent, handleCommitStatusEvent } from './controllers/review';
|
||||
import { handleGiteaWebhook } from './controllers/review';
|
||||
import config from './config';
|
||||
|
||||
// 创建Hono应用实例
|
||||
@@ -12,13 +12,11 @@ app.get('/', (c) => {
|
||||
return c.json({
|
||||
status: 'ok',
|
||||
message: 'AI Code Review 服务运行中',
|
||||
version: '1.1.0',
|
||||
version: '2.0.0',
|
||||
webhookSecurityEnabled: webhookSecretConfigured,
|
||||
configuration: {
|
||||
webhookEndpoints: {
|
||||
pullRequest: '/webhook/gitea/pull_request',
|
||||
commitStatus: '/webhook/gitea/status',
|
||||
legacy: '/webhook/gitea (仅支持Pull Request事件)'
|
||||
unified: '/webhook/gitea (支持Pull Request和Commit Status事件)'
|
||||
},
|
||||
signature: webhookSecretConfigured
|
||||
? '签名验证已启用 (使用X-Gitea-Signature头)'
|
||||
@@ -27,14 +25,8 @@ app.get('/', (c) => {
|
||||
});
|
||||
});
|
||||
|
||||
// Gitea webhook路由 - 处理PR事件
|
||||
app.post('/webhook/gitea/pull_request', handlePullRequestEvent);
|
||||
|
||||
// Gitea webhook路由 - 处理提交状态更新事件
|
||||
app.post('/webhook/gitea/status', handleCommitStatusEvent);
|
||||
|
||||
// 向后兼容的路由(将保留一段时间)
|
||||
app.post('/webhook/gitea', handlePullRequestEvent);
|
||||
// 统一的Gitea webhook路由 - 处理所有事件类型
|
||||
app.post('/webhook/gitea', handleGiteaWebhook);
|
||||
|
||||
// 启动服务器
|
||||
const port = config.app.port;
|
||||
|
||||
Reference in New Issue
Block a user