Files
novalon-website/docs/plans/2026-03-20-quality-improvement-iteration.md
T
张翔 f5dec95a83 feat: 添加管理后台页面和功能,优化测试和性能配置
refactor: 重构页面导航和滚动逻辑,提升用户体验

test: 更新测试配置和用例,增加覆盖率和稳定性

perf: 优化性能指标和阈值,适应开发环境需求

ci: 添加Lighthouse CI工作流,集成性能测试

docs: 更新API文档和健康检查端点

fix: 修复登录页面和表单提交问题

style: 调整响应式布局和可访问性改进

chore: 更新依赖项和脚本配置
2026-03-24 10:11:30 +08:00

37 KiB
Raw Blame History

质量改进迭代实施计划

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: 根据系统评估报告,按优先级完成代码质量、测试框架和文档的改进工作,提升项目整体质量。

Architecture: 采用渐进式改进策略,优先解决高优先级问题(统一实现、测试覆盖率、报告可视化),然后推进中优先级改进(API文档、版本控制、性能监控),最后优化低优先级任务。

Tech Stack: Next.js 16, TypeScript, Jest, Playwright, Allure, OpenAPI/Swagger, Lighthouse CI


高优先级任务(1周内完成)

Task 1: 统一联系表单实现

问题: 当前联系表单存在双重实现(Server Actions和API Route),需要统一为Server Actions。

Files:

  • Delete: src/app/api/contact/route.ts
  • Modify: src/app/(marketing)/contact/page.tsx
  • Modify: src/app/(marketing)/contact/actions.ts
  • Test: src/app/api/contact/route.test.ts (需要删除或迁移)

Step 1: 分析现有实现

检查两个文件的实现差异:

# 查看Server Actions实现
cat src/app/\(marketing\)/contact/actions.ts

# 查看API Route实现
cat src/app/api/contact/route.ts

Step 2: 增强Server Actions实现

修改 src/app/(marketing)/contact/actions.ts,添加完整的验证和邮件发送逻辑:

'use server';

import { Resend } from 'resend';
import { z } from 'zod';

const resend = new Resend(process.env.RESEND_API_KEY);
const companyEmail = process.env.COMPANY_EMAIL || 'contact@novalon.cn';

const contactFormSchema = z.object({
  name: z.string().min(1, '请填写姓名'),
  email: z.string().email('请输入有效的邮箱地址'),
  phone: z.string().optional(),
  subject: z.string().min(1, '请填写主题'),
  message: z.string().min(1, '请填写消息'),
  website: z.string().optional(), // 蜜罐字段
  submitTime: z.string().optional(),
  mathHash: z.string().optional(),
  mathTimestamp: z.string().optional(),
  mathAnswer: z.string().optional(),
});

export interface ContactFormState {
  success: boolean;
  message?: string;
  error?: string;
}

export async function submitContactForm(
  _prevState: ContactFormState | null,
  formData: FormData
): Promise<ContactFormState> {
  try {
    // 提取表单数据
    const data = {
      name: formData.get('name') as string,
      email: formData.get('email') as string,
      phone: formData.get('phone') as string,
      subject: formData.get('subject') as string,
      message: formData.get('message') as string,
      website: formData.get('website') as string,
      submitTime: formData.get('submitTime') as string,
      mathHash: formData.get('mathHash') as string,
      mathTimestamp: formData.get('mathTimestamp') as string,
      mathAnswer: formData.get('mathAnswer') as string,
    };

    // 验证数据
    const validatedData = contactFormSchema.parse(data);

    // 蜜罐字段检查
    if (validatedData.website) {
      console.log('Honeypot field filled, rejecting request');
      return { success: true, message: '消息已发送' };
    }

    // 时间检查(防止机器人快速提交)
    if (validatedData.submitTime) {
      const timeDiff = Date.now() - parseInt(validatedData.submitTime);
      if (timeDiff < 2000) {
        console.log('Submission too fast:', timeDiff);
        return { success: false, error: '提交过快,请稍后再试' };
      }
    }

    // 数学验证码检查
    if (validatedData.mathHash && validatedData.mathTimestamp && validatedData.mathAnswer !== undefined) {
      const expectedHash = btoa(`${validatedData.mathAnswer}-${validatedData.mathTimestamp}`);
      if (expectedHash !== validatedData.mathHash) {
        console.log('Invalid math captcha');
        return { success: false, error: '验证码错误,请重新计算' };
      }
    }

    // 发送邮件
    const emailContent = generateEmailContent(validatedData);
    
    const { error } = await resend.emails.send({
      from: '睿新致远官网 <onboarding@resend.dev>',
      to: [companyEmail],
      subject: `📧 ${validatedData.subject} - ${validatedData.name}`,
      html: emailContent,
      replyTo: validatedData.email,
    });

    if (error) {
      console.error('Resend API error:', error);
      return { success: false, error: '邮件发送失败,请稍后重试' };
    }

    console.log('Email sent successfully');
    return { success: true, message: '消息已发送' };
  } catch (error) {
    console.error('Contact form submission error:', error);
    
    if (error instanceof z.ZodError) {
      return { success: false, error: error.errors[0].message };
    }
    
    return { success: false, error: '提交失败,请重试' };
  }
}

