diff --git a/.cursor/rules/admin/react-vite.mdc b/.cursor/rules/admin/react-vite.mdc new file mode 100644 index 0000000..6aada16 --- /dev/null +++ b/.cursor/rules/admin/react-vite.mdc @@ -0,0 +1,624 @@ +# 管理后台 React + Vite 开发规则 + +## 🏗️ 项目架构 + +### 目录结构 +``` +admin/ +├── src/ +│ ├── components/ # React 组件 +│ │ ├── ui/ # shadcn/ui 基础组件 +│ │ ├── DashboardLayout.tsx # 布局组件 +│ │ ├── ProtectedRoute.tsx # 路由守卫 +│ │ └── ErrorBoundary.tsx # 错误边界 +│ ├── pages/ # 页面组件 +│ │ ├── Dashboard.tsx # 仪表盘 +│ │ ├── Photos.tsx # 照片管理 +│ │ ├── Categories.tsx # 分类管理 +│ │ ├── Users.tsx # 用户管理 +│ │ └── LoginPage.tsx # 登录页 +│ ├── services/ # API 服务 +│ │ ├── api.ts # API 基础配置 +│ │ ├── authService.ts # 认证服务 +│ │ ├── photoService.ts # 照片服务 +│ │ └── categoryService.ts # 分类服务 +│ ├── stores/ # 状态管理 +│ │ └── authStore.ts # 认证状态 +│ ├── types/ # TypeScript 类型 +│ │ └── index.ts # 类型定义 +│ ├── utils/ # 工具函数 +│ ├── lib/ # 库配置 +│ │ └── utils.ts # shadcn utils +│ ├── App.tsx # 应用根组件 +│ └── main.tsx # 应用入口 +├── public/ # 静态资源 +├── index.html # HTML 模板 +├── vite.config.ts # Vite 配置 +└── package.json # 依赖配置 +``` + +## 🎯 开发规范 + +### 应用入口配置 +```tsx +// main.tsx +import React from 'react' +import ReactDOM from 'react-dom/client' +import { BrowserRouter } from 'react-router-dom' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { ReactQueryDevtools } from '@tanstack/react-query-devtools' +import App from './App' +import './index.css' + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 5 * 60 * 1000, // 5分钟 + retry: 2, + }, + }, +}) + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + + + + +) +``` + +### 应用根组件 +```tsx +// App.tsx +import { Routes, Route, Navigate } from 'react-router-dom' +import { Toaster } from '@/components/ui/toaster' +import { ErrorBoundary } from '@/components/ErrorBoundary' +import { ProtectedRoute } from '@/components/ProtectedRoute' +import { DashboardLayout } from '@/components/DashboardLayout' +import { LoginPage } from '@/pages/LoginPage' +import { Dashboard } from '@/pages/Dashboard' +import { Photos } from '@/pages/Photos' +import { PhotoUpload } from '@/pages/PhotoUpload' +import { Categories } from '@/pages/Categories' +import { Users } from '@/pages/Users' +import { Settings } from '@/pages/Settings' + +function App() { + return ( + + + } /> + + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + } + /> + + + + ) +} + +export default App +``` + +## 🧩 核心组件 + +### 布局组件 +```tsx +// components/DashboardLayout.tsx +import { useState } from 'react' +import { Link, useLocation } from 'react-router-dom' +import { + LayoutDashboard, + Image, + FolderOpen, + Users, + Settings, + LogOut, + Menu +} from 'lucide-react' +import { Button } from '@/components/ui/button' +import { useAuthStore } from '@/stores/authStore' + +const navigation = [ + { name: '仪表盘', href: '/dashboard', icon: LayoutDashboard }, + { name: '照片管理', href: '/photos', icon: Image }, + { name: '分类管理', href: '/categories', icon: FolderOpen }, + { name: '用户管理', href: '/users', icon: Users }, + { name: '系统设置', href: '/settings', icon: Settings }, +] + +interface DashboardLayoutProps { + children: React.ReactNode +} + +export function DashboardLayout({ children }: DashboardLayoutProps) { + const [sidebarOpen, setSidebarOpen] = useState(false) + const location = useLocation() + const { user, logout } = useAuthStore() + + return ( +
+ {/* 侧边栏 */} +
+
+

+ 摄影管理 +

+ +
+ + + +
+ +
+
+ + {/* 主内容区 */} +
+ {/* 顶部导航 */} +
+
+

+ {navigation.find(item => item.href === location.pathname)?.name || '管理后台'} +

