refactor: 完成静态网站转换,移除所有 CMS 和动态功能

- 删除数据库相关代码 (src/db/)
- 删除 API 路由 (src/app/api/)
- 删除认证相关代码 (src/lib/auth/, src/providers/)
- 删除监控和安全中间件 (src/lib/security/, src/lib/monitoring/)
- 删除 hooks (use-news, use-products, use-services)
- 更新组件为静态数据源
- 添加 nginx 静态配置和部署脚本
- 添加 static-link 组件
This commit is contained in:
张翔
2026-04-21 07:53:56 +08:00
parent cd1d6aa28a
commit 6403489954
197 changed files with 654 additions and 24762 deletions
-134
View File
@@ -1,134 +0,0 @@
import { renderHook, waitFor } from '@testing-library/react';
import { useNews } from './use-news';
import { contentService } from '@/lib/api/services';
import { NewsItem } from '@/lib/api/types';
jest.mock('@/lib/api/services');
describe('useNews', () => {
const mockNewsData: NewsItem[] = [
{
id: '1',
title: '测试新闻1',
excerpt: '测试摘要1',
content: '测试内容1',
date: '2024-01-01',
category: '公司新闻',
slug: 'test-news-1',
},
{
id: '2',
title: '测试新闻2',
excerpt: '测试摘要2',
content: '测试内容2',
date: '2024-01-02',
category: '产品发布',
slug: 'test-news-2',
},
];
beforeEach(() => {
jest.clearAllMocks();
});
it('应该成功获取新闻数据', async () => {
(contentService.getNews as jest.Mock).mockResolvedValue(mockNewsData);
const { result } = renderHook(() => useNews());
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.news).toEqual(mockNewsData);
expect(result.current.error).toBeNull();
expect(contentService.getNews).toHaveBeenCalledWith(undefined, undefined, 'desc');
});
it('应该支持分类筛选', async () => {
const categories = ['公司新闻'];
(contentService.getNews as jest.Mock).mockResolvedValue([mockNewsData[0]]);
const { result } = renderHook(() => useNews(categories));
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.news).toEqual([mockNewsData[0]]);
expect(contentService.getNews).toHaveBeenCalledWith(categories, undefined, 'desc');
});
it('应该支持数量限制', async () => {
const limit = 1;
(contentService.getNews as jest.Mock).mockResolvedValue([mockNewsData[0]]);
const { result } = renderHook(() => useNews(undefined, limit));
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.news).toEqual([mockNewsData[0]]);
expect(contentService.getNews).toHaveBeenCalledWith(undefined, limit, 'desc');
});
it('应该支持排序', async () => {
(contentService.getNews as jest.Mock).mockResolvedValue(mockNewsData);
const { result } = renderHook(() => useNews(undefined, undefined, 'asc'));
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(contentService.getNews).toHaveBeenCalledWith(undefined, undefined, 'asc');
});
it('应该处理获取失败的情况', async () => {
const error = new Error('获取新闻失败');
(contentService.getNews as jest.Mock).mockRejectedValue(error);
const { result } = renderHook(() => useNews());
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.news).toEqual([]);
expect(result.current.error).toEqual(error);
});
it('应该在加载时设置loading状态', () => {
(contentService.getNews as jest.Mock).mockImplementation(
() => new Promise(() => {})
);
const { result } = renderHook(() => useNews());
expect(result.current.loading).toBe(true);
expect(result.current.news).toEqual([]);
expect(result.current.error).toBeNull();
});
it('应该在参数变化时重新获取数据', async () => {
(contentService.getNews as jest.Mock).mockResolvedValue(mockNewsData);
const { result, rerender } = renderHook(
({ categories }) => useNews(categories),
{ initialProps: { categories: ['公司新闻'] } }
);
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(contentService.getNews).toHaveBeenCalledTimes(1);
rerender({ categories: ['产品发布'] });
await waitFor(() => {
expect(contentService.getNews).toHaveBeenCalledTimes(2);
});
});
});
-32
View File
@@ -1,32 +0,0 @@
import { useState, useEffect } from 'react';
import { contentService } from '@/lib/api/services';
import { NewsItem } from '@/lib/api/types';
export function useNews(
categories?: string[],
limit?: number,
sortOrder: 'asc' | 'desc' = 'desc'
) {
const [news, setNews] = useState<NewsItem[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
async function fetchNews() {
try {
setLoading(true);
setError(null);
const data = await contentService.getNews(categories, limit, sortOrder);
setNews(data);
} catch (err) {
setError(err instanceof Error ? err : new Error('Failed to fetch news'));
} finally {
setLoading(false);
}
}
fetchNews();
}, [categories, limit, sortOrder]);
return { news, loading, error };
}
-121
View File
@@ -1,121 +0,0 @@
import { renderHook, waitFor } from '@testing-library/react';
import { useProducts } from './use-products';
import { contentService } from '@/lib/api/services';
import { Product } from '@/lib/api/types';
jest.mock('@/lib/api/services');
describe('useProducts', () => {
const mockProductsData: Product[] = [
{
id: '1',
title: '测试产品1',
description: '测试描述1',
category: '软件产品',
features: ['功能1', '功能2'],
benefits: ['优势1', '优势2'],
slug: 'test-product-1',
},
{
id: '2',
title: '测试产品2',
description: '测试描述2',
category: '云服务',
features: ['功能3', '功能4'],
benefits: ['优势3', '优势4'],
slug: 'test-product-2',
},
];
beforeEach(() => {
jest.clearAllMocks();
});
it('应该成功获取产品数据', async () => {
(contentService.getProducts as jest.Mock).mockResolvedValue(mockProductsData);
const { result } = renderHook(() => useProducts());
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.products).toEqual(mockProductsData);
expect(result.current.error).toBeNull();
expect(contentService.getProducts).toHaveBeenCalledWith(undefined);
});
it('应该支持精选产品筛选', async () => {
const featuredIds = ['1'];
(contentService.getProducts as jest.Mock).mockResolvedValue([mockProductsData[0]]);
const { result } = renderHook(() => useProducts(featuredIds));
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.products).toEqual([mockProductsData[0]]);
expect(contentService.getProducts).toHaveBeenCalledWith(featuredIds);
});
it('应该处理获取失败的情况', async () => {
const error = new Error('获取产品失败');
(contentService.getProducts as jest.Mock).mockRejectedValue(error);
const { result } = renderHook(() => useProducts());
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.products).toEqual([]);
expect(result.current.error).toEqual(error);
});
it('应该在加载时设置loading状态', () => {
(contentService.getProducts as jest.Mock).mockImplementation(
() => new Promise(() => {})
);
const { result } = renderHook(() => useProducts());
expect(result.current.loading).toBe(true);
expect(result.current.products).toEqual([]);
expect(result.current.error).toBeNull();
});
it('应该在featuredIds变化时重新获取数据', async () => {
(contentService.getProducts as jest.Mock).mockResolvedValue(mockProductsData);
const { result, rerender } = renderHook(
({ featuredIds }) => useProducts(featuredIds),
{ initialProps: { featuredIds: ['1'] } }
);
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(contentService.getProducts).toHaveBeenCalledTimes(1);
rerender({ featuredIds: ['2'] });
await waitFor(() => {
expect(contentService.getProducts).toHaveBeenCalledTimes(2);
});
});
it('应该处理空数组featuredIds', async () => {
(contentService.getProducts as jest.Mock).mockResolvedValue(mockProductsData);
const { result } = renderHook(() => useProducts([]));
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.products).toEqual(mockProductsData);
expect(contentService.getProducts).toHaveBeenCalledWith([]);
});
});
-28
View File
@@ -1,28 +0,0 @@
import { useState, useEffect } from 'react';
import { contentService } from '@/lib/api/services';
import { Product } from '@/lib/api/types';
export function useProducts(featuredIds?: string[]) {
const [products, setProducts] = useState<Product[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
async function fetchProducts() {
try {
setLoading(true);
setError(null);
const data = await contentService.getProducts(featuredIds);
setProducts(data);
} catch (err) {
setError(err instanceof Error ? err : new Error('Failed to fetch products'));
} finally {
setLoading(false);
}
}
fetchProducts();
}, [featuredIds]);
return { products, loading, error };
}
-121
View File
@@ -1,121 +0,0 @@
import { renderHook, waitFor } from '@testing-library/react';
import { useServices } from './use-services';
import { contentService } from '@/lib/api/services';
import { Service } from '@/lib/api/types';
jest.mock('@/lib/api/services');
describe('useServices', () => {
const mockServicesData: Service[] = [
{
id: '1',
title: '测试服务1',
description: '测试描述1',
icon: 'Code',
features: ['功能1', '功能2'],
benefits: ['优势1', '优势2'],
slug: 'test-service-1',
},
{
id: '2',
title: '测试服务2',
description: '测试描述2',
icon: 'Cloud',
features: ['功能3', '功能4'],
benefits: ['优势3', '优势4'],
slug: 'test-service-2',
},
];
beforeEach(() => {
jest.clearAllMocks();
});
it('应该成功获取服务数据', async () => {
(contentService.getServices as jest.Mock).mockResolvedValue(mockServicesData);
const { result } = renderHook(() => useServices());
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.services).toEqual(mockServicesData);
expect(result.current.error).toBeNull();
expect(contentService.getServices).toHaveBeenCalledWith(undefined);
});
it('应该支持服务ID筛选', async () => {
const ids = ['1'];
(contentService.getServices as jest.Mock).mockResolvedValue([mockServicesData[0]]);
const { result } = renderHook(() => useServices(ids));
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.services).toEqual([mockServicesData[0]]);
expect(contentService.getServices).toHaveBeenCalledWith(ids);
});
it('应该处理获取失败的情况', async () => {
const error = new Error('获取服务失败');
(contentService.getServices as jest.Mock).mockRejectedValue(error);
const { result } = renderHook(() => useServices());
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.services).toEqual([]);
expect(result.current.error).toEqual(error);
});
it('应该在加载时设置loading状态', () => {
(contentService.getServices as jest.Mock).mockImplementation(
() => new Promise(() => {})
);
const { result } = renderHook(() => useServices());
expect(result.current.loading).toBe(true);
expect(result.current.services).toEqual([]);
expect(result.current.error).toBeNull();
});
it('应该在ids变化时重新获取数据', async () => {
(contentService.getServices as jest.Mock).mockResolvedValue(mockServicesData);
const { result, rerender } = renderHook(
({ ids }) => useServices(ids),
{ initialProps: { ids: ['1'] } }
);
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(contentService.getServices).toHaveBeenCalledTimes(1);
rerender({ ids: ['2'] });
await waitFor(() => {
expect(contentService.getServices).toHaveBeenCalledTimes(2);
});
});
it('应该处理空数组ids', async () => {
(contentService.getServices as jest.Mock).mockResolvedValue(mockServicesData);
const { result } = renderHook(() => useServices([]));
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.services).toEqual(mockServicesData);
expect(contentService.getServices).toHaveBeenCalledWith([]);
});
});
-28
View File
@@ -1,28 +0,0 @@
import { useState, useEffect } from 'react';
import { contentService } from '@/lib/api/services';
import { Service } from '@/lib/api/types';
export function useServices(ids?: string[]) {
const [services, setServices] = useState<Service[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
async function fetchServices() {
try {
setLoading(true);
setError(null);
const data = await contentService.getServices(ids);
setServices(data);
} catch (err) {
setError(err instanceof Error ? err : new Error('Failed to fetch services'));
} finally {
setLoading(false);
}
}
fetchServices();
}, [ids]);
return { services, loading, error };
}