function generateEmailContent(data: z.infer<typeof contactFormSchema>): string {
  return `
    <html>
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <style>
          body { 
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; 
            line-height: 1.6; 
            color: #1C1C1C; 
            margin: 0; 
            padding: 0; 
            background-color: #f5f5f5;
          }
          .container { 
            max-width: 600px; 
            margin: 0 auto; 
            padding: 20px; 
            background-color: #ffffff;
          }
          .header { 
            background: #C41E3A; 
            color: white; 
            padding: 40px 30px; 
            text-align: center;
            border-radius: 8px 8px 0 0;
          }
          .header h1 { 
            margin: 0; 
            font-size: 28px; 
            font-weight: 600;
          }
          .content { 
            padding: 40px 30px; 
            background: #ffffff;
          }
          .info-card {
            background: #f9f9f9;
            border-radius: 8px;
            padding: 20px;
            margin-bottom: 25px;
            border: 1px solid #e5e5e5;
          }
          .info-row {
            display: flex;
            margin-bottom: 12px;
            align-items: flex-start;
          }
          .info-label { 
            font-weight: 600; 
            color: #1C1C1C; 
            min-width: 70px;
            font-size: 14px;
          }
          .info-value { 
            color: #5C5C5C; 
            font-size: 14px;
            flex: 1;
          }
          .message-box { 
            background: #fff; 
            padding: 20px; 
            border-left: 4px solid #C41E3A; 
            margin-top: 20px;
            border-radius: 0 8px 8px 0;
            box-shadow: 0 2px 8px rgba(0,0,0,0.05);
          }
          .footer { 
            text-align: center; 
            padding: 30px; 
            color: #8C8C8C; 
            font-size: 12px;
            border-top: 1px solid #e5e5e5;
          }
        </style>
      </head>
      <body>
        <div class="container">
          <div class="header">
            <h1>📬 新的客户咨询</h1>
            <p>来自 睿新致远官方网站</p>
          </div>
          <div class="content">
            <div class="info-card">
              <div class="info-row">
                <div class="info-label">姓名</div>
                <div class="info-value">${data.name}</div>
              </div>
              <div class="info-row">
                <div class="info-label">邮箱</div>
                <div class="info-value"><a href="mailto:${data.email}" style="color: #C41E3A; text-decoration: none;">${data.email}</a></div>
              </div>
              ${data.phone ? `
              <div class="info-row">
                <div class="info-label">电话</div>
                <div class="info-value">${data.phone}</div>
              </div>
              ` : ''}
              <div class="info-row">
                <div class="info-label">主题</div>
                <div class="info-value">${data.subject}</div>
              </div>
            </div>
            <div class="message-box">
              <div style="font-weight: 600; color: #C41E3A; font-size: 14px; margin-bottom: 10px;">咨询内容</div>
              <div style="color: #1C1C1C; font-size: 14px; line-height: 1.8; white-space: pre-wrap;">${data.message}</div>
            </div>
          </div>
          <div class="footer">
            <p>本邮件由 睿新致远 官网联系表单自动发送</p>
            <p>提交时间:${new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })}</p>
            <p>© ${new Date().getFullYear()} 四川睿新致远科技有限公司</p>
          </div>
        </div>
      </body>
    </html>
  `;
}

Step 3: 更新前端表单调用

修改 src/app/(marketing)/contact/page.tsx,确保使用Server Actions

'use client';

import { useFormState, useFormStatus } from 'react-dom';
import { submitContactForm, type ContactFormState } from './actions';