+
+ + 欢迎, {user?.username} + +
+
+
+ + {/* 页面内容 */} +
+ {children} +
+
+
+ ) +} +``` + +### 路由守卫 +```tsx +// components/ProtectedRoute.tsx +import { Navigate } from 'react-router-dom' +import { useAuthStore } from '@/stores/authStore' +import { Loading } from '@/components/Loading' + +interface ProtectedRouteProps { + children: React.ReactNode +} + +export function ProtectedRoute({ children }: ProtectedRouteProps) { + const { token, isLoading } = useAuthStore() + + if (isLoading) { + return + } + + if (!token) { + return + } + + return <>{children} +} +``` + +## 🎛️ 状态管理 (Zustand) + +### 认证状态管理 +```typescript +// stores/authStore.ts +import { create } from 'zustand' +import { persist } from 'zustand/middleware' +import { authService } from '@/services/authService' + +interface User { + id: string + username: string + email: string + role: string +} + +interface AuthState { + user: User | null + token: string | null + isLoading: boolean + + // Actions + login: (credentials: LoginCredentials) => Promise + logout: () => void + checkAuth: () => Promise +} + +export const useAuthStore = create()( + persist( + (set, get) => ({ + user: null, + token: null, + isLoading: false, + + login: async (credentials) => { + set({ isLoading: true }) + try { + const response = await authService.login(credentials) + set({ + user: response.user, + token: response.token, + isLoading: false, + }) + } catch (error) { + set({ isLoading: false }) + throw error + } + }, + + logout: () => { + set({ user: null, token: null }) + // 清除持久化数据 + localStorage.removeItem('auth-storage') + }, + + checkAuth: async () => { + const { token } = get() + if (!token) return + + try { + const user = await authService.getCurrentUser() + set({ user }) + } catch (error) { + // Token 无效,清除状态 + get().logout() + } + }, + }), + { + name: 'auth-storage', + partialize: (state) => ({ + token: state.token, + user: state.user + }), + } + ) +) +``` + +## 🌐 API 服务层 + +### API 基础配置 +```typescript +// services/api.ts +import axios, { AxiosError } from 'axios' +import { useAuthStore } from '@/stores/authStore' + +export const api = axios.create({ + baseURL: import.meta.env.VITE_API_URL || 'http://localhost:8888/api/v1', + timeout: 10000, +}) + +// 请求拦截器 +api.interceptors.request.use((config) => { + const token = useAuthStore.getState().token + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config +}) + +// 响应拦截器 +api.interceptors.response.use( + (response) => response, + (error: AxiosError) => { + if (error.response?.status === 401) { + useAuthStore.getState().logout() + window.location.href = '/login' + } + return Promise.reject(error) + } +) + +// API 响应类型 +export interface ApiResponse { + code: number + msg: string + data: T +} +``` + +### 照片服务 +```typescript +// services/photoService.ts +import { api, ApiResponse } from './api' + +export interface Photo { + id: string + title: string + description?: string + filename: string + thumbnail: string + category_id?: string + user_id: string + created_at: string + updated_at: string +} + +export interface PhotoListResponse { + photos: Photo[] + total: number + page: number + limit: number +} + +export const photoService = { + // 获取照片列表 + getPhotos: async (params: { + page?: number + limit?: number + category_id?: string + keyword?: string + }): Promise => { + const { data } = await api.get>('/photos', { + params, + }) + return data.data + }, + + // 上传照片 + uploadPhoto: async (formData: FormData): Promise => { + const { data } = await api.post>('/photos', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }) + return data.data + }, + + // 更新照片 + updatePhoto: async (id: string, updates: Partial): Promise => { + await api.put(`/photos/${id}`, updates) + }, + + // 删除照片 + deletePhoto: async (id: string): Promise => { + await api.delete(`/photos/${id}`) + }, + + // 获取照片详情 + getPhoto: async (id: string): Promise => { + const { data } = await api.get>(`/photos/${id}`) + return data.data + }, +} +``` + +## 📱 页面组件 + +### 照片管理页面 +```tsx +// pages/Photos.tsx +import { useState } from 'react' +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' +import { Plus, Edit, Trash2 } from 'lucide-react' +import { Button } from '@/components/ui/button' +import { Card, CardContent } from '@/components/ui/card' +import { Badge } from '@/components/ui/badge' +import { useToast } from '@/hooks/use-toast' +import { photoService } from '@/services/photoService' + +export function Photos() { + const [page, setPage] = useState(1) + const { toast } = useToast() + const queryClient = useQueryClient() + + // 获取照片列表 + const { data, isLoading, error } = useQuery({ + queryKey: ['photos', page], + queryFn: () => photoService.getPhotos({ page, limit: 12 }), + }) + + // 删除照片 + const deleteMutation = useMutation({ + mutationFn: photoService.deletePhoto, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['photos'] }) + toast({ + title: '成功', + description: '照片已删除', + }) + }, + onError: (error) => { + toast({ + title: '错误', + description: '删除失败', + variant: 'destructive', + }) + }, + }) + + const handleDelete = (id: string) => { + if (confirm('确认删除这张照片吗?')) { + deleteMutation.mutate(id) + } + } + + if (isLoading) return
加载中...
+ if (error) return
加载失败
+ + return ( +
+
+

照片管理

+ +
+ +
+ {data?.photos.map((photo) => ( + + +
+ {photo.title} +
+
+ + +
+
+
+
+

{photo.title}

+ {photo.description && ( +

+ {photo.description} +

+ )} +
+ + {photo.category_id ? '已分类' : '未分类'} + + + {new Date(photo.created_at).toLocaleDateString()} + +
+
+
+
+ ))} +
+ + {/* 分页 */} + {data && data.total > 12 && ( +
+ + +
+ )} +
+ ) +} +``` + +## 🔧 开发工具配置 + +### Vite 配置 +```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'), + }, + }, + server: { + port: 5173, + proxy: { + '/api': { + target: 'http://localhost:8888', + changeOrigin: true, + }, + }, + }, + build: { + outDir: 'dist', + sourcemap: true, + }, +}) +``` + +### 环境变量 +```bash +# .env.local +VITE_API_URL=http://localhost:8888/api/v1 +VITE_APP_TITLE=摄影作品管理后台 +``` + +参考 [TASK_PROGRESS.md](mdc:TASK_PROGRESS.md) 了解管理后台开发进度。 +description: +globs: +alwaysApply: false +--- diff --git a/.cursor/rules/backend/api-development.mdc b/.cursor/rules/backend/api-development.mdc new file mode 100644 index 0000000..fab3808 --- /dev/null +++ b/.cursor/rules/backend/api-development.mdc @@ -0,0 +1,750 @@ +--- +globs: backend/api/**/*.api +alwaysApply: false +--- +# 后端 API 开发规则 + +## 🔄 API 开发工作流 + +### 1. 需求分析 +- 明确API功能和参数 +- 确定请求/响应格式 +- 考虑错误处理场景 + +### 2. API 定义 (.api文件) +在 `backend/api/desc/` 目录下定义: + +```api +// photo.api +syntax = "v1" + +info( + title: "Photography Photo API" + desc: "照片管理相关API" + author: "iriver" + email: "iriver@example.com" + version: "v1" +) + +import "common.api" + +type ( + UploadPhotoRequest { + Title string `form:"title"` + Description string `form:"description,optional"` + CategoryId string `form:"category_id,optional"` + File string `form:"file"` + } + + UploadPhotoResponse { + Id string `json:"id"` + Title string `json:"title"` + Filename string `json:"filename"` + Thumbnail string `json:"thumbnail"` + CreatedAt string `json:"created_at"` + } + + UpdatePhotoRequest { + Id string `path:"id"` + Title string `json:"title,optional"` + Description string `json:"description,optional"` + CategoryId string `json:"category_id,optional"` + } +) + +@server( + group: photo + prefix: /api/v1 +) +service photography-api { + @doc "上传照片" + @handler uploadPhoto + post /photos (UploadPhotoRequest) returns (UploadPhotoResponse) + + @doc "更新照片信息" + @handler updatePhoto + put /photos/:id (UpdatePhotoRequest) returns (BaseResponse) + + @doc "删除照片" + @handler deletePhoto + delete /photos/:id (IdPathRequest) returns (BaseResponse) + + @doc "获取照片详情" + @handler getPhoto + get /photos/:id (IdPathRequest) returns (PhotoResponse) + + @doc "获取照片列表" + @handler getPhotoList + get /photos (PhotoListRequest) returns (PhotoListResponse) +} +``` + +### 3. 代码生成 +```bash +cd backend +make api +``` + +### 4. Handler 实现模式 +```go +func (h *UploadPhotoHandler) UploadPhoto(w http.ResponseWriter, r *http.Request) { + var req types.UploadPhotoRequest + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := photo.NewUploadPhotoLogic(r.Context(), h.svcCtx) + resp, err := l.UploadPhoto(&req, r) + response.Response(w, resp, err) +} +``` + +### 5. Logic 实现模式 +```go +func (l *UploadPhotoLogic) UploadPhoto(req *types.UploadPhotoRequest, r *http.Request) (resp *types.UploadPhotoResponse, err error) { + // 1. 参数验证 + if err = l.validateRequest(req); err != nil { + return nil, err + } + + // 2. 获取上传文件 + fileHeader, err := l.getUploadFile(r) + if err != nil { + return nil, err + } + + // 3. 文件处理 + savedPath, thumbnail, err := l.processFile(fileHeader) + if err != nil { + return nil, err + } + + // 4. 数据持久化 + photo, err := l.savePhoto(req, savedPath, thumbnail) + if err != nil { + return nil, err + } + + // 5. 构造响应 + return l.buildResponse(photo), nil +} +``` + +## 📋 API 接口规范 + +### 请求规范 +```go +// 路径参数 +type IdPathRequest { + Id string `path:"id"` +} + +// 查询参数 +type PhotoListRequest { + Page int `form:"page,default=1"` + Limit int `form:"limit,default=10"` + CategoryId string `form:"category_id,optional"` + Keyword string `form:"keyword,optional"` +} + +// JSON请求体 +type UpdatePhotoRequest { + Title string `json:"title,optional"` + Description string `json:"description,optional"` + CategoryId string `json:"category_id,optional"` +} + +// 文件上传 (multipart/form-data) +type UploadPhotoRequest { + Title string `form:"title"` + File string `form:"file"` +} +``` + +### 响应规范 +```go +// 基础响应 +type BaseResponse { + Code int `json:"code"` + Msg string `json:"msg"` + Data any `json:"data"` +} + +// 单个资源响应 +type PhotoResponse { + BaseResponse + Data Photo `json:"data"` +} + +// 列表响应 +type PhotoListResponse { + BaseResponse + Data PhotoListData `json:"data"` +} + +type PhotoListData { + List []Photo `json:"list"` + Total int `json:"total"` + Page int `json:"page"` + Limit int `json:"limit"` +} +``` + +## 🧪 API 测试策略 + +### 1. 手动测试 (curl) +```bash +# 健康检查 +curl -X GET "http://localhost:8888/api/v1/health" + +# 用户登录 +curl -X POST "http://localhost:8888/api/v1/auth/login" \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"123456"}' + +# 上传照片 +curl -X POST "http://localhost:8888/api/v1/photos" \ + -H "Authorization: Bearer $TOKEN" \ + -F "title=测试照片" \ + -F "description=这是一张测试照片" \ + -F "file=@test.jpg" + +# 获取照片列表 +curl -X GET "http://localhost:8888/api/v1/photos?page=1&limit=10" \ + -H "Authorization: Bearer $TOKEN" + +# 更新照片 +curl -X PUT "http://localhost:8888/api/v1/photos/123" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"title":"更新后的标题"}' + +# 删除照片 +curl -X DELETE "http://localhost:8888/api/v1/photos/123" \ + -H "Authorization: Bearer $TOKEN" +``` + +### 2. HTTP文件测试 +创建 `test_api.http` 文件: +```http +### 登录获取token +POST http://localhost:8888/api/v1/auth/login +Content-Type: application/json + +{ + "username": "admin", + "password": "123456" +} + +### 设置变量 +@token = {{login.response.body.data.token}} + +### 上传照片 +POST http://localhost:8888/api/v1/photos +Authorization: Bearer {{token}} +Content-Type: multipart/form-data; boundary=boundary + +--boundary +Content-Disposition: form-data; name="title" + +测试照片标题 +--boundary +Content-Disposition: form-data; name="file"; filename="test.jpg" +Content-Type: image/jpeg + +< ./test.jpg +--boundary-- + +### 获取照片列表 +GET http://localhost:8888/api/v1/photos?page=1&limit=5 +Authorization: Bearer {{token}} +``` + +## 🛡️ 安全和验证 + +### 认证验证 +```go +// 获取当前用户ID +func (l *UploadPhotoLogic) getCurrentUserID() (string, error) { + userID := l.ctx.Value("userID") + if userID == nil { + return "", errors.New("用户未认证") + } + return userID.(string), nil +} +``` + +### 参数验证 +```go +func (l *UploadPhotoLogic) validateRequest(req *types.UploadPhotoRequest) error { + if req.Title == "" { + return errorx.NewDefaultError("照片标题不能为空") + } + + if len(req.Title) > 100 { + return errorx.NewDefaultError("照片标题不能超过100个字符") + } + + return nil +} +``` + +### 文件验证 +```go +func (l *UploadPhotoLogic) validateFile(fileHeader *multipart.FileHeader) error { + // 文件大小验证 + if fileHeader.Size > file.MaxFileSize { + return errorx.NewDefaultError("文件大小不能超过10MB") + } + + // 文件类型验证 + if !file.IsImageFile(fileHeader.Filename) { + return errorx.NewDefaultError("只支持图片文件") + } + + return nil +} +``` + +## 📊 错误处理 + +### 标准错误响应 +```go +// 参数错误 +return nil, errorx.NewCodeError(400, "参数错误") + +// 认证错误 +return nil, errorx.NewCodeError(401, "未认证") + +// 权限错误 +return nil, errorx.NewCodeError(403, "权限不足") + +// 资源不存在 +return nil, errorx.NewCodeError(404, "照片不存在") + +// 服务器错误 +return nil, errorx.NewCodeError(500, "服务器内部错误") +``` + +### 业务错误处理 +```go +func (l *UploadPhotoLogic) handleBusinessError(err error) error { + switch { + case errors.Is(err, sql.ErrNoRows): + return errorx.NewCodeError(404, "资源不存在") + case strings.Contains(err.Error(), "duplicate"): + return errorx.NewCodeError(409, "资源已存在") + default: + logx.Errorf("业务处理失败: %v", err) + return errorx.NewCodeError(500, "处理失败") + } +} +``` + +## 🔧 开发工具 + +### Makefile 命令 +```makefile +# 生成API代码 +api: + goctl api go -api api/desc/photography.api -dir ./ + +# 启动服务 +run: + go run cmd/api/main.go + +# 构建 +build: + go build -o bin/photography-api cmd/api/main.go + +# 测试 +test: + go test ./... +``` + +### 调试技巧 +```go +// 添加调试日志 +logx.Infof("处理上传照片请求: %+v", req) +logx.Errorf("文件保存失败: %v", err) + +// 检查请求context +userID := l.ctx.Value("userID") +logx.Infof("当前用户: %v", userID) +``` + +当前API开发状态参考 [TASK_PROGRESS.md](mdc:TASK_PROGRESS.md)。 +# 后端 API 开发规则 + +## 🔄 API 开发工作流 + +### 1. 需求分析 +- 明确API功能和参数 +- 确定请求/响应格式 +- 考虑错误处理场景 + +### 2. API 定义 (.api文件) +在 `backend/api/desc/` 目录下定义: + +```api +// photo.api +syntax = "v1" + +info( + title: "Photography Photo API" + desc: "照片管理相关API" + author: "iriver" + email: "iriver@example.com" + version: "v1" +) + +import "common.api" + +type ( + UploadPhotoRequest { + Title string `form:"title"` + Description string `form:"description,optional"` + CategoryId string `form:"category_id,optional"` + File string `form:"file"` + } + + UploadPhotoResponse { + Id string `json:"id"` + Title string `json:"title"` + Filename string `json:"filename"` + Thumbnail string `json:"thumbnail"` + CreatedAt string `json:"created_at"` + } + + UpdatePhotoRequest { + Id string `path:"id"` + Title string `json:"title,optional"` + Description string `json:"description,optional"` + CategoryId string `json:"category_id,optional"` + } +) + +@server( + group: photo + prefix: /api/v1 +) +service photography-api { + @doc "上传照片" + @handler uploadPhoto + post /photos (UploadPhotoRequest) returns (UploadPhotoResponse) + + @doc "更新照片信息" + @handler updatePhoto + put /photos/:id (UpdatePhotoRequest) returns (BaseResponse) + + @doc "删除照片" + @handler deletePhoto + delete /photos/:id (IdPathRequest) returns (BaseResponse) + + @doc "获取照片详情" + @handler getPhoto + get /photos/:id (IdPathRequest) returns (PhotoResponse) + + @doc "获取照片列表" + @handler getPhotoList + get /photos (PhotoListRequest) returns (PhotoListResponse) +} +``` + +### 3. 代码生成 +```bash +cd backend +make api +``` + +### 4. Handler 实现模式 +```go +func (h *UploadPhotoHandler) UploadPhoto(w http.ResponseWriter, r *http.Request) { + var req types.UploadPhotoRequest + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := photo.NewUploadPhotoLogic(r.Context(), h.svcCtx) + resp, err := l.UploadPhoto(&req, r) + response.Response(w, resp, err) +} +``` + +### 5. Logic 实现模式 +```go +func (l *UploadPhotoLogic) UploadPhoto(req *types.UploadPhotoRequest, r *http.Request) (resp *types.UploadPhotoResponse, err error) { + // 1. 参数验证 + if err = l.validateRequest(req); err != nil { + return nil, err + } + + // 2. 获取上传文件 + fileHeader, err := l.getUploadFile(r) + if err != nil { + return nil, err + } + + // 3. 文件处理 + savedPath, thumbnail, err := l.processFile(fileHeader) + if err != nil { + return nil, err + } + + // 4. 数据持久化 + photo, err := l.savePhoto(req, savedPath, thumbnail) + if err != nil { + return nil, err + } + + // 5. 构造响应 + return l.buildResponse(photo), nil +} +``` + +## 📋 API 接口规范 + +### 请求规范 +```go +// 路径参数 +type IdPathRequest { + Id string `path:"id"` +} + +// 查询参数 +type PhotoListRequest { + Page int `form:"page,default=1"` + Limit int `form:"limit,default=10"` + CategoryId string `form:"category_id,optional"` + Keyword string `form:"keyword,optional"` +} + +// JSON请求体 +type UpdatePhotoRequest { + Title string `json:"title,optional"` + Description string `json:"description,optional"` + CategoryId string `json:"category_id,optional"` +} + +// 文件上传 (multipart/form-data) +type UploadPhotoRequest { + Title string `form:"title"` + File string `form:"file"` +} +``` + +### 响应规范 +```go +// 基础响应 +type BaseResponse { + Code int `json:"code"` + Msg string `json:"msg"` + Data any `json:"data"` +} + +// 单个资源响应 +type PhotoResponse { + BaseResponse + Data Photo `json:"data"` +} + +// 列表响应 +type PhotoListResponse { + BaseResponse + Data PhotoListData `json:"data"` +} + +type PhotoListData { + List []Photo `json:"list"` + Total int `json:"total"` + Page int `json:"page"` + Limit int `json:"limit"` +} +``` + +## 🧪 API 测试策略 + +### 1. 手动测试 (curl) +```bash +# 健康检查 +curl -X GET "http://localhost:8888/api/v1/health" + +# 用户登录 +curl -X POST "http://localhost:8888/api/v1/auth/login" \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"123456"}' + +# 上传照片 +curl -X POST "http://localhost:8888/api/v1/photos" \ + -H "Authorization: Bearer $TOKEN" \ + -F "title=测试照片" \ + -F "description=这是一张测试照片" \ + -F "file=@test.jpg" + +# 获取照片列表 +curl -X GET "http://localhost:8888/api/v1/photos?page=1&limit=10" \ + -H "Authorization: Bearer $TOKEN" + +# 更新照片 +curl -X PUT "http://localhost:8888/api/v1/photos/123" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"title":"更新后的标题"}' + +# 删除照片 +curl -X DELETE "http://localhost:8888/api/v1/photos/123" \ + -H "Authorization: Bearer $TOKEN" +``` + +### 2. HTTP文件测试 +创建 `test_api.http` 文件: +```http +### 登录获取token +POST http://localhost:8888/api/v1/auth/login +Content-Type: application/json + +{ + "username": "admin", + "password": "123456" +} + +### 设置变量 +@token = {{login.response.body.data.token}} + +### 上传照片 +POST http://localhost:8888/api/v1/photos +Authorization: Bearer {{token}} +Content-Type: multipart/form-data; boundary=boundary + +--boundary +Content-Disposition: form-data; name="title" + +测试照片标题 +--boundary +Content-Disposition: form-data; name="file"; filename="test.jpg" +Content-Type: image/jpeg + +< ./test.jpg +--boundary-- + +### 获取照片列表 +GET http://localhost:8888/api/v1/photos?page=1&limit=5 +Authorization: Bearer {{token}} +``` + +## 🛡️ 安全和验证 + +### 认证验证 +```go +// 获取当前用户ID +func (l *UploadPhotoLogic) getCurrentUserID() (string, error) { + userID := l.ctx.Value("userID") + if userID == nil { + return "", errors.New("用户未认证") + } + return userID.(string), nil +} +``` + +### 参数验证 +```go +func (l *UploadPhotoLogic) validateRequest(req *types.UploadPhotoRequest) error { + if req.Title == "" { + return errorx.NewDefaultError("照片标题不能为空") + } + + if len(req.Title) > 100 { + return errorx.NewDefaultError("照片标题不能超过100个字符") + } + + return nil +} +``` + +### 文件验证 +```go +func (l *UploadPhotoLogic) validateFile(fileHeader *multipart.FileHeader) error { + // 文件大小验证 + if fileHeader.Size > file.MaxFileSize { + return errorx.NewDefaultError("文件大小不能超过10MB") + } + + // 文件类型验证 + if !file.IsImageFile(fileHeader.Filename) { + return errorx.NewDefaultError("只支持图片文件") + } + + return nil +} +``` + +## 📊 错误处理 + +### 标准错误响应 +```go +// 参数错误 +return nil, errorx.NewCodeError(400, "参数错误") + +// 认证错误 +return nil, errorx.NewCodeError(401, "未认证") + +// 权限错误 +return nil, errorx.NewCodeError(403, "权限不足") + +// 资源不存在 +return nil, errorx.NewCodeError(404, "照片不存在") + +// 服务器错误 +return nil, errorx.NewCodeError(500, "服务器内部错误") +``` + +### 业务错误处理 +```go +func (l *UploadPhotoLogic) handleBusinessError(err error) error { + switch { + case errors.Is(err, sql.ErrNoRows): + return errorx.NewCodeError(404, "资源不存在") + case strings.Contains(err.Error(), "duplicate"): + return errorx.NewCodeError(409, "资源已存在") + default: + logx.Errorf("业务处理失败: %v", err) + return errorx.NewCodeError(500, "处理失败") + } +} +``` + +## 🔧 开发工具 + +### Makefile 命令 +```makefile +# 生成API代码 +api: + goctl api go -api api/desc/photography.api -dir ./ + +# 启动服务 +run: + go run cmd/api/main.go + +# 构建 +build: + go build -o bin/photography-api cmd/api/main.go + +# 测试 +test: + go test ./... +``` + +### 调试技巧 +```go +// 添加调试日志 +logx.Infof("处理上传照片请求: %+v", req) +logx.Errorf("文件保存失败: %v", err) + +// 检查请求context +userID := l.ctx.Value("userID") +logx.Infof("当前用户: %v", userID) +``` + +当前API开发状态参考 [TASK_PROGRESS.md](mdc:TASK_PROGRESS.md)。 diff --git a/.cursor/rules/backend/go-zero-framework.mdc b/.cursor/rules/backend/go-zero-framework.mdc new file mode 100644 index 0000000..c6395fe --- /dev/null +++ b/.cursor/rules/backend/go-zero-framework.mdc @@ -0,0 +1,492 @@ +--- +globs: backend/*,backend/**/*.go +alwaysApply: false +--- +# Go-Zero 框架开发规则 + +## 🚀 架构规范 + +### 核心概念 +- **Handler**: HTTP请求处理层,只做参数验证和调用Logic +- **Logic**: 业务逻辑层,包含核心业务代码 +- **Model**: 数据访问层,处理数据库操作 +- **Types**: 请求/响应类型定义 + +### 文件结构 +``` +backend/ +├── cmd/api/main.go # 服务启动入口 +├── etc/photography-api.yaml # 配置文件 +├── api/desc/ # API定义文件 +├── internal/ +│ ├── handler/ # HTTP处理器 +│ ├── logic/ # 业务逻辑 +│ ├── model/ # 数据模型 +│ ├── middleware/ # 中间件 +│ ├── svc/ # 服务上下文 +│ └── types/ # 类型定义 +└── pkg/ # 工具包 +``` + +## 🎯 开发流程 + +### 1. API定义 +在 `api/desc/` 目录下定义接口: +```api +service photography-api { + @handler uploadPhoto + post /api/v1/photos (UploadPhotoRequest) returns (UploadPhotoResponse) +} + +type UploadPhotoRequest { + Title string `form:"title"` + Description string `form:"description,optional"` + File string `form:"file"` +} + +type UploadPhotoResponse { + Id string `json:"id"` + Title string `json:"title"` + Filename string `json:"filename"` + Thumbnail string `json:"thumbnail"` +} +``` + +### 2. 代码生成 +```bash +cd backend +make api # 生成handler和logic骨架 +``` + +### 3. Handler实现 +```go +func (h *UploadPhotoHandler) UploadPhoto(w http.ResponseWriter, r *http.Request) { + var req types.UploadPhotoRequest + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := logic.NewUploadPhotoLogic(r.Context(), h.svcCtx) + resp, err := l.UploadPhoto(&req) + response.Response(w, resp, err) +} +``` + +### 4. Logic实现 +```go +func (l *UploadPhotoLogic) UploadPhoto(req *types.UploadPhotoRequest) (*types.UploadPhotoResponse, error) { + // 1. 参数验证 + if req.Title == "" { + return nil, errorx.NewDefaultError("照片标题不能为空") + } + + // 2. 业务逻辑 + photoID := uuid.New().String() + + // 3. 数据持久化 + photo := &model.Photo{ + ID: photoID, + Title: req.Title, + // ... + } + + err := l.svcCtx.PhotoModel.Insert(l.ctx, photo) + if err != nil { + return nil, err + } + + return &types.UploadPhotoResponse{ + Id: photoID, + Title: req.Title, + // ... + }, nil +} +``` + +## 🔧 工具包使用 + +### 文件处理 +使用 [pkg/utils/file/file.go](mdc:backend/pkg/utils/file/file.go): +```go +import "photography/pkg/utils/file" + +// 验证图片文件 +if !file.IsImageFile(filename) { + return errors.New("不支持的文件类型") +} + +// 保存文件并生成缩略图 +savedPath, thumbnail, err := file.SaveImage(fileData, filename) +``` + +### JWT认证 +使用 [pkg/utils/jwt/jwt.go](mdc:backend/pkg/utils/jwt/jwt.go): +```go +import "photography/pkg/utils/jwt" + +// 生成token +token, err := jwt.GenerateToken(userID, username) + +// 验证token +userID, err := jwt.ParseToken(tokenString) +``` + +### 错误处理 +使用 [pkg/errorx/errorx.go](mdc:backend/pkg/errorx/errorx.go): +```go +import "photography/pkg/errorx" + +// 业务错误 +return nil, errorx.NewDefaultError("用户不存在") + +// 自定义错误码 +return nil, errorx.NewCodeError(40001, "参数错误") +``` + +## 🛡️ 中间件 + +### JWT认证中间件 +在 `internal/middleware/auth.go` 中实现: +```go +func (m *AuthMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + token := r.Header.Get("Authorization") + if token == "" { + httpx.Error(w, errors.New("未提供认证token")) + return + } + + userID, err := jwt.ParseToken(strings.TrimPrefix(token, "Bearer ")) + if err != nil { + httpx.Error(w, errors.New("token无效")) + return + } + + // 将用户ID注入到context + ctx := context.WithValue(r.Context(), "userID", userID) + next(w, r.WithContext(ctx)) + }) +} +``` + +## 📊 数据模型 + +### 模型定义示例 +```go +type Photo struct { + ID string `db:"id" json:"id"` + Title string `db:"title" json:"title"` + Description string `db:"description" json:"description"` + Filename string `db:"filename" json:"filename"` + Thumbnail string `db:"thumbnail" json:"thumbnail"` + CategoryID string `db:"category_id" json:"category_id"` + UserID string `db:"user_id" json:"user_id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` +} +``` + +### 数据库操作 +```go +// 插入 +err := l.svcCtx.PhotoModel.Insert(l.ctx, photo) + +// 查询 +photo, err := l.svcCtx.PhotoModel.FindOne(l.ctx, photoID) + +// 更新 +err := l.svcCtx.PhotoModel.Update(l.ctx, photo) + +// 删除 +err := l.svcCtx.PhotoModel.Delete(l.ctx, photoID) +``` + +## 🔄 配置管理 + +### 配置文件: `etc/photography-api.yaml` +```yaml +Name: photography-api +Host: 0.0.0.0 +Port: 8888 + +Auth: + AccessSecret: your-secret-key + AccessExpire: 86400 + +DataSource: photography.db + +Log: + ServiceName: photography-api + Mode: file + Path: logs + Level: info +``` + +## 🧪 测试规范 + +### 单元测试 +```go +func TestUploadPhotoLogic(t *testing.T) { + // 准备测试数据 + req := &types.UploadPhotoRequest{ + Title: "测试照片", + File: "test.jpg", + } + + // 执行测试 + logic := NewUploadPhotoLogic(context.Background(), svcCtx) + resp, err := logic.UploadPhoto(req) + + // 断言结果 + assert.NoError(t, err) + assert.NotEmpty(t, resp.Id) + assert.Equal(t, "测试照片", resp.Title) +} +``` + +参考 [TASK_PROGRESS.md](mdc:TASK_PROGRESS.md) 了解当前后端开发进度。 +# Go-Zero 框架开发规则 + +## 🚀 架构规范 + +### 核心概念 +- **Handler**: HTTP请求处理层,只做参数验证和调用Logic +- **Logic**: 业务逻辑层,包含核心业务代码 +- **Model**: 数据访问层,处理数据库操作 +- **Types**: 请求/响应类型定义 + +### 文件结构 +``` +backend/ +├── cmd/api/main.go # 服务启动入口 +├── etc/photography-api.yaml # 配置文件 +├── api/desc/ # API定义文件 +├── internal/ +│ ├── handler/ # HTTP处理器 +│ ├── logic/ # 业务逻辑 +│ ├── model/ # 数据模型 +│ ├── middleware/ # 中间件 +│ ├── svc/ # 服务上下文 +│ └── types/ # 类型定义 +└── pkg/ # 工具包 +``` + +## 🎯 开发流程 + +### 1. API定义 +在 `api/desc/` 目录下定义接口: +```api +service photography-api { + @handler uploadPhoto + post /api/v1/photos (UploadPhotoRequest) returns (UploadPhotoResponse) +} + +type UploadPhotoRequest { + Title string `form:"title"` + Description string `form:"description,optional"` + File string `form:"file"` +} + +type UploadPhotoResponse { + Id string `json:"id"` + Title string `json:"title"` + Filename string `json:"filename"` + Thumbnail string `json:"thumbnail"` +} +``` + +### 2. 代码生成 +```bash +cd backend +make api # 生成handler和logic骨架 +``` + +### 3. Handler实现 +```go +func (h *UploadPhotoHandler) UploadPhoto(w http.ResponseWriter, r *http.Request) { + var req types.UploadPhotoRequest + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := logic.NewUploadPhotoLogic(r.Context(), h.svcCtx) + resp, err := l.UploadPhoto(&req) + response.Response(w, resp, err) +} +``` + +### 4. Logic实现 +```go +func (l *UploadPhotoLogic) UploadPhoto(req *types.UploadPhotoRequest) (*types.UploadPhotoResponse, error) { + // 1. 参数验证 + if req.Title == "" { + return nil, errorx.NewDefaultError("照片标题不能为空") + } + + // 2. 业务逻辑 + photoID := uuid.New().String() + + // 3. 数据持久化 + photo := &model.Photo{ + ID: photoID, + Title: req.Title, + // ... + } + + err := l.svcCtx.PhotoModel.Insert(l.ctx, photo) + if err != nil { + return nil, err + } + + return &types.UploadPhotoResponse{ + Id: photoID, + Title: req.Title, + // ... + }, nil +} +``` + +## 🔧 工具包使用 + +### 文件处理 +使用 [pkg/utils/file/file.go](mdc:backend/pkg/utils/file/file.go): +```go +import "photography/pkg/utils/file" + +// 验证图片文件 +if !file.IsImageFile(filename) { + return errors.New("不支持的文件类型") +} + +// 保存文件并生成缩略图 +savedPath, thumbnail, err := file.SaveImage(fileData, filename) +``` + +### JWT认证 +使用 [pkg/utils/jwt/jwt.go](mdc:backend/pkg/utils/jwt/jwt.go): +```go +import "photography/pkg/utils/jwt" + +// 生成token +token, err := jwt.GenerateToken(userID, username) + +// 验证token +userID, err := jwt.ParseToken(tokenString) +``` + +### 错误处理 +使用 [pkg/errorx/errorx.go](mdc:backend/pkg/errorx/errorx.go): +```go +import "photography/pkg/errorx" + +// 业务错误 +return nil, errorx.NewDefaultError("用户不存在") + +// 自定义错误码 +return nil, errorx.NewCodeError(40001, "参数错误") +``` + +## 🛡️ 中间件 + +### JWT认证中间件 +在 `internal/middleware/auth.go` 中实现: +```go +func (m *AuthMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + token := r.Header.Get("Authorization") + if token == "" { + httpx.Error(w, errors.New("未提供认证token")) + return + } + + userID, err := jwt.ParseToken(strings.TrimPrefix(token, "Bearer ")) + if err != nil { + httpx.Error(w, errors.New("token无效")) + return + } + + // 将用户ID注入到context + ctx := context.WithValue(r.Context(), "userID", userID) + next(w, r.WithContext(ctx)) + }) +} +``` + +## 📊 数据模型 + +### 模型定义示例 +```go +type Photo struct { + ID string `db:"id" json:"id"` + Title string `db:"title" json:"title"` + Description string `db:"description" json:"description"` + Filename string `db:"filename" json:"filename"` + Thumbnail string `db:"thumbnail" json:"thumbnail"` + CategoryID string `db:"category_id" json:"category_id"` + UserID string `db:"user_id" json:"user_id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` +} +``` + +### 数据库操作 +```go +// 插入 +err := l.svcCtx.PhotoModel.Insert(l.ctx, photo) + +// 查询 +photo, err := l.svcCtx.PhotoModel.FindOne(l.ctx, photoID) + +// 更新 +err := l.svcCtx.PhotoModel.Update(l.ctx, photo) + +// 删除 +err := l.svcCtx.PhotoModel.Delete(l.ctx, photoID) +``` + +## 🔄 配置管理 + +### 配置文件: `etc/photography-api.yaml` +```yaml +Name: photography-api +Host: 0.0.0.0 +Port: 8888 + +Auth: + AccessSecret: your-secret-key + AccessExpire: 86400 + +DataSource: photography.db + +Log: + ServiceName: photography-api + Mode: file + Path: logs + Level: info +``` + +## 🧪 测试规范 + +### 单元测试 +```go +func TestUploadPhotoLogic(t *testing.T) { + // 准备测试数据 + req := &types.UploadPhotoRequest{ + Title: "测试照片", + File: "test.jpg", + } + + // 执行测试 + logic := NewUploadPhotoLogic(context.Background(), svcCtx) + resp, err := logic.UploadPhoto(req) + + // 断言结果 + assert.NoError(t, err) + assert.NotEmpty(t, resp.Id) + assert.Equal(t, "测试照片", resp.Title) +} +``` + +参考 [TASK_PROGRESS.md](mdc:TASK_PROGRESS.md) 了解当前后端开发进度。 diff --git a/.cursor/rules/common/code-style.mdc b/.cursor/rules/common/code-style.mdc new file mode 100644 index 0000000..bf38f4f --- /dev/null +++ b/.cursor/rules/common/code-style.mdc @@ -0,0 +1,262 @@ +--- +description: Code style and naming conventions +--- + +# 代码风格和约定规则 + +## 📝 通用代码风格 + +### 文件命名 +- **Go文件**: `camelCase.go` (例: `uploadPhotoHandler.go`) +- **TypeScript**: `kebab-case.tsx` 或 `PascalCase.tsx` (组件) +- **API文件**: `kebab-case.api` (例: `photo.api`) +- **配置文件**: `kebab-case.yaml/.json` + +### 注释规范 +```go +// ✅ Go - 函数注释 +// UploadPhoto 上传照片到服务器 +// 支持JPEG、PNG、GIF、WebP格式,最大10MB +func (l *UploadPhotoLogic) UploadPhoto(req *types.UploadPhotoRequest) (*types.UploadPhotoResponse, error) { + // 实现逻辑 +} +``` + +```typescript +// ✅ TypeScript - 接口注释 +/** + * 照片数据接口 + * @interface Photo + */ +interface Photo { + /** 照片唯一标识符 */ + id: string + /** 照片标题 */ + title: string + /** 文件名 */ + filename: string +} +``` + +## 🎯 命名约定 + +### 变量命名 +```go +// ✅ Go - 驼峰命名 +var photoID string +var userList []User +var maxFileSize int64 = 10 * 1024 * 1024 // 10MB + +// ❌ 避免 +var photo_id string +var PhotoId string +``` + +```typescript +// ✅ TypeScript - 驼峰命名 +const photoList: Photo[] = [] +const isLoading = false +const handlePhotoUpload = () => {} + +// ✅ 常量 - 大写下划线 +const MAX_FILE_SIZE = 10 * 1024 * 1024 +const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL +``` + +### 函数命名 +- **动词开头**: `getPhotoList`, `uploadPhoto`, `deleteCategory` +- **布尔值**: `isVisible`, `hasPermission`, `canEdit` +- **事件处理**: `handleClick`, `onPhotoSelect`, `onUploadSuccess` + +## 🛡️ 错误处理 + +### Go 错误处理 +```go +// ✅ 标准错误处理 +func (l *UploadPhotoLogic) UploadPhoto(req *types.UploadPhotoRequest) (*types.UploadPhotoResponse, error) { + if !file.IsImageFile(req.File) { + return nil, errorx.NewDefaultError("不支持的文件类型") + } + + photoID, err := l.savePhoto(req) + if err != nil { + logx.Errorf("保存照片失败: %v", err) + return nil, errorx.NewDefaultError("照片保存失败") + } + + return &types.UploadPhotoResponse{ + Id: photoID, + // ... + }, nil +} +``` + +### TypeScript 错误处理 +```typescript +// ✅ 异步操作错误处理 +try { + const response = await api.post('/photos', formData) + return response.data +} catch (error) { + if (axios.isAxiosError(error)) { + const message = error.response?.data?.msg || '上传失败' + throw new Error(message) + } + throw error +} +``` + +## 📦 导入组织 + +### Go 导入顺序 +```go +import ( + // 标准库 + "context" + "fmt" + "net/http" + + // 第三方库 + "github.com/zeromicro/go-zero/rest/httpx" + "github.com/zeromicro/go-zero/core/logx" + + // 本地包 + "photography/internal/logic/photo" + "photography/internal/svc" + "photography/internal/types" +) +``` + +### TypeScript 导入顺序 +```typescript +// React 相关 +import React, { useState, useEffect } from 'react' +import { useRouter } from 'next/router' + +// 第三方库 +import axios from 'axios' +import { useQuery } from '@tanstack/react-query' + +// UI 组件 +import { Button } from '@/components/ui/button' +import { Card } from '@/components/ui/card' + +// 本地模块 +import { api } from '@/lib/api' +import { Photo } from '@/types/api' +import { useAuthStore } from '@/stores/authStore' +``` + +## 🎨 CSS/样式约定 + +### Tailwind CSS 类名顺序 +```tsx +// ✅ 推荐顺序:布局 → 尺寸 → 间距 → 颜色 → 其他 +
+ {photo.title} +
+``` + +### 响应式设计 +```tsx +// ✅ 移动优先响应式 +
+ {photos.map(photo => ( + + ))} +
+``` + +## 🔧 类型定义 + +### TypeScript 接口规范 +```typescript +// ✅ 明确的接口定义 +interface PhotoCardProps { + photo: Photo + onEdit?: (id: string) => void + onDelete?: (id: string) => void + className?: string +} + +// ✅ API 响应类型 +interface ApiResponse { + code: number + msg: string + data: T +} + +type PhotoListResponse = ApiResponse<{ + photos: Photo[] + total: number + page: number + limit: number +}> +``` + +### Go 结构体规范 +```go +// ✅ 结构体标签完整 +type Photo struct { + ID string `json:"id" db:"id"` + Title string `json:"title" db:"title"` + Description string `json:"description" db:"description"` + Filename string `json:"filename" db:"filename"` + Thumbnail string `json:"thumbnail" db:"thumbnail"` + CategoryID string `json:"category_id" db:"category_id"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` +} +``` + +## 📊 性能约定 + +### 避免性能陷阱 +```typescript +// ✅ 使用 useMemo 避免重复计算 +const expensiveValue = useMemo(() => { + return photos.filter(photo => photo.category_id === selectedCategory) +}, [photos, selectedCategory]) + +// ✅ 使用 useCallback 避免重复渲染 +const handlePhotoSelect = useCallback((id: string) => { + setSelectedPhoto(photos.find(p => p.id === id)) +}, [photos]) +``` + +## 🔒 安全约定 + +### 输入验证 +```go +// ✅ 后端输入验证 +if req.Title == "" { + return nil, errorx.NewDefaultError("照片标题不能为空") +} + +if len(req.Title) > 100 { + return nil, errorx.NewDefaultError("照片标题不能超过100个字符") +} +``` + +```typescript +// ✅ 前端输入验证 +const validatePhoto = (data: PhotoFormData): string[] => { + const errors: string[] = [] + + if (!data.title.trim()) { + errors.push('标题不能为空') + } + + if (data.title.length > 100) { + errors.push('标题不能超过100个字符') + } + + return errors +} +``` + +遵循这些约定可以保持代码的一致性和可维护性。 diff --git a/.cursor/rules/common/development-workflow.mdc b/.cursor/rules/common/development-workflow.mdc new file mode 100644 index 0000000..11172b2 --- /dev/null +++ b/.cursor/rules/common/development-workflow.mdc @@ -0,0 +1,151 @@ +--- +description: General development workflow and best practices +--- + +# 开发工作流规则 + +## 🎯 当前优先级任务 + +基于 [TASK_PROGRESS.md](mdc:TASK_PROGRESS.md),当前高优先级任务: + +### 🔥 立即处理 +1. **完善照片更新和删除业务逻辑** - 后端CRUD完整性 +2. **完善分类更新和删除业务逻辑** - 后端CRUD完整性 +3. **前端与后端API集成测试** - 端到端功能验证 + +### 📋 本周目标 +- 实现完整的照片和分类CRUD操作 +- 前后端API集成调试 +- 用户认证流程实现 + +## 🔄 开发流程规范 + +### Git 工作流 +```bash +# 功能开发 +git checkout -b feature/photo-update-api +git add . +git commit -m "feat: 实现照片更新API" +git push origin feature/photo-update-api + +# 代码审查后合并 +git checkout main +git merge feature/photo-update-api +``` + +### 提交信息规范 +```bash +feat: 新功能 +fix: 修复bug +docs: 文档更新 +style: 代码格式 +refactor: 重构 +test: 测试 +chore: 构建/工具 +``` + +## 🧪 测试策略 + +### 后端测试 +```bash +# API 测试 +curl -X GET http://localhost:8888/api/v1/health +curl -X POST http://localhost:8888/api/v1/auth/login \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"123456"}' + +# 功能测试 +make test +``` + +### 前端测试 +```bash +# 启动开发服务器 +cd admin && bun run dev # 管理后台:5173 +cd frontend && pnpm dev # 用户界面:3000 +cd ui && pnpm dev # 组件库:6006 +``` + +## 🚀 部署流程 + +### 开发环境 +```bash +# 后端 +cd backend +make run # 端口:8888 + +# 前端项目并行启动 +cd admin && bun run dev & +cd frontend && pnpm dev & +``` + +### 生产环境准备 +1. 配置PostgreSQL数据库 +2. 更新CI/CD流程 +3. 配置反向代理 +4. 设置监控和日志 + +## 📁 文件组织原则 + +### 后端文件 +- 新Handler: `internal/handler/{module}/{action}Handler.go` +- 新Logic: `internal/logic/{module}/{action}Logic.go` +- 新API: `api/desc/{module}.api` + +### 前端文件 +- 新页面: `src/pages/{PageName}.tsx` +- 新组件: `src/components/{ComponentName}.tsx` +- 新服务: `src/services/{serviceName}.ts` + +## 🔧 开发工具配置 + +### VS Code 推荐插件 +- Go语言:`golang.go` +- React:`ES7+ React/Redux/React-Native snippets` +- Tailwind:`Tailwind CSS IntelliSense` +- API测试:`REST Client` + +### 本地环境变量 +```bash +# backend/.env +DATABASE_URL="sqlite:photography.db" +JWT_SECRET="your-secret-key" +UPLOAD_PATH="./uploads" + +# frontend/.env.local +NEXT_PUBLIC_API_URL="http://localhost:8888/api/v1" +``` + +## 🛠️ 故障排除 + +### 常见问题 +1. **端口冲突**: 检查8888(后端)、3000(前端)、5173(管理后台) +2. **依赖问题**: 删除node_modules重新安装 +3. **Go模块**: 运行`go mod tidy`清理依赖 +4. **API调用失败**: 检查CORS设置和认证token + +### 调试命令 +```bash +# 检查服务状态 +lsof -i :8888 +netstat -tlnp | grep 8888 + +# 查看日志 +tail -f backend/logs/photography.log +``` + +## 📊 进度跟踪 + +### 完成标准 +- ✅ 代码通过测试 +- ✅ API接口可正常调用 +- ✅ 前端界面功能正常 +- ✅ 更新TASK_PROGRESS.md状态 + +### 每日更新 +在 [TASK_PROGRESS.md](mdc:TASK_PROGRESS.md) 记录: +- 完成的任务 +- 遇到的问题 +- 明日计划 + +保持项目进度透明化和可追踪性。 diff --git a/.cursor/rules/common/git-workflow.mdc b/.cursor/rules/common/git-workflow.mdc new file mode 100644 index 0000000..eac874f --- /dev/null +++ b/.cursor/rules/common/git-workflow.mdc @@ -0,0 +1,248 @@ +# Git 工作流规则 + +## 🌿 分支管理 + +### 分支命名规范 +```bash +# 功能分支 +feature/photo-upload-api +feature/admin-dashboard +feature/responsive-design + +# 修复分支 +fix/photo-delete-bug +fix/login-redirect-issue + +# 优化分支 +refactor/api-structure +refactor/component-organization + +# 文档分支 +docs/api-documentation +docs/deployment-guide +``` + +### 分支工作流 +```bash +# 1. 从主分支创建功能分支 +git checkout main +git pull origin main +git checkout -b feature/photo-update-api + +# 2. 开发和提交 +git add . +git commit -m "feat: 实现照片更新API" + +# 3. 推送分支 +git push origin feature/photo-update-api + +# 4. 创建Pull Request +# 在GitHub/GitLab上创建PR + +# 5. 代码审查后合并 +git checkout main +git pull origin main +git branch -d feature/photo-update-api +``` + +## 📝 提交信息规范 + +### 提交类型 +```bash +feat: 新功能 +fix: 修复bug +docs: 文档更新 +style: 代码格式化(不影响代码逻辑) +refactor: 重构(既不是新增功能,也不是修复bug) +perf: 性能优化 +test: 添加测试 +chore: 构建过程或辅助工具的变动 +ci: CI/CD配置文件和脚本的变动 +``` + +### 提交信息格式 +```bash +(): + + + +