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

1587 lines
37 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 质量改进迭代实施计划
> **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: 分析现有实现**
检查两个文件的实现差异:
```bash
# 查看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`,添加完整的验证和邮件发送逻辑:
```typescript
'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
```typescript
'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文件**
```bash
rm src/app/api/contact/route.ts
rm src/app/api/contact/route.test.ts
```
**Step 5: 更新测试**
创建新的测试文件 `src/app/(marketing)/contact/actions.test.ts`
```typescript
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: 运行测试验证**
```bash
npm run test:unit
```
Expected: 所有测试通过
**Step 7: 手动测试**
```bash
npm run dev
# 访问 http://localhost:3000/contact
# 填写表单并提交
# 验证邮件发送成功
```
**Step 8: 提交更改**
```bash
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: 安装依赖**
```bash
npm install --save-dev @types/jest jest ts-jest
```
**Step 2: 创建Jest配置文件**
创建 `jest.config.ts`
```typescript
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`
```typescript
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` 中添加:
```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: 运行覆盖率测试**
```bash
npm run test:unit:coverage
```
Expected: 生成覆盖率报告,显示覆盖率百分比
**Step 6: 查看覆盖率报告**
```bash
npm run test:coverage:open
```
Expected: 在浏览器中打开HTML覆盖率报告
**Step 7: 添加覆盖率徽章(可选)**
`README.md` 中添加:
```markdown
[![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: 提交更改**
```bash
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依赖**
```bash
npm install --save-dev allure-playwright
```
**Step 2: 更新Playwright配置**
修改 `e2e/playwright.config.ts`,确保Allure reporter已配置:
```typescript
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`
```bash
#!/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` 中添加:
```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: 运行测试生成报告**
```bash
cd e2e
npm test
cd ..
npm run test:report
```
Expected:
- 测试执行完成
- Allure报告在浏览器中打开
- 显示测试统计、趋势、详情
**Step 6: 添加CI集成(可选)**
创建 `.github/workflows/test-report.yml`
```yaml
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: 提交更改**
```bash
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: 安装依赖**
```bash
npm install swagger-jsdoc swagger-ui-react
npm install --save-dev @types/swagger-jsdoc
```
**Step 2: 创建OpenAPI配置**
创建 `src/lib/openapi.ts`
```typescript
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`
```typescript
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`
```typescript
'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`
```typescript
/**
* @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脚本**
```json
{
"scripts": {
"docs:api": "next dev -p 3000",
"docs:generate": "ts-node scripts/generate-api-docs.ts"
}
}
```
**Step 7: 测试API文档**
```bash
npm run dev
# 访问 http://localhost:3000/api-docs
```
Expected:
- Swagger UI界面正常显示
- 所有API端点都有文档
- 可以在线测试API
**Step 8: 提交更改**
```bash
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: 创建版本化目录结构**
```bash
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目录:
```bash
# 迁移管理后台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`
```typescript
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`
```typescript
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`
```typescript
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调用:
```bash
# 查找所有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路径:
```typescript
// 旧路径
await page.goto('/api/admin/content');
// 新路径
await page.goto('/api/v1/admin/content');
```
**Step 8: 运行测试验证**
```bash
npm run test:unit
npm run test:e2e
```
Expected: 所有测试通过
**Step 9: 提交更改**
```bash
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**
```bash
npm install --save-dev @lhci/cli@0.10.x
```
**Step 2: 创建Lighthouse CI配置**
创建 `.lighthouserc.json`
```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`
```yaml
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脚本**
```json
{
"scripts": {
"lighthouse": "lhci autorun",
"lighthouse:collect": "lhci collect",
"lighthouse:assert": "lhci assert",
"lighthouse:upload": "lhci upload"
}
}
```
**Step 5: 本地测试**
```bash
# 启动应用
npm run build
npm start &
# 运行Lighthouse CI
npm run lighthouse
```
Expected:
- 生成性能报告
- 所有断言通过
- 报告保存在 `.lighthouseci/` 目录
**Step 6: 查看报告**
```bash
# 打开最新的报告
open .lighthouseci/lhr-*.html
```
**Step 7: 提交更改**
```bash
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: 安装依赖**
```bash
npm install --save-dev jest-openapi
```
**Step 2: 创建契约测试配置**
创建 `tests/api-contracts/setup.ts`
```typescript
import { matchers } from 'jest-openapi';
import { specs } from '@/lib/openapi';
expect.extend(matchers(specs));
```
**Step 3: 创建契约测试**
创建 `tests/api-contracts/content.api.test.ts`
```typescript
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: 运行契约测试**
```bash
npm run test:api-contracts
```
**Step 5: 提交更改**
```bash
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`
```typescript
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: 运行性能分析**
```bash
ts-node scripts/analyze-test-performance.ts
```
**Step 3: 优化慢速测试**
根据分析结果优化测试:
1. **减少等待时间**
```typescript
// 不推荐
await page.waitForTimeout(5000);
// 推荐
await page.waitForSelector('[data-testid="result"]');
```
2. **并行执行**
```typescript
// playwright.config.ts
{
fullyParallel: true,
workers: '75%',
}
```
3. **拆分大测试**
```typescript
// 将一个大测试拆分为多个小测试
test.describe('User Registration', () => {
test('should fill registration form', async ({ page }) => {
// 只测试表单填写
});
test('should submit registration', async ({ page }) => {
// 只测试提交
});
});
```
**Step 4: 提交更改**
```bash
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
**负责人**: 张翔
**状态**: 待执行