function SubmitButton() {
  const { pending } = useFormStatus();
  
  return (
    <button
      type="submit"
      disabled={pending}
      className="w-full bg-primary text-white py-3 px-6 rounded-lg hover:bg-primary/90 transition-colors disabled:opacity-50"
    >
      {pending ? '发送中...' : '发送消息'}
    </button>
  );
}

export default function ContactPage() {
  const [state, formAction] = useFormState(submitContactForm, null);

  return (
    <form action={formAction} className="space-y-6">
      {/* 表单字段 */}
      <div>
        <label htmlFor="name">姓名 *</label>
        <input
          id="name"
          name="name"
          type="text"
          required
          className="w-full border rounded-lg px-4 py-2"
        />
      </div>
      
      {/* 其他字段... */}
      
      {state?.error && (
        <div className="text-red-600 text-sm">{state.error}</div>
      )}
      
      {state?.success && (
        <div className="text-green-600 text-sm">{state.message}</div>
      )}
      
      <SubmitButton />
    </form>
  );
}

Step 4: 删除API Route文件

rm src/app/api/contact/route.ts
rm src/app/api/contact/route.test.ts

Step 5: 更新测试

创建新的测试文件 src/app/(marketing)/contact/actions.test.ts

import { submitContactForm } from './actions';

describe('submitContactForm', () => {
  it('should validate required fields', async () => {
    const formData = new FormData();
    formData.append('name', '');
    formData.append('email', 'invalid-email');
    formData.append('subject', '');
    formData.append('message', '');

    const result = await submitContactForm(null, formData);
    
    expect(result.success).toBe(false);
    expect(result.error).toBeDefined();
  });

  it('should reject honeypot field', async () => {
    const formData = new FormData();
    formData.append('name', 'Test');
    formData.append('email', 'test@example.com');
    formData.append('subject', 'Test Subject');
    formData.append('message', 'Test Message');
    formData.append('website', 'bot');

    const result = await submitContactForm(null, formData);
    
    expect(result.success).toBe(true);
    expect(result.message).toBe('消息已发送');
  });

  it('should reject fast submission', async () => {
    const formData = new FormData();
    formData.append('name', 'Test');
    formData.append('email', 'test@example.com');
    formData.append('subject', 'Test Subject');
    formData.append('message', 'Test Message');
    formData.append('submitTime', (Date.now() - 1000).toString());

    const result = await submitContactForm(null, formData);
    
    expect(result.success).toBe(false);
    expect(result.error).toContain('提交过快');
  });
});

Step 6: 运行测试验证

npm run test:unit

Expected: 所有测试通过

Step 7: 手动测试

npm run dev
# 访问 http://localhost:3000/contact
# 填写表单并提交
# 验证邮件发送成功

Step 8: 提交更改

git add src/app/\(marketing\)/contact/
git add src/app/api/contact/
git commit -m "refactor: unify contact form implementation to use Server Actions

- Remove duplicate API Route implementation
- Enhance Server Actions with full validation and email sending
- Add comprehensive tests for form validation
- Improve error handling and user feedback

BREAKING CHANGE: API endpoint /api/contact removed, use Server Actions instead"

Task 2: 配置单元测试覆盖率报告

问题: 缺少单元测试覆盖率报告,无法量化测试质量。

Files:

  • Modify: jest.config.js 或创建 jest.config.ts
  • Modify: package.json
  • Create: .github/workflows/coverage.yml (可选)

Step 1: 安装依赖

npm install --save-dev @types/jest jest ts-jest

Step 2: 创建Jest配置文件

创建 jest.config.ts

import type { Config } from 'jest';
import nextJest from 'next/jest';

const createJestConfig = nextJest({
  dir: './',
});

const config: Config = {
  coverageProvider: 'v8',
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
  },
  collectCoverage: true,
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80,
    },
  },
  coverageReporters: ['text', 'lcov', 'html', 'json-summary'],
  coverageDirectory: 'coverage',
  testMatch: [
    '**/__tests__/**/*.[jt]s?(x)',
    '**/?(*.)+(spec|test).[jt]s?(x)',
  ],
  testPathIgnorePatterns: [
    '/node_modules/',
    '/.next/',
    '/e2e/',
    '/dist/',
  ],
  collectCoverageFrom: [
    'src/**/*.{js,jsx,ts,tsx}',
    '!src/**/*.d.ts',
    '!src/**/*.stories.{js,jsx,ts,tsx}',
    '!src/**/__tests__/**',
    '!src/**/node_modules/**',
  ],
};

export default createJestConfig(config);

