diff --git a/CLAUDE.md b/CLAUDE.md index a0576e0..a6ac9ed 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -40,9 +40,14 @@ photography/ ├── .gitea/workflows/ # ⚙️ CI/CD 模块 │ ├── CLAUDE.md # CI/CD 配置指导 │ └── deploy-frontend.yml # 自动部署工作流 -├── admin/ # 📋 管理后台 (预留) -├── backend/ # 🔧 后端 API (预留) -└── ui/ # 🎨 UI 组件备份 +├── admin/ # 📋 管理后台模块 +│ └── CLAUDE.md # 管理后台开发指导 +├── backend/ # 🔧 后端 API 模块 +│ └── CLAUDE.md # 后端开发指导 +├── ui/ # 🎨 UI 备份模块 +│ └── CLAUDE.md # UI 备份模块管理 +└── scripts/ # 🛠️ 工具脚本 + └── README.md # 脚本说明 ``` ## 🎯 模块化工作指南 @@ -73,6 +78,30 @@ cd .gitea/workflows/ ``` **适用场景**: 自动部署、构建流程、环境配置、工作流优化 +#### 📋 管理后台开发 +```bash +# 切换到管理后台模块 +cd admin/ +# 参考 admin/CLAUDE.md +``` +**适用场景**: 管理界面开发、用户管理、内容管理、权限控制 + +#### 🔧 后端 API 开发 +```bash +# 切换到后端模块 +cd backend/ +# 参考 backend/CLAUDE.md +``` +**适用场景**: API 接口开发、数据库设计、认证服务、文件存储 + +#### 🎨 UI 备份和实验 +```bash +# 切换到 UI 备份模块 +cd ui/ +# 参考 ui/CLAUDE.md +``` +**适用场景**: 组件备份、A/B 测试、实验性功能、版本对比 + #### 📚 文档和架构 ```bash # 在根目录工作 @@ -168,6 +197,9 @@ git push origin main # 触发自动部署 - **前端问题**: 查看 `frontend/CLAUDE.md` - **部署问题**: 查看 `docs/deployment/CLAUDE.md` - **CI/CD 问题**: 查看 `.gitea/workflows/CLAUDE.md` +- **管理后台问题**: 查看 `admin/CLAUDE.md` +- **后端问题**: 查看 `backend/CLAUDE.md` +- **UI 备份问题**: 查看 `ui/CLAUDE.md` ### 通用问题 ```bash @@ -190,11 +222,16 @@ cd frontend && make clean && make install - ✅ 代码质量控制 (ESLint + Prettier + TypeScript) - ✅ Pre-commit hooks +### 开发中功能 +- 📋 管理后台 (架构设计完成) +- 📋 后端 API (架构设计完成) +- 📋 UI 备份系统 (已实现) + ### 计划中功能 -- 📋 管理后台 -- 📋 后端 API - 📋 多环境部署 - 📋 性能监控 +- 📋 AI 功能集成 +- 📋 数据分析系统 ## 🎨 模块协调原则 @@ -202,12 +239,18 @@ cd frontend && make clean && make install 1. **前端模块** (`frontend/CLAUDE.md`): 组件、样式、前端逻辑变更时 2. **部署模块** (`docs/deployment/CLAUDE.md`): 服务器、配置、部署流程变更时 3. **CI/CD 模块** (`.gitea/workflows/CLAUDE.md`): 工作流、构建流程变更时 -4. **根目录** (`CLAUDE.md`): 项目架构、模块关系变更时 +4. **管理后台模块** (`admin/CLAUDE.md`): 后台功能、权限管理变更时 +5. **后端模块** (`backend/CLAUDE.md`): API 接口、数据库架构变更时 +6. **UI 备份模块** (`ui/CLAUDE.md`): 组件备份、实验功能变更时 +7. **根目录** (`CLAUDE.md`): 项目架构、模块关系变更时 ### 模块间通信 -- 前端构建产物 → 部署模块使用 -- CI/CD 协调 → 所有模块的构建和部署 -- 配置变更 → 相关模块的 CLAUDE.md 同步更新 +- **前端 ↔ 后端**: API 接口调用和数据交换 +- **管理后台 ↔ 后端**: 管理接口和数据操作 +- **UI 备份 ↔ 前端**: 组件同步和实验功能验证 +- **前端构建产物 → 部署模块**: 静态文件部署 +- **CI/CD 协调 → 所有模块**: 构建和部署流程 +- **配置变更 → 相关模块**: CLAUDE.md 同步更新 ## 🔄 最佳实践 @@ -219,11 +262,45 @@ cd frontend && make clean && make install 5. 提交代码触发自动部署 ### 文档维护 -- 每个模块的 CLAUDE.md 保持独立和聚焦 -- 模块间的依赖关系在根目录 CLAUDE.md 中说明 -- 重要的全局配置统一在根目录管理 +- **模块独立**: 每个模块的 CLAUDE.md 保持独立和聚焦 +- **架构统一**: 模块间的依赖关系在根目录 CLAUDE.md 中说明 +- **配置集中**: 重要的全局配置统一在根目录管理 +- **及时更新**: 功能变更后立即更新对应的 CLAUDE.md +- **一致性**: 保持各模块文档的格式和风格一致 ### 上下文优化 -- Claude 工作时只需关注单个模块的 CLAUDE.md -- 减少上下文长度,提高处理效率 -- 模块化降低复杂性,提高开发效率 \ No newline at end of file +- **聚焦开发**: Claude 工作时只需关注单个模块的 CLAUDE.md +- **减少负载**: 避免加载无关模块的文档,减少上下文长度 +- **提高效率**: 模块化降低复杂性,提高开发效率 +- **避免幻觉**: 精确的模块指导减少 AI 产生错误信息的可能性 +- **快速定位**: 问题出现时能快速定位到相关模块和文档 + +## 📋 模块 CLAUDE.md 文件列表 + +### 核心模块 +- ✅ **根目录** (`CLAUDE.md`) - 项目总览和模块协调 +- ✅ **前端模块** (`frontend/CLAUDE.md`) - 前端开发指导 +- ✅ **部署模块** (`docs/deployment/CLAUDE.md`) - 部署配置指导 +- ✅ **CI/CD 模块** (`.gitea/workflows/CLAUDE.md`) - 自动化部署指导 + +### 新增模块 +- ✅ **管理后台模块** (`admin/CLAUDE.md`) - 管理界面开发指导 +- ✅ **后端模块** (`backend/CLAUDE.md`) - API 服务开发指导 +- ✅ **UI 备份模块** (`ui/CLAUDE.md`) - 组件备份和实验指导 + +### 辅助模块 +- ✅ **工具脚本** (`scripts/README.md`) - 自动化脚本说明 + +## 🎯 使用建议 + +### 选择正确的模块 +1. **明确任务类型**: 根据要解决的问题选择对应的模块 +2. **切换工作目录**: 进入相应的模块目录进行开发 +3. **参考模块文档**: 仔细阅读模块的 CLAUDE.md 文件 +4. **遵循模块规范**: 按照模块的开发规范和流程进行工作 + +### 跨模块协作 +1. **理解依赖关系**: 了解模块间的依赖和数据流向 +2. **协调接口变更**: 涉及多个模块时,确保接口一致性 +3. **同步更新文档**: 跨模块变更时,同步更新相关文档 +4. **测试集成功能**: 确保模块间的集成功能正常工作 \ No newline at end of file diff --git a/admin/CLAUDE.md b/admin/CLAUDE.md new file mode 100644 index 0000000..ae7adfd --- /dev/null +++ b/admin/CLAUDE.md @@ -0,0 +1,690 @@ +# 管理后台模块 - CLAUDE.md + +此文件为 Claude Code 在管理后台模块工作时提供指导。 + +## 🎯 模块概览 + +管理后台是摄影作品集项目的管理界面,用于内容管理、用户管理和系统配置。 + +### 功能特性 +- 📸 照片管理:上传、编辑、删除照片 +- 🏷️ 分类管理:相册分类、标签管理 +- 👥 用户管理:用户权限、访问控制 +- 📊 数据统计:访问统计、照片数据 +- ⚙️ 系统配置:站点设置、主题配置 + +### 技术栈 +- **前端**: React + TypeScript + Vite +- **组件**: shadcn/ui + Radix UI +- **状态管理**: Zustand / React Query +- **路由**: React Router +- **认证**: JWT Token +- **构建**: Vite + TypeScript + +## 📁 目录结构 + +``` +admin/ +├── CLAUDE.md # 🔍 当前文件 - 管理后台指导 +├── src/ # 源代码目录 +│ ├── components/ # 组件目录 +│ │ ├── ui/ # shadcn/ui 基础组件 +│ │ ├── layout/ # 布局组件 +│ │ ├── photo/ # 照片管理组件 +│ │ ├── category/ # 分类管理组件 +│ │ ├── logs/ # 日志管理组件 +│ │ └── common/ # 通用组件 +│ ├── pages/ # 页面组件 +│ │ ├── dashboard/ # 仪表板 +│ │ ├── photos/ # 照片管理 +│ │ ├── categories/ # 分类管理 +│ │ ├── logs/ # 日志管理 +│ │ └── settings/ # 系统设置 +│ ├── hooks/ # 自定义 Hooks +│ ├── services/ # API 服务 +│ ├── store/ # 状态管理 +│ ├── utils/ # 工具函数 +│ └── types/ # TypeScript 类型定义 +├── public/ # 静态资源 +├── package.json # 项目配置 +├── vite.config.ts # Vite 配置 +├── tsconfig.json # TypeScript 配置 +├── tailwind.config.js # Tailwind 配置 +└── README.md # 模块说明 +``` + +## 🚀 开发环境配置 + +### 环境要求 +- **Node.js**: 18+ +- **包管理器**: npm/yarn/pnpm +- **编辑器**: VS Code (推荐) +- **后端依赖**: Golang 后端 API 服务 + +### 初始化项目 +```bash +# 进入管理后台目录 +cd admin/ + +# 安装依赖 +npm install + +# 启动开发服务器 +npm run dev + +# 构建生产版本 +npm run build +``` + +### 环境变量配置 +```bash +# .env.development +VITE_APP_TITLE=摄影作品集管理后台 +VITE_API_BASE_URL=http://localhost:8080/api +VITE_UPLOAD_URL=http://localhost:8080/api/upload + +# .env.production +VITE_APP_TITLE=摄影作品集管理后台 +VITE_API_BASE_URL=https://api.photography.iriver.top +VITE_UPLOAD_URL=https://api.photography.iriver.top/upload +``` + +## 🏗️ 项目架构 + +### 目录结构详解 + +#### 🧩 组件架构 +``` +src/components/ +├── ui/ # shadcn/ui 基础组件 +│ ├── button.tsx # 按钮组件 +│ ├── input.tsx # 输入框组件 +│ ├── table.tsx # 表格组件 +│ ├── modal.tsx # 弹窗组件 +│ ├── card.tsx # 卡片组件 +│ └── form.tsx # 表单组件 +├── layout/ # 布局组件 +│ ├── header.tsx # 顶部导航 +│ ├── sidebar.tsx # 侧边栏 +│ └── main-layout.tsx # 主布局 +├── photo/ # 照片管理组件 +│ ├── photo-list.tsx # 照片列表 +│ ├── photo-form.tsx # 照片表单 +│ ├── photo-upload.tsx # 照片上传 +│ └── photo-detail.tsx # 照片详情 +├── category/ # 分类管理组件 +│ ├── category-tree.tsx # 分类树 +│ ├── category-form.tsx # 分类表单 +│ └── category-stats.tsx # 分类统计 +├── logs/ # 日志管理组件 +│ ├── log-viewer.tsx # 日志查看器 +│ ├── log-filter.tsx # 日志过滤器 +│ └── log-detail.tsx # 日志详情 +└── common/ # 通用组件 + ├── loading.tsx # 加载组件 + ├── error-boundary.tsx # 错误边界 + └── confirmation.tsx # 确认对话框 +``` + +#### 📱 页面结构 +``` +src/pages/ +├── dashboard/ # 仪表板 +│ └── index.tsx # 首页 +├── photos/ # 照片管理 +│ ├── index.tsx # 照片列表 +│ ├── edit.tsx # 照片编辑 +│ └── upload.tsx # 照片上传 +├── categories/ # 分类管理 +│ └── index.tsx # 分类管理 +├── tags/ # 标签管理 +│ └── index.tsx # 标签管理 +├── logs/ # 日志管理 +│ ├── index.tsx # 日志列表 +│ └── detail.tsx # 日志详情 +└── settings/ # 系统设置 + └── index.tsx # 系统设置 +``` + +#### 🔄 状态管理 +``` +src/store/ +├── auth.ts # 认证状态 (Zustand) +├── photo.ts # 照片状态 +├── logs.ts # 日志状态 +└── ui.ts # UI 状态 + +src/hooks/ +├── useAuth.ts # 认证 Hook +├── usePhotos.ts # 照片数据 (React Query) +├── useCategories.ts # 分类数据 +├── useLogs.ts # 日志数据 +└── useUpload.ts # 上传功能 +``` + +## 🔌 API 集成 + +### API 接口规范 +```typescript +// src/services/photo.ts +interface PhotoAPI { + // 获取照片列表 + getPhotos(params: PhotoListParams): Promise + + // 上传照片 + uploadPhoto(file: File, metadata: PhotoMetadata): Promise + + // 更新照片信息 + updatePhoto(id: string, data: PhotoUpdateData): Promise + + // 删除照片 + deletePhoto(id: string): Promise + + // 批量操作 + batchUpdatePhotos(ids: string[], data: Partial): Promise +} + +// src/services/category.ts +interface CategoryAPI { + getCategories(): Promise + getCategoryTree(): Promise + createCategory(data: CategoryCreateData): Promise + updateCategory(id: string, data: CategoryUpdateData): Promise + deleteCategory(id: string): Promise +} + +// src/services/logs.ts +interface LogsAPI { + getLogs(params: LogListParams): Promise + getLogDetail(traceId: string): Promise + getLogStats(): Promise +} +``` + +### 数据类型定义 +```typescript +// src/types/photo.ts +interface Photo { + id: string + title: string + description: string + originalFilename: string + fileSize: number + status: 'processing' | 'published' | 'draft' | 'archived' + categories: Category[] + tags: Tag[] + formats: PhotoFormat[] + camera?: string + lens?: string + iso?: number + aperture?: string + shutterSpeed?: string + focalLength?: string + takenAt?: string + createdAt: string + updatedAt: string +} + +// src/types/category.ts +interface Category { + id: string + name: string + slug: string + description: string + parentId?: string + sortOrder: number + isActive: boolean + photoCount: number + createdAt: string + updatedAt: string +} + +// src/types/logs.ts +interface LogEntry { + id: string + timestamp: string + level: 'error' | 'warn' | 'info' | 'debug' + message: string + traceId: string + userId?: string + action?: string + details?: Record +} +``` + +## 🎨 UI 设计规范 + +### 设计原则 +- **一致性**: 保持界面元素的一致性 +- **易用性**: 简洁直观的操作流程 +- **响应式**: 适配不同屏幕尺寸 +- **可访问性**: 支持键盘导航和屏幕阅读器 + +### 主题配置 +```typescript +// tailwind.config.js +module.exports = { + content: ['./src/**/*.{ts,tsx}'], + theme: { + extend: { + colors: { + primary: { + DEFAULT: '#d4af37', + 50: '#fefce8', + 500: '#d4af37', + 900: '#713f12' + }, + border: 'hsl(var(--border))', + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + muted: 'hsl(var(--muted))', + 'muted-foreground': 'hsl(var(--muted-foreground))' + }, + fontFamily: { + sans: ['Inter', 'system-ui', 'sans-serif'] + } + } + }, + plugins: [require('tailwindcss-animate')] +} + +// src/lib/utils.ts +import { clsx, type ClassValue } from 'clsx' +import { twMerge } from 'tailwind-merge' + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} +``` + +## 🔐 权限管理 + +### 角色权限设计 +```typescript +// src/types/permission.ts +interface Permission { + id: string + name: string + code: string + description: string +} + +interface Role { + id: string + name: string + permissions: Permission[] +} + +interface User { + id: string + username: string + email: string + role: Role + isActive: boolean +} +``` + +### 路由保护 +```typescript +// src/components/auth/ProtectedRoute.tsx +import { useAuth } from '@/hooks/useAuth' +import { Navigate, useLocation } from 'react-router-dom' + +interface ProtectedRouteProps { + children: React.ReactNode + requiredRole?: string +} + +export function ProtectedRoute({ children, requiredRole }: ProtectedRouteProps) { + const { user, isLoading } = useAuth() + const location = useLocation() + + if (isLoading) { + return
Loading...
+ } + + if (!user) { + return + } + + if (requiredRole && user.role !== requiredRole) { + return + } + + return <>{children} +} + +// src/hooks/useAuth.ts +import { create } from 'zustand' +import { persist } from 'zustand/middleware' + +interface AuthState { + user: User | null + token: string | null + login: (token: string, user: User) => void + logout: () => void + isLoggedIn: boolean +} + +export const useAuthStore = create()( + persist( + (set, get) => ({ + user: null, + token: null, + isLoggedIn: false, + login: (token, user) => set({ token, user, isLoggedIn: true }), + logout: () => set({ token: null, user: null, isLoggedIn: false }) + }), + { name: 'auth-storage' } + ) +) +``` + +## 🚀 构建和部署 + +### 开发命令 +```bash +# 安装依赖 +npm install + +# 启动开发服务器 +npm run dev + +# 类型检查 +npm run type-check + +# 代码检查 +npm run lint + +# 代码格式化 +npm run format + +# 构建生产版本 +npm run build + +# 预览构建结果 +npm run preview +``` + +### 部署配置 +```typescript +// vite.config.ts +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import path from 'path' + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src') + } + }, + base: '/admin/', // 部署路径 + build: { + outDir: 'dist', + assetsDir: 'static', + rollupOptions: { + output: { + manualChunks: { + 'react-vendor': ['react', 'react-dom', 'react-router-dom'], + 'ui-vendor': ['@radix-ui/react-dialog', '@radix-ui/react-select'], + 'utils-vendor': ['clsx', 'tailwind-merge', 'date-fns'] + } + } + } + } +}) +``` + +## 🔧 开发指南 + +### 代码规范 +- 使用 TypeScript 进行类型约束 +- 遵循 React Hooks 最佳实践 +- 组件命名采用 PascalCase +- 文件命名采用 kebab-case +- 使用 ESLint + Prettier 进行代码格式化 + +### 组件开发 +```tsx +// src/components/photo/photo-list.tsx +import { useState } from 'react' +import { usePhotos } from '@/hooks/usePhotos' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { Badge } from '@/components/ui/badge' + +interface PhotoListProps { + onPhotoSelect?: (photo: Photo) => void +} + +export function PhotoList({ onPhotoSelect }: PhotoListProps) { + const [search, setSearch] = useState('') + const [page, setPage] = useState(1) + + const { data: photosData, isLoading, error } = usePhotos({ + search, + page, + limit: 20 + }) + + if (isLoading) return
Loading...
+ if (error) return
Error loading photos
+ + return ( +
+ {/* 搜索栏 */} +
+ setSearch(e.target.value)} + className="max-w-sm" + /> + +
+ + {/* 照片网格 */} +
+ {photosData?.photos.map((photo) => ( + onPhotoSelect?.(photo)} + > + +
+ + + {photo.title} +
+ + {photo.status} + +
+
+ + ))} +
+
+ ) +} +``` + +### API 请求封装 +```typescript +// src/services/api.ts +import axios from 'axios' +import { useAuthStore } from '@/store/auth' +import { toast } from 'sonner' + +const api = axios.create({ + baseURL: import.meta.env.VITE_API_BASE_URL, + timeout: 10000 +}) + +// 请求拦截器 +api.interceptors.request.use( + (config) => { + const { token } = useAuthStore.getState() + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config + }, + (error) => Promise.reject(error) +) + +// 响应拦截器 +api.interceptors.response.use( + (response) => response.data, + (error) => { + const message = error.response?.data?.message || '请求失败' + toast.error(message) + + // 401 错误自动跳转登录 + if (error.response?.status === 401) { + useAuthStore.getState().logout() + window.location.href = '/login' + } + + return Promise.reject(error) + } +) + +export default api + +// src/hooks/usePhotos.ts +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' +import { photoService } from '@/services/photo' + +export function usePhotos(params: PhotoListParams) { + return useQuery({ + queryKey: ['photos', params], + queryFn: () => photoService.getPhotos(params) + }) +} + +export function useCreatePhoto() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: photoService.createPhoto, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['photos'] }) + toast.success('照片创建成功') + } + }) +} +``` + +## 🔄 与其他模块的集成 + +### 与前端展示的关系 +- 管理后台修改的数据通过 API 影响前端展示 +- 照片和相册的增删改查直接影响用户访问体验 +- 主题设置和配置修改需要前端重新获取 + +### 与后端 API 的关系 +- 依赖后端提供的 REST API 接口 +- 需要后端提供认证和权限控制 +- 文件上传需要后端存储支持 + +### 与部署模块的关系 +- 构建产物需要部署到 Web 服务器 +- 需要配置反向代理到管理后台路径 +- 与前端项目共享域名和 SSL 证书 + +## 🐛 问题排查 + +### 常见问题 +1. **登录失败**: 检查 API 地址和 Token 存储 +2. **图片上传失败**: 检查文件大小和格式限制 +3. **权限错误**: 检查用户角色和权限配置 +4. **页面空白**: 检查路由配置和组件导入 + +### 调试技巧 +```bash +# 查看开发环境变量 +npm run dev --debug + +# 查看构建详情 +npm run build --debug + +# 分析构建产物 +npm run analyze +``` + +## 📈 性能优化 + +### 代码分割 +```typescript +// 路由懒加载 +const PhotoList = () => import('@/views/photos/PhotoList.vue') +const AlbumList = () => import('@/views/albums/AlbumList.vue') + +// 组件懒加载 +const LazyComponent = defineAsyncComponent(() => import('./Component.vue')) +``` + +### 缓存策略 +```typescript +// src/lib/query-client.ts +import { QueryClient } from '@tanstack/react-query' + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 5 * 60 * 1000, // 5分钟缓存 + cacheTime: 10 * 60 * 1000, // 10分钟 + refetchOnWindowFocus: false, + retry: (failureCount, error: any) => { + if (error?.response?.status === 404) return false + return failureCount < 3 + } + } + } +}) + +// 使用示例 +export function usePhotos(params: PhotoListParams) { + return useQuery({ + queryKey: ['photos', params], + queryFn: () => photoService.getPhotos(params), + staleTime: 5 * 60 * 1000 // 5分钟缓存 + }) +} +``` + +## 🔮 未来规划 + +### 功能扩展 +- 📊 高级数据分析和报表 +- 🔄 批量操作和导入导出 +- 📱 移动端管理应用 +- 🎨 可视化主题编辑器 +- 🔌 插件系统支持 + +### 技术升级 +- 升级到 React 19 最新特性 +- 引入 React Server Components +- 支持 PWA 离线访问 +- 集成 AI 辅助功能 +- 支持 Micro Frontend 架构 + +## 📚 参考资料 + +- [React 官方文档](https://react.dev/) +- [shadcn/ui 文档](https://ui.shadcn.com/) +- [Radix UI 文档](https://radix-ui.com/) +- [Tailwind CSS 文档](https://tailwindcss.com/) +- [React Query 文档](https://tanstack.com/query/) +- [Zustand 文档](https://zustand-demo.pmnd.rs/) +- [Vite 配置指南](https://vitejs.dev/) +- [TypeScript 最佳实践](https://www.typescriptlang.org/) + +--- + +💡 **开发提示**: 在开始开发前,请确保已经阅读根目录的 CLAUDE.md 文件,了解项目整体架构和模块间的关系。开发过程中遇到问题,可以参考对应模块的 CLAUDE.md 文件寻找解决方案。 \ No newline at end of file diff --git a/backend/CLAUDE.md b/backend/CLAUDE.md new file mode 100644 index 0000000..4761e6c --- /dev/null +++ b/backend/CLAUDE.md @@ -0,0 +1,994 @@ +# 后端模块 - CLAUDE.md + +此文件为 Claude Code 在后端模块工作时提供指导。 + +## 🎯 模块概览 + +后端模块提供摄影作品集项目的 API 服务,包括照片管理、用户认证、数据存储等核心功能。 + +### 功能特性 +- 🔐 用户认证和授权管理 +- 📸 照片和相册 CRUD 操作 +- 📁 文件上传和存储管理 +- 🎨 主题和配置管理 +- 📊 访问统计和数据分析 +- 🔄 数据同步和缓存 + +### 技术栈 +- **语言**: Go 1.21+ +- **框架**: Gin + GORM +- **数据库**: PostgreSQL / MySQL +- **缓存**: Redis +- **存储**: MinIO / AWS S3 +- **认证**: JWT Token +- **部署**: Docker + Docker Compose + +## 📁 项目结构 + +``` +backend/ +├── CLAUDE.md # 🔍 当前文件 - 后端开发指导 +├── cmd/ # 应用入口 +│ └── server/ # 服务器启动 +│ └── main.go +├── internal/ # 内部模块 +│ ├── api/ # API 层 +│ │ ├── handlers/ # 请求处理器 +│ │ ├── middleware/ # 中间件 +│ │ └── routes/ # 路由定义 +│ ├── service/ # 业务逻辑层 +│ │ ├── auth/ # 认证服务 +│ │ ├── photo/ # 照片服务 +│ │ ├── album/ # 相册服务 +│ │ └── user/ # 用户服务 +│ ├── repository/ # 数据访问层 +│ │ ├── postgres/ # PostgreSQL 实现 +│ │ └── redis/ # Redis 实现 +│ ├── models/ # 数据模型 +│ ├── config/ # 配置管理 +│ └── utils/ # 工具函数 +├── pkg/ # 公共包 +│ ├── logger/ # 日志包 +│ ├── validator/ # 验证包 +│ └── response/ # 响应包 +├── migrations/ # 数据库迁移 +├── docs/ # API 文档 +├── scripts/ # 部署脚本 +├── configs/ # 配置文件 +│ ├── config.yaml # 主配置 +│ ├── config.dev.yaml # 开发环境 +│ └── config.prod.yaml # 生产环境 +├── docker-compose.yml # 容器编排 +├── Dockerfile # 容器构建 +├── Makefile # 构建脚本 +├── go.mod # Go 模块定义 +├── go.sum # 依赖校验 +└── README.md # 模块说明 +``` + +## 🚀 开发环境配置 + +### 环境要求 +- **Go**: 1.21+ +- **Docker**: 20.10+ +- **Docker Compose**: 2.0+ +- **PostgreSQL**: 14+ +- **Redis**: 6.2+ + +### 快速启动 +```bash +# 进入后端目录 +cd backend/ + +# 启动开发环境 (Docker) +make dev-up + +# 运行数据库迁移 +make migrate + +# 启动开发服务器 +make dev + +# 或者直接运行 +go run cmd/server/main.go +``` + +### 环境变量配置 +```bash +# .env +# 数据库配置 +DB_HOST=localhost +DB_PORT=5432 +DB_USER=postgres +DB_PASSWORD=password +DB_NAME=photography + +# Redis 配置 +REDIS_HOST=localhost +REDIS_PORT=6379 +REDIS_PASSWORD= + +# JWT 配置 +JWT_SECRET=your-secret-key +JWT_EXPIRES_IN=24h + +# 文件存储配置 +STORAGE_TYPE=local # local, s3, minio +STORAGE_PATH=./uploads +AWS_REGION=us-east-1 +AWS_BUCKET=photography-bucket + +# 服务器配置 +PORT=8080 +GIN_MODE=debug +``` + +## 🏗️ 项目架构 + +### 分层架构 +``` +┌─────────────────┐ +│ API Layer │ ← handlers, middleware, routes +├─────────────────┤ +│ Service Layer │ ← business logic +├─────────────────┤ +│Repository Layer │ ← data access +├─────────────────┤ +│ Models Layer │ ← data structures +└─────────────────┘ +``` + +### 目录结构详解 + +#### 🎯 API 层 +``` +internal/api/ +├── handlers/ # 请求处理器 +│ ├── auth.go # 认证相关 +│ ├── photo.go # 照片管理 +│ ├── album.go # 相册管理 +│ ├── user.go # 用户管理 +│ └── upload.go # 文件上传 +├── middleware/ # 中间件 +│ ├── auth.go # 认证中间件 +│ ├── cors.go # CORS 处理 +│ ├── logger.go # 日志中间件 +│ └── rate_limit.go # 限流中间件 +└── routes/ # 路由定义 + ├── api_v1.go # API v1 路由 + └── admin.go # 管理后台路由 +``` + +#### 💼 业务逻辑层 +``` +internal/service/ +├── auth/ # 认证服务 +│ ├── auth.go # 认证逻辑 +│ ├── jwt.go # JWT 处理 +│ └── permission.go # 权限控制 +├── photo/ # 照片服务 +│ ├── photo.go # 照片操作 +│ ├── metadata.go # 元数据处理 +│ └── thumbnail.go # 缩略图生成 +├── album/ # 相册服务 +│ ├── album.go # 相册操作 +│ └── organization.go # 组织管理 +└── user/ # 用户服务 + ├── user.go # 用户管理 + └── profile.go # 用户资料 +``` + +#### 🗄️ 数据访问层 +``` +internal/repository/ +├── postgres/ # PostgreSQL 实现 +│ ├── photo.go # 照片数据操作 +│ ├── album.go # 相册数据操作 +│ ├── user.go # 用户数据操作 +│ └── migration.go # 数据迁移 +└── redis/ # Redis 实现 + ├── cache.go # 缓存操作 + └── session.go # 会话管理 +``` + +## 🗃️ 数据库设计 + +### 数据模型定义 +```go +// internal/models/photo.go +type Photo struct { + ID uint `gorm:"primaryKey" json:"id"` + Title string `gorm:"not null" json:"title"` + Description string `json:"description"` + URL string `gorm:"not null" json:"url"` + ThumbnailURL string `json:"thumbnail_url"` + AlbumID uint `json:"album_id"` + UserID uint `json:"user_id"` + Tags []Tag `gorm:"many2many:photo_tags" json:"tags"` + Metadata PhotoMetadata `gorm:"type:jsonb" json:"metadata"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// internal/models/album.go +type Album struct { + ID uint `gorm:"primaryKey" json:"id"` + Name string `gorm:"not null" json:"name"` + Description string `json:"description"` + CoverPhotoID *uint `json:"cover_photo_id"` + CoverPhoto *Photo `gorm:"foreignKey:CoverPhotoID" json:"cover_photo"` + Photos []Photo `gorm:"foreignKey:AlbumID" json:"photos"` + IsPublic bool `gorm:"default:true" json:"is_public"` + UserID uint `json:"user_id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// internal/models/user.go +type User struct { + ID uint `gorm:"primaryKey" json:"id"` + Username string `gorm:"unique;not null" json:"username"` + Email string `gorm:"unique;not null" json:"email"` + Password string `gorm:"not null" json:"-"` + Role string `gorm:"default:user" json:"role"` + IsActive bool `gorm:"default:true" json:"is_active"` + Profile UserProfile `gorm:"foreignKey:UserID" json:"profile"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} +``` + +### 数据库迁移 +```sql +-- migrations/001_create_users.sql +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + username VARCHAR(50) UNIQUE NOT NULL, + email VARCHAR(100) UNIQUE NOT NULL, + password VARCHAR(255) NOT NULL, + role VARCHAR(20) DEFAULT 'user', + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- migrations/002_create_albums.sql +CREATE TABLE albums ( + id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + description TEXT, + cover_photo_id INTEGER, + is_public BOOLEAN DEFAULT true, + user_id INTEGER REFERENCES users(id), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- migrations/003_create_photos.sql +CREATE TABLE photos ( + id SERIAL PRIMARY KEY, + title VARCHAR(200) NOT NULL, + description TEXT, + url VARCHAR(500) NOT NULL, + thumbnail_url VARCHAR(500), + album_id INTEGER REFERENCES albums(id), + user_id INTEGER REFERENCES users(id), + metadata JSONB, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +``` + +## 🔌 API 接口设计 + +### RESTful API 规范 +```go +// internal/api/handlers/photo.go +type PhotoHandler struct { + photoService *service.PhotoService +} + +// GET /api/v1/photos +func (h *PhotoHandler) GetPhotos(c *gin.Context) { + params := &service.PhotoListParams{} + if err := c.ShouldBindQuery(params); err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + + photos, total, err := h.photoService.GetPhotos(params) + if err != nil { + c.JSON(500, gin.H{"error": err.Error()}) + return + } + + c.JSON(200, gin.H{ + "data": photos, + "total": total, + "page": params.Page, + "per_page": params.PerPage, + }) +} + +// POST /api/v1/photos +func (h *PhotoHandler) CreatePhoto(c *gin.Context) { + var req service.CreatePhotoRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + + userID := c.GetUint("user_id") + req.UserID = userID + + photo, err := h.photoService.CreatePhoto(&req) + if err != nil { + c.JSON(500, gin.H{"error": err.Error()}) + return + } + + c.JSON(201, gin.H{"data": photo}) +} + +// PUT /api/v1/photos/:id +func (h *PhotoHandler) UpdatePhoto(c *gin.Context) { + id, _ := strconv.ParseUint(c.Param("id"), 10, 32) + + var req service.UpdatePhotoRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + + photo, err := h.photoService.UpdatePhoto(uint(id), &req) + if err != nil { + c.JSON(500, gin.H{"error": err.Error()}) + return + } + + c.JSON(200, gin.H{"data": photo}) +} + +// DELETE /api/v1/photos/:id +func (h *PhotoHandler) DeletePhoto(c *gin.Context) { + id, _ := strconv.ParseUint(c.Param("id"), 10, 32) + + err := h.photoService.DeletePhoto(uint(id)) + if err != nil { + c.JSON(500, gin.H{"error": err.Error()}) + return + } + + c.JSON(204, nil) +} +``` + +### API 路由配置 +```go +// internal/api/routes/api_v1.go +func SetupAPIV1Routes(r *gin.Engine, handlers *Handlers) { + api := r.Group("/api/v1") + + // 公开接口 + api.GET("/photos", handlers.Photo.GetPhotos) + api.GET("/photos/:id", handlers.Photo.GetPhoto) + api.GET("/albums", handlers.Album.GetAlbums) + api.GET("/albums/:id", handlers.Album.GetAlbum) + + // 认证相关 + auth := api.Group("/auth") + { + auth.POST("/login", handlers.Auth.Login) + auth.POST("/register", handlers.Auth.Register) + auth.POST("/refresh", handlers.Auth.RefreshToken) + } + + // 需要认证的接口 + protected := api.Group("/", middleware.AuthRequired()) + { + protected.POST("/photos", handlers.Photo.CreatePhoto) + protected.PUT("/photos/:id", handlers.Photo.UpdatePhoto) + protected.DELETE("/photos/:id", handlers.Photo.DeletePhoto) + protected.POST("/upload", handlers.Upload.UploadFile) + } + + // 管理员接口 + admin := api.Group("/admin", middleware.AdminRequired()) + { + admin.GET("/users", handlers.User.GetUsers) + admin.PUT("/users/:id", handlers.User.UpdateUser) + admin.DELETE("/users/:id", handlers.User.DeleteUser) + } +} +``` + +## 🔐 认证和授权 + +### JWT 认证实现 +```go +// internal/service/auth/jwt.go +type JWTService struct { + secretKey []byte + expiresIn time.Duration +} + +func NewJWTService(secretKey string, expiresIn time.Duration) *JWTService { + return &JWTService{ + secretKey: []byte(secretKey), + expiresIn: expiresIn, + } +} + +func (s *JWTService) GenerateToken(userID uint, role string) (string, error) { + claims := jwt.MapClaims{ + "user_id": userID, + "role": role, + "exp": time.Now().Add(s.expiresIn).Unix(), + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + return token.SignedString(s.secretKey) +} + +func (s *JWTService) ValidateToken(tokenString string) (*jwt.Token, error) { + return jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + return s.secretKey, nil + }) +} +``` + +### 权限控制中间件 +```go +// internal/api/middleware/auth.go +func AuthRequired() gin.HandlerFunc { + return func(c *gin.Context) { + tokenString := c.GetHeader("Authorization") + if tokenString == "" { + c.JSON(401, gin.H{"error": "Authorization header required"}) + c.Abort() + return + } + + // 移除 "Bearer " 前缀 + if strings.HasPrefix(tokenString, "Bearer ") { + tokenString = tokenString[7:] + } + + token, err := jwtService.ValidateToken(tokenString) + if err != nil || !token.Valid { + c.JSON(401, gin.H{"error": "Invalid token"}) + c.Abort() + return + } + + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + c.JSON(401, gin.H{"error": "Invalid token claims"}) + c.Abort() + return + } + + c.Set("user_id", uint(claims["user_id"].(float64))) + c.Set("role", claims["role"].(string)) + c.Next() + } +} + +func AdminRequired() gin.HandlerFunc { + return gin.HandlerFunc(func(c *gin.Context) { + AuthRequired()(c) + + if c.IsAborted() { + return + } + + role := c.GetString("role") + if role != "admin" { + c.JSON(403, gin.H{"error": "Admin access required"}) + c.Abort() + return + } + + c.Next() + }) +} +``` + +## 📁 文件存储管理 + +### 存储接口设计 +```go +// internal/service/storage/interface.go +type StorageService interface { + Upload(file multipart.File, filename string) (string, error) + Delete(filename string) error + GetURL(filename string) string +} + +// internal/service/storage/local.go +type LocalStorage struct { + basePath string + baseURL string +} + +func (s *LocalStorage) Upload(file multipart.File, filename string) (string, error) { + filepath := path.Join(s.basePath, filename) + + out, err := os.Create(filepath) + if err != nil { + return "", err + } + defer out.Close() + + _, err = io.Copy(out, file) + if err != nil { + return "", err + } + + return s.GetURL(filename), nil +} + +func (s *LocalStorage) GetURL(filename string) string { + return fmt.Sprintf("%s/%s", s.baseURL, filename) +} + +// internal/service/storage/s3.go +type S3Storage struct { + client *s3.Client + bucket string + region string +} + +func (s *S3Storage) Upload(file multipart.File, filename string) (string, error) { + _, err := s.client.PutObject(context.TODO(), &s3.PutObjectInput{ + Bucket: aws.String(s.bucket), + Key: aws.String(filename), + Body: file, + }) + + if err != nil { + return "", err + } + + return s.GetURL(filename), nil +} +``` + +### 图片处理 +```go +// internal/service/photo/thumbnail.go +import ( + "image" + "image/jpeg" + "image/png" + "golang.org/x/image/draw" +) + +func GenerateThumbnail(src image.Image, width, height int) (image.Image, error) { + bounds := src.Bounds() + srcWidth := bounds.Max.X + srcHeight := bounds.Max.Y + + // 计算缩放比例 + scaleX := float64(width) / float64(srcWidth) + scaleY := float64(height) / float64(srcHeight) + scale := math.Min(scaleX, scaleY) + + newWidth := int(float64(srcWidth) * scale) + newHeight := int(float64(srcHeight) * scale) + + // 创建新图像 + dst := image.NewRGBA(image.Rect(0, 0, newWidth, newHeight)) + + // 缩放图像 + draw.BiLinear.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Over, nil) + + return dst, nil +} +``` + +## 🚀 构建和部署 + +### Makefile 配置 +```makefile +# Makefile +.PHONY: build run test clean docker-build docker-run + +# 构建 +build: + go build -o bin/server cmd/server/main.go + +# 运行 +run: + go run cmd/server/main.go + +# 测试 +test: + go test ./... + +# 代码检查 +lint: + golangci-lint run + +# 清理 +clean: + rm -rf bin/ + +# 数据库迁移 +migrate: + migrate -path migrations -database "postgres://user:pass@localhost/dbname?sslmode=disable" up + +# Docker 构建 +docker-build: + docker build -t photography-backend . + +# Docker 运行 +docker-run: + docker-compose up -d + +# 开发环境 +dev-up: + docker-compose -f docker-compose.dev.yml up -d + +dev-down: + docker-compose -f docker-compose.dev.yml down + +# 生产环境 +prod-up: + docker-compose -f docker-compose.prod.yml up -d + +prod-down: + docker-compose -f docker-compose.prod.yml down +``` + +### Docker 配置 +```dockerfile +# Dockerfile +FROM golang:1.21-alpine AS builder + +WORKDIR /app +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main cmd/server/main.go + +FROM alpine:latest +RUN apk --no-cache add ca-certificates +WORKDIR /root/ + +COPY --from=builder /app/main . +COPY --from=builder /app/configs ./configs +COPY --from=builder /app/migrations ./migrations + +EXPOSE 8080 +CMD ["./main"] +``` + +### Docker Compose 配置 +```yaml +# docker-compose.yml +version: '3.8' + +services: + backend: + build: . + ports: + - "8080:8080" + depends_on: + - postgres + - redis + environment: + - DB_HOST=postgres + - DB_PORT=5432 + - DB_USER=postgres + - DB_PASSWORD=password + - DB_NAME=photography + - REDIS_HOST=redis + - REDIS_PORT=6379 + volumes: + - ./uploads:/app/uploads + - ./configs:/app/configs + + postgres: + image: postgres:14 + environment: + - POSTGRES_DB=photography + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=password + volumes: + - postgres_data:/var/lib/postgresql/data + ports: + - "5432:5432" + + redis: + image: redis:6.2 + ports: + - "6379:6379" + volumes: + - redis_data:/data + +volumes: + postgres_data: + redis_data: +``` + +## 🔧 开发指南 + +### 代码规范 +- 使用 Go 标准格式化工具 `go fmt` +- 遵循 Go 命名规范 +- 使用 `golangci-lint` 进行代码检查 +- 编写单元测试,覆盖率不低于 80% + +### 错误处理 +```go +// pkg/response/response.go +type Response struct { + Code int `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data,omitempty"` +} + +func Success(data interface{}) *Response { + return &Response{ + Code: 200, + Message: "success", + Data: data, + } +} + +func Error(code int, message string) *Response { + return &Response{ + Code: code, + Message: message, + } +} + +// internal/utils/errors.go +var ( + ErrUserNotFound = errors.New("user not found") + ErrPhotoNotFound = errors.New("photo not found") + ErrAlbumNotFound = errors.New("album not found") + ErrInvalidPassword = errors.New("invalid password") + ErrUnauthorized = errors.New("unauthorized") + ErrPermissionDenied = errors.New("permission denied") +) +``` + +### 日志管理 +```go +// pkg/logger/logger.go +import ( + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +var Logger *zap.Logger + +func init() { + config := zap.NewProductionConfig() + config.EncoderConfig.TimeKey = "timestamp" + config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder + + Logger, _ = config.Build() +} + +func Info(msg string, fields ...zap.Field) { + Logger.Info(msg, fields...) +} + +func Error(msg string, fields ...zap.Field) { + Logger.Error(msg, fields...) +} + +func Debug(msg string, fields ...zap.Field) { + Logger.Debug(msg, fields...) +} +``` + +### 测试编写 +```go +// internal/service/photo/photo_test.go +func TestPhotoService_CreatePhoto(t *testing.T) { + mockRepo := &MockPhotoRepository{} + service := NewPhotoService(mockRepo) + + req := &CreatePhotoRequest{ + Title: "Test Photo", + Description: "Test Description", + URL: "https://example.com/photo.jpg", + AlbumID: 1, + UserID: 1, + } + + expectedPhoto := &models.Photo{ + ID: 1, + Title: req.Title, + Description: req.Description, + URL: req.URL, + AlbumID: req.AlbumID, + UserID: req.UserID, + } + + mockRepo.On("Create", mock.AnythingOfType("*models.Photo")).Return(expectedPhoto, nil) + + photo, err := service.CreatePhoto(req) + + assert.NoError(t, err) + assert.Equal(t, expectedPhoto.Title, photo.Title) + assert.Equal(t, expectedPhoto.Description, photo.Description) + mockRepo.AssertExpectations(t) +} +``` + +## 🔄 与其他模块的集成 + +### 与前端的集成 +- 提供 RESTful API 供前端调用 +- 支持 CORS 跨域请求 +- 返回标准化的 JSON 响应格式 +- 提供 API 文档 (Swagger) + +### 与管理后台的集成 +- 提供管理员专用的 API 接口 +- 支持批量操作和数据导入导出 +- 提供系统监控和统计信息 +- 支持权限管理和用户管理 + +### 与部署模块的集成 +- 提供 Docker 容器化部署 +- 支持负载均衡和水平扩展 +- 集成健康检查和监控 +- 支持优雅关闭和重启 + +## 🐛 问题排查 + +### 常见问题 +1. **数据库连接失败**: 检查数据库配置和网络连接 +2. **JWT 认证失败**: 检查密钥配置和 Token 格式 +3. **文件上传失败**: 检查存储配置和权限设置 +4. **API 响应慢**: 检查数据库查询和缓存配置 + +### 调试技巧 +```bash +# 查看日志 +docker-compose logs -f backend + +# 进入容器 +docker-compose exec backend sh + +# 数据库查询 +docker-compose exec postgres psql -U postgres -d photography + +# Redis 查询 +docker-compose exec redis redis-cli +``` + +## 📊 性能优化 + +### 数据库优化 +```sql +-- 添加索引 +CREATE INDEX idx_photos_album_id ON photos(album_id); +CREATE INDEX idx_photos_user_id ON photos(user_id); +CREATE INDEX idx_photos_created_at ON photos(created_at); + +-- 查询优化 +EXPLAIN ANALYZE SELECT * FROM photos WHERE album_id = 1; +``` + +### 缓存策略 +```go +// internal/service/photo/photo.go +func (s *PhotoService) GetPhoto(id uint) (*models.Photo, error) { + cacheKey := fmt.Sprintf("photo:%d", id) + + // 从缓存获取 + if cached, err := s.cache.Get(cacheKey); err == nil { + var photo models.Photo + if err := json.Unmarshal(cached, &photo); err == nil { + return &photo, nil + } + } + + // 从数据库获取 + photo, err := s.repo.GetByID(id) + if err != nil { + return nil, err + } + + // 存入缓存 + if data, err := json.Marshal(photo); err == nil { + s.cache.Set(cacheKey, data, 5*time.Minute) + } + + return photo, nil +} +``` + +## 📈 监控和日志 + +### 健康检查 +```go +// internal/api/handlers/health.go +func (h *HealthHandler) Check(c *gin.Context) { + status := gin.H{ + "status": "ok", + "timestamp": time.Now().Unix(), + "version": "1.0.0", + } + + // 检查数据库连接 + if err := h.db.Ping(); err != nil { + status["database"] = "error" + status["status"] = "error" + } else { + status["database"] = "ok" + } + + // 检查 Redis 连接 + if err := h.redis.Ping().Err(); err != nil { + status["redis"] = "error" + status["status"] = "error" + } else { + status["redis"] = "ok" + } + + if status["status"] == "error" { + c.JSON(500, status) + } else { + c.JSON(200, status) + } +} +``` + +### 指标收集 +```go +// internal/middleware/metrics.go +func PrometheusMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + start := time.Now() + + c.Next() + + duration := time.Since(start) + status := c.Writer.Status() + + // 记录请求指标 + requestDuration.WithLabelValues(c.Request.Method, c.FullPath()).Observe(duration.Seconds()) + requestCount.WithLabelValues(c.Request.Method, c.FullPath(), fmt.Sprintf("%d", status)).Inc() + } +} +``` + +## 🔮 未来规划 + +### 功能扩展 +- 📊 高级搜索和过滤 +- 🔄 数据同步和备份 +- 📱 移动端 API 优化 +- 🤖 AI 图像识别和标记 +- 📈 实时分析和推荐 + +### 技术升级 +- 迁移到微服务架构 +- 集成消息队列 (RabbitMQ/Kafka) +- 支持分布式存储 +- 集成 Elasticsearch 搜索 +- 支持 GraphQL API + +## 📚 参考资料 + +- [Go 官方文档](https://golang.org/doc/) +- [Gin 框架文档](https://gin-gonic.com/docs/) +- [GORM 文档](https://gorm.io/docs/) +- [PostgreSQL 文档](https://www.postgresql.org/docs/) +- [Docker 最佳实践](https://docs.docker.com/develop/dev-best-practices/) + +--- + +💡 **开发提示**: 开始开发前,请确保已经阅读根目录的 CLAUDE.md 文件,了解项目整体架构。开发过程中建议先实现核心功能,再逐步完善其他特性。务必编写单元测试和集成测试,确保代码质量。 \ No newline at end of file diff --git a/ui/CLAUDE.md b/ui/CLAUDE.md new file mode 100644 index 0000000..0bf5116 --- /dev/null +++ b/ui/CLAUDE.md @@ -0,0 +1,604 @@ +# UI 备份模块 - CLAUDE.md + +此文件为 Claude Code 在 UI 备份模块工作时提供指导。 + +## 🎯 模块概览 + +UI 备份模块是主前端模块 (`frontend/`) 的备份和实验环境,用于保存稳定版本、测试新功能和组件开发。 + +### 模块用途 +- 🔄 主前端代码的备份版本 +- 🧪 新功能和组件的实验环境 +- 📦 组件库的独立维护 +- 🔧 A/B 测试和版本对比 +- 🛠️ 紧急回滚的备用版本 + +### 技术栈 +- **前端**: Next.js 15 + React 19 + TypeScript +- **组件**: shadcn/ui + Radix UI +- **样式**: Tailwind CSS +- **构建**: Next.js 内置构建系统 +- **包管理**: pnpm (与主前端区分) + +## 📁 目录结构 + +``` +ui/ +├── CLAUDE.md # 🔍 当前文件 - UI 备份模块指导 +├── app/ # Next.js 应用目录 +│ ├── globals.css # 全局样式 +│ ├── layout.tsx # 根布局 +│ └── page.tsx # 主页 +├── components/ # 组件目录 +│ ├── ui/ # shadcn/ui 组件 +│ │ ├── button.tsx # 按钮组件 +│ │ ├── card.tsx # 卡片组件 +│ │ ├── dialog.tsx # 对话框组件 +│ │ └── ... # 其他 UI 组件 +│ ├── about-view.tsx # 关于页面组件 +│ ├── contact-view.tsx # 联系页面组件 +│ ├── filter-bar.tsx # 过滤栏组件 +│ ├── loading-spinner.tsx # 加载组件 +│ ├── navigation.tsx # 导航组件 +│ ├── photo-gallery.tsx # 照片画廊组件 +│ ├── photo-modal.tsx # 照片弹窗组件 +│ ├── theme-provider.tsx # 主题提供器 +│ ├── timeline-stats.tsx # 时间线统计 +│ └── timeline-view.tsx # 时间线视图 +├── hooks/ # 自定义 Hook +│ ├── use-mobile.tsx # 移动端检测 +│ └── use-toast.ts # 提示信息 +├── lib/ # 工具库 +│ └── utils.ts # 工具函数 +├── public/ # 静态资源 +│ ├── placeholder-logo.png # 占位图标 +│ ├── placeholder-user.jpg # 占位头像 +│ └── placeholder.jpg # 占位图片 +├── styles/ # 样式文件 +│ └── globals.css # 全局样式 +├── components.json # shadcn/ui 配置 +├── next.config.mjs # Next.js 配置 +├── package.json # 依赖配置 +├── pnpm-lock.yaml # pnpm 锁文件 +├── postcss.config.mjs # PostCSS 配置 +├── tailwind.config.ts # Tailwind 配置 +├── tsconfig.json # TypeScript 配置 +└── README.md # 模块说明 +``` + +## 🔄 与主前端的关系 + +### 同步策略 +```bash +# 从主前端同步到 UI 备份 +# 1. 同步核心组件 +cp -r frontend/components/ui/* ui/components/ui/ + +# 2. 同步配置文件 +cp frontend/tailwind.config.ts ui/ +cp frontend/components.json ui/ + +# 3. 同步样式文件 +cp frontend/app/globals.css ui/app/ +cp frontend/styles/globals.css ui/styles/ + +# 4. 同步工具函数 +cp frontend/lib/utils.ts ui/lib/ +``` + +### 版本控制 +```bash +# 创建备份分支 +git checkout -b ui-backup-v1.0.0 + +# 标记稳定版本 +git tag ui-v1.0.0 + +# 推送备份版本 +git push origin ui-backup-v1.0.0 +git push origin ui-v1.0.0 +``` + +## 🧪 实验环境配置 + +### 开发环境设置 +```bash +# 进入 UI 备份目录 +cd ui/ + +# 安装依赖 (使用 pnpm) +pnpm install + +# 启动开发服务器 +pnpm dev + +# 构建项目 +pnpm build + +# 启动生产预览 +pnpm start +``` + +### 环境变量配置 +```bash +# .env.local +NEXT_PUBLIC_API_URL=http://localhost:3001 +NEXT_PUBLIC_MOCK_API=true +NEXT_PUBLIC_EXPERIMENTAL_FEATURES=true +``` + +### 实验性功能开关 +```typescript +// lib/experimental.ts +export const experimentalFeatures = { + // 新组件测试 + newPhotoGallery: process.env.NEXT_PUBLIC_EXPERIMENTAL_FEATURES === 'true', + + // A/B 测试 + alternativeNavigation: false, + + // 性能优化 + optimizedImages: true, + + // 新主题 + darkModeV2: false, +} as const + +// 使用示例 +import { experimentalFeatures } from '@/lib/experimental' + +export default function PhotoGallery() { + if (experimentalFeatures.newPhotoGallery) { + return + } + + return +} +``` + +## 🎨 组件库管理 + +### 组件分类 +``` +components/ +├── ui/ # 基础 UI 组件 +│ ├── button.tsx # 按钮 - 基础交互 +│ ├── input.tsx # 输入框 - 表单控件 +│ ├── card.tsx # 卡片 - 内容容器 +│ ├── dialog.tsx # 对话框 - 模态框 +│ ├── dropdown-menu.tsx # 下拉菜单 - 菜单控件 +│ ├── navigation-menu.tsx # 导航菜单 - 导航控件 +│ ├── tabs.tsx # 标签页 - 切换控件 +│ ├── toast.tsx # 提示信息 - 反馈组件 +│ └── ... # 其他基础组件 +├── business/ # 业务组件 +│ ├── photo-gallery.tsx # 照片画廊 +│ ├── photo-modal.tsx # 照片弹窗 +│ ├── timeline-view.tsx # 时间线视图 +│ ├── filter-bar.tsx # 过滤栏 +│ └── navigation.tsx # 站点导航 +├── layout/ # 布局组件 +│ ├── header.tsx # 页头 +│ ├── footer.tsx # 页脚 +│ └── sidebar.tsx # 侧边栏 +└── experimental/ # 实验性组件 + ├── new-photo-gallery.tsx # 新照片画廊 + ├── advanced-filter.tsx # 高级过滤器 + └── ai-search.tsx # AI 搜索 +``` + +### 组件开发规范 +```typescript +// components/experimental/new-photo-gallery.tsx +import { useState, useCallback } from 'react' +import { Card, CardContent } from '@/components/ui/card' +import { Button } from '@/components/ui/button' + +interface Photo { + id: string + src: string + alt: string + title: string + description?: string +} + +interface NewPhotoGalleryProps { + photos: Photo[] + onPhotoClick?: (photo: Photo) => void + className?: string +} + +export function NewPhotoGallery({ + photos, + onPhotoClick, + className +}: NewPhotoGalleryProps) { + const [selectedPhoto, setSelectedPhoto] = useState(null) + + const handlePhotoClick = useCallback((photo: Photo) => { + setSelectedPhoto(photo) + onPhotoClick?.(photo) + }, [onPhotoClick]) + + return ( +
+ {photos.map((photo) => ( + + +
handlePhotoClick(photo)} + > + {photo.alt} +
+
+ {photo.title && ( +
+

{photo.title}

+ {photo.description && ( +

+ {photo.description} +

+ )} +
+ )} + + + ))} +
+ ) +} +``` + +## 🔧 版本管理和同步 + +### 版本管理策略 +```bash +# 1. 创建功能分支 +git checkout -b feature/new-component + +# 2. 开发和测试 +# ... 组件开发 ... + +# 3. 合并到 UI 备份主分支 +git checkout main +git merge feature/new-component + +# 4. 测试稳定后,同步到主前端 +cp ui/components/experimental/new-component.tsx frontend/components/ +``` + +### 自动同步脚本 +```bash +#!/bin/bash +# scripts/sync-ui-to-frontend.sh + +set -e + +UI_DIR="ui" +FRONTEND_DIR="frontend" + +echo "🔄 开始同步 UI 备份到主前端..." + +# 同步基础组件 +echo "📦 同步基础组件..." +rsync -av --exclude='experimental' $UI_DIR/components/ui/ $FRONTEND_DIR/components/ui/ + +# 同步配置文件 +echo "⚙️ 同步配置文件..." +cp $UI_DIR/tailwind.config.ts $FRONTEND_DIR/ +cp $UI_DIR/components.json $FRONTEND_DIR/ + +# 同步样式文件 +echo "🎨 同步样式文件..." +cp $UI_DIR/app/globals.css $FRONTEND_DIR/app/ +cp $UI_DIR/styles/globals.css $FRONTEND_DIR/styles/ + +# 同步工具函数 +echo "🛠️ 同步工具函数..." +cp $UI_DIR/lib/utils.ts $FRONTEND_DIR/lib/ + +echo "✅ 同步完成!" +``` + +### 反向同步脚本 +```bash +#!/bin/bash +# scripts/sync-frontend-to-ui.sh + +set -e + +FRONTEND_DIR="frontend" +UI_DIR="ui" + +echo "🔄 开始同步主前端到 UI 备份..." + +# 创建备份 +echo "💾 创建备份..." +timestamp=$(date +%Y%m%d_%H%M%S) +cp -r $UI_DIR $UI_DIR.backup.$timestamp + +# 同步核心文件 +echo "📦 同步核心文件..." +rsync -av --exclude='experimental' --exclude='backup.*' $FRONTEND_DIR/ $UI_DIR/ + +# 保留实验性功能 +echo "🧪 保留实验性功能..." +if [ -d "$UI_DIR.backup.$timestamp/components/experimental" ]; then + cp -r $UI_DIR.backup.$timestamp/components/experimental $UI_DIR/components/ +fi + +echo "✅ 同步完成!" +``` + +## 🧪 A/B 测试和实验 + +### A/B 测试框架 +```typescript +// lib/ab-testing.ts +interface ABTest { + name: string + variants: { + control: React.ComponentType + treatment: React.ComponentType + } + percentage: number // 0-100 + enabled: boolean +} + +class ABTestManager { + private tests: Map = new Map() + + registerTest(test: ABTest) { + this.tests.set(test.name, test) + } + + getVariant(testName: string): 'control' | 'treatment' | null { + const test = this.tests.get(testName) + if (!test || !test.enabled) return null + + // 基于用户 ID 或 session 的一致性分组 + const hash = this.hashString(testName + this.getUserId()) + const percentage = hash % 100 + + return percentage < test.percentage ? 'treatment' : 'control' + } + + renderComponent(testName: string, props: any) { + const test = this.tests.get(testName) + if (!test) return null + + const variant = this.getVariant(testName) + if (variant === 'treatment') { + return + } + return + } + + private hashString(str: string): number { + let hash = 0 + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i) + hash = ((hash << 5) - hash) + char + hash = hash & hash // Convert to 32-bit integer + } + return Math.abs(hash) + } + + private getUserId(): string { + // 实现用户 ID 获取逻辑 + return sessionStorage.getItem('userId') || 'anonymous' + } +} + +export const abTestManager = new ABTestManager() + +// 使用示例 +abTestManager.registerTest({ + name: 'photo-gallery-layout', + variants: { + control: OriginalPhotoGallery, + treatment: NewPhotoGallery + }, + percentage: 50, + enabled: true +}) +``` + +### 实验性功能管理 +```typescript +// components/experimental/feature-flag.tsx +import { experimentalFeatures } from '@/lib/experimental' + +interface FeatureFlagProps { + feature: keyof typeof experimentalFeatures + children: React.ReactNode + fallback?: React.ReactNode +} + +export function FeatureFlag({ feature, children, fallback = null }: FeatureFlagProps) { + if (experimentalFeatures[feature]) { + return <>{children} + } + + return <>{fallback} +} + +// 使用示例 +}> + + +``` + +## 🚀 构建和部署 + +### 构建配置 +```json +{ + "scripts": { + "dev": "next dev -p 3002", + "build": "next build", + "start": "next start -p 3002", + "lint": "next lint", + "type-check": "tsc --noEmit", + "export": "next build && next export", + "sync-from-frontend": "./scripts/sync-frontend-to-ui.sh", + "sync-to-frontend": "./scripts/sync-ui-to-frontend.sh" + } +} +``` + +### Next.js 配置 +```typescript +// next.config.mjs +/** @type {import('next').NextConfig} */ +const nextConfig = { + // 输出静态文件 + output: 'export', + + // 基础路径 (用于部署到子路径) + basePath: '/ui', + + // 禁用图片优化 (静态导出) + images: { + unoptimized: true + }, + + // 实验性功能 + experimental: { + appDir: true, + serverComponentsExternalPackages: [] + }, + + // 环境变量 + env: { + NEXT_PUBLIC_UI_VERSION: process.env.npm_package_version || '1.0.0' + } +} + +export default nextConfig +``` + +### 部署配置 +```bash +# 构建并部署到 UI 预览环境 +npm run build +npm run export + +# 部署到独立的 UI 预览域名 +rsync -av out/ user@server:/var/www/ui-preview/ +``` + +## 🔄 与其他模块的集成 + +### 与主前端的协作 +- 新功能在 UI 备份中实验和测试 +- 稳定后同步到主前端 +- 主前端的 bug 修复反向同步到 UI 备份 + +### 与管理后台的关系 +- 共享基础组件库 +- 管理后台可以复用 UI 备份中的组件 +- 保持设计系统的一致性 + +### 与后端的集成 +- 使用相同的 API 接口 +- 支持 Mock API 进行独立开发 +- 与后端版本保持兼容 + +## 🐛 问题排查 + +### 常见问题 +1. **组件不同步**: 检查同步脚本和版本控制 +2. **样式冲突**: 检查 Tailwind 配置和全局样式 +3. **依赖版本不一致**: 检查 package.json 和锁文件 +4. **构建失败**: 检查 TypeScript 配置和依赖 + +### 调试技巧 +```bash +# 比较两个版本的差异 +diff -r frontend/components ui/components + +# 检查依赖差异 +diff frontend/package.json ui/package.json + +# 清理缓存 +rm -rf ui/.next ui/node_modules +cd ui && pnpm install +``` + +## 📊 监控和分析 + +### 性能监控 +```typescript +// lib/performance.ts +export function measurePerformance(name: string, fn: () => void) { + const start = performance.now() + fn() + const end = performance.now() + + console.log(`${name} took ${end - start} milliseconds`) + + // 发送到分析服务 + if (typeof window !== 'undefined' && (window as any).gtag) { + (window as any).gtag('event', 'timing_complete', { + name: name, + value: Math.round(end - start) + }) + } +} +``` + +### 实验数据收集 +```typescript +// lib/analytics.ts +export function trackExperiment(testName: string, variant: string) { + if (typeof window !== 'undefined' && (window as any).gtag) { + (window as any).gtag('event', 'experiment_view', { + experiment_id: testName, + variant_id: variant + }) + } +} + +export function trackConversion(testName: string, variant: string, action: string) { + if (typeof window !== 'undefined' && (window as any).gtag) { + (window as any).gtag('event', 'experiment_conversion', { + experiment_id: testName, + variant_id: variant, + action: action + }) + } +} +``` + +## 🔮 未来规划 + +### 功能扩展 +- 🔧 可视化组件编辑器 +- 📊 实验结果分析面板 +- 🎨 设计系统自动化 +- 🤖 AI 辅助组件生成 +- 📱 响应式设计测试工具 + +### 工作流优化 +- 自动化同步流程 +- 持续集成测试 +- 可视化差异对比 +- 自动化 A/B 测试报告 + +## 📚 参考资料 + +- [Next.js 文档](https://nextjs.org/docs) +- [React 19 新特性](https://react.dev/blog/2024/04/25/react-19) +- [shadcn/ui 文档](https://ui.shadcn.com/) +- [Tailwind CSS 文档](https://tailwindcss.com/docs) +- [A/B 测试最佳实践](https://www.optimizely.com/optimization-glossary/ab-testing/) + +--- + +💡 **使用提示**: UI 备份模块主要用于实验和备份,不建议在生产环境中直接使用。所有稳定的功能都应该同步到主前端模块。在进行实验时,请确保做好数据备份,避免影响主前端的稳定性。 \ No newline at end of file