feat: 优化 webhook 去重策略实现多级匹配

- 新增 findExistingProject 函数实现智能去重
- 优先级1: GitHub URL 完全匹配(最准确)
- 优先级2: Website URL 完全匹配
- 优先级3: slug 匹配(兜底)
- 增强日志输出,清晰显示匹配方式和项目 ID

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-27 10:29:59 +08:00
co-authored by Claude
parent 31ed4b7c25
commit 23d4c7df49
+94 -5
View File
@@ -7,6 +7,80 @@ import {
type ProjectInput,
} from '@/lib/validations'
/**
* 多级去重策略:查找已存在的项目
*
* 优先级:
* 1. GitHub URL 完全匹配(最准确)
* 2. Website URL 完全匹配
* 3. slug 匹配(兜底)
*
* @param projectData - 项目数据
* @returns 已存在的项目,如果不存在则返回 null
*/
async function findExistingProject(projectData: ProjectInput) {
// 优先级1: 通过 GitHub URL 匹配
const githubLink = projectData.links.find((link) => link.type === 'GITHUB')
if (githubLink) {
const existingByGithub = await prisma.externalLink.findFirst({
where: {
type: 'GITHUB',
url: githubLink.url,
},
include: {
project: true,
},
})
if (existingByGithub) {
console.log(
`[Webhook] Found existing project by GitHub URL: ${githubLink.url}`
)
return existingByGithub.project
}
}
// 优先级2: 通过 Website URL 匹配
const websiteLink = projectData.links.find(
(link) => link.type === 'WEBSITE'
)
if (websiteLink) {
const existingByWebsite = await prisma.externalLink.findFirst({
where: {
type: 'WEBSITE',
url: websiteLink.url,
},
include: {
project: true,
},
})
if (existingByWebsite) {
console.log(
`[Webhook] Found existing project by Website URL: ${websiteLink.url}`
)
return existingByWebsite.project
}
}
// 优先级3: 通过 slug 匹配(兜底)
const slug =
projectData.nameEn?.toLowerCase().replace(/\s+/g, '-') ||
projectData.name.toLowerCase().replace(/\s+/g, '-')
const existingBySlug = await prisma.project.findUnique({
where: { slug },
})
if (existingBySlug) {
console.log(`[Webhook] Found existing project by slug: ${slug}`)
return existingBySlug
}
console.log(`[Webhook] No existing project found, will create new one`)
return null
}
export async function POST(request: NextRequest) {
const startTime = Date.now()
@@ -77,6 +151,9 @@ export async function POST(request: NextRequest) {
try {
const validProject = projectValidation.data as ProjectInput
// 多级去重:查找已存在的项目
const existingProject = await findExistingProject(validProject)
// Upsert tags
const tagConnections = await Promise.all(
validProject.tags.map(async (tag) => {
@@ -101,13 +178,21 @@ export async function POST(request: NextRequest) {
validProject.nameEn?.toLowerCase().replace(/\s+/g, '-') ||
validProject.name.toLowerCase().replace(/\s+/g, '-')
// Upsert project
const existingProject = await prisma.project.findUnique({
where: { slug },
})
if (existingProject) {
// Update existing project
const matchMethod = existingProject.slug === slug
? 'slug'
: validProject.links.some(
(l) =>
existingProject.links.some((el) => el.url === l.url)
)
? 'url'
: 'unknown'
console.log(
`[Webhook] Updating project "${validProject.name}" (matched by ${matchMethod}, id: ${existingProject.id})`
)
await prisma.project.update({
where: { id: existingProject.id },
data: {
@@ -142,6 +227,10 @@ export async function POST(request: NextRequest) {
results.updated++
} else {
// Create new project
console.log(
`[Webhook] Creating new project "${validProject.name}" (slug: ${slug})`
)
await prisma.project.create({
data: {
name: validProject.name,