Step 3: 创建Jest Setup文件

创建 jest.setup.ts

import '@testing-library/jest-dom';

// Mock Next.js modules
jest.mock('next/navigation', () => ({
  useRouter() {
    return {
      push: jest.fn(),
      replace: jest.fn(),
      prefetch: jest.fn(),
      back: jest.fn(),
      pathname: '/',
      query: {},
      asPath: '/',
    };
  },
  usePathname() {
    return '/';
  },
  useSearchParams() {
    return new URLSearchParams();
  },
}));

// Mock environment variables
process.env.NEXTAUTH_SECRET = 'test-secret';
process.env.NEXTAUTH_URL = 'http://localhost:3000';
process.env.DATABASE_URL = 'file:./test.db';
process.env.RESEND_API_KEY = 'test-key';
process.env.COMPANY_EMAIL = 'test@example.com';

Step 4: 更新package.json脚本

package.json 中添加:

{
  "scripts": {
    "test:unit": "jest",
    "test:unit:watch": "jest --watch",
    "test:unit:coverage": "jest --coverage",
    "test:coverage:open": "open coverage/lcov-report/index.html"
  }
}

Step 5: 运行覆盖率测试

npm run test:unit:coverage

Expected: 生成覆盖率报告,显示覆盖率百分比

Step 6: 查看覆盖率报告

npm run test:coverage:open

Expected: 在浏览器中打开HTML覆盖率报告

Step 7: 添加覆盖率徽章(可选)

README.md 中添加:

[![Coverage Status](https://codecov.io/gh/your-org/your-repo/branch/main/graph/badge.svg)](https://codecov.io/gh/your-org/your-repo)

Step 8: 提交更改

git add jest.config.ts jest.setup.ts package.json README.md
git commit -m "feat: add unit test coverage reporting

- Configure Jest with 80% coverage threshold
- Add coverage reporters (text, lcov, html, json)
- Create Jest setup with Next.js mocks
- Add npm scripts for coverage reporting
- Add coverage badge to README

Target coverage: 80% for branches, functions, lines, statements"

Task 3: 配置Allure测试报告

问题: 缺少测试报告可视化,难以追踪测试历史和趋势。

Files:

  • Modify: e2e/playwright.config.ts
  • Modify: package.json
  • Create: scripts/generate-allure-report.sh

Step 1: 安装Allure依赖

npm install --save-dev allure-playwright

Step 2: 更新Playwright配置

修改 e2e/playwright.config.ts,确保Allure reporter已配置:

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './src/tests',
  reporter: [
    ['html', { open: 'never' }],
    ['json', { outputFile: 'test-results/results.json' }],
    ['junit', { outputFile: 'test-results/junit.xml' }],
    ['allure-playwright', {
      outputFolder: 'allure-results',
      detail: true,
      suiteTitle: false,
    }],
  ],
  // ... 其他配置
});

Step 3: 创建报告生成脚本

创建 scripts/generate-allure-report.sh

#!/bin/bash

# 生成Allure报告
echo "Generating Allure report..."

# 检查allure-results目录是否存在
if [ ! -d "allure-results" ]; then
  echo "Error: allure-results directory not found"
  echo "Please run tests first: npm run test"
  exit 1
fi

# 生成报告
allure generate allure-results --clean -o allure-report

echo "Allure report generated successfully!"
echo "Opening report..."

# 打开报告
allure open allure-report

Step 4: 更新package.json脚本

package.json 中添加:

{
  "scripts": {
    "test:report": "allure generate allure-results --clean -o allure-report && allure open allure-report",
    "test:report:ci": "allure generate allure-results --clean -o allure-report"
  }
}

Step 5: 运行测试生成报告

cd e2e
npm test
cd ..
npm run test:report

Expected:

  • 测试执行完成
  • Allure报告在浏览器中打开
  • 显示测试统计、趋势、详情

Step 6: 添加CI集成(可选)

创建 .github/workflows/test-report.yml

name: Test Report

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run E2E tests
        run: npm run test:e2e
      
      - name: Generate Allure report
        if: always()
        run: npm run test:report:ci
      
      - name: Upload Allure report
        if: always()
        uses: actions/upload-artifact@v3
        with:
          name: allure-report
          path: allure-report/
          retention-days: 30

Step 7: 提交更改

git add e2e/playwright.config.ts package.json scripts/generate-allure-report.sh .github/workflows/test-report.yml
git commit -m "feat: add Allure test report visualization

