diff --git a/src/app/api/contact/route.ts b/src/app/api/contact/route.ts index 44c7dae..e75b967 100644 --- a/src/app/api/contact/route.ts +++ b/src/app/api/contact/route.ts @@ -1,107 +1,85 @@ import { NextRequest, NextResponse } from 'next/server'; import { Resend } from 'resend'; import { z } from 'zod'; +import { generateNotificationEmail, generateConfirmationEmail } from '@/lib/email-templates'; +import { COMPANY_INFO } from '@/lib/constants'; -const contactSchema = z.object({ - name: z.string().min(1, '姓名不能为空').max(50, '姓名不能超过50个字符'), - phone: z.string().optional(), - email: z.string().min(1, '邮箱不能为空').email('邮箱格式不正确').max(100, '邮箱不能超过100个字符'), - subject: z.string().min(1, '主题不能为空').max(100, '主题不能超过100个字符'), - message: z.string().min(1, '消息内容不能为空').max(1000, '消息内容不能超过1000个字符'), +const contactFormSchema = z.object({ + name: z.string().min(2, '姓名至少需要2个字符').max(50, '姓名不能超过50个字符'), + phone: z.string().regex(/^1[3-9]\d{9}$/, '请输入有效的手机号码'), + email: z.string().email('请输入有效的邮箱地址'), + message: z.string().min(10, '留言内容至少需要10个字符').max(1000, '留言内容不能超过1000字符'), + csrfToken: z.string().min(1, 'CSRF Token 不能为空'), }); -const RATE_LIMIT_WINDOW = 60 * 60 * 1000; // 1小时 -const RATE_LIMIT_MAX_REQUESTS = 10; +type ContactFormData = z.infer; -interface RateLimitRecord { - count: number; - resetTime: number; -} - -const rateLimitStore: Record = {}; - -function checkRateLimit(ip: string): { allowed: boolean; remaining: number } { - const now = Date.now(); - const record = rateLimitStore[ip]; - - if (!record || now > record.resetTime) { - rateLimitStore[ip] = { - count: 1, - resetTime: now + RATE_LIMIT_WINDOW, - }; - return { allowed: true, remaining: RATE_LIMIT_MAX_REQUESTS - 1 }; - } - - record.count += 1; - const remaining = Math.max(0, RATE_LIMIT_MAX_REQUESTS - record.count); - - if (record.count > RATE_LIMIT_MAX_REQUESTS) { - return { allowed: false, remaining: 0 }; - } - - return { allowed: true, remaining }; -} +const resend = new Resend(process.env.RESEND_API_KEY); export async function POST(request: NextRequest) { - const clientIp = request.headers.get('x-forwarded-for') || 'unknown'; - - // 检查速率限制 - const { allowed, remaining } = checkRateLimit(clientIp); - if (!allowed) { - return NextResponse.json( - { error: '请求过于频繁,请稍后再试' }, - { status: 429, headers: { 'X-RateLimit-Remaining': '0' } } - ); - } - try { const body = await request.json(); - const validatedData = contactSchema.parse(body); - const { name, phone, email, subject, message } = validatedData; + const result = contactFormSchema.safeParse(body); - const resend = new Resend(process.env.RESEND_API_KEY); - - await resend.emails.send({ - from: process.env.FROM_EMAIL || 'No reply ', - to: [process.env.CONTACT_EMAIL || 'contact@novalon.cn'], - subject: `[${subject}] ${name}`, - html: ` -
-

新消息通知

-
-

姓名:${name}

- ${phone ? `

电话:${phone}

` : ''} -

邮箱:${email}

-

主题:${subject}

-

消息内容:

-
${message}
-
-

此邮件由系统自动发送,请勿直接回复。

-
- `, - }); - - return NextResponse.json( - { success: true, remaining }, - { status: 200, headers: { 'X-RateLimit-Remaining': remaining.toString() } } - ); - } catch (error) { - if (error instanceof z.ZodError) { + if (!result.success) { + const errors = result.error.flatten().fieldErrors; return NextResponse.json( - { error: '数据验证失败', details: error.issues }, + { + success: false, + message: '表单验证失败', + errors, + }, { status: 400 } ); } + const data: ContactFormData = result.data; + + const companyEmail = process.env.COMPANY_EMAIL || COMPANY_INFO.email; + + const [notificationResult, confirmationResult] = await Promise.all([ + resend.emails.send({ + from: `${COMPANY_INFO.name} `, + to: [companyEmail], + subject: `[官网留言] 来自 ${data.name} 的咨询`, + html: generateNotificationEmail(data), + }), + resend.emails.send({ + from: `${COMPANY_INFO.name} `, + to: [data.email], + subject: `感谢您的留言 - ${COMPANY_INFO.name}`, + html: generateConfirmationEmail(data), + }), + ]); + + if (notificationResult.error) { + console.error('Notification email failed:', notificationResult.error); + return NextResponse.json( + { + success: false, + message: '邮件发送失败,请稍后重试', + }, + { status: 500 } + ); + } + + if (confirmationResult.error) { + console.error('Confirmation email failed:', confirmationResult.error); + } + + return NextResponse.json({ + success: true, + message: '邮件发送成功', + }); + } catch (error) { console.error('Contact form error:', error); return NextResponse.json( - { error: '发送失败,请稍后再试' }, + { + success: false, + message: '服务器错误,请稍后重试', + }, { status: 500 } ); } } - -export async function OPTIONS() { - return NextResponse.json({}, { status: 200 }); -}