fix: 修复代码审查发现的安全性和性能问题

主要修复:
- 安全性:API 密钥使用恒定时间比较防止时序攻击
- 安全性:URL 验证仅允许 http/https 协议,防止 XSS
- 安全性:移除 Markdown 的 rehypeRaw 插件增强 XSS 防护
- 安全性:Webhook 错误信息脱敏,生产环境不暴露敏感数据
- 性能:项目详情页使用并行数据获取
- 性能:Webhook 批量查询标签,避免 N+1 查询问题
- 类型安全:移除 any 类型,使用 Prisma 类型注解
- 类型安全:为 useProjects 函数添加返回类型
- 代码质量:创建 slug 工具函数统一 slug 生成逻辑
- 代码质量:搜索字符串添加最小长度限制(2字符)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-01-16 14:43:46 +08:00
co-authored by Claude
parent 5721206516
commit 2a37a83072
6 changed files with 145 additions and 60 deletions
+39 -7
View File
@@ -1,4 +1,25 @@
import { prisma } from '@/lib/prisma'
import type { Prisma } from '@prisma/client'
// 定义带有标签和链接的项目类型
export type ProjectWithTagsAndLinks = Prisma.ProjectGetPayload<{
include: {
tags: { include: { tag: true } }
links: true
}
}>
// 定义扁平化标签的项目类型
export type ProjectWithFlatTags = Omit<ProjectWithTagsAndLinks, 'tags'> & {
tags: Prisma.TagGetPayload<{}>[]
}
// 定义标签计数类型
export type TagWithProjectCount = Prisma.TagGetPayload<{
include: {
_count: { select: { projects: true } }
}
}>
export async function getProjects(options?: {
search?: string
@@ -6,7 +27,15 @@ export async function getProjects(options?: {
status?: 'ACTIVE' | 'ARCHIVED'
page?: number
limit?: number
}) {
}): Promise<{
projects: ProjectWithFlatTags[]
pagination: {
page: number
limit: number
total: number
totalPages: number
}
}> {
const {
search,
tag,
@@ -15,11 +44,12 @@ export async function getProjects(options?: {
limit = 20,
} = options || {}
const where: any = {
const where: Prisma.ProjectWhereInput = {
status,
}
if (search) {
// 添加搜索字符串长度验证
if (search && search.length >= 2 && search.length <= 100) {
where.OR = [
{ name: { contains: search, mode: 'insensitive' } },
{ nameEn: { contains: search, mode: 'insensitive' } },
@@ -31,7 +61,9 @@ export async function getProjects(options?: {
if (tag) {
where.tags = {
some: {
slug: tag,
tag: {
slug: tag,
},
},
}
}
@@ -73,7 +105,7 @@ export async function getProjects(options?: {
}
}
export async function getProjectBySlug(slug: string) {
export async function getProjectBySlug(slug: string): Promise<ProjectWithFlatTags | null> {
const project = await prisma.project.findUnique({
where: { slug },
include: {
@@ -97,7 +129,7 @@ export async function getProjectBySlug(slug: string) {
}
}
export async function getAllTags() {
export async function getAllTags(): Promise<TagWithProjectCount[]> {
return prisma.tag.findMany({
include: {
_count: {
@@ -110,7 +142,7 @@ export async function getAllTags() {
})
}
export async function getTagsWithProjectCounts() {
export async function getTagsWithProjectCounts(): Promise<TagWithProjectCount[]> {
const tags = await prisma.tag.findMany({
include: {
_count: {