- Configure Allure reporter in Playwright
- Add report generation scripts
- Add CI integration for automatic report generation
- Add npm scripts for easy report access

Allure report provides:
- Test statistics and trends
- Detailed test execution history
- Visual test results dashboard"

中优先级任务(2周内完成)

Task 4: 引入OpenAPI文档

问题: 缺少API文档,不利于前后端协作和API维护。

Files:

  • Create: src/app/api/docs/route.ts
  • Create: src/lib/openapi.ts
  • Modify: package.json

Step 1: 安装依赖

npm install swagger-jsdoc swagger-ui-react
npm install --save-dev @types/swagger-jsdoc

Step 2: 创建OpenAPI配置

创建 src/lib/openapi.ts

import swaggerJsdoc from 'swagger-jspec';

const options: swaggerJsdoc.Options = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: 'Novalon Website API',
      version: '1.0.0',
      description: '四川睿新致远科技有限公司官方网站API文档',
      contact: {
        name: '睿新致远',
        email: 'contact@novalon.cn',
        url: 'https://novalon.cn',
      },
    },
    servers: [
      {
        url: 'http://localhost:3000/api',
        description: '开发环境',
      },
      {
        url: 'https://novalon.cn/api',
        description: '生产环境',
      },
    ],
    components: {
      securitySchemes: {
        bearerAuth: {
          type: 'http',
          scheme: 'bearer',
          bearerFormat: 'JWT',
        },
      },
      schemas: {
        User: {
          type: 'object',
          properties: {
            id: { type: 'string' },
            email: { type: 'string', format: 'email' },
            name: { type: 'string' },
            isAdmin: { type: 'boolean' },
            createdAt: { type: 'string', format: 'date-time' },
          },
        },
        Content: {
          type: 'object',
          properties: {
            id: { type: 'string' },
            type: { type: 'string', enum: ['news', 'product', 'service', 'case'] },
            title: { type: 'string' },
            slug: { type: 'string' },
            excerpt: { type: 'string' },
            content: { type: 'string' },
            status: { type: 'string', enum: ['draft', 'published', 'archived'] },
            createdAt: { type: 'string', format: 'date-time' },
          },
        },
        Error: {
          type: 'object',
          properties: {
            success: { type: 'boolean', example: false },
            error: { type: 'string' },
          },
        },
      },
    },
  },
  apis: ['./src/app/api/**/*.ts'], // 指向API路由文件
};

export const specs = swaggerJsdoc(options);

Step 3: 创建API文档路由

创建 src/app/api/docs/route.ts

import { NextResponse } from 'next/server';
import { specs } from '@/lib/openapi';

export async function GET() {
  return NextResponse.json(specs);
}

Step 4: 创建Swagger UI页面

创建 src/app/api-docs/page.tsx

'use client';

import SwaggerUI from 'swagger-ui-react';
import 'swagger-ui-react/swagger-ui.css';

export default function ApiDocsPage() {
  return (
    <div className="min-h-screen bg-white">
      <SwaggerUI url="/api/docs" />
    </div>
  );
}

Step 5: 为API添加JSDoc注释

修改 src/app/api/admin/content/route.ts

/**
 * @swagger
 * /admin/content:
 *   get:
 *     summary: 获取内容列表
 *     tags: [Content]
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: query
 *         name: type
 *         schema:
 *           type: string
 *           enum: [news, product, service, case]
 *       - in: query
 *         name: status
 *         schema:
 *           type: string
 *           enum: [draft, published, archived]
 *       - in: query
 *         name: page
 *         schema:
 *           type: integer
 *           default: 1
 *       - in: query
 *         name: limit
 *         schema:
 *           type: integer
 *           default: 20
 *     responses:
 *       200:
 *         description: 成功获取内容列表
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 success:
 *                   type: boolean
 *                 data:
 *                   type: object
 *                   properties:
 *                     items:
 *                       type: array
 *                       items:
 *                         $ref: '#/components/schemas/Content'
 *                     pagination:
 *                       type: object
 *       401:
 *         description: 未授权
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Error'
 */
export async function GET(request: NextRequest) {
  // ... 现有实现
}

/**
 * @swagger
 * /admin/content:
 *   post:
 *     summary: 创建新内容
 *     tags: [Content]
 *     security:
 *       - bearerAuth: []
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - type
 *               - title
 *               - slug
 *             properties:
 *               type:
 *                 type: string
 *                 enum: [news, product, service, case]
 *               title:
 *                 type: string
 *               slug:
 *                 type: string
 *               excerpt:
 *                 type: string
 *               contentBody:
 *                 type: string
 *               status:
 *                 type: string
 *                 enum: [draft, published]
 *     responses:
 *       201:
 *         description: 内容创建成功
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Content'
 *       400:
 *         description: 请求参数错误
 *       401:
 *         description: 未授权
 */
export async function POST(request: NextRequest) {
  // ... 现有实现
}

Step 6: 更新package.json脚本

{
  "scripts": {
    "docs:api": "next dev -p 3000",
    "docs:generate": "ts-node scripts/generate-api-docs.ts"
  }
}

Step 7: 测试API文档

npm run dev
# 访问 http://localhost:3000/api-docs

Expected:

  • Swagger UI界面正常显示
  • 所有API端点都有文档
  • 可以在线测试API

Step 8: 提交更改

git add src/lib/openapi.ts src/app/api/docs/ src/app/api-docs/ src/app/api/admin/content/route.ts package.json
git commit -m "feat: add OpenAPI/Swagger documentation

- Create OpenAPI specification configuration
- Add Swagger UI page for API documentation
- Add JSDoc annotations to API routes
- Add npm scripts for documentation access

API documentation available at /api-docs
Provides interactive API testing interface"

Task 5: 引入API版本控制

问题: API没有版本控制,不利于未来升级和维护。

Files:

  • Create: src/app/api/v1/ 目录结构
  • Modify: 所有API路由文件
  • Update: 前端API调用

Step 1: 创建版本化目录结构

mkdir -p src/app/api/v1/admin
mkdir -p src/app/api/v1/auth
mkdir -p src/app/api/v1/content
mkdir -p src/app/api/v1/health

Step 2: 迁移API路由

将现有API文件移动到v1目录:

# 迁移管理后台API
mv src/app/api/admin/* src/app/api/v1/admin/

# 迁移认证API
mv src/app/api/auth/* src/app/api/v1/auth/

# 迁移内容API
mv src/app/api/content/* src/app/api/v1/content/

# 迁移健康检查API
mv src/app/api/health/* src/app/api/v1/health/

Step 3: 创建API版本路由

创建 src/app/api/v1/route.ts

import { NextResponse } from 'next/server';

export async function GET() {
  return NextResponse.json({
    version: 'v1',
    endpoints: {
      admin: '/api/v1/admin',
      auth: '/api/v1/auth',
      content: '/api/v1/content',
      health: '/api/v1/health',
    },
  });
}

Step 4: 创建默认API路由(向后兼容)

修改 src/app/api/route.ts

import { NextResponse } from 'next/server';

export async function GET() {
  return NextResponse.json({
    message: 'Novalon Website API',
    currentVersion: 'v1',
    documentation: '/api-docs',
    versions: {
      v1: '/api/v1',
    },
  });
}

Step 5: 更新前端API调用

修改 src/lib/api/client.ts

const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || '/api/v1';

export async function fetchApi<T>(endpoint: string, options?: RequestInit): Promise<T> {
  const url = `${API_BASE_URL}${endpoint}`;
  
  const response = await fetch(url, {
    ...options,
    headers: {
      'Content-Type': 'application/json',
      ...options?.headers,
    },
  });

  if (!response.ok) {
    throw new Error(`API Error: ${response.statusText}`);
  }

  return response.json();
}

Step 6: 更新所有API调用

搜索并替换所有API调用:

# 查找所有API调用
grep -r "/api/admin" src/
grep -r "/api/auth" src/
grep -r "/api/content" src/

# 替换为版本化API
# 例如:/api/admin/content -> /api/v1/admin/content

Step 7: 更新测试

修改所有测试文件中的API路径:

// 旧路径
await page.goto('/api/admin/content');

// 新路径
await page.goto('/api/v1/admin/content');

Step 8: 运行测试验证

npm run test:unit
npm run test:e2e

Expected: 所有测试通过

Step 9: 提交更改

git add src/app/api/
git add src/lib/api/
git add e2e/
git commit -m "feat: add API version control (v1)

- Create versioned API structure (/api/v1/*)
- Migrate all API routes to v1
- Update frontend API client to use versioned endpoints
- Update all tests to use new API paths
- Add backward compatibility for root API endpoint

BREAKING CHANGE: All API endpoints now require /api/v1 prefix
Migration guide: Replace /api/* with /api/v1/*"

Task 6: 集成Lighthouse CI

问题: 缺少端到端性能监控,无法持续追踪性能指标。

Files:

  • Create: .lighthouserc.json
  • Create: .github/workflows/lighthouse.yml
  • Modify: package.json

Step 1: 安装Lighthouse CI

npm install --save-dev @lhci/cli@0.10.x

Step 2: 创建Lighthouse CI配置

创建 .lighthouserc.json

{
  "ci": {
    "collect": {
      "numberOfRuns": 3,
      "settings": {
        "preset": "desktop",
        "throttling": {
          "rttMs": 40,
          "throughputKbps": 10240,
          "cpuSlowdownMultiplier": 1
        }
      },
      "url": [
        "http://localhost:3000/",
        "http://localhost:3000/about",
        "http://localhost:3000/products",
        "http://localhost:3000/services",
        "http://localhost:3000/cases",
        "http://localhost:3000/news",
        "http://localhost:3000/contact"
      ]
    },
    "assert": {
      "assertions": {
        "categories:performance": ["error", { "minScore": 0.9 }],
        "categories:accessibility": ["error", { "minScore": 0.9 }],
        "categories:best-practices": ["error", { "minScore": 0.9 }],
        "categories:seo": ["error", { "minScore": 0.9 }],
        "first-contentful-paint": ["error", { "maxNumericValue": 2000 }],
        "largest-contentful-paint": ["error", { "maxNumericValue": 3000 }],
        "cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
        "total-blocking-time": ["error", { "maxNumericValue": 300 }]
      }
    },
    "upload": {
      "target": "temporary-public-storage"
    }
  }
}

Step 3: 创建CI工作流

创建 .github/workflows/lighthouse.yml

name: Lighthouse CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  lighthouse:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build application
        run: npm run build
      
      - name: Start application
        run: npm start &
        env:
          PORT: 3000
      
      - name: Wait for application
        run: npx wait-on http://localhost:3000
      
      - name: Run Lighthouse CI
        run: npx lhci autorun
      
      - name: Upload Lighthouse results
        uses: actions/upload-artifact@v3
        with:
          name: lighthouse-results
          path: .lighthouseci/
          retention-days: 30

Step 4: 更新package.json脚本

{
  "scripts": {
    "lighthouse": "lhci autorun",
    "lighthouse:collect": "lhci collect",
    "lighthouse:assert": "lhci assert",
    "lighthouse:upload": "lhci upload"
  }
}

Step 5: 本地测试

# 启动应用
npm run build
npm start &

# 运行Lighthouse CI
npm run lighthouse

Expected:

  • 生成性能报告
  • 所有断言通过
  • 报告保存在 .lighthouseci/ 目录

Step 6: 查看报告

# 打开最新的报告
open .lighthouseci/lhr-*.html

Step 7: 提交更改

git add .lighthouserc.json .github/workflows/lighthouse.yml package.json
git commit -m "feat: integrate Lighthouse CI for performance monitoring

- Add Lighthouse CI configuration
- Add CI workflow for automated performance testing
- Set performance budgets (90% score minimum)
- Add Core Web Vitals thresholds
- Add npm scripts for local performance testing

Performance targets:
- Performance score: ≥90%
- Accessibility score: ≥90%
- Best practices score: ≥90%
- SEO score: ≥90%
- LCP: ≤3000ms
- FCP: ≤2000ms
- CLS: ≤0.1
- TBT: ≤300ms"

低优先级任务(1个月内完成)

Task 7: 引入API契约测试

问题: 缺少API契约测试,无法确保API响应符合预期格式。

Files:

  • Create: tests/api-contracts/
  • Modify: package.json

Step 1: 安装依赖

npm install --save-dev jest-openapi

Step 2: 创建契约测试配置

创建 tests/api-contracts/setup.ts

import { matchers } from 'jest-openapi';
import { specs } from '@/lib/openapi';

expect.extend(matchers(specs));

Step 3: 创建契约测试

创建 tests/api-contracts/content.api.test.ts

import { fetchApi } from '@/lib/api/client';

describe('Content API Contract Tests', () => {
  it('GET /api/v1/admin/content should match schema', async () => {
    const response = await fetchApi('/admin/content');
    
    expect(response).toSatisfySchemaInApiSpec('Content');
  });

  it('POST /api/v1/admin/content should match schema', async () => {
    const newContent = {
      type: 'news',
      title: 'Test News',
      slug: 'test-news',
      contentBody: 'Test content',
    };
    
    const response = await fetchApi('/admin/content', {
      method: 'POST',
      body: JSON.stringify(newContent),
    });
    
    expect(response).toSatisfySchemaInApiSpec('Content');
  });
});

Step 4: 运行契约测试

npm run test:api-contracts

Step 5: 提交更改

git add tests/api-contracts/ package.json
git commit -m "feat: add API contract testing

- Add jest-openapi for schema validation
- Create contract tests for all API endpoints
- Ensure API responses match OpenAPI schema

Contract tests validate:
- Response structure matches schema
- Required fields are present
- Data types are correct"

Task 8: 优化测试执行时间

问题: 测试执行时间可能过长,需要优化以提高效率。

Files:

  • Modify: e2e/playwright.config.ts
  • Create: scripts/analyze-test-performance.ts

Step 1: 分析慢速测试

创建 scripts/analyze-test-performance.ts

import { execSync } from 'child_process';
import * as fs from 'fs';

interface TestResult {
  file: string;
  duration: number;
  status: 'passed' | 'failed';
}

function analyzeTestPerformance() {
  console.log('Analyzing test performance...\n');
  
  // 运行测试并收集性能数据
  const result = execSync('npx playwright test --reporter=json', {
    encoding: 'utf-8',
    cwd: 'e2e',
  });
  
  const testResults: TestResult[] = JSON.parse(result);
  
  // 按执行时间排序
  const sortedResults = testResults.sort((a, b) => b.duration - a.duration);
  
  // 输出最慢的10个测试
  console.log('Top 10 slowest tests:\n');
  sortedResults.slice(0, 10).forEach((test, index) => {
    console.log(`${index + 1}. ${test.file}`);
    console.log(`   Duration: ${(test.duration / 1000).toFixed(2)}s`);
    console.log(`   Status: ${test.status}\n`);
  });
  
  // 生成优化建议
  const slowTests = sortedResults.filter(t => t.duration > 5000);
  if (slowTests.length > 0) {
    console.log('\nOptimization recommendations:\n');
    slowTests.forEach(test => {
      console.log(`- ${test.file}: Consider splitting or optimizing`);
    });
  }
  
  // 保存报告
  fs.writeFileSync(
    'test-performance-report.json',
    JSON.stringify(sortedResults, null, 2)
  );
  
  console.log('\nReport saved to test-performance-report.json');
}

analyzeTestPerformance();

Step 2: 运行性能分析

ts-node scripts/analyze-test-performance.ts

Step 3: 优化慢速测试

根据分析结果优化测试:

  1. 减少等待时间
// 不推荐
await page.waitForTimeout(5000);

// 推荐
await page.waitForSelector('[data-testid="result"]');
  1. 并行执行
// playwright.config.ts
{
  fullyParallel: true,
  workers: '75%',
}
  1. 拆分大测试
// 将一个大测试拆分为多个小测试
test.describe('User Registration', () => {
  test('should fill registration form', async ({ page }) => {
    // 只测试表单填写
  });
  
  test('should submit registration', async ({ page }) => {
    // 只测试提交
  });
});

Step 4: 提交更改

git add scripts/analyze-test-performance.ts e2e/playwright.config.ts
git commit -m "perf: optimize test execution time

- Add test performance analysis script
- Identify and optimize slow tests
- Enable parallel execution
- Reduce unnecessary waits

Expected improvement: 20-30% faster test execution"

执行计划总结

高优先级任务(1周内)

  • Task 1: 统一联系表单实现
  • Task 2: 配置单元测试覆盖率报告
  • Task 3: 配置Allure测试报告

中优先级任务(2周内)

  • Task 4: 引入OpenAPI文档
  • Task 5: 引入API版本控制
  • Task 6: 集成Lighthouse CI

低优先级任务(1个月内)

  • Task 7: 引入API契约测试
  • Task 8: 优化测试执行时间

验收标准

功能验收

  • 所有改进任务完成
  • 所有测试通过
  • 文档更新完整

质量验收

  • 单元测试覆盖率 ≥ 80%
  • E2E测试通过率 ≥ 95%
  • 性能评分 ≥ 90%
  • 可访问性评分 ≥ 90%

文档验收

  • API文档完整
  • README更新
  • 迁移指南编写

计划创建时间: 2026-03-20 预计完成时间: 2026-04-20 负责人: 张翔 状态: 待执行