diff --git a/docs/development/saved-docs/api-design.md b/docs/development/saved-docs/api-design.md
new file mode 100644
index 0000000..01f3bfa
--- /dev/null
+++ b/docs/development/saved-docs/api-design.md
@@ -0,0 +1,1569 @@
+# 摄影作品集网站 - API接口设计文档
+
+## 1. API 概述
+
+### 1.1 设计原则
+- **RESTful 设计**: 遵循 REST 架构风格
+- **统一响应格式**: 标准化的 JSON 响应结构
+- **版本控制**: API 版本化管理
+- **安全认证**: JWT 令牌认证机制
+- **错误处理**: 详细的错误码和错误信息
+- **性能优化**: 支持分页、筛选、排序
+
+### 1.2 基础信息
+```yaml
+Base URL: https://api.photography.iriver.top
+API Version: v1
+Content-Type: application/json
+Authentication: Bearer Token (JWT)
+```
+
+### 1.3 通用响应格式
+
+#### 1.3.1 成功响应
+```json
+{
+ "success": true,
+ "code": 200,
+ "message": "Success",
+ "data": {
+ // 具体数据内容
+ },
+ "meta": {
+ "timestamp": "2024-01-15T10:30:00Z",
+ "request_id": "req_123456789"
+ }
+}
+```
+
+#### 1.3.2 分页响应
+```json
+{
+ "success": true,
+ "code": 200,
+ "message": "Success",
+ "data": [
+ // 数据列表
+ ],
+ "pagination": {
+ "page": 1,
+ "limit": 20,
+ "total": 150,
+ "total_pages": 8,
+ "has_next": true,
+ "has_prev": false
+ },
+ "meta": {
+ "timestamp": "2024-01-15T10:30:00Z",
+ "request_id": "req_123456789"
+ }
+}
+```
+
+#### 1.3.3 错误响应
+```json
+{
+ "success": false,
+ "code": 400,
+ "message": "Bad Request",
+ "error": {
+ "type": "VALIDATION_ERROR",
+ "details": [
+ {
+ "field": "title",
+ "message": "Title is required"
+ }
+ ]
+ },
+ "meta": {
+ "timestamp": "2024-01-15T10:30:00Z",
+ "request_id": "req_123456789"
+ }
+}
+```
+
+### 1.4 HTTP 状态码规范
+
+| 状态码 | 说明 | 使用场景 |
+|--------|------|----------|
+| 200 | OK | 请求成功 |
+| 201 | Created | 资源创建成功 |
+| 204 | No Content | 删除成功 |
+| 400 | Bad Request | 请求参数错误 |
+| 401 | Unauthorized | 未认证 |
+| 403 | Forbidden | 权限不足 |
+| 404 | Not Found | 资源不存在 |
+| 409 | Conflict | 资源冲突 |
+| 422 | Unprocessable Entity | 数据验证失败 |
+| 429 | Too Many Requests | 请求过于频繁 |
+| 500 | Internal Server Error | 服务器内部错误 |
+
+## 2. 认证与授权
+
+### 2.1 认证机制
+
+#### 2.1.1 登录认证
+```http
+POST /v1/auth/login
+Content-Type: application/json
+
+{
+ "username": "admin",
+ "password": "password123"
+}
+```
+
+**响应:**
+```json
+{
+ "success": true,
+ "code": 200,
+ "message": "Login successful",
+ "data": {
+ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
+ "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
+ "token_type": "Bearer",
+ "expires_in": 86400,
+ "user": {
+ "id": 1,
+ "username": "admin",
+ "email": "admin@example.com",
+ "role": "admin",
+ "display_name": "管理员",
+ "avatar_url": "https://example.com/avatar.jpg"
+ }
+ }
+}
+```
+
+#### 2.1.2 令牌刷新
+```http
+POST /v1/auth/refresh
+Content-Type: application/json
+
+{
+ "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
+}
+```
+
+#### 2.1.3 登出
+```http
+POST /v1/auth/logout
+Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
+```
+
+#### 2.1.4 用户信息
+```http
+GET /v1/auth/profile
+Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
+```
+
+### 2.2 权限控制
+
+#### 2.2.1 权限级别
+```json
+{
+ "roles": {
+ "super_admin": {
+ "name": "超级管理员",
+ "permissions": ["*"]
+ },
+ "admin": {
+ "name": "管理员",
+ "permissions": [
+ "photo.*",
+ "category.*",
+ "tag.*",
+ "user.read",
+ "user.update",
+ "settings.*"
+ ]
+ },
+ "editor": {
+ "name": "编辑者",
+ "permissions": [
+ "photo.read",
+ "photo.create",
+ "photo.update",
+ "category.read",
+ "tag.read",
+ "tag.create"
+ ]
+ },
+ "user": {
+ "name": "普通用户",
+ "permissions": [
+ "photo.read",
+ "category.read",
+ "tag.read"
+ ]
+ }
+ }
+}
+```
+
+## 3. 照片管理 API
+
+### 3.1 照片列表
+
+#### 3.1.1 获取照片列表
+```http
+GET /v1/photos?page=1&limit=20&status=published&category=1&tag=nature&search=sunset&sort_by=created_at&sort_order=desc
+```
+
+**查询参数:**
+| 参数 | 类型 | 必填 | 说明 | 示例 |
+|------|------|------|------|------|
+| page | integer | 否 | 页码,默认1 | 1 |
+| limit | integer | 否 | 每页数量,默认20 | 20 |
+| status | string | 否 | 状态筛选 | published, draft, archived |
+| category | integer | 否 | 分类ID | 1 |
+| tag | string | 否 | 标签名称 | nature |
+| search | string | 否 | 搜索关键词 | sunset |
+| sort_by | string | 否 | 排序字段 | created_at, taken_at, title |
+| sort_order | string | 否 | 排序方向 | asc, desc |
+| year | integer | 否 | 年份筛选 | 2024 |
+| month | integer | 否 | 月份筛选 | 1 |
+
+**响应:**
+```json
+{
+ "success": true,
+ "code": 200,
+ "data": [
+ {
+ "id": 1,
+ "title": "城市夜景",
+ "description": "繁华都市的夜晚景色",
+ "slug": "city-night-view-001",
+ "status": "published",
+ "visibility": "public",
+ "taken_at": "2024-01-15T18:30:00Z",
+ "created_at": "2024-01-15T20:00:00Z",
+ "updated_at": "2024-01-15T20:00:00Z",
+ "view_count": 156,
+ "like_count": 23,
+ "formats": {
+ "thumb_small": "https://cdn.example.com/photos/1/thumb_small.jpg",
+ "thumb_medium": "https://cdn.example.com/photos/1/thumb_medium.jpg",
+ "thumb_large": "https://cdn.example.com/photos/1/thumb_large.jpg",
+ "display": "https://cdn.example.com/photos/1/display.jpg",
+ "webp": "https://cdn.example.com/photos/1/display.webp"
+ },
+ "exif": {
+ "camera": "Canon EOS R5",
+ "lens": "RF 24-70mm f/2.8L IS USM",
+ "iso": 800,
+ "aperture": "f/2.8",
+ "shutter_speed": "1/125",
+ "focal_length": "50mm"
+ },
+ "location": {
+ "name": "上海外滩",
+ "latitude": 31.23037,
+ "longitude": 121.47370,
+ "country": "China",
+ "city": "Shanghai"
+ },
+ "categories": [
+ {
+ "id": 1,
+ "name": "城市风光",
+ "slug": "cityscape",
+ "color": "#3498db"
+ }
+ ],
+ "tags": [
+ {
+ "id": 1,
+ "name": "夜景",
+ "slug": "night-view",
+ "color": "#2c3e50"
+ },
+ {
+ "id": 2,
+ "name": "城市",
+ "slug": "city",
+ "color": "#e74c3c"
+ }
+ ]
+ }
+ ],
+ "pagination": {
+ "page": 1,
+ "limit": 20,
+ "total": 150,
+ "total_pages": 8,
+ "has_next": true,
+ "has_prev": false
+ }
+}
+```
+
+### 3.2 照片详情
+
+#### 3.2.1 获取照片详情
+```http
+GET /v1/photos/{id}
+```
+
+**路径参数:**
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| id | integer | 是 | 照片ID |
+
+**响应:**
+```json
+{
+ "success": true,
+ "code": 200,
+ "data": {
+ "id": 1,
+ "title": "城市夜景",
+ "description": "繁华都市的夜晚景色,灯火通明的建筑群构成了美丽的天际线...",
+ "slug": "city-night-view-001",
+ "status": "published",
+ "visibility": "public",
+ "sort_order": 0,
+ "taken_at": "2024-01-15T18:30:00Z",
+ "created_at": "2024-01-15T20:00:00Z",
+ "updated_at": "2024-01-15T20:00:00Z",
+ "view_count": 156,
+ "like_count": 23,
+ "download_count": 5,
+ "file_info": {
+ "original_filename": "DSC_0001.jpg",
+ "file_size": 2048576,
+ "mime_type": "image/jpeg"
+ },
+ "formats": {
+ "original": "https://cdn.example.com/photos/1/original.jpg",
+ "jpg": "https://cdn.example.com/photos/1/display.jpg",
+ "webp": "https://cdn.example.com/photos/1/display.webp",
+ "thumb_small": "https://cdn.example.com/photos/1/thumb_small.jpg",
+ "thumb_medium": "https://cdn.example.com/photos/1/thumb_medium.jpg",
+ "thumb_large": "https://cdn.example.com/photos/1/thumb_large.jpg"
+ },
+ "exif": {
+ "camera": "Canon EOS R5",
+ "lens": "RF 24-70mm f/2.8L IS USM",
+ "iso": 800,
+ "aperture": "f/2.8",
+ "shutter_speed": "1/125",
+ "focal_length": "50mm"
+ },
+ "location": {
+ "name": "上海外滩",
+ "latitude": 31.23037,
+ "longitude": 121.47370,
+ "country": "China",
+ "city": "Shanghai"
+ },
+ "categories": [
+ {
+ "id": 1,
+ "name": "城市风光",
+ "slug": "cityscape",
+ "description": "城市景观摄影作品",
+ "color": "#3498db",
+ "is_primary": true
+ }
+ ],
+ "tags": [
+ {
+ "id": 1,
+ "name": "夜景",
+ "slug": "night-view",
+ "color": "#2c3e50",
+ "confidence": 1.0,
+ "source": "manual"
+ },
+ {
+ "id": 2,
+ "name": "城市",
+ "slug": "city",
+ "color": "#e74c3c",
+ "confidence": 0.95,
+ "source": "ai"
+ }
+ ],
+ "metadata": {
+ "weather": "clear",
+ "temperature": "15°C",
+ "processing_notes": "调整了曝光和对比度"
+ }
+ }
+}
+```
+
+### 3.3 照片操作
+
+#### 3.3.1 创建照片 (用于上传后的元数据创建)
+```http
+POST /v1/photos
+Authorization: Bearer {token}
+Content-Type: application/json
+
+{
+ "title": "城市夜景",
+ "description": "繁华都市的夜晚景色",
+ "file_id": "upload_123456789",
+ "status": "published",
+ "visibility": "public",
+ "taken_at": "2024-01-15T18:30:00Z",
+ "location": {
+ "name": "上海外滩",
+ "latitude": 31.23037,
+ "longitude": 121.47370
+ },
+ "categories": [1, 2],
+ "tags": ["夜景", "城市", "建筑"],
+ "metadata": {
+ "weather": "clear",
+ "temperature": "15°C"
+ }
+}
+```
+
+#### 3.3.2 更新照片信息
+```http
+PUT /v1/photos/{id}
+Authorization: Bearer {token}
+Content-Type: application/json
+
+{
+ "title": "上海外滩夜景",
+ "description": "更新后的描述",
+ "status": "published",
+ "categories": [1, 3],
+ "tags": ["夜景", "城市", "外滩"]
+}
+```
+
+#### 3.3.3 删除照片
+```http
+DELETE /v1/photos/{id}
+Authorization: Bearer {token}
+```
+
+#### 3.3.4 批量操作
+```http
+POST /v1/photos/batch
+Authorization: Bearer {token}
+Content-Type: application/json
+
+{
+ "photo_ids": [1, 2, 3, 4, 5],
+ "action": "update_status",
+ "data": {
+ "status": "published"
+ }
+}
+```
+
+**支持的批量操作:**
+- `update_status`: 批量更新状态
+- `add_tags`: 批量添加标签
+- `remove_tags`: 批量移除标签
+- `add_categories`: 批量添加分类
+- `remove_categories`: 批量移除分类
+- `delete`: 批量删除
+
+### 3.4 照片搜索
+
+#### 3.4.1 全文搜索
+```http
+GET /v1/photos/search?q=夜景&category=1&tags=城市,建筑&location=上海&date_from=2024-01-01&date_to=2024-12-31
+```
+
+**查询参数:**
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| q | string | 是 | 搜索关键词 |
+| category | integer | 否 | 分类ID |
+| tags | string | 否 | 标签名称,逗号分隔 |
+| location | string | 否 | 地点名称 |
+| date_from | string | 否 | 开始日期 (YYYY-MM-DD) |
+| date_to | string | 否 | 结束日期 (YYYY-MM-DD) |
+| camera | string | 否 | 相机型号 |
+| lens | string | 否 | 镜头型号 |
+
+**响应:**
+```json
+{
+ "success": true,
+ "code": 200,
+ "data": {
+ "photos": [
+ {
+ "id": 1,
+ "title": "城市夜景",
+ "score": 0.95,
+ "highlight": {
+ "title": "夜景",
+ "description": "繁华都市的夜晚景色"
+ }
+ }
+ ],
+ "facets": {
+ "categories": [
+ {"name": "城市风光", "count": 15},
+ {"name": "建筑摄影", "count": 8}
+ ],
+ "tags": [
+ {"name": "夜景", "count": 12},
+ {"name": "城市", "count": 20}
+ ],
+ "years": [
+ {"year": 2024, "count": 25},
+ {"year": 2023, "count": 18}
+ ]
+ }
+ },
+ "pagination": {
+ "page": 1,
+ "limit": 20,
+ "total": 25
+ }
+}
+```
+
+## 4. 文件上传 API
+
+### 4.1 文件上传
+
+#### 4.1.1 单文件上传
+```http
+POST /v1/upload/single
+Authorization: Bearer {token}
+Content-Type: multipart/form-data
+
+file: (binary data)
+```
+
+**响应:**
+```json
+{
+ "success": true,
+ "code": 201,
+ "data": {
+ "file_id": "upload_123456789",
+ "original_filename": "DSC_0001.jpg",
+ "file_size": 2048576,
+ "mime_type": "image/jpeg",
+ "upload_url": "https://temp.example.com/upload_123456789.jpg",
+ "status": "uploaded",
+ "exif_extracted": true,
+ "processing_status": "pending"
+ }
+}
+```
+
+#### 4.1.2 多文件上传
+```http
+POST /v1/upload/multiple
+Authorization: Bearer {token}
+Content-Type: multipart/form-data
+
+files[]: (binary data)
+files[]: (binary data)
+...
+```
+
+**响应:**
+```json
+{
+ "success": true,
+ "code": 201,
+ "data": {
+ "uploaded": [
+ {
+ "file_id": "upload_123456789",
+ "original_filename": "DSC_0001.jpg",
+ "file_size": 2048576,
+ "status": "uploaded"
+ }
+ ],
+ "failed": [
+ {
+ "filename": "invalid_file.txt",
+ "error": "Invalid file type"
+ }
+ ],
+ "summary": {
+ "total": 5,
+ "success": 4,
+ "failed": 1
+ }
+ }
+}
+```
+
+#### 4.1.3 分块上传
+```http
+POST /v1/upload/chunked/init
+Authorization: Bearer {token}
+Content-Type: application/json
+
+{
+ "filename": "large_photo.raw",
+ "file_size": 52428800,
+ "mime_type": "image/raw",
+ "chunk_size": 1048576
+}
+```
+
+**响应:**
+```json
+{
+ "success": true,
+ "code": 201,
+ "data": {
+ "upload_id": "chunked_123456789",
+ "chunk_size": 1048576,
+ "total_chunks": 50,
+ "upload_urls": [
+ "https://temp.example.com/chunked_123456789/chunk_0",
+ "https://temp.example.com/chunked_123456789/chunk_1"
+ ]
+ }
+}
+```
+
+#### 4.1.4 上传状态查询
+```http
+GET /v1/upload/status/{file_id}
+Authorization: Bearer {token}
+```
+
+**响应:**
+```json
+{
+ "success": true,
+ "code": 200,
+ "data": {
+ "file_id": "upload_123456789",
+ "status": "processing",
+ "progress": 75,
+ "current_step": "generating_thumbnails",
+ "steps": [
+ {"name": "uploaded", "status": "completed"},
+ {"name": "exif_extraction", "status": "completed"},
+ {"name": "generating_thumbnails", "status": "processing"},
+ {"name": "optimization", "status": "pending"}
+ ],
+ "estimated_time_remaining": 30
+ }
+}
+```
+
+### 4.2 文件处理
+
+#### 4.2.1 重新处理文件
+```http
+POST /v1/upload/{file_id}/reprocess
+Authorization: Bearer {token}
+Content-Type: application/json
+
+{
+ "formats": ["thumb_small", "thumb_medium", "webp"],
+ "force": true
+}
+```
+
+#### 4.2.2 删除上传文件
+```http
+DELETE /v1/upload/{file_id}
+Authorization: Bearer {token}
+```
+
+## 5. 分类管理 API
+
+### 5.1 分类操作
+
+#### 5.1.1 获取分类列表
+```http
+GET /v1/categories?include_stats=true&include_tree=true
+```
+
+**响应:**
+```json
+{
+ "success": true,
+ "code": 200,
+ "data": {
+ "categories": [
+ {
+ "id": 1,
+ "name": "城市风光",
+ "slug": "cityscape",
+ "description": "城市景观摄影作品",
+ "parent_id": null,
+ "level": 0,
+ "path": "1",
+ "cover_photo": {
+ "id": 15,
+ "title": "都市夜景",
+ "thumb_url": "https://cdn.example.com/photos/15/thumb_medium.jpg"
+ },
+ "color": "#3498db",
+ "icon": "building",
+ "sort_order": 1,
+ "is_active": true,
+ "is_featured": true,
+ "photo_count": 45,
+ "direct_photo_count": 30,
+ "created_at": "2024-01-01T00:00:00Z",
+ "updated_at": "2024-01-15T10:30:00Z"
+ }
+ ],
+ "tree": [
+ {
+ "id": 1,
+ "name": "城市风光",
+ "slug": "cityscape",
+ "photo_count": 45,
+ "children": [
+ {
+ "id": 2,
+ "name": "夜景摄影",
+ "slug": "night-photography",
+ "photo_count": 15,
+ "children": []
+ },
+ {
+ "id": 3,
+ "name": "建筑摄影",
+ "slug": "architecture",
+ "photo_count": 30,
+ "children": []
+ }
+ ]
+ }
+ ],
+ "stats": {
+ "total_categories": 12,
+ "max_level": 3,
+ "featured_count": 5
+ }
+ }
+}
+```
+
+#### 5.1.2 获取分类详情
+```http
+GET /v1/categories/{id}
+```
+
+#### 5.1.3 创建分类
+```http
+POST /v1/categories
+Authorization: Bearer {token}
+Content-Type: application/json
+
+{
+ "name": "自然风景",
+ "slug": "nature-landscape",
+ "description": "自然风景摄影作品",
+ "parent_id": null,
+ "color": "#27ae60",
+ "icon": "tree",
+ "sort_order": 2,
+ "is_featured": true,
+ "seo_title": "自然风景摄影作品集",
+ "seo_description": "欣赏美丽的自然风景摄影作品"
+}
+```
+
+#### 5.1.4 更新分类
+```http
+PUT /v1/categories/{id}
+Authorization: Bearer {token}
+Content-Type: application/json
+
+{
+ "name": "自然风光",
+ "description": "更新后的描述",
+ "color": "#2ecc71"
+}
+```
+
+#### 5.1.5 删除分类
+```http
+DELETE /v1/categories/{id}
+Authorization: Bearer {token}
+```
+
+#### 5.1.6 设置分类封面
+```http
+PUT /v1/categories/{id}/cover
+Authorization: Bearer {token}
+Content-Type: application/json
+
+{
+ "photo_id": 15
+}
+```
+
+### 5.2 分类照片管理
+
+#### 5.2.1 获取分类下的照片
+```http
+GET /v1/categories/{id}/photos?page=1&limit=20&sort_by=created_at&sort_order=desc
+```
+
+#### 5.2.2 添加照片到分类
+```http
+POST /v1/categories/{id}/photos
+Authorization: Bearer {token}
+Content-Type: application/json
+
+{
+ "photo_ids": [1, 2, 3],
+ "is_primary": true
+}
+```
+
+#### 5.2.3 从分类移除照片
+```http
+DELETE /v1/categories/{id}/photos
+Authorization: Bearer {token}
+Content-Type: application/json
+
+{
+ "photo_ids": [1, 2, 3]
+}
+```
+
+## 6. 标签管理 API
+
+### 6.1 标签操作
+
+#### 6.1.1 获取标签列表
+```http
+GET /v1/tags?group=all&sort_by=usage_count&sort_order=desc&limit=50
+```
+
+**响应:**
+```json
+{
+ "success": true,
+ "code": 200,
+ "data": [
+ {
+ "id": 1,
+ "name": "夜景",
+ "slug": "night-view",
+ "description": "夜晚拍摄的景色",
+ "color": "#2c3e50",
+ "icon": "moon",
+ "tag_group": "style",
+ "usage_count": 45,
+ "trend_score": 8.5,
+ "is_active": true,
+ "is_featured": true,
+ "created_at": "2024-01-01T00:00:00Z",
+ "last_used_at": "2024-01-15T10:30:00Z"
+ }
+ ],
+ "groups": {
+ "style": {"name": "摄影风格", "count": 12},
+ "subject": {"name": "拍摄主题", "count": 18},
+ "technique": {"name": "拍摄技法", "count": 8},
+ "location": {"name": "地理位置", "count": 25}
+ }
+}
+```
+
+#### 6.1.2 标签搜索建议
+```http
+GET /v1/tags/suggestions?q=夜&limit=10
+```
+
+**响应:**
+```json
+{
+ "success": true,
+ "code": 200,
+ "data": [
+ {
+ "id": 1,
+ "name": "夜景",
+ "slug": "night-view",
+ "usage_count": 45,
+ "match_score": 0.95
+ },
+ {
+ "id": 15,
+ "name": "夜市",
+ "slug": "night-market",
+ "usage_count": 12,
+ "match_score": 0.8
+ }
+ ]
+}
+```
+
+#### 6.1.3 创建标签
+```http
+POST /v1/tags
+Authorization: Bearer {token}
+Content-Type: application/json
+
+{
+ "name": "极光",
+ "slug": "aurora",
+ "description": "极光摄影作品",
+ "color": "#9b59b6",
+ "tag_group": "subject",
+ "is_featured": true
+}
+```
+
+#### 6.1.4 更新标签
+```http
+PUT /v1/tags/{id}
+Authorization: Bearer {token}
+Content-Type: application/json
+
+{
+ "name": "北极光",
+ "description": "更新后的描述",
+ "color": "#8e44ad"
+}
+```
+
+#### 6.1.5 删除标签
+```http
+DELETE /v1/tags/{id}
+Authorization: Bearer {token}
+```
+
+### 6.2 标签统计
+
+#### 6.2.1 标签云数据
+```http
+GET /v1/tags/cloud?min_usage=5&max_tags=50
+```
+
+**响应:**
+```json
+{
+ "success": true,
+ "code": 200,
+ "data": [
+ {
+ "id": 1,
+ "name": "夜景",
+ "usage_count": 45,
+ "relative_size": 100,
+ "color": "#2c3e50"
+ },
+ {
+ "id": 2,
+ "name": "城市",
+ "usage_count": 38,
+ "relative_size": 84,
+ "color": "#e74c3c"
+ }
+ ]
+}
+```
+
+#### 6.2.2 趋势标签
+```http
+GET /v1/tags/trending?period=30d&limit=10
+```
+
+## 7. 时间线 API
+
+### 7.1 时间线数据
+
+#### 7.1.1 获取时间线
+```http
+GET /v1/timeline?year=2024&include_photos=true&photos_limit=5
+```
+
+**响应:**
+```json
+{
+ "success": true,
+ "code": 200,
+ "data": {
+ "years": [
+ {
+ "year": 2024,
+ "photo_count": 156,
+ "months": [
+ {
+ "month": 1,
+ "month_name": "一月",
+ "photo_count": 25,
+ "photos": [
+ {
+ "id": 1,
+ "title": "城市夜景",
+ "thumb_url": "https://cdn.example.com/photos/1/thumb_medium.jpg",
+ "taken_at": "2024-01-15T18:30:00Z"
+ }
+ ],
+ "events": [
+ {
+ "id": 1,
+ "title": "首次夜景拍摄",
+ "description": "第一次尝试城市夜景摄影",
+ "date": "2024-01-15",
+ "type": "milestone"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "stats": {
+ "total_photos": 456,
+ "year_range": [2020, 2024],
+ "most_active_month": {
+ "year": 2024,
+ "month": 3,
+ "count": 45
+ },
+ "photos_by_year": [
+ {"year": 2024, "count": 156},
+ {"year": 2023, "count": 189},
+ {"year": 2022, "count": 111}
+ ]
+ }
+ }
+}
+```
+
+#### 7.1.2 获取指定月份详情
+```http
+GET /v1/timeline/{year}/{month}?include_photos=true
+```
+
+### 7.2 时间线事件
+
+#### 7.2.1 创建事件
+```http
+POST /v1/timeline/events
+Authorization: Bearer {token}
+Content-Type: application/json
+
+{
+ "title": "获得摄影比赛奖项",
+ "description": "在城市摄影大赛中获得金奖",
+ "date": "2024-03-15",
+ "type": "achievement",
+ "related_photos": [15, 23, 31]
+}
+```
+
+#### 7.2.2 更新事件
+```http
+PUT /v1/timeline/events/{id}
+Authorization: Bearer {token}
+Content-Type: application/json
+
+{
+ "title": "更新后的标题",
+ "description": "更新后的描述"
+}
+```
+
+#### 7.2.3 删除事件
+```http
+DELETE /v1/timeline/events/{id}
+Authorization: Bearer {token}
+```
+
+## 8. 系统设置 API
+
+### 8.1 设置管理
+
+#### 8.1.1 获取系统设置
+```http
+GET /v1/settings?category=all&include_public=true
+```
+
+**响应:**
+```json
+{
+ "success": true,
+ "code": 200,
+ "data": {
+ "general": {
+ "site_title": "摄影作品集",
+ "site_description": "专业摄影师作品展示平台",
+ "site_keywords": "摄影,作品集,艺术,创作",
+ "site_author": "摄影师姓名",
+ "site_email": "contact@example.com"
+ },
+ "upload": {
+ "max_file_size": 52428800,
+ "allowed_types": ["image/jpeg", "image/png", "image/raw"],
+ "max_files_per_batch": 50,
+ "auto_publish": false,
+ "generate_thumbnails": true
+ },
+ "image": {
+ "quality_jpg": 85,
+ "quality_webp": 80,
+ "max_width": 1920,
+ "max_height": 1080,
+ "watermark_enabled": false
+ },
+ "display": {
+ "photos_per_page": 20,
+ "thumbnail_size": 300,
+ "theme_primary_color": "#d4af37",
+ "theme_secondary_color": "#2d2d2d",
+ "enable_dark_mode": true,
+ "enable_animations": true
+ }
+ }
+}
+```
+
+#### 8.1.2 更新系统设置
+```http
+PUT /v1/settings
+Authorization: Bearer {token}
+Content-Type: application/json
+
+{
+ "site_title": "我的摄影作品集",
+ "upload_max_file_size": 104857600,
+ "display_photos_per_page": 24
+}
+```
+
+#### 8.1.3 重置设置
+```http
+POST /v1/settings/reset
+Authorization: Bearer {token}
+Content-Type: application/json
+
+{
+ "categories": ["upload", "image"],
+ "confirm": true
+}
+```
+
+### 8.2 缓存管理
+
+#### 8.2.1 清理缓存
+```http
+POST /v1/settings/cache/clear
+Authorization: Bearer {token}
+Content-Type: application/json
+
+{
+ "types": ["photos", "categories", "tags", "settings"],
+ "confirm": true
+}
+```
+
+#### 8.2.2 预热缓存
+```http
+POST /v1/settings/cache/warm
+Authorization: Bearer {token}
+Content-Type: application/json
+
+{
+ "types": ["popular_photos", "category_tree", "tag_cloud"]
+}
+```
+
+#### 8.2.3 缓存统计
+```http
+GET /v1/settings/cache/stats
+Authorization: Bearer {token}
+```
+
+**响应:**
+```json
+{
+ "success": true,
+ "code": 200,
+ "data": {
+ "redis": {
+ "connected": true,
+ "used_memory": "15.2MB",
+ "total_keys": 1247,
+ "hit_rate": 0.89
+ },
+ "categories": {
+ "photos": {"keys": 456, "hit_rate": 0.92},
+ "categories": {"keys": 12, "hit_rate": 0.95},
+ "tags": {"keys": 89, "hit_rate": 0.87},
+ "settings": {"keys": 1, "hit_rate": 0.99}
+ }
+ }
+}
+```
+
+## 9. 统计分析 API
+
+### 9.1 仪表板统计
+
+#### 9.1.1 仪表板数据
+```http
+GET /v1/dashboard/stats?period=30d
+```
+
+**响应:**
+```json
+{
+ "success": true,
+ "code": 200,
+ "data": {
+ "summary": {
+ "total_photos": 456,
+ "total_categories": 12,
+ "total_tags": 89,
+ "total_views": 15420,
+ "storage_used": 2684354560,
+ "storage_limit": 10737418240
+ },
+ "recent": {
+ "new_photos": 15,
+ "new_views": 234,
+ "new_likes": 67,
+ "period": "7d"
+ },
+ "trends": {
+ "uploads": [
+ {"date": "2024-01-01", "count": 5},
+ {"date": "2024-01-02", "count": 8},
+ {"date": "2024-01-03", "count": 3}
+ ],
+ "views": [
+ {"date": "2024-01-01", "count": 156},
+ {"date": "2024-01-02", "count": 234},
+ {"date": "2024-01-03", "count": 189}
+ ]
+ },
+ "popular": {
+ "categories": [
+ {"name": "城市风光", "count": 45, "percentage": 28.5},
+ {"name": "自然风景", "count": 38, "percentage": 24.1}
+ ],
+ "tags": [
+ {"name": "夜景", "count": 45},
+ {"name": "城市", "count": 38}
+ ],
+ "photos": [
+ {
+ "id": 15,
+ "title": "都市夜景",
+ "view_count": 1247,
+ "like_count": 89
+ }
+ ]
+ },
+ "system_status": {
+ "database": "healthy",
+ "redis": "healthy",
+ "storage": "healthy",
+ "queue": "healthy"
+ }
+ }
+}
+```
+
+### 9.2 详细统计
+
+#### 9.2.1 照片统计
+```http
+GET /v1/stats/photos?group_by=month&year=2024
+```
+
+#### 9.2.2 访问统计
+```http
+GET /v1/stats/views?period=30d&group_by=day
+```
+
+#### 9.2.3 存储统计
+```http
+GET /v1/stats/storage
+```
+
+## 10. 用户管理 API
+
+### 10.1 用户操作
+
+#### 10.1.1 获取用户列表
+```http
+GET /v1/users?page=1&limit=20&role=all&status=active&search=admin
+```
+
+#### 10.1.2 创建用户
+```http
+POST /v1/users
+Authorization: Bearer {token}
+Content-Type: application/json
+
+{
+ "username": "editor",
+ "email": "editor@example.com",
+ "password": "password123",
+ "role": "editor",
+ "display_name": "编辑者",
+ "is_active": true
+}
+```
+
+#### 10.1.3 更新用户
+```http
+PUT /v1/users/{id}
+Authorization: Bearer {token}
+Content-Type: application/json
+
+{
+ "display_name": "高级编辑者",
+ "role": "admin",
+ "is_active": true
+}
+```
+
+#### 10.1.4 删除用户
+```http
+DELETE /v1/users/{id}
+Authorization: Bearer {token}
+```
+
+### 10.2 用户会话管理
+
+#### 10.2.1 获取用户会话
+```http
+GET /v1/users/{id}/sessions
+Authorization: Bearer {token}
+```
+
+#### 10.2.2 强制下线
+```http
+DELETE /v1/users/{id}/sessions/{session_id}
+Authorization: Bearer {token}
+```
+
+## 11. 错误处理
+
+### 11.1 错误码定义
+
+| 错误码 | HTTP状态码 | 说明 |
+|--------|------------|------|
+| VALIDATION_ERROR | 400 | 请求参数验证失败 |
+| AUTHENTICATION_REQUIRED | 401 | 需要认证 |
+| INVALID_TOKEN | 401 | 无效的令牌 |
+| TOKEN_EXPIRED | 401 | 令牌已过期 |
+| PERMISSION_DENIED | 403 | 权限不足 |
+| RESOURCE_NOT_FOUND | 404 | 资源不存在 |
+| RESOURCE_CONFLICT | 409 | 资源冲突 |
+| UNPROCESSABLE_ENTITY | 422 | 数据处理失败 |
+| RATE_LIMIT_EXCEEDED | 429 | 请求频率超限 |
+| INTERNAL_SERVER_ERROR | 500 | 服务器内部错误 |
+| SERVICE_UNAVAILABLE | 503 | 服务不可用 |
+
+### 11.2 错误响应示例
+
+#### 11.2.1 验证错误
+```json
+{
+ "success": false,
+ "code": 400,
+ "message": "Validation failed",
+ "error": {
+ "type": "VALIDATION_ERROR",
+ "details": [
+ {
+ "field": "title",
+ "message": "Title is required",
+ "code": "REQUIRED"
+ },
+ {
+ "field": "email",
+ "message": "Invalid email format",
+ "code": "INVALID_FORMAT"
+ }
+ ]
+ }
+}
+```
+
+#### 11.2.2 权限错误
+```json
+{
+ "success": false,
+ "code": 403,
+ "message": "Permission denied",
+ "error": {
+ "type": "PERMISSION_DENIED",
+ "details": {
+ "required_permission": "photo.delete",
+ "user_role": "editor"
+ }
+ }
+}
+```
+
+#### 11.2.3 资源不存在
+```json
+{
+ "success": false,
+ "code": 404,
+ "message": "Resource not found",
+ "error": {
+ "type": "RESOURCE_NOT_FOUND",
+ "details": {
+ "resource_type": "photo",
+ "resource_id": 999
+ }
+ }
+}
+```
+
+## 12. API 限流
+
+### 12.1 限流策略
+
+| 端点类型 | 限制 | 时间窗口 |
+|----------|------|----------|
+| 认证端点 | 5次/IP | 1分钟 |
+| 上传端点 | 10次/用户 | 1分钟 |
+| 搜索端点 | 60次/用户 | 1分钟 |
+| 一般端点 | 1000次/用户 | 1小时 |
+| 管理端点 | 500次/用户 | 1小时 |
+
+### 12.2 限流响应头
+
+```http
+X-RateLimit-Limit: 1000
+X-RateLimit-Remaining: 999
+X-RateLimit-Reset: 1642636800
+X-RateLimit-Window: 3600
+```
+
+### 12.3 限流超出响应
+
+```json
+{
+ "success": false,
+ "code": 429,
+ "message": "Rate limit exceeded",
+ "error": {
+ "type": "RATE_LIMIT_EXCEEDED",
+ "details": {
+ "limit": 1000,
+ "window": 3600,
+ "reset_at": "2024-01-15T11:00:00Z"
+ }
+ }
+}
+```
+
+## 13. WebHook API
+
+### 13.1 WebHook 配置
+
+#### 13.1.1 创建 WebHook
+```http
+POST /v1/webhooks
+Authorization: Bearer {token}
+Content-Type: application/json
+
+{
+ "url": "https://example.com/webhook",
+ "events": ["photo.created", "photo.updated", "photo.deleted"],
+ "secret": "webhook_secret_key",
+ "is_active": true
+}
+```
+
+#### 13.1.2 WebHook 事件类型
+
+| 事件类型 | 描述 | 数据载荷 |
+|----------|------|----------|
+| photo.created | 照片创建 | 完整照片数据 |
+| photo.updated | 照片更新 | 更新后的照片数据 |
+| photo.deleted | 照片删除 | 删除的照片ID |
+| category.created | 分类创建 | 完整分类数据 |
+| category.updated | 分类更新 | 更新后的分类数据 |
+| category.deleted | 分类删除 | 删除的分类ID |
+| user.login | 用户登录 | 用户基本信息 |
+| system.backup | 系统备份 | 备份状态信息 |
+
+### 13.2 WebHook 载荷示例
+
+```json
+{
+ "event": "photo.created",
+ "timestamp": "2024-01-15T10:30:00Z",
+ "data": {
+ "id": 1,
+ "title": "城市夜景",
+ "status": "published",
+ "created_at": "2024-01-15T10:30:00Z"
+ },
+ "signature": "sha256=abcdef123456..."
+}
+```
+
+## 14. API 版本控制
+
+### 14.1 版本策略
+
+- **URL 版本控制**: `/v1/`, `/v2/`
+- **向后兼容**: 至少支持2个主版本
+- **废弃通知**: 通过响应头通知
+
+### 14.2 版本响应头
+
+```http
+API-Version: 1.0
+API-Deprecated: false
+API-Sunset: 2025-01-15T00:00:00Z
+```
+
+### 14.3 版本变更日志
+
+#### 版本 1.0.0 (当前)
+- 初始 API 版本
+- 支持照片、分类、标签管理
+- 用户认证和权限控制
+
+#### 版本 1.1.0 (计划)
+- 添加 AI 标签推荐
+- 支持视频文件
+- 增强搜索功能
+
+## 15. 总结
+
+这个API接口设计文档提供了摄影作品集网站的完整API规范,包括:
+
+### 🎯 设计特点
+- **RESTful 风格**: 符合REST架构原则
+- **统一响应格式**: 标准化的JSON响应
+- **完整的CRUD操作**: 支持所有资源的增删改查
+- **灵活的查询**: 丰富的筛选、排序、搜索功能
+
+### 🔒 安全机制
+- **JWT认证**: 基于令牌的认证机制
+- **权限控制**: 细粒度的角色权限管理
+- **请求限流**: 防止API滥用
+- **数据验证**: 严格的输入验证
+
+### 📊 功能丰富
+- **文件上传**: 支持单文件、多文件、分块上传
+- **图片处理**: 自动生成多种格式和尺寸
+- **全文搜索**: 强大的搜索和筛选功能
+- **统计分析**: 详细的数据统计和趋势分析
+
+### 🛠️ 开发友好
+- **详细文档**: 完整的接口说明和示例
+- **错误处理**: 清晰的错误码和错误信息
+- **版本控制**: 科学的API版本管理
+- **WebHook支持**: 事件驱动的集成能力
+
+这个API设计为Golang后端实现提供了完整的接口规范,可以支持前端和管理后台的所有功能需求。
\ No newline at end of file
diff --git a/docs/development/saved-docs/management-backend.md b/docs/development/saved-docs/management-backend.md
new file mode 100644
index 0000000..cc0e882
--- /dev/null
+++ b/docs/development/saved-docs/management-backend.md
@@ -0,0 +1,1823 @@
+# 摄影作品集网站 - 管理后台开发文档
+
+## 1. 项目概述
+
+### 1.1 项目定位
+基于现有摄影作品集网站的管理后台系统,提供完整的内容管理、用户管理和系统配置功能。
+
+### 1.2 技术栈
+- **后端**: Golang + Gin + GORM + PostgreSQL + Redis
+- **前端**: React + TypeScript + Tailwind CSS + shadcn/ui
+- **文件存储**: MinIO/AWS S3 + 本地存储
+- **图片处理**: libvips + 多格式转换
+- **认证**: JWT + Session管理
+- **部署**: Docker + Caddy
+
+### 1.3 设计原则
+- **用户友好**: 直观的界面设计,简化操作流程
+- **高性能**: 异步图片处理,智能缓存策略
+- **可扩展**: 模块化设计,支持功能扩展
+- **安全可靠**: 多层权限控制,操作日志审计
+
+## 2. 管理后台功能模块详细设计
+
+### 2.1 仪表板模块 (Dashboard)
+
+#### 2.1.1 核心功能
+- **数据统计**: 照片总数、分类数量、标签数量、存储使用情况
+- **近期活动**: 最近上传、最近修改、访问统计
+- **快捷操作**: 快速上传、批量处理、系统设置
+- **系统状态**: 服务器状态、缓存状态、队列状态
+
+#### 2.1.2 界面设计
+```
+┌─────────────────────────────────────────────────────────┐
+│ 仪表板 Dashboard │
+├─────────────────────────────────────────────────────────┤
+│ 📊 统计卡片 │
+│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
+│ │ 总照片 │ │ 总分类 │ │ 总标签 │ │ 存储用量│ │
+│ │ 1,234 │ │ 12 │ │ 45 │ │ 2.5GB │ │
+│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
+│ │
+│ 📈 上传趋势图表 │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ [折线图显示最近30天上传趋势] │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ │
+│ 📋 近期活动 │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ • 上传了 "城市夜景" 系列 5 张照片 │ │
+│ │ • 创建了新分类 "建筑摄影" │ │
+│ │ • 更新了标签 "城市风光" │ │
+│ └─────────────────────────────────────────────────────┘ │
+└─────────────────────────────────────────────────────────┘
+```
+
+#### 2.1.3 数据接口
+```go
+// GET /api/admin/dashboard/stats
+type DashboardStats struct {
+ TotalPhotos int `json:"total_photos"`
+ TotalCategories int `json:"total_categories"`
+ TotalTags int `json:"total_tags"`
+ StorageUsed int64 `json:"storage_used"`
+ StorageLimit int64 `json:"storage_limit"`
+ RecentUploads int `json:"recent_uploads"`
+
+ // 上传趋势 (最近30天)
+ UploadTrend []struct {
+ Date string `json:"date"`
+ Count int `json:"count"`
+ } `json:"upload_trend"`
+
+ // 热门分类
+ PopularCategories []struct {
+ Name string `json:"name"`
+ Count int `json:"count"`
+ } `json:"popular_categories"`
+
+ // 系统状态
+ SystemStatus struct {
+ DatabaseStatus string `json:"database_status"`
+ RedisStatus string `json:"redis_status"`
+ StorageStatus string `json:"storage_status"`
+ QueueStatus string `json:"queue_status"`
+ } `json:"system_status"`
+}
+```
+
+### 2.2 照片管理模块 (Photo Management)
+
+#### 2.2.1 照片列表页面
+```
+┌─────────────────────────────────────────────────────────┐
+│ 照片管理 Photo Management │
+├─────────────────────────────────────────────────────────┤
+│ 🔍 [搜索框] 📂 [分类筛选] 🏷️ [标签筛选] 📅 [时间筛选] │
+│ ➕ 上传照片 📤 批量操作 ⚙️ 设置 │
+├─────────────────────────────────────────────────────────┤
+│ 📷 照片网格 (支持列表/网格视图切换) │
+│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
+│ │ [缩略图] │ │ [缩略图] │ │ [缩略图] │ │ [缩略图] │ │
+│ │ 标题 │ │ 标题 │ │ 标题 │ │ 标题 │ │
+│ │ 分类 │ │ 分类 │ │ 分类 │ │ 分类 │ │
+│ │ 日期 │ │ 日期 │ │ 日期 │ │ 日期 │ │
+│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
+│ │
+│ [更多照片...] │
+│ │
+│ ⬅️ 上一页 [1] [2] [3] ... [10] 下一页 ➡️ │
+└─────────────────────────────────────────────────────────┘
+```
+
+#### 2.2.2 照片详情/编辑页面
+```
+┌─────────────────────────────────────────────────────────┐
+│ 照片详情 Photo Detail │
+├─────────────────────────────────────────────────────────┤
+│ ⬅️ 返回列表 📝 编辑模式 🗑️ 删除 💾 保存 │
+├─────────────────────────────────────────────────────────┤
+│ 📸 图片预览 │ 📋 基本信息 │
+│ ┌─────────────────────────────┐ │ ┌─────────────────────┐ │
+│ │ │ │ │ 标题: [输入框] │ │
+│ │ [原图显示] │ │ │ 描述: [文本框] │ │
+│ │ │ │ │ 分类: [下拉选择] │ │
+│ │ │ │ │ 标签: [标签输入] │ │
+│ │ │ │ │ 状态: [开关] │ │
+│ │ │ │ │ 拍摄时间: [日期] │ │
+│ │ │ │ │ 位置: [输入框] │ │
+│ │ │ │ └─────────────────────┘ │
+│ └─────────────────────────────┘ │ │
+│ │ 📷 EXIF信息 │
+│ 🎨 图片处理 │ ┌─────────────────────┐ │
+│ ┌─────────────────────────────┐ │ │ 相机: Canon EOS R5 │ │
+│ │ [裁剪] [旋转] [滤镜] [调色] │ │ │ 镜头: 24-70mm f/2.8 │ │
+│ │ [缩放] [水印] [格式转换] │ │ │ 光圈: f/2.8 │ │
+│ └─────────────────────────────┘ │ │ 快门: 1/60s │ │
+│ │ │ ISO: 400 │ │
+│ │ │ 焦距: 35mm │ │
+│ │ └─────────────────────┘ │
+└─────────────────────────────────────────────────────────┘
+```
+
+#### 2.2.3 批量上传组件
+```
+┌─────────────────────────────────────────────────────────┐
+│ 批量上传 Batch Upload │
+├─────────────────────────────────────────────────────────┤
+│ 📁 拖拽上传区域 │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ 📸 拖拽图片到这里 或 点击选择文件 │ │
+│ │ 支持 JPG, PNG, RAW, TIFF │ │
+│ │ 最大文件大小: 50MB │ │
+│ │ 最多同时上传: 20个文件 │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ │
+│ 📋 上传队列 │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ 📸 IMG_001.jpg [██████████] 100% ✅ 完成 │ │
+│ │ 📸 IMG_002.jpg [█████████▒] 90% ⏳ 上传中 │ │
+│ │ 📸 IMG_003.jpg [██▒▒▒▒▒▒▒▒] 20% ⏳ 上传中 │ │
+│ │ 📸 IMG_004.jpg [▒▒▒▒▒▒▒▒▒▒] 0% ⏸️ 等待 │ │
+│ │ 📸 IMG_005.jpg [▒▒▒▒▒▒▒▒▒▒] 0% ⏸️ 等待 │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ │
+│ ⚙️ 批量设置 │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ 默认分类: [下拉选择] │ │
+│ │ 默认标签: [标签输入] │ │
+│ │ 图片质量: [滑块] 85% │ │
+│ │ 生成缩略图: [✅] 生成WebP格式: [✅] │ │
+│ │ 提取EXIF: [✅] 自动分类: [✅] │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ │
+│ 🎯 操作按钮 │
+│ [⏸️ 暂停全部] [▶️ 继续全部] [🗑️ 清空队列] [⚙️ 高级设置] │
+└─────────────────────────────────────────────────────────┘
+```
+
+### 2.3 分类管理模块 (Category Management)
+
+#### 2.3.1 分类树形结构
+```
+┌─────────────────────────────────────────────────────────┐
+│ 分类管理 Category Management │
+├─────────────────────────────────────────────────────────┤
+│ ➕ 新建分类 📊 分类统计 🔄 重新排序 ⚙️ 批量操作 │
+├─────────────────────────────────────────────────────────┤
+│ 📂 分类树 (拖拽排序) │ 📋 分类详情 │
+│ ┌─────────────────────────────────┐ │ ┌─────────────────┐ │
+│ │ 📁 风景摄影 (125) │ │ │ 分类名: 风景摄影 │ │
+│ │ ├─ 🏔️ 山川 (45) │ │ │ 父分类: 无 │ │
+│ │ ├─ 🌊 海岸 (32) │ │ │ 描述: [文本框] │ │
+│ │ ├─ 🌲 森林 (28) │ │ │ 颜色: [🔴] │ │
+│ │ └─ 🌅 日出日落 (20) │ │ │ 封面: [选择图片] │ │
+│ │ │ │ │ 状态: [✅] 启用 │ │
+│ │ 📁 人像摄影 (89) │ │ │ 排序: 1 │ │
+│ │ ├─ 👤 肖像 (34) │ │ │ 创建时间: 2024-01│ │
+│ │ ├─ 👥 群像 (28) │ │ │ 照片数量: 125 │ │
+│ │ ├─ 💃 艺术 (15) │ │ └─────────────────┘ │
+│ │ └─ 📸 街头 (12) │ │ │
+│ │ │ │ 🎨 操作 │
+│ │ 📁 建筑摄影 (67) │ │ ┌─────────────────┐ │
+│ │ ├─ 🏢 现代建筑 (23) │ │ │ [📝 编辑] │ │
+│ │ ├─ 🏛️ 古建筑 (22) │ │ │ [👁️ 查看照片] │ │
+│ │ ├─ 🌃 夜景 (12) │ │ │ [📊 统计] │ │
+│ │ └─ 🏘️ 城市风光 (10) │ │ │ [🗑️ 删除] │ │
+│ │ │ │ └─────────────────┘ │
+│ └─────────────────────────────────┘ │ │
+└─────────────────────────────────────────────────────────┘
+```
+
+#### 2.3.2 分类编辑表单
+```
+┌─────────────────────────────────────────────────────────┐
+│ 编辑分类 Edit Category │
+├─────────────────────────────────────────────────────────┤
+│ 📝 基本信息 │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ 分类名称: [输入框] *必填 │ │
+│ │ 父分类: [下拉选择] - 选择父分类 │ │
+│ │ 分类描述: [文本框] - 详细描述 │ │
+│ │ 分类颜色: [🔴🟡🟢🔵🟣] - 选择标识色 │ │
+│ │ 排序权重: [数字输入] - 数字越小排序越前 │ │
+│ │ 状态: [开关] 启用/禁用 │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ │
+│ 🖼️ 封面设置 │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ 封面图片: [选择图片] [上传新图片] │ │
+│ │ ┌─────────────────┐ │ │
+│ │ │ [预览图片] │ │ │
+│ │ │ │ │ │
+│ │ └─────────────────┘ │ │
+│ │ 图片尺寸: 建议 400x300 像素 │ │
+│ │ 文件大小: 不超过 2MB │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ │
+│ ⚙️ 高级设置 │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ SEO设置: │ │
+│ │ URL别名: [输入框] - 用于URL路径 │ │
+│ │ Meta描述: [文本框] - 搜索引擎描述 │ │
+│ │ 关键词: [标签输入] - SEO关键词 │ │
+│ │ │ │
+│ │ 权限设置: │ │
+│ │ 可见性: [公开/私有/仅登录用户] │ │
+│ │ 管理权限: [仅管理员/编辑者/所有用户] │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ │
+│ 💾 操作按钮 │
+│ [💾 保存] [🔄 重置] [👁️ 预览] [❌ 取消] │
+└─────────────────────────────────────────────────────────┘
+```
+
+### 2.4 标签管理模块 (Tag Management)
+
+#### 2.4.1 标签云展示
+```
+┌─────────────────────────────────────────────────────────┐
+│ 标签管理 Tag Management │
+├─────────────────────────────────────────────────────────┤
+│ ➕ 新建标签 📊 使用统计 🔄 批量操作 🔍 搜索标签 │
+├─────────────────────────────────────────────────────────┤
+│ ☁️ 标签云 (按使用频率显示) │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ 自然 (120) 日落 (89) 肖像 (76) │ │
+│ │ 城市 (145) 风景 (156) 黑白 (45) 建筑 (67) │ │
+│ │ 艺术 (34) 旅行 (123) 海岸 (32) │ │
+│ │ 山川 (45) 夜景 (28) 街头 (23) 现代 (56) │ │
+│ │ 森林 (28) 抽象 (19) 光影 (67) │ │
+│ │ 情感 (23) 色彩 (89) 构图 (45) 纹理 (34) │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ │
+│ 📊 使用统计 │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ 🔥 热门标签 │ │
+│ │ 1. 风景 (156张) ████████████████████████████████ │ │
+│ │ 2. 城市 (145张) ████████████████████████████████ │ │
+│ │ 3. 旅行 (123张) ████████████████████████████ │ │
+│ │ 4. 自然 (120张) ████████████████████████████ │ │
+│ │ 5. 日落 (89张) ████████████████████████ │ │
+│ │ │ │
+│ │ 📈 增长趋势 │ │
+│ │ 本周新增: 12个标签 │ │
+│ │ 本月活跃: 45个标签 │ │
+│ │ 平均每张照片: 3.2个标签 │ │
+│ └─────────────────────────────────────────────────────┘ │
+└─────────────────────────────────────────────────────────┘
+```
+
+#### 2.4.2 标签列表管理
+```
+┌─────────────────────────────────────────────────────────┐
+│ 标签列表 Tag List │
+├─────────────────────────────────────────────────────────┤
+│ 🔍 [搜索标签] 📊 [按使用量排序] 🏷️ [按颜色筛选] [批量] │
+├─────────────────────────────────────────────────────────┤
+│ 标签名 │ 颜色 │ 使用数量 │ 创建时间 │ 状态 │ 操作 │
+│ ─────────┼──────┼─────────┼──────────┼─────┼────── │
+│ 风景 │ 🟢 │ 156张 │ 2024-01-01│ 启用 │ 编辑 │
+│ 城市 │ 🔵 │ 145张 │ 2024-01-01│ 启用 │ 编辑 │
+│ 旅行 │ 🟡 │ 123张 │ 2024-01-02│ 启用 │ 编辑 │
+│ 自然 │ 🟢 │ 120张 │ 2024-01-01│ 启用 │ 编辑 │
+│ 日落 │ 🟠 │ 89张 │ 2024-01-03│ 启用 │ 编辑 │
+│ 色彩 │ 🎨 │ 89张 │ 2024-01-05│ 启用 │ 编辑 │
+│ 肖像 │ 🟣 │ 76张 │ 2024-01-02│ 启用 │ 编辑 │
+│ 光影 │ ⚪ │ 67张 │ 2024-01-04│ 启用 │ 编辑 │
+│ 建筑 │ 🔴 │ 67张 │ 2024-01-03│ 启用 │ 编辑 │
+│ 现代 │ 🔘 │ 56张 │ 2024-01-06│ 启用 │ 编辑 │
+├─────────────────────────────────────────────────────────┤
+│ ⬅️ 上一页 [1] [2] [3] ... [10] 下一页 ➡️ │
+└─────────────────────────────────────────────────────────┘
+```
+
+### 2.5 用户管理模块 (User Management)
+
+#### 2.5.1 用户列表页面
+```
+┌─────────────────────────────────────────────────────────┐
+│ 用户管理 User Management │
+├─────────────────────────────────────────────────────────┤
+│ ➕ 新建用户 👥 角色管理 🔍 搜索用户 📊 用户统计 │
+├─────────────────────────────────────────────────────────┤
+│ 用户名 │ 邮箱 │ 角色 │ 状态 │ 最后登录 │ 操作 │
+│ ─────────┼──────────┼───────┼─────┼─────────┼────── │
+│ admin │ admin@.. │ 管理员 │ 🟢活跃│ 2小时前 │ 编辑 │
+│ editor │ editor@.. │ 编辑者 │ 🟢活跃│ 1天前 │ 编辑 │
+│ user001 │ user001@..│ 用户 │ 🟡离线│ 3天前 │ 编辑 │
+│ user002 │ user002@..│ 用户 │ 🔴禁用│ 1周前 │ 编辑 │
+│ guest │ guest@.. │ 访客 │ 🟢活跃│ 5分钟前 │ 编辑 │
+└─────────────────────────────────────────────────────────┘
+```
+
+#### 2.5.2 用户编辑表单
+```
+┌─────────────────────────────────────────────────────────┐
+│ 编辑用户 Edit User │
+├─────────────────────────────────────────────────────────┤
+│ 👤 基本信息 │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ 用户名: [输入框] *必填 │ │
+│ │ 邮箱: [输入框] *必填 │ │
+│ │ 姓名: [输入框] 显示名称 │ │
+│ │ 头像: [选择图片] [上传新头像] │ │
+│ │ 角色: [下拉选择] 管理员/编辑者/用户/访客 │ │
+│ │ 状态: [开关] 启用/禁用 │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ │
+│ 🔐 权限设置 │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ 照片管理: [✅] 查看 [✅] 创建 [✅] 编辑 [❌] 删除 │ │
+│ │ 分类管理: [✅] 查看 [✅] 创建 [❌] 编辑 [❌] 删除 │ │
+│ │ 标签管理: [✅] 查看 [✅] 创建 [❌] 编辑 [❌] 删除 │ │
+│ │ 用户管理: [❌] 查看 [❌] 创建 [❌] 编辑 [❌] 删除 │ │
+│ │ 系统设置: [❌] 查看 [❌] 编辑 │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ │
+│ 📊 用户统计 │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ 注册时间: 2024-01-15 │ │
+│ │ 最后登录: 2024-01-20 14:30 │ │
+│ │ 登录次数: 25次 │ │
+│ │ 上传照片: 45张 │ │
+│ │ 创建分类: 3个 │ │
+│ │ 创建标签: 12个 │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ │
+│ 💾 操作按钮 │
+│ [💾 保存] [🔄 重置密码] [🚫 禁用用户] [❌ 取消] │
+└─────────────────────────────────────────────────────────┘
+```
+
+### 2.6 系统设置模块 (System Settings)
+
+#### 2.6.1 网站配置
+```
+┌─────────────────────────────────────────────────────────┐
+│ 系统设置 System Settings │
+├─────────────────────────────────────────────────────────┤
+│ 🌐 网站配置 📁 文件设置 🎨 主题设置 💾 缓存设置 │
+├─────────────────────────────────────────────────────────┤
+│ 🌐 网站基本信息 │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ 网站名称: [输入框] 摄影作品集 │ │
+│ │ 网站描述: [文本框] 专业摄影作品展示平台 │ │
+│ │ 网站关键词: [标签输入] 摄影,作品集,艺术 │ │
+│ │ 网站Logo: [选择图片] [上传新Logo] │ │
+│ │ 网站图标: [选择图片] [上传Favicon] │ │
+│ │ 联系邮箱: [输入框] admin@photography.com │ │
+│ │ 版权信息: [输入框] © 2024 Photography Portfolio │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ │
+│ 📁 文件上传设置 │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ 允许格式: [多选] JPG PNG GIF WEBP RAW TIFF │ │
+│ │ 最大大小: [数字输入] 50 MB │ │
+│ │ 图片质量: [滑块] 85% │ │
+│ │ 缩略图尺寸: [输入框] 300x300 │ │
+│ │ 水印设置: [开关] 启用 [文本/图片] [位置选择] │ │
+│ │ 自动压缩: [开关] 启用 │ │
+│ │ 存储方式: [单选] 本地存储/MinIO/AWS S3 │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ │
+│ 🎨 主题设置 │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ 默认主题: [下拉选择] 浅色主题 │ │
+│ │ 主色调: [颜色选择器] #3b82f6 │ │
+│ │ 辅助色: [颜色选择器] #6b7280 │ │
+│ │ 背景色: [颜色选择器] #ffffff │ │
+│ │ 字体设置: [下拉选择] 系统默认 │ │
+│ │ 布局样式: [单选] 网格布局/列表布局/瀑布流 │ │
+│ └─────────────────────────────────────────────────────┘ │
+└─────────────────────────────────────────────────────────┘
+```
+
+### 2.7 日志管理模块 (Log Management)
+
+#### 2.7.1 日志查看器
+```
+┌─────────────────────────────────────────────────────────┐
+│ 日志管理 Log Management │
+├─────────────────────────────────────────────────────────┤
+│ 🔍 [搜索] 📊 [级别筛选] 📅 [时间范围] 🔄 [自动刷新] │
+├─────────────────────────────────────────────────────────┤
+│ 时间 │ 级别 │ 模块 │ 消息 │
+│ ───────────┼─────┼────────┼─────────────────────── │
+│ 14:30:25 │ INFO │ Upload │ 文件上传成功: IMG_001.jpg │
+│ 14:30:20 │ WARN │ Cache │ Redis连接超时,使用备用缓存 │
+│ 14:30:15 │ ERROR│ DB │ 数据库查询超时 │
+│ 14:30:10 │ DEBUG│ API │ GET /api/photos 200 │
+│ 14:30:05 │ INFO │ Auth │ 用户admin登录成功 │
+│ 14:30:00 │ INFO │ System │ 系统启动完成 │
+├─────────────────────────────────────────────────────────┤
+│ 📊 统计信息 │
+│ 总计: 1,234条 | 错误: 12条 | 警告: 45条 | 信息: 1,177条 │
+└─────────────────────────────────────────────────────────┘
+```
+
+## 3. 技术实现方案
+
+### 3.1 前端架构
+
+#### 3.1.1 项目结构
+```
+admin/
+├── src/
+│ ├── components/ # 通用组件
+│ │ ├── Layout/ # 布局组件
+│ │ ├── Form/ # 表单组件
+│ │ ├── Table/ # 表格组件
+│ │ ├── Upload/ # 上传组件
+│ │ └── Chart/ # 图表组件
+│ ├── pages/ # 页面组件
+│ │ ├── Dashboard/ # 仪表板
+│ │ ├── Photos/ # 照片管理
+│ │ ├── Categories/ # 分类管理
+│ │ ├── Tags/ # 标签管理
+│ │ ├── Users/ # 用户管理
+│ │ ├── Settings/ # 系统设置
+│ │ └── Logs/ # 日志管理
+│ ├── services/ # API服务
+│ ├── stores/ # 状态管理
+│ ├── utils/ # 工具函数
+│ ├── types/ # TypeScript类型
+│ └── styles/ # 样式文件
+├── public/ # 静态资源
+├── package.json
+├── tsconfig.json
+└── tailwind.config.js
+```
+
+#### 3.1.2 核心依赖
+```json
+{
+ "dependencies": {
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-router-dom": "^6.8.0",
+ "typescript": "^4.9.0",
+ "tailwindcss": "^3.2.0",
+ "@radix-ui/react-dialog": "^1.0.2",
+ "@radix-ui/react-dropdown-menu": "^2.0.1",
+ "@radix-ui/react-select": "^1.2.0",
+ "lucide-react": "^0.263.0",
+ "zustand": "^4.3.0",
+ "axios": "^1.3.0",
+ "react-query": "^3.39.0",
+ "react-hook-form": "^7.43.0",
+ "zod": "^3.20.0",
+ "recharts": "^2.5.0",
+ "react-dropzone": "^14.2.0"
+ }
+}
+```
+
+### 3.2 后端API设计
+
+#### 3.2.1 RESTful API 结构
+```
+/api/admin/
+├── auth/ # 认证相关
+│ ├── POST /login # 登录
+│ ├── POST /logout # 登出
+│ ├── POST /refresh # 刷新token
+│ └── GET /profile # 用户信息
+├── dashboard/ # 仪表板
+│ ├── GET /stats # 统计数据
+│ └── GET /activities # 近期活动
+├── photos/ # 照片管理
+│ ├── GET / # 照片列表
+│ ├── POST / # 创建照片
+│ ├── GET /:id # 照片详情
+│ ├── PUT /:id # 更新照片
+│ ├── DELETE /:id # 删除照片
+│ ├── POST /upload # 上传文件
+│ └── POST /batch # 批量操作
+├── categories/ # 分类管理
+│ ├── GET / # 分类列表
+│ ├── POST / # 创建分类
+│ ├── GET /:id # 分类详情
+│ ├── PUT /:id # 更新分类
+│ ├── DELETE /:id # 删除分类
+│ └── PUT /reorder # 重新排序
+├── tags/ # 标签管理
+│ ├── GET / # 标签列表
+│ ├── POST / # 创建标签
+│ ├── GET /:id # 标签详情
+│ ├── PUT /:id # 更新标签
+│ ├── DELETE /:id # 删除标签
+│ └── GET /suggestions # 标签建议
+├── users/ # 用户管理
+│ ├── GET / # 用户列表
+│ ├── POST / # 创建用户
+│ ├── GET /:id # 用户详情
+│ ├── PUT /:id # 更新用户
+│ ├── DELETE /:id # 删除用户
+│ └── PUT /:id/password # 修改密码
+├── settings/ # 系统设置
+│ ├── GET / # 获取设置
+│ ├── PUT / # 更新设置
+│ └── POST /test # 测试配置
+└── logs/ # 日志管理
+ ├── GET / # 日志列表
+ └── GET /stats # 日志统计
+```
+
+#### 3.2.2 数据模型定义
+```go
+// 照片模型
+type Photo struct {
+ ID uint `json:"id" gorm:"primaryKey"`
+ Title string `json:"title" gorm:"size:255;not null"`
+ Description string `json:"description" gorm:"type:text"`
+ Filename string `json:"filename" gorm:"size:255;not null"`
+ FilePath string `json:"file_path" gorm:"size:500;not null"`
+ FileSize int64 `json:"file_size"`
+ MimeType string `json:"mime_type" gorm:"size:100"`
+ Width int `json:"width"`
+ Height int `json:"height"`
+ CategoryID uint `json:"category_id"`
+ Category Category `json:"category" gorm:"foreignKey:CategoryID"`
+ Tags []Tag `json:"tags" gorm:"many2many:photo_tags;"`
+ EXIF string `json:"exif" gorm:"type:jsonb"`
+ TakenAt time.Time `json:"taken_at"`
+ Location string `json:"location" gorm:"size:255"`
+ IsPublic bool `json:"is_public" gorm:"default:true"`
+ Status string `json:"status" gorm:"size:20;default:'draft'"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+}
+
+// 分类模型
+type Category struct {
+ ID uint `json:"id" gorm:"primaryKey"`
+ Name string `json:"name" gorm:"size:100;not null"`
+ Description string `json:"description" gorm:"type:text"`
+ ParentID *uint `json:"parent_id"`
+ Parent *Category `json:"parent" gorm:"foreignKey:ParentID"`
+ Children []Category `json:"children" gorm:"foreignKey:ParentID"`
+ Color string `json:"color" gorm:"size:7;default:'#3b82f6'"`
+ CoverImage string `json:"cover_image" gorm:"size:500"`
+ Sort int `json:"sort" gorm:"default:0"`
+ IsActive bool `json:"is_active" gorm:"default:true"`
+ PhotoCount int `json:"photo_count" gorm:"-"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+}
+
+// 标签模型
+type Tag struct {
+ ID uint `json:"id" gorm:"primaryKey"`
+ Name string `json:"name" gorm:"size:50;not null;unique"`
+ Color string `json:"color" gorm:"size:7;default:'#6b7280'"`
+ UseCount int `json:"use_count" gorm:"default:0"`
+ IsActive bool `json:"is_active" gorm:"default:true"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+}
+
+// 用户模型
+type User struct {
+ ID uint `json:"id" gorm:"primaryKey"`
+ Username string `json:"username" gorm:"size:50;not null;unique"`
+ Email string `json:"email" gorm:"size:100;not null;unique"`
+ Password string `json:"-" gorm:"size:255;not null"`
+ Name string `json:"name" gorm:"size:100"`
+ Avatar string `json:"avatar" gorm:"size:500"`
+ Role string `json:"role" gorm:"size:20;default:'user'"`
+ IsActive bool `json:"is_active" gorm:"default:true"`
+ LastLogin time.Time `json:"last_login"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+}
+```
+
+### 3.3 数据库设计
+
+#### 3.3.1 核心表结构
+```sql
+-- 照片表
+CREATE TABLE photos (
+ id SERIAL PRIMARY KEY,
+ title VARCHAR(255) NOT NULL,
+ description TEXT,
+ filename VARCHAR(255) NOT NULL,
+ file_path VARCHAR(500) NOT NULL,
+ file_size BIGINT,
+ mime_type VARCHAR(100),
+ width INTEGER,
+ height INTEGER,
+ category_id INTEGER REFERENCES categories(id),
+ exif JSONB,
+ taken_at TIMESTAMP,
+ location VARCHAR(255),
+ is_public BOOLEAN DEFAULT true,
+ status VARCHAR(20) DEFAULT 'draft',
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+-- 分类表
+CREATE TABLE categories (
+ id SERIAL PRIMARY KEY,
+ name VARCHAR(100) NOT NULL,
+ description TEXT,
+ parent_id INTEGER REFERENCES categories(id),
+ color VARCHAR(7) DEFAULT '#3b82f6',
+ cover_image VARCHAR(500),
+ sort INTEGER DEFAULT 0,
+ is_active BOOLEAN DEFAULT true,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+-- 标签表
+CREATE TABLE tags (
+ id SERIAL PRIMARY KEY,
+ name VARCHAR(50) NOT NULL UNIQUE,
+ color VARCHAR(7) DEFAULT '#6b7280',
+ use_count INTEGER DEFAULT 0,
+ is_active BOOLEAN DEFAULT true,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+-- 照片标签关联表
+CREATE TABLE photo_tags (
+ photo_id INTEGER REFERENCES photos(id) ON DELETE CASCADE,
+ tag_id INTEGER REFERENCES tags(id) ON DELETE CASCADE,
+ PRIMARY KEY (photo_id, tag_id)
+);
+
+-- 用户表
+CREATE TABLE users (
+ id SERIAL PRIMARY KEY,
+ username VARCHAR(50) NOT NULL UNIQUE,
+ email VARCHAR(100) NOT NULL UNIQUE,
+ password VARCHAR(255) NOT NULL,
+ name VARCHAR(100),
+ avatar VARCHAR(500),
+ role VARCHAR(20) DEFAULT 'user',
+ is_active BOOLEAN DEFAULT true,
+ last_login TIMESTAMP,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+-- 系统设置表
+CREATE TABLE settings (
+ id SERIAL PRIMARY KEY,
+ key VARCHAR(100) NOT NULL UNIQUE,
+ value TEXT,
+ type VARCHAR(20) DEFAULT 'string',
+ group_name VARCHAR(50),
+ description TEXT,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+-- 操作日志表
+CREATE TABLE activity_logs (
+ id SERIAL PRIMARY KEY,
+ user_id INTEGER REFERENCES users(id),
+ action VARCHAR(50) NOT NULL,
+ resource_type VARCHAR(50),
+ resource_id INTEGER,
+ details JSONB,
+ ip_address INET,
+ user_agent TEXT,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+```
+
+### 3.4 认证与权限
+
+#### 3.4.1 JWT认证流程
+```
+1. 用户登录 → 验证账号密码
+2. 生成JWT Token → 包含用户信息和权限
+3. 前端存储Token → localStorage/sessionStorage
+4. 请求携带Token → Authorization Header
+5. 后端验证Token → 中间件验证
+6. 权限检查 → 基于角色的访问控制
+```
+
+#### 3.4.2 角色权限定义
+```go
+// 角色定义
+const (
+ RoleAdmin = "admin" // 管理员 - 所有权限
+ RoleEditor = "editor" // 编辑者 - 内容管理权限
+ RoleUser = "user" // 用户 - 基本权限
+ RoleGuest = "guest" // 访客 - 只读权限
+)
+
+// 权限定义
+type Permission struct {
+ Resource string // 资源类型
+ Actions []string // 允许的操作
+}
+
+// 角色权限映射
+var RolePermissions = map[string][]Permission{
+ RoleAdmin: {
+ {Resource: "photos", Actions: []string{"create", "read", "update", "delete"}},
+ {Resource: "categories", Actions: []string{"create", "read", "update", "delete"}},
+ {Resource: "tags", Actions: []string{"create", "read", "update", "delete"}},
+ {Resource: "users", Actions: []string{"create", "read", "update", "delete"}},
+ {Resource: "settings", Actions: []string{"read", "update"}},
+ {Resource: "logs", Actions: []string{"read"}},
+ },
+ RoleEditor: {
+ {Resource: "photos", Actions: []string{"create", "read", "update", "delete"}},
+ {Resource: "categories", Actions: []string{"create", "read", "update"}},
+ {Resource: "tags", Actions: []string{"create", "read", "update"}},
+ },
+ RoleUser: {
+ {Resource: "photos", Actions: []string{"read"}},
+ {Resource: "categories", Actions: []string{"read"}},
+ {Resource: "tags", Actions: []string{"read"}},
+ },
+ RoleGuest: {
+ {Resource: "photos", Actions: []string{"read"}},
+ {Resource: "categories", Actions: []string{"read"}},
+ },
+}
+```
+
+### 3.5 文件上传与处理
+
+#### 3.5.1 上传流程
+```
+1. 前端选择文件 → 文件验证 (格式、大小)
+2. 创建上传任务 → 生成临时文件路径
+3. 分块上传 → 支持断点续传
+4. 文件合并 → 合并所有分块
+5. 图片处理 → 生成缩略图、提取EXIF
+6. 存储文件 → 本地存储或云存储
+7. 更新数据库 → 保存文件信息
+8. 返回结果 → 上传成功确认
+```
+
+#### 3.5.2 图片处理
+```go
+// 图片处理服务
+type ImageProcessor struct {
+ storage StorageService
+ thumbnails []ThumbnailConfig
+ watermark WatermarkConfig
+ quality int
+}
+
+// 缩略图配置
+type ThumbnailConfig struct {
+ Name string
+ Width int
+ Height int
+ Crop bool
+}
+
+// 处理上传的图片
+func (p *ImageProcessor) ProcessImage(file *multipart.FileHeader) (*ImageResult, error) {
+ // 1. 验证文件格式
+ if !p.isValidImage(file) {
+ return nil, ErrInvalidImageFormat
+ }
+
+ // 2. 打开图片
+ img, err := bimg.NewFromFile(file.Filename)
+ if err != nil {
+ return nil, err
+ }
+
+ // 3. 提取EXIF信息
+ exif, err := p.extractEXIF(img)
+ if err != nil {
+ logger.Warn("Failed to extract EXIF", "error", err)
+ }
+
+ // 4. 生成缩略图
+ thumbnails := make(map[string]string)
+ for _, config := range p.thumbnails {
+ thumbnail, err := p.createThumbnail(img, config)
+ if err != nil {
+ logger.Error("Failed to create thumbnail", "config", config, "error", err)
+ continue
+ }
+ thumbnails[config.Name] = thumbnail
+ }
+
+ // 5. 添加水印 (可选)
+ if p.watermark.Enabled {
+ img, err = p.addWatermark(img, p.watermark)
+ if err != nil {
+ logger.Warn("Failed to add watermark", "error", err)
+ }
+ }
+
+ // 6. 保存原图
+ processed, err := img.Process(bimg.Options{
+ Quality: p.quality,
+ Type: bimg.JPEG,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ // 7. 上传到存储
+ originalPath, err := p.storage.Save(processed, "originals")
+ if err != nil {
+ return nil, err
+ }
+
+ return &ImageResult{
+ OriginalPath: originalPath,
+ Thumbnails: thumbnails,
+ EXIF: exif,
+ Width: img.Size().Width,
+ Height: img.Size().Height,
+ }, nil
+}
+```
+
+### 3.6 性能优化
+
+#### 3.6.1 前端优化
+```javascript
+// 1. 代码分割
+const LazyPhotoManagement = lazy(() => import('./pages/Photos'));
+const LazyDashboard = lazy(() => import('./pages/Dashboard'));
+
+// 2. 虚拟滚动 (大量照片列表)
+import { FixedSizeGrid } from 'react-window';
+
+// 3. 图片懒加载
+const LazyImage = ({ src, alt, ...props }) => {
+ const [isLoaded, setIsLoaded] = useState(false);
+ const [isInView, setIsInView] = useState(false);
+ const imgRef = useRef();
+
+ useEffect(() => {
+ const observer = new IntersectionObserver(
+ ([entry]) => {
+ if (entry.isIntersecting) {
+ setIsInView(true);
+ observer.disconnect();
+ }
+ },
+ { threshold: 0.1 }
+ );
+
+ if (imgRef.current) {
+ observer.observe(imgRef.current);
+ }
+
+ return () => observer.disconnect();
+ }, []);
+
+ return (
+
+ {isInView && (
+

setIsLoaded(true)}
+ style={{ opacity: isLoaded ? 1 : 0 }}
+ />
+ )}
+
+ );
+};
+
+// 4. 搜索防抖
+const useDebounce = (value, delay) => {
+ const [debouncedValue, setDebouncedValue] = useState(value);
+
+ useEffect(() => {
+ const handler = setTimeout(() => {
+ setDebouncedValue(value);
+ }, delay);
+
+ return () => {
+ clearTimeout(handler);
+ };
+ }, [value, delay]);
+
+ return debouncedValue;
+};
+```
+
+#### 3.6.2 后端优化
+```go
+// 1. 数据库查询优化
+func (r *PhotoRepository) GetPhotosWithPagination(page, limit int, filters PhotoFilters) ([]Photo, int, error) {
+ var photos []Photo
+ var total int64
+
+ query := r.db.Model(&Photo{}).
+ Preload("Category").
+ Preload("Tags")
+
+ // 添加筛选条件
+ if filters.CategoryID > 0 {
+ query = query.Where("category_id = ?", filters.CategoryID)
+ }
+
+ if len(filters.Tags) > 0 {
+ query = query.Joins("JOIN photo_tags ON photos.id = photo_tags.photo_id").
+ Where("photo_tags.tag_id IN (?)", filters.Tags)
+ }
+
+ if filters.Search != "" {
+ query = query.Where("title ILIKE ? OR description ILIKE ?",
+ "%"+filters.Search+"%", "%"+filters.Search+"%")
+ }
+
+ // 计算总数
+ err := query.Count(&total).Error
+ if err != nil {
+ return nil, 0, err
+ }
+
+ // 分页查询
+ offset := (page - 1) * limit
+ err = query.Offset(offset).Limit(limit).
+ Order("created_at DESC").
+ Find(&photos).Error
+
+ return photos, int(total), err
+}
+
+// 2. Redis缓存
+func (s *PhotoService) GetPhotosByCategory(categoryID uint) ([]Photo, error) {
+ cacheKey := fmt.Sprintf("photos:category:%d", categoryID)
+
+ // 尝试从缓存获取
+ cached, err := s.cache.Get(cacheKey)
+ if err == nil {
+ var photos []Photo
+ if err := json.Unmarshal(cached, &photos); err == nil {
+ return photos, nil
+ }
+ }
+
+ // 从数据库获取
+ photos, err := s.repo.GetPhotosByCategory(categoryID)
+ if err != nil {
+ return nil, err
+ }
+
+ // 缓存结果
+ if data, err := json.Marshal(photos); err == nil {
+ s.cache.Set(cacheKey, data, 10*time.Minute)
+ }
+
+ return photos, nil
+}
+
+// 3. 并发处理
+func (s *PhotoService) ProcessBatchUpload(files []*multipart.FileHeader) error {
+ const maxWorkers = 5
+ jobs := make(chan *multipart.FileHeader, len(files))
+ results := make(chan error, len(files))
+
+ // 启动工作协程
+ for i := 0; i < maxWorkers; i++ {
+ go func() {
+ for file := range jobs {
+ err := s.ProcessSingleUpload(file)
+ results <- err
+ }
+ }()
+ }
+
+ // 发送任务
+ for _, file := range files {
+ jobs <- file
+ }
+ close(jobs)
+
+ // 收集结果
+ var errors []error
+ for i := 0; i < len(files); i++ {
+ if err := <-results; err != nil {
+ errors = append(errors, err)
+ }
+ }
+
+ if len(errors) > 0 {
+ return fmt.Errorf("批量上传失败: %v", errors)
+ }
+
+ return nil
+}
+```
+
+## 4. 部署与监控
+
+### 4.1 Docker部署
+
+#### 4.1.1 Dockerfile
+```dockerfile
+# 前端构建
+FROM node:18-alpine AS frontend-builder
+WORKDIR /app
+COPY admin/package*.json ./
+RUN npm ci
+COPY admin/ .
+RUN npm run build
+
+# 后端构建
+FROM golang:1.21-alpine AS backend-builder
+WORKDIR /app
+COPY go.mod go.sum ./
+RUN go mod download
+COPY . .
+RUN go build -o main cmd/server/main.go
+
+# 生产镜像
+FROM alpine:latest
+RUN apk --no-cache add ca-certificates tzdata
+WORKDIR /root/
+COPY --from=backend-builder /app/main .
+COPY --from=frontend-builder /app/dist ./web
+COPY config/ ./config/
+COPY migrations/ ./migrations/
+EXPOSE 8080
+CMD ["./main"]
+```
+
+#### 4.1.2 docker-compose.yml
+```yaml
+version: '3.8'
+
+services:
+ app:
+ build: .
+ ports:
+ - "8080:8080"
+ depends_on:
+ - postgres
+ - redis
+ environment:
+ - DATABASE_URL=postgres://user:password@postgres:5432/photography
+ - REDIS_URL=redis://redis:6379
+ volumes:
+ - uploads:/app/uploads
+ - logs:/app/logs
+
+ postgres:
+ image: postgres:15
+ environment:
+ POSTGRES_DB: photography
+ POSTGRES_USER: user
+ POSTGRES_PASSWORD: password
+ volumes:
+ - postgres_data:/var/lib/postgresql/data
+
+ redis:
+ image: redis:7-alpine
+ volumes:
+ - redis_data:/data
+
+ nginx:
+ image: nginx:alpine
+ ports:
+ - "80:80"
+ - "443:443"
+ volumes:
+ - ./nginx.conf:/etc/nginx/nginx.conf
+ - ./ssl:/etc/nginx/ssl
+ - uploads:/var/www/uploads
+
+volumes:
+ postgres_data:
+ redis_data:
+ uploads:
+ logs:
+```
+
+### 4.2 监控与日志
+
+#### 4.2.1 健康检查
+```go
+// 健康检查端点
+func (h *HealthHandler) CheckHealth(c *gin.Context) {
+ health := map[string]interface{}{
+ "status": "ok",
+ "timestamp": time.Now(),
+ "services": map[string]interface{}{},
+ }
+
+ // 检查数据库连接
+ if err := h.db.Raw("SELECT 1").Error; err != nil {
+ health["services"]["database"] = map[string]interface{}{
+ "status": "error",
+ "error": err.Error(),
+ }
+ health["status"] = "error"
+ } else {
+ health["services"]["database"] = map[string]interface{}{
+ "status": "ok",
+ }
+ }
+
+ // 检查Redis连接
+ if err := h.redis.Ping().Err(); err != nil {
+ health["services"]["redis"] = map[string]interface{}{
+ "status": "error",
+ "error": err.Error(),
+ }
+ health["status"] = "error"
+ } else {
+ health["services"]["redis"] = map[string]interface{}{
+ "status": "ok",
+ }
+ }
+
+ // 检查存储服务
+ if err := h.storage.HealthCheck(); err != nil {
+ health["services"]["storage"] = map[string]interface{}{
+ "status": "error",
+ "error": err.Error(),
+ }
+ health["status"] = "error"
+ } else {
+ health["services"]["storage"] = map[string]interface{}{
+ "status": "ok",
+ }
+ }
+
+ if health["status"] == "ok" {
+ c.JSON(http.StatusOK, health)
+ } else {
+ c.JSON(http.StatusServiceUnavailable, health)
+ }
+}
+```
+
+#### 4.2.2 日志配置
+```go
+// 日志配置
+func InitLogger(config *Config) *logrus.Logger {
+ logger := logrus.New()
+
+ // 设置日志级别
+ level, err := logrus.ParseLevel(config.Logger.Level)
+ if err != nil {
+ level = logrus.InfoLevel
+ }
+ logger.SetLevel(level)
+
+ // 设置日志格式
+ if config.Logger.Format == "json" {
+ logger.SetFormatter(&logrus.JSONFormatter{
+ TimestampFormat: "2006-01-02 15:04:05",
+ FieldMap: logrus.FieldMap{
+ logrus.FieldKeyTime: "timestamp",
+ logrus.FieldKeyLevel: "level",
+ logrus.FieldKeyMsg: "message",
+ },
+ })
+ } else {
+ logger.SetFormatter(&logrus.TextFormatter{
+ FullTimestamp: true,
+ TimestampFormat: "2006-01-02 15:04:05",
+ })
+ }
+
+ // 设置日志输出
+ if config.Logger.Output == "file" {
+ logFile := &lumberjack.Logger{
+ Filename: config.Logger.Filename,
+ MaxSize: config.Logger.MaxSize,
+ MaxAge: config.Logger.MaxAge,
+ MaxBackups: config.Logger.MaxBackups,
+ LocalTime: true,
+ Compress: config.Logger.Compress,
+ }
+ logger.SetOutput(logFile)
+ }
+
+ return logger
+}
+```
+
+## 5. 测试策略
+
+### 5.1 前端测试
+
+#### 5.1.1 组件测试
+```typescript
+// PhotoCard.test.tsx
+import { render, screen, fireEvent } from '@testing-library/react';
+import { PhotoCard } from './PhotoCard';
+import { Photo } from '../types';
+
+const mockPhoto: Photo = {
+ id: 1,
+ title: 'Test Photo',
+ description: 'Test description',
+ filename: 'test.jpg',
+ thumbnailUrl: '/thumbnails/test.jpg',
+ category: { id: 1, name: 'Test Category' },
+ tags: [{ id: 1, name: 'test' }],
+ createdAt: '2024-01-01T00:00:00Z',
+};
+
+describe('PhotoCard', () => {
+ it('renders photo information correctly', () => {
+ render();
+
+ expect(screen.getByText('Test Photo')).toBeInTheDocument();
+ expect(screen.getByText('Test description')).toBeInTheDocument();
+ expect(screen.getByText('Test Category')).toBeInTheDocument();
+ expect(screen.getByText('test')).toBeInTheDocument();
+ });
+
+ it('calls onEdit when edit button is clicked', () => {
+ const mockOnEdit = jest.fn();
+ render();
+
+ fireEvent.click(screen.getByText('编辑'));
+ expect(mockOnEdit).toHaveBeenCalledWith(mockPhoto);
+ });
+
+ it('calls onDelete when delete button is clicked', () => {
+ const mockOnDelete = jest.fn();
+ render();
+
+ fireEvent.click(screen.getByText('删除'));
+ expect(mockOnDelete).toHaveBeenCalledWith(mockPhoto.id);
+ });
+});
+```
+
+#### 5.1.2 API测试
+```typescript
+// photoService.test.ts
+import { photoService } from './photoService';
+import { mockApi } from '../test/mocks';
+
+describe('PhotoService', () => {
+ beforeEach(() => {
+ mockApi.reset();
+ });
+
+ it('fetches photos successfully', async () => {
+ const mockPhotos = [
+ { id: 1, title: 'Photo 1' },
+ { id: 2, title: 'Photo 2' },
+ ];
+
+ mockApi.onGet('/api/admin/photos').reply(200, {
+ code: 0,
+ data: { photos: mockPhotos, total: 2 },
+ });
+
+ const result = await photoService.getPhotos();
+ expect(result.photos).toEqual(mockPhotos);
+ expect(result.total).toBe(2);
+ });
+
+ it('handles upload with progress', async () => {
+ const mockFile = new File(['test'], 'test.jpg', { type: 'image/jpeg' });
+ const mockProgressCallback = jest.fn();
+
+ mockApi.onPost('/api/admin/photos/upload').reply(200, {
+ code: 0,
+ data: { id: 1, filename: 'test.jpg' },
+ });
+
+ const result = await photoService.uploadPhoto(mockFile, mockProgressCallback);
+ expect(result.id).toBe(1);
+ expect(mockProgressCallback).toHaveBeenCalled();
+ });
+});
+```
+
+### 5.2 后端测试
+
+#### 5.2.1 单元测试
+```go
+// photo_service_test.go
+package service
+
+import (
+ "testing"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/mock"
+ "photography-backend/internal/domain"
+ "photography-backend/internal/repository/mocks"
+)
+
+func TestPhotoService_CreatePhoto(t *testing.T) {
+ // 准备测试数据
+ mockRepo := new(mocks.PhotoRepository)
+ mockStorage := new(mocks.StorageService)
+ service := NewPhotoService(mockRepo, mockStorage)
+
+ photo := &domain.Photo{
+ Title: "Test Photo",
+ Description: "Test description",
+ CategoryID: 1,
+ }
+
+ // 设置mock期望
+ mockRepo.On("Create", mock.AnythingOfType("*domain.Photo")).Return(nil).Run(func(args mock.Arguments) {
+ p := args.Get(0).(*domain.Photo)
+ p.ID = 1
+ })
+
+ // 执行测试
+ result, err := service.CreatePhoto(photo)
+
+ // 验证结果
+ assert.NoError(t, err)
+ assert.Equal(t, uint(1), result.ID)
+ assert.Equal(t, "Test Photo", result.Title)
+ mockRepo.AssertExpectations(t)
+}
+
+func TestPhotoService_GetPhotosByCategory(t *testing.T) {
+ mockRepo := new(mocks.PhotoRepository)
+ mockStorage := new(mocks.StorageService)
+ service := NewPhotoService(mockRepo, mockStorage)
+
+ expectedPhotos := []domain.Photo{
+ {ID: 1, Title: "Photo 1", CategoryID: 1},
+ {ID: 2, Title: "Photo 2", CategoryID: 1},
+ }
+
+ mockRepo.On("GetByCategory", uint(1)).Return(expectedPhotos, nil)
+
+ result, err := service.GetPhotosByCategory(1)
+
+ assert.NoError(t, err)
+ assert.Len(t, result, 2)
+ assert.Equal(t, expectedPhotos[0].Title, result[0].Title)
+ mockRepo.AssertExpectations(t)
+}
+```
+
+#### 5.2.2 集成测试
+```go
+// photo_api_test.go
+package api
+
+import (
+ "bytes"
+ "encoding/json"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+ "github.com/gin-gonic/gin"
+ "github.com/stretchr/testify/assert"
+ "photography-backend/internal/domain"
+ "photography-backend/test/testutils"
+)
+
+func TestPhotoAPI_GetPhotos(t *testing.T) {
+ // 设置测试数据库
+ db := testutils.SetupTestDB()
+ defer testutils.CleanupTestDB(db)
+
+ // 插入测试数据
+ testutils.SeedPhotos(db)
+
+ // 创建测试路由
+ router := gin.New()
+ photoHandler := NewPhotoHandler(db)
+ router.GET("/api/admin/photos", photoHandler.GetPhotos)
+
+ // 创建测试请求
+ req, _ := http.NewRequest("GET", "/api/admin/photos?page=1&limit=10", nil)
+ req.Header.Set("Authorization", "Bearer "+testutils.GetTestToken())
+
+ // 执行请求
+ w := httptest.NewRecorder()
+ router.ServeHTTP(w, req)
+
+ // 验证响应
+ assert.Equal(t, http.StatusOK, w.Code)
+
+ var response struct {
+ Code int `json:"code"`
+ Data struct {
+ Photos []domain.Photo `json:"photos"`
+ Total int `json:"total"`
+ } `json:"data"`
+ }
+
+ err := json.Unmarshal(w.Body.Bytes(), &response)
+ assert.NoError(t, err)
+ assert.Equal(t, 0, response.Code)
+ assert.Greater(t, response.Data.Total, 0)
+ assert.Greater(t, len(response.Data.Photos), 0)
+}
+
+func TestPhotoAPI_CreatePhoto(t *testing.T) {
+ db := testutils.SetupTestDB()
+ defer testutils.CleanupTestDB(db)
+
+ router := gin.New()
+ photoHandler := NewPhotoHandler(db)
+ router.POST("/api/admin/photos", photoHandler.CreatePhoto)
+
+ photoData := map[string]interface{}{
+ "title": "Test Photo",
+ "description": "Test description",
+ "category_id": 1,
+ }
+
+ jsonData, _ := json.Marshal(photoData)
+ req, _ := http.NewRequest("POST", "/api/admin/photos", bytes.NewBuffer(jsonData))
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Authorization", "Bearer "+testutils.GetTestToken())
+
+ w := httptest.NewRecorder()
+ router.ServeHTTP(w, req)
+
+ assert.Equal(t, http.StatusCreated, w.Code)
+
+ var response struct {
+ Code int `json:"code"`
+ Data domain.Photo `json:"data"`
+ }
+
+ err := json.Unmarshal(w.Body.Bytes(), &response)
+ assert.NoError(t, err)
+ assert.Equal(t, 0, response.Code)
+ assert.Equal(t, "Test Photo", response.Data.Title)
+}
+```
+
+## 6. 文档与维护
+
+### 6.1 API文档
+
+#### 6.1.1 OpenAPI规范
+```yaml
+openapi: 3.0.0
+info:
+ title: 摄影作品集管理后台API
+ version: 1.0.0
+ description: 摄影作品集网站管理后台的RESTful API
+
+paths:
+ /api/admin/photos:
+ get:
+ summary: 获取照片列表
+ parameters:
+ - name: page
+ in: query
+ schema:
+ type: integer
+ default: 1
+ - name: limit
+ in: query
+ schema:
+ type: integer
+ default: 20
+ - name: category_id
+ in: query
+ schema:
+ type: integer
+ - name: tags
+ in: query
+ schema:
+ type: array
+ items:
+ type: integer
+ - name: search
+ in: query
+ schema:
+ type: string
+ responses:
+ 200:
+ description: 成功获取照片列表
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ code:
+ type: integer
+ example: 0
+ message:
+ type: string
+ example: success
+ data:
+ type: object
+ properties:
+ photos:
+ type: array
+ items:
+ $ref: '#/components/schemas/Photo'
+ total:
+ type: integer
+ example: 100
+ page:
+ type: integer
+ example: 1
+ limit:
+ type: integer
+ example: 20
+ post:
+ summary: 创建照片
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PhotoCreate'
+ responses:
+ 201:
+ description: 成功创建照片
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ code:
+ type: integer
+ example: 0
+ message:
+ type: string
+ example: success
+ data:
+ $ref: '#/components/schemas/Photo'
+
+components:
+ schemas:
+ Photo:
+ type: object
+ properties:
+ id:
+ type: integer
+ example: 1
+ title:
+ type: string
+ example: "Beautiful Sunset"
+ description:
+ type: string
+ example: "A beautiful sunset over the ocean"
+ filename:
+ type: string
+ example: "sunset.jpg"
+ file_path:
+ type: string
+ example: "/uploads/2024/01/sunset.jpg"
+ file_size:
+ type: integer
+ example: 1024000
+ width:
+ type: integer
+ example: 1920
+ height:
+ type: integer
+ example: 1080
+ category:
+ $ref: '#/components/schemas/Category'
+ tags:
+ type: array
+ items:
+ $ref: '#/components/schemas/Tag'
+ created_at:
+ type: string
+ format: date-time
+ example: "2024-01-01T00:00:00Z"
+ updated_at:
+ type: string
+ format: date-time
+ example: "2024-01-01T00:00:00Z"
+
+ PhotoCreate:
+ type: object
+ required:
+ - title
+ - category_id
+ properties:
+ title:
+ type: string
+ example: "Beautiful Sunset"
+ description:
+ type: string
+ example: "A beautiful sunset over the ocean"
+ category_id:
+ type: integer
+ example: 1
+ tag_ids:
+ type: array
+ items:
+ type: integer
+ example: [1, 2, 3]
+
+ Category:
+ type: object
+ properties:
+ id:
+ type: integer
+ example: 1
+ name:
+ type: string
+ example: "Landscape"
+ description:
+ type: string
+ example: "Landscape photography"
+ parent_id:
+ type: integer
+ nullable: true
+ example: null
+ color:
+ type: string
+ example: "#3b82f6"
+ is_active:
+ type: boolean
+ example: true
+
+ Tag:
+ type: object
+ properties:
+ id:
+ type: integer
+ example: 1
+ name:
+ type: string
+ example: "sunset"
+ color:
+ type: string
+ example: "#f59e0b"
+ use_count:
+ type: integer
+ example: 25
+ is_active:
+ type: boolean
+ example: true
+
+ securitySchemes:
+ bearerAuth:
+ type: http
+ scheme: bearer
+ bearerFormat: JWT
+
+security:
+ - bearerAuth: []
+```
+
+### 6.2 部署文档
+
+#### 6.2.1 生产环境部署
+```bash
+#!/bin/bash
+# deploy.sh - 生产环境部署脚本
+
+set -e
+
+echo "🚀 开始部署摄影作品集管理后台..."
+
+# 1. 拉取最新代码
+echo "📥 拉取最新代码..."
+git pull origin main
+
+# 2. 构建前端
+echo "🔨 构建前端..."
+cd admin
+npm install
+npm run build
+cd ..
+
+# 3. 构建后端
+echo "🔨 构建后端..."
+go mod tidy
+go build -o photography-backend cmd/server/main.go
+
+# 4. 数据库迁移
+echo "🗄️ 执行数据库迁移..."
+./photography-backend migrate
+
+# 5. 重启服务
+echo "🔄 重启服务..."
+sudo systemctl restart photography-backend
+
+# 6. 检查服务状态
+echo "🔍 检查服务状态..."
+sleep 5
+if curl -f http://localhost:8080/health > /dev/null 2>&1; then
+ echo "✅ 部署成功!"
+else
+ echo "❌ 部署失败!"
+ exit 1
+fi
+
+echo "🎉 部署完成!"
+```
+
+#### 6.2.2 环境配置
+```yaml
+# config/production.yaml
+app:
+ name: "photography-backend"
+ version: "1.0.0"
+ environment: "production"
+ port: 8080
+ debug: false
+
+database:
+ host: "localhost"
+ port: 5432
+ username: "photography"
+ password: "${DB_PASSWORD}"
+ database: "photography"
+ ssl_mode: "require"
+ max_open_conns: 100
+ max_idle_conns: 10
+ conn_max_lifetime: 300
+
+redis:
+ host: "localhost"
+ port: 6379
+ password: "${REDIS_PASSWORD}"
+ database: 0
+ pool_size: 100
+ min_idle_conns: 10
+
+storage:
+ type: "s3"
+ s3:
+ region: "us-west-2"
+ bucket: "photography-uploads"
+ access_key: "${S3_ACCESS_KEY}"
+ secret_key: "${S3_SECRET_KEY}"
+ endpoint: ""
+ use_ssl: true
+
+logger:
+ level: "info"
+ format: "json"
+ output: "file"
+ filename: "logs/app.log"
+ max_size: 100
+ max_age: 30
+ compress: true
+
+jwt:
+ secret: "${JWT_SECRET}"
+ expire_hours: 24
+
+upload:
+ max_file_size: 52428800 # 50MB
+ allowed_types: ["image/jpeg", "image/png", "image/gif", "image/webp"]
+ thumbnail_sizes:
+ - { name: "small", width: 300, height: 300 }
+ - { name: "medium", width: 800, height: 600 }
+ - { name: "large", width: 1200, height: 900 }
+```
+
+## 7. 总结
+
+摄影作品集管理后台是一个功能完善的内容管理系统,涵盖了现代Web应用所需的各个方面:
+
+### 7.1 核心特性
+- **直观的用户界面**: 基于React和shadcn/ui的现代化界面
+- **强大的照片管理**: 支持批量上传、处理、分类和标签
+- **灵活的权限系统**: 基于角色的访问控制
+- **高性能优化**: 多级缓存、懒加载、虚拟滚动等
+- **完善的监控**: 日志管理、健康检查、性能监控
+
+### 7.2 技术优势
+- **前后端分离**: 便于独立开发和部署
+- **类型安全**: TypeScript确保代码质量
+- **模块化设计**: 易于维护和扩展
+- **云原生部署**: Docker容器化部署
+- **自动化流程**: CI/CD集成部署
+
+### 7.3 扩展性
+- **微服务架构**: 支持后续拆分为微服务
+- **多存储支持**: 本地存储、MinIO、AWS S3
+- **插件系统**: 支持功能插件扩展
+- **API标准化**: RESTful API设计
+
+这个管理后台为摄影作品集网站提供了强大的后台管理能力,能够满足专业摄影师和摄影工作室的各种需求。通过模块化的设计和现代化的技术栈,系统具有良好的可维护性和扩展性,能够随着业务需求的增长而持续演进。
\ No newline at end of file
diff --git a/docs/development/saved-docs/operations-monitoring.md b/docs/development/saved-docs/operations-monitoring.md
new file mode 100644
index 0000000..d13aee7
--- /dev/null
+++ b/docs/development/saved-docs/operations-monitoring.md
@@ -0,0 +1,710 @@
+# 摄影作品集网站 - 运维监控方案
+
+## 🎯 方案概述
+
+这是一个**简单实用的日志管理方案**,专注于日志收集和问题修复。日志查看功能集成到管理后台,提供友好的Web界面。
+
+### 设计原则
+- **集成化**: 日志查看功能集成到管理后台
+- **用户友好**: 提供美观易用的Web界面
+- **问题导向**: 专注于快速定位和修复问题
+- **低维护成本**: 几乎零维护的方案
+- **渐进式**: 后续可以根据需要扩展
+
+## 📋 核心组件
+
+### 日志管理架构
+```
+┌─────────────────────────────────────────────────────────────┐
+│ 后端应用日志 │
+│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
+│ │ 错误日志 │ │ 访问日志 │ │ 业务日志 │ │
+│ │ (JSON格式) │ │ (HTTP日志) │ │ (操作日志) │ │
+│ └─────────────┘ └─────────────┘ └─────────────┘ │
+├─────────────────────────────────────────────────────────────┤
+│ 管理后台日志模块 │
+│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
+│ │ 日志查看器 │ │ 实时监控 │ │ 统计分析 │ │
+│ │ (Web界面) │ │ (自动刷新) │ │ (图表展示) │ │
+│ └─────────────┘ └─────────────┘ └─────────────┘ │
+├─────────────────────────────────────────────────────────────┤
+│ API接口层 │
+│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
+│ │ 日志查询API │ │ 统计API │ │ 搜索API │ │
+│ │(/api/logs) │ │(/api/stats) │ │(/api/search)│ │
+│ └─────────────┘ └─────────────┘ └─────────────┘ │
+└─────────────────────────────────────────────────────────────┘
+```
+
+## 🔧 技术选择
+
+### 日志方案
+- **日志存储**: 本地文件 (JSON格式)
+- **日志轮转**: lumberjack.v2
+- **日志查看**: 管理后台Web界面
+- **Trace ID**: 集成OpenTracing (已在架构文档中添加)
+- **权限控制**: 基于管理后台的用户权限
+
+### 集成方式
+- **前端**: 管理后台的日志管理模块
+- **后端**: Gin路由提供日志查询API
+- **认证**: 复用管理后台的登录认证
+- **权限**: 仅管理员可访问日志功能
+
+## 📝 日志配置
+
+### 1. 应用日志配置
+
+#### 日志配置文件 (config.yaml)
+```yaml
+# config/config.yaml
+logger:
+ level: "info"
+ format: "json"
+ output: "file"
+ filename: "/app/logs/app.log"
+ max_size: 100 # MB
+ max_age: 7 # days
+ compress: true
+
+# 可选:如果需要链路追踪
+tracing:
+ enabled: true
+ service_name: "photography-backend"
+ jaeger:
+ endpoint: "http://localhost:14268/api/traces"
+ sampling_rate: 1.0
+```
+
+### 2. 日志格式标准化
+
+#### 统一的日志格式
+```json
+{
+ "timestamp": "2024-01-15T10:30:00Z",
+ "level": "info",
+ "message": "Photo created successfully",
+ "service": "photography-backend",
+ "trace_id": "abc123def456",
+ "request_id": "req-789",
+ "user_id": "user-123",
+ "operation": "create_photo",
+ "photo_id": 1001,
+ "duration": 0.5,
+ "error": null
+}
+```
+
+### 3. 日志分类
+
+#### 三种核心日志类型
+```bash
+# 日志目录结构
+logs/
+├── app.log # 应用日志 (所有级别)
+├── error.log # 错误日志 (ERROR级别)
+└── access.log # HTTP访问日志
+```
+
+#### 日志级别使用
+```go
+// 日志级别使用指南
+logger.Info("正常业务操作") // 记录重要的业务操作
+logger.Warn("需要关注的情况") // 记录警告信息
+logger.Error("错误情况") // 记录错误信息
+logger.Debug("调试信息") // 开发调试用
+```
+
+## 🚀 集成到管理后台
+
+### 1. 后端集成步骤
+
+#### 在main.go中注册日志路由
+```go
+// cmd/server/main.go
+func main() {
+ // ... 其他初始化代码
+
+ // 创建Gin引擎
+ r := gin.Default()
+
+ // 注册API路由
+ apiGroup := r.Group("/api")
+ {
+ // 其他API路由...
+ }
+
+ // 注册管理后台路由
+ adminGroup := r.Group("/admin/api")
+ {
+ // 注册日志管理路由
+ admin.RegisterLogRoutes(adminGroup, "logs/app.log")
+
+ // 其他管理后台路由...
+ }
+
+ r.Run(":8080")
+}
+```
+
+#### 日志处理器实现
+```go
+// internal/api/handlers/admin/logs_handler.go
+package admin
+
+import (
+ "bufio"
+ "encoding/json"
+ "net/http"
+ "os"
+ "strconv"
+ "strings"
+
+ "github.com/gin-gonic/gin"
+ "photography-backend/pkg/middleware"
+)
+
+// LogEntry 日志条目
+type LogEntry struct {
+ Timestamp string `json:"timestamp"`
+ Level string `json:"level"`
+ Message string `json:"message"`
+ TraceID string `json:"trace_id,omitempty"`
+ UserID string `json:"user_id,omitempty"`
+ Operation string `json:"operation,omitempty"`
+}
+
+// LogHandler 日志处理器
+type LogHandler struct {
+ logFile string
+}
+
+// GetLogs 获取日志列表
+func (h *LogHandler) GetLogs(c *gin.Context) {
+ // 获取参数
+ levelFilter := c.Query("level")
+ searchFilter := c.Query("search")
+ traceID := c.Query("trace_id")
+ lines := 100
+ if l := c.Query("lines"); l != "" {
+ if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 && parsed <= 1000 {
+ lines = parsed
+ }
+ }
+
+ // 读取日志文件
+ logs, err := h.readLogs(lines, levelFilter, searchFilter, traceID)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{
+ "error": "读取日志失败",
+ "details": err.Error(),
+ })
+ return
+ }
+
+ c.JSON(http.StatusOK, gin.H{
+ "code": 0,
+ "message": "success",
+ "data": gin.H{
+ "logs": logs,
+ "total": len(logs),
+ },
+ })
+}
+
+// GetLogStats 获取日志统计
+func (h *LogHandler) GetLogStats(c *gin.Context) {
+ stats := map[string]int{
+ "total": 0,
+ "error": 0,
+ "warn": 0,
+ "info": 0,
+ "debug": 0,
+ }
+
+ file, err := os.Open(h.logFile)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{
+ "error": "读取日志文件失败",
+ "details": err.Error(),
+ })
+ return
+ }
+ defer file.Close()
+
+ scanner := bufio.NewScanner(file)
+ for scanner.Scan() {
+ line := scanner.Text()
+ if line == "" {
+ continue
+ }
+
+ stats["total"]++
+
+ // 解析日志级别
+ var entry LogEntry
+ if err := json.Unmarshal([]byte(line), &entry); err == nil {
+ if count, exists := stats[entry.Level]; exists {
+ stats[entry.Level] = count + 1
+ }
+ }
+ }
+
+ c.JSON(http.StatusOK, gin.H{
+ "code": 0,
+ "message": "success",
+ "data": stats,
+ })
+}
+
+// RegisterLogRoutes 注册日志相关路由
+func RegisterLogRoutes(r *gin.RouterGroup, logFile string) {
+ logHandler := NewLogHandler(logFile)
+
+ // 需要管理员权限的路由组
+ adminGroup := r.Group("")
+ adminGroup.Use(middleware.RequireAuth()) // 需要登录
+ adminGroup.Use(middleware.RequireAdmin()) // 需要管理员权限
+
+ {
+ adminGroup.GET("/logs", logHandler.GetLogs)
+ adminGroup.GET("/logs/stats", logHandler.GetLogStats)
+ }
+}
+```
+
+### 2. 前端集成步骤
+
+#### 管理后台日志查看组件
+```javascript
+// admin/src/pages/Logs/LogViewer.jsx
+import React, { useState, useEffect } from 'react';
+import {
+ Card,
+ Table,
+ Select,
+ Input,
+ Button,
+ Tag,
+ Space,
+ Statistic,
+ Row,
+ Col,
+ message
+} from 'antd';
+
+const LogViewer = () => {
+ const [logs, setLogs] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [filters, setFilters] = useState({
+ level: '',
+ search: '',
+ trace_id: '',
+ lines: 100,
+ });
+ const [stats, setStats] = useState({});
+ const [autoRefresh, setAutoRefresh] = useState(false);
+
+ // 日志级别配置
+ const levelConfig = {
+ error: { color: 'red', icon: '❌' },
+ warn: { color: 'orange', icon: '⚠️' },
+ info: { color: 'blue', icon: 'ℹ️' },
+ debug: { color: 'default', icon: '🐛' },
+ };
+
+ // 获取日志数据
+ const fetchLogs = async () => {
+ setLoading(true);
+ try {
+ const response = await adminApi.get('/logs', { params: filters });
+ setLogs(response.data.data.logs || []);
+ } catch (error) {
+ message.error('获取日志失败');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ // 获取统计数据
+ const fetchStats = async () => {
+ try {
+ const response = await adminApi.get('/logs/stats');
+ setStats(response.data.data || {});
+ } catch (error) {
+ console.error('获取统计数据失败', error);
+ }
+ };
+
+ useEffect(() => {
+ fetchLogs();
+ fetchStats();
+ }, [filters]);
+
+ // 自动刷新
+ useEffect(() => {
+ let interval;
+ if (autoRefresh) {
+ interval = setInterval(() => {
+ fetchLogs();
+ fetchStats();
+ }, 5000);
+ }
+ return () => interval && clearInterval(interval);
+ }, [autoRefresh, filters]);
+
+ // 表格列配置
+ const columns = [
+ {
+ title: '时间',
+ dataIndex: 'timestamp',
+ key: 'timestamp',
+ width: 180,
+ render: (timestamp) => new Date(timestamp).toLocaleString(),
+ },
+ {
+ title: '级别',
+ dataIndex: 'level',
+ key: 'level',
+ width: 80,
+ render: (level) => {
+ const config = levelConfig[level] || levelConfig.info;
+ return (
+
+ {config.icon} {level.toUpperCase()}
+
+ );
+ },
+ },
+ {
+ title: 'Trace ID',
+ dataIndex: 'trace_id',
+ key: 'trace_id',
+ width: 120,
+ render: (traceId) => traceId ? (
+
+ ) : '-',
+ },
+ {
+ title: '消息',
+ dataIndex: 'message',
+ key: 'message',
+ ellipsis: true,
+ render: (message, record) => {
+ // 高亮搜索关键词
+ if (filters.search && message.toLowerCase().includes(filters.search.toLowerCase())) {
+ const regex = new RegExp(`(${filters.search})`, 'gi');
+ const parts = message.split(regex);
+ return parts.map((part, index) =>
+ part.toLowerCase() === filters.search.toLowerCase() ?
+ {part} : part
+ );
+ }
+ return message;
+ },
+ },
+ ];
+
+ return (
+
+ {/* 统计卡片 */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* 过滤控件 */}
+
+
+
+
+ setFilters(prev => ({ ...prev, search: e.target.value }))}
+ />
+
+ setFilters(prev => ({ ...prev, trace_id: e.target.value }))}
+ />
+
+
+
+
+
+
+
+
+
+ {/* 日志表格 */}
+
+ `${record.timestamp}-${index}`}
+ pagination={{
+ showSizeChanger: false,
+ showQuickJumper: true,
+ showTotal: (total) => `共 ${total} 条日志`,
+ }}
+ scroll={{ x: 800 }}
+ />
+
+
+ );
+};
+
+export default LogViewer;
+```
+
+### 3. 配置更新
+
+#### 更新应用配置文件
+```yaml
+# config/config.yaml
+app:
+ name: "photography-backend"
+ version: "1.0.0"
+ environment: "production"
+
+logger:
+ level: "info"
+ format: "json"
+ output: "file"
+ filename: "logs/app.log"
+ max_size: 100
+ max_age: 7
+ compress: true
+
+tracing:
+ enabled: true
+ service_name: "photography-backend"
+ sampling_rate: 1.0
+
+admin:
+ log_access: true # 启用日志访问功能
+ log_retention_days: 30 # 日志保留天数
+```
+
+### 4. 一键部署脚本
+
+#### 部署管理后台日志功能
+```bash
+#!/bin/bash
+# deploy-admin-logs.sh
+
+echo "🚀 部署管理后台日志功能..."
+
+# 创建必要目录
+mkdir -p logs
+mkdir -p admin/src/pages/Logs
+
+# 确保日志文件存在
+touch logs/app.log
+chmod 664 logs/app.log
+
+# 创建日志轮转配置
+cat > /etc/logrotate.d/photography-backend << 'EOF'
+/path/to/photography/logs/*.log {
+ daily
+ rotate 30
+ compress
+ delaycompress
+ missingok
+ notifempty
+ create 664 app app
+ postrotate
+ systemctl reload photography-backend
+ endscript
+}
+EOF
+
+# 设置权限
+chown -R app:app logs/
+chmod 755 logs/
+
+echo "✅ 管理后台日志功能部署完成!"
+echo ""
+echo "📋 访问方式:"
+echo " 1. 登录管理后台: http://localhost:8080/admin"
+echo " 2. 进入日志管理页面"
+echo " 3. 使用管理员账号访问"
+echo ""
+echo "🔧 API地址:"
+echo " 日志列表: GET /admin/api/logs"
+echo " 日志统计: GET /admin/api/logs/stats"
+```
+
+## 🔧 故障排查流程
+
+### 1. 问题诊断步骤
+```bash
+# 快速诊断脚本
+#!/bin/bash
+# scripts/quick-diagnosis.sh
+
+echo "🔍 快速问题诊断"
+
+# 1. 检查服务状态
+echo "1. 检查服务状态..."
+docker-compose ps
+
+# 2. 检查最近错误
+echo "2. 最近10条错误日志..."
+tail -n 1000 logs/app.log | grep -i error | tail -10 | jq -r '.timestamp + " " + .message'
+
+# 3. 检查磁盘空间
+echo "3. 检查磁盘空间..."
+df -h
+
+# 4. 检查日志文件大小
+echo "4. 检查日志文件大小..."
+ls -lh logs/
+
+# 5. 检查内存使用
+echo "5. 检查内存使用..."
+docker stats --no-stream photography-backend
+
+echo "✅ 诊断完成"
+```
+
+### 2. 常见问题解决
+
+#### 问题1:无法找到错误原因
+```bash
+# 1. 获取完整的错误上下文
+grep -B 5 -A 5 "error_message" logs/app.log
+
+# 2. 按时间范围查找
+grep "2024-01-15T10:" logs/app.log | grep -i error
+```
+
+#### 问题2:需要追踪特定用户的操作
+```bash
+# 按用户ID查找
+grep "user-123" logs/app.log | jq -r '.timestamp + " " + .message'
+
+# 按操作类型查找
+grep "create_photo" logs/app.log | jq -r '.timestamp + " " + .message'
+```
+
+#### 问题3:日志文件太大
+```bash
+# 手动轮转日志
+mv logs/app.log logs/app.log.old
+sudo systemctl restart photography-backend
+
+# 或者使用logrotate
+sudo logrotate -f /etc/logrotate.d/photography-backend
+```
+
+## 🎯 总结
+
+这个集成到管理后台的日志方案提供了:
+
+### ✨ 核心特性
+- **🏢 集成化管理** - 完全集成到管理后台,统一的用户体验
+- **🔐 权限控制** - 基于管理后台的认证和授权体系
+- **📊 专业界面** - 使用React组件,美观且专业
+- **🔍 强大搜索** - 支持关键词、级别、Trace ID多维度过滤
+- **⏰ 实时监控** - 自动刷新功能,实时观察系统状态
+- **📈 统计分析** - 直观的统计卡片,快速了解系统健康状况
+
+### 🚀 部署简单
+1. 运行部署脚本: `./deploy-admin-logs.sh`
+2. 访问管理后台: `http://localhost:8080/admin`
+3. 登录管理员账号,进入日志管理页面
+
+### 💡 使用场景
+- **错误排查**: 管理员快速定位和分析错误日志
+- **性能监控**: 通过Trace ID追踪完整请求链路
+- **运营监控**: 实时观察系统运行状态
+- **历史分析**: 搜索和分析历史日志数据
+- **团队协作**: 多个管理员可以同时查看和分析日志
+
+### 🔧 技术优势
+- **集成性**: 完全集成到现有管理后台
+- **安全性**: 基于管理后台的权限控制
+- **专业性**: 使用成熟的UI组件库
+- **可扩展**: 易于添加新的日志分析功能
+- **用户友好**: 直观的界面,无需学习成本
+
+### 📋 API接口
+- `GET /admin/api/logs` - 获取日志列表
+- `GET /admin/api/logs/stats` - 获取日志统计
+
+这就是最适合生产环境的日志管理方案 - **专业、安全、易用**!
\ No newline at end of file
diff --git a/docs/development/saved-docs/v1.0-overview.md b/docs/development/saved-docs/v1.0-overview.md
new file mode 100644
index 0000000..151045f
--- /dev/null
+++ b/docs/development/saved-docs/v1.0-overview.md
@@ -0,0 +1,249 @@
+# 摄影作品集网站 v1.0 - 开发文档
+
+## 📋 v1.0 版本概述
+
+v1.0 是摄影作品集网站的核心功能版本,实现了完整的摄影作品展示、管理和用户交互功能。
+
+### 🎯 版本目标
+- 构建稳定可靠的摄影作品集展示平台
+- 实现高效的照片管理和处理系统
+- 提供完善的用户认证和权限控制
+- 支持多种图片格式和优化策略
+
+### 📅 开发周期
+- **开始时间**: 2024-01-15
+- **预计完成**: 2024-04-15
+- **当前状态**: 设计阶段
+
+## 🏗️ 架构设计
+
+### 整体架构
+```
+┌─────────────────────────────────────────────────────────┐
+│ 用户界面层 │
+│ ┌─────────────────┐ ┌─────────────────┐ │
+│ │ 前端展示网站 │ │ 管理后台界面 │ │
+│ │ (Next.js) │ │ (React) │ │
+│ └─────────────────┘ └─────────────────┘ │
+├─────────────────────────────────────────────────────────┤
+│ API 接口层 │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ RESTful API │ │
+│ │ (Gin Framework) │ │
+│ └─────────────────────────────────────────────────────┘ │
+├─────────────────────────────────────────────────────────┤
+│ 业务逻辑层 │
+│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
+│ │ 照片管理 │ │ 用户认证 │ │ 文件处理 │ │
+│ │ 服务 │ │ 服务 │ │ 服务 │ │
+│ └─────────────┘ └─────────────┘ └─────────────┘ │
+├─────────────────────────────────────────────────────────┤
+│ 数据访问层 │
+│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
+│ │ PostgreSQL │ │ Redis │ │ MinIO/S3 │ │
+│ │ 数据库 │ │ 缓存 │ │ 对象存储 │ │
+│ └─────────────┘ └─────────────┘ └─────────────┘ │
+└─────────────────────────────────────────────────────────┘
+```
+
+### 技术选型
+```yaml
+前端技术栈:
+ - Framework: Next.js 15 + React 19
+ - Language: TypeScript
+ - Styling: Tailwind CSS
+ - State: TanStack Query + Zustand
+ - UI Components: shadcn/ui + Radix UI
+
+后端技术栈:
+ - Framework: Golang + Gin
+ - Database: PostgreSQL 15
+ - Cache: Redis 7
+ - ORM: GORM
+ - Authentication: JWT
+ - Image Processing: libvips/bimg
+
+存储和部署:
+ - Object Storage: MinIO/AWS S3
+ - Web Server: Caddy
+ - Container: Docker + Docker Compose
+ - CI/CD: Gitea Actions
+```
+
+## 🚀 功能特性
+
+### 核心功能
+- ✅ **前端展示**
+ - 响应式照片网格展示
+ - 时间线视图
+ - 照片详情模态框
+ - 分类和标签筛选
+
+- 🔄 **管理后台**
+ - 照片批量上传和管理
+ - 分类和标签管理
+ - 用户权限控制
+ - 系统设置配置
+
+- 🔄 **后端API**
+ - 完整的CRUD操作
+ - 文件上传处理
+ - 图片多格式转换
+ - 用户认证授权
+
+- 🔄 **数据管理**
+ - PostgreSQL关系数据库
+ - Redis缓存加速
+ - MinIO对象存储
+ - 数据备份恢复
+
+### 性能特性
+- **图片优化**: 多格式转换 (JPG, WebP, AVIF)
+- **懒加载**: 视口外图片延迟加载
+- **缓存策略**: 多级缓存提升响应速度
+- **CDN加速**: 静态资源分发优化
+
+### 安全特性
+- **用户认证**: JWT令牌机制
+- **权限控制**: 基于角色的访问控制
+- **数据加密**: 敏感数据加密存储
+- **API限流**: 防止接口滥用
+
+## 📈 开发进度
+
+### 当前状态
+
+| 模块 | 设计 | 开发 | 测试 | 部署 | 完成度 |
+|------|------|------|------|------|--------|
+| 前端展示 | ✅ | ✅ | ✅ | ✅ | 100% |
+| 管理后台 | ✅ | ⏳ | ⏳ | ⏳ | 20% |
+| 后端API | ✅ | ⏳ | ⏳ | ⏳ | 15% |
+| 数据库 | ✅ | ⏳ | ⏳ | ⏳ | 30% |
+| 图片处理 | ✅ | ⏳ | ⏳ | ⏳ | 10% |
+| 用户认证 | ✅ | ⏳ | ⏳ | ⏳ | 5% |
+| 文件存储 | ✅ | ⏳ | ⏳ | ⏳ | 5% |
+| 部署配置 | ✅ | ⏳ | ⏳ | ⏳ | 10% |
+
+### 里程碑计划
+
+#### 第一阶段 (2024-01-15 ~ 2024-02-15)
+- [x] 完成技术选型和架构设计
+- [x] 完成详细设计文档编写
+- [ ] 搭建开发环境和基础框架
+- [ ] 实现管理后台核心界面
+
+#### 第二阶段 (2024-02-15 ~ 2024-03-15)
+- [ ] 完成后端API核心功能
+- [ ] 实现数据库表结构和迁移
+- [ ] 完成用户认证和权限系统
+- [ ] 实现图片上传和处理功能
+
+#### 第三阶段 (2024-03-15 ~ 2024-04-15)
+- [ ] 完成前后端功能对接
+- [ ] 实现文件存储和CDN配置
+- [ ] 完成系统测试和性能优化
+- [ ] 部署到生产环境
+
+## 🔧 开发环境
+
+### 环境要求
+```yaml
+开发环境:
+ - Node.js: 18+
+ - Golang: 1.21+
+ - PostgreSQL: 15+
+ - Redis: 7+
+ - Docker: 24+
+
+推荐配置:
+ - 内存: 8GB+
+ - 硬盘: 50GB+
+ - 操作系统: macOS/Linux
+```
+
+### 快速开始
+```bash
+# 1. 克隆项目
+git clone
+cd photography
+
+# 2. 前端开发
+cd frontend
+make setup
+make dev
+
+# 3. 后端开发 (开发中)
+cd backend
+make setup
+make dev
+
+# 4. 数据库初始化
+make migrate-up
+```
+
+## 🧪 测试策略
+
+### 测试类型
+- **单元测试**: 核心业务逻辑测试
+- **集成测试**: API接口和数据库交互测试
+- **端到端测试**: 完整用户流程测试
+- **性能测试**: 系统负载和响应时间测试
+
+### 测试覆盖率目标
+- **后端代码**: 80%+
+- **前端组件**: 70%+
+- **API接口**: 90%+
+
+## 📊 质量保证
+
+### 代码质量
+- **代码规范**: ESLint + Prettier (前端), golangci-lint (后端)
+- **类型检查**: TypeScript 严格模式
+- **代码审查**: Pull Request必须通过审查
+- **自动化测试**: CI/CD流水线集成
+
+### 性能指标
+- **页面加载时间**: < 3s
+- **图片处理时间**: < 10s
+- **API响应时间**: < 500ms
+- **数据库查询**: < 100ms
+
+## 📋 发布计划
+
+### 发布流程
+1. **功能开发**: 在feature分支开发
+2. **代码审查**: 创建Pull Request
+3. **测试验证**: 自动化测试通过
+4. **合并主分支**: 合并到main分支
+5. **部署发布**: 自动部署到生产环境
+
+### 版本管理
+- **版本号**: 遵循语义化版本规范
+- **发布说明**: 详细的变更日志
+- **回滚策略**: 快速回滚机制
+
+## 💡 未来规划
+
+### v1.1 增强功能
+- 移动端PWA支持
+- 图片水印功能
+- 批量操作优化
+- 搜索功能增强
+
+### v1.2 扩展功能
+- 评论系统
+- 社交分享
+- 数据导出
+- 多语言支持
+
+### v2.0 升级计划
+- 微服务架构
+- AI智能标签
+- 实时通知
+- 高级分析
+
+---
+
+📅 **最后更新**: 2024-01-15
+📝 **文档版本**: v1.0
+👨💻 **维护者**: Claude Code Assistant
\ No newline at end of file
diff --git a/docs/v1/admin/管理后台开发文档.md b/docs/v1/admin/管理后台开发文档.md
index 07cc1a4..822b623 100644
--- a/docs/v1/admin/管理后台开发文档.md
+++ b/docs/v1/admin/管理后台开发文档.md
@@ -278,7 +278,79 @@ type DashboardStats struct {
└─────────────────────────────────────────────────────────┘
```
-### 2.6 系统设置模块 (System Settings)
+### 2.6 日志管理模块 (Log Management)
+
+#### 2.6.1 日志查看页面
+```
+┌─────────────────────────────────────────────────────────────┐
+│ 日志管理 Log Management │
+├─────────────────────────────────────────────────────────────┤
+│ 📊 统计卡片 │
+│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
+│ │ 总日志 │ │ 错误数 │ │ 警告数 │ │ 信息数 │ │
+│ │ 12,345 │ │ 23 │ │ 156 │ │ 11,234 │ │
+│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
+│ │
+│ 🔍 过滤控件 │
+│ [日志级别▼] [搜索关键词...] [Trace ID...] [100条▼] [刷新] │
+│ │
+│ 📋 日志列表 │
+│ ┌─────────────────────────────────────────────────────────┐ │
+│ │ 时间 级别 Trace ID 消息 │ │
+│ │ 2024-01-15 10:30 INFO trace-abc123 Photo created │ │
+│ │ 2024-01-15 10:31 WARN trace-def456 Cache miss │ │
+│ │ 2024-01-15 10:32 ERROR trace-abc123 Process failed │ │
+│ │ 2024-01-15 10:33 INFO trace-ghi789 User login │ │
+│ └─────────────────────────────────────────────────────────┘ │
+│ │
+│ 📄 分页: [← 上一页] [1] [2] [3] [下一页 →] │
+│ ⏰ [自动刷新] 🗑️ [清空显示] │
+└─────────────────────────────────────────────────────────────┘
+```
+
+#### 2.6.2 日志详情展示
+```
+┌─────────────────────────────────────────────────────────────┐
+│ 日志详情 Log Details │
+├─────────────────────────────────────────────────────────────┤
+│ ← 返回列表 │
+│ │
+│ 📋 基本信息 │
+│ ┌─────────────────────────────────────────────────────────┐ │
+│ │ 时间: 2024-01-15 10:32:45 │ │
+│ │ 级别: ERROR │ │
+│ │ 消息: Failed to process image │ │
+│ │ Trace ID: trace-abc123 [点击查看相关日志] │ │
+│ │ 用户: admin (user-123) │ │
+│ │ 操作: image_process │ │
+│ └─────────────────────────────────────────────────────────┘ │
+│ │
+│ 🔍 相关日志 (同一Trace ID) │
+│ ┌─────────────────────────────────────────────────────────┐ │
+│ │ 10:30:00 INFO Photo upload started │ │
+│ │ 10:31:30 INFO File validation passed │ │
+│ │ 10:32:00 WARN Large file detected │ │
+│ │ 10:32:45 ERROR Failed to process image │ │
+│ └─────────────────────────────────────────────────────────┘ │
+│ │
+│ 📊 统计信息 │
+│ ┌─────────────────────────────────────────────────────────┐ │
+│ │ 该Trace ID共有 4 条日志 │ │
+│ │ 错误: 1 | 警告: 1 | 信息: 2 │ │
+│ │ 耗时: 2分45秒 │ │
+│ └─────────────────────────────────────────────────────────┘ │
+└─────────────────────────────────────────────────────────────┘
+```
+
+#### 2.6.3 日志管理功能
+- **实时查看**: 支持自动刷新,实时监控系统状态
+- **智能过滤**: 按级别、关键词、Trace ID、时间范围过滤
+- **链路追踪**: 点击Trace ID查看完整请求链路
+- **统计分析**: 显示各级别日志数量统计
+- **搜索高亮**: 搜索关键词在日志中高亮显示
+- **权限控制**: 仅管理员可访问日志管理功能
+
+### 2.7 系统设置模块 (System Settings)
#### 2.6.1 基本设置页面
```
@@ -335,6 +407,7 @@ backend/
│ │ │ ├── tag.go # 标签管理
│ │ │ ├── timeline.go # 时间线管理
│ │ │ ├── settings.go # 系统设置
+│ │ │ ├── logs.go # 日志管理
│ │ │ └── upload.go # 文件上传
│ │ ├── middleware/ # 中间件
│ │ │ ├── auth.go # 认证中间件
@@ -351,6 +424,7 @@ backend/
│ │ ├── tag_service.go # 标签服务
│ │ ├── timeline_service.go # 时间线服务
│ │ ├── upload_service.go # 上传服务
+│ │ ├── logs_service.go # 日志服务
│ │ └── settings_service.go # 设置服务
│ ├── repository/ # 数据访问层
│ │ ├── photo_repo.go # 照片数据访问
@@ -445,6 +519,11 @@ admin/
│ │ │ ├── category-tree.tsx
│ │ │ ├── category-form.tsx
│ │ │ └── category-stats.tsx
+│ │ ├── logs/ # 日志管理组件
+│ │ │ ├── log-viewer.tsx
+│ │ │ ├── log-filter.tsx
+│ │ │ ├── log-detail.tsx
+│ │ │ └── log-stats.tsx
│ │ └── common/ # 通用组件
│ │ ├── loading.tsx
│ │ ├── error-boundary.tsx
@@ -460,22 +539,28 @@ admin/
│ │ │ └── index.tsx
│ │ ├── tags/
│ │ │ └── index.tsx
+│ │ ├── logs/
+│ │ │ ├── index.tsx
+│ │ │ └── detail.tsx
│ │ └── settings/
│ │ └── index.tsx
│ ├── hooks/ # 自定义Hooks
│ │ ├── useAuth.ts
│ │ ├── usePhotos.ts
│ │ ├── useCategories.ts
+│ │ ├── useLogs.ts
│ │ └── useUpload.ts
│ ├── services/ # API服务
│ │ ├── api.ts
│ │ ├── auth.ts
│ │ ├── photo.ts
│ │ ├── category.ts
+│ │ ├── logs.ts
│ │ └── upload.ts
│ ├── store/ # 状态管理
│ │ ├── auth.ts
│ │ ├── photo.ts
+│ │ ├── logs.ts
│ │ └── ui.ts
│ ├── utils/ # 工具函数
│ │ ├── format.ts
@@ -484,6 +569,7 @@ admin/
│ └── types/ # 类型定义
│ ├── api.ts
│ ├── photo.ts
+│ ├── logs.ts
│ └── user.ts
├── public/ # 静态资源
├── package.json
diff --git a/docs/v1/backend/Golang项目架构文档.md b/docs/v1/backend/Golang项目架构文档.md
index 92b66af..8facec1 100644
--- a/docs/v1/backend/Golang项目架构文档.md
+++ b/docs/v1/backend/Golang项目架构文档.md
@@ -36,6 +36,8 @@ joho/godotenv // 环境变量
// 日志系统
sirupsen/logrus // 结构化日志
lumberjack.v2 // 日志轮转
+opentracing/opentracing-go // 链路追踪
+jaegertracing/jaeger-client-go // Jaeger客户端
// 验证和工具
go-playground/validator // 数据验证
@@ -64,6 +66,7 @@ photography-backend/
│ ├── config/ # 配置管理
│ ├── database/ # 数据库连接
│ ├── logger/ # 日志系统
+│ ├── tracing/ # 链路追踪
│ ├── middleware/ # 中间件
│ ├── storage/ # 存储服务
│ ├── cache/ # 缓存服务
@@ -127,6 +130,7 @@ package domain
import (
"time"
"github.com/google/uuid"
+ "github.com/opentracing/opentracing-go"
)
// Photo 照片领域实体
@@ -462,8 +466,19 @@ func NewPhotoService(
// GetPhotos 获取照片列表
func (s *photoServiceImpl) GetPhotos(ctx context.Context, req dto.PhotoListRequest) (*dto.PhotoListResponse, error) {
+ // 创建span用于链路追踪
+ span, ctx := opentracing.StartSpanFromContext(ctx, "PhotoService.GetPhotos")
+ defer span.Finish()
+
+ // 获取trace ID
+ traceID := s.getTraceID(span)
+ logger := s.logger.WithField("trace_id", traceID)
+
// 参数验证
if err := req.Validate(); err != nil {
+ span.SetTag("error", true)
+ span.LogKV("event", "validation_error", "error", err.Error())
+ logger.WithError(err).Error("Invalid request parameters")
return nil, fmt.Errorf("invalid request: %w", err)
}
@@ -482,7 +497,9 @@ func (s *photoServiceImpl) GetPhotos(ctx context.Context, req dto.PhotoListReque
// 从数据库查询
photos, total, err := s.photoRepo.FindWithPagination(ctx, filter)
if err != nil {
- s.logger.WithError(err).Error("Failed to get photos from repository")
+ span.SetTag("error", true)
+ span.LogKV("event", "repository_error", "error", err.Error())
+ logger.WithError(err).Error("Failed to get photos from repository")
return nil, fmt.Errorf("failed to get photos: %w", err)
}
@@ -509,13 +526,50 @@ func (s *photoServiceImpl) GetPhotos(ctx context.Context, req dto.PhotoListReque
s.cacheService.Set(ctx, cacheKey, data, 10*time.Minute)
}
+ // 记录成功日志
+ span.SetTag("photos_count", len(photos))
+ span.SetTag("total_count", total)
+ logger.WithFields(logrus.Fields{
+ "photos_count": len(photos),
+ "total_count": total,
+ "page": req.Page,
+ "limit": req.Limit,
+ }).Info("Photos retrieved successfully")
+
return response, nil
}
+// getTraceID 获取trace ID
+func (s *photoServiceImpl) getTraceID(span opentracing.Span) string {
+ if span == nil {
+ return ""
+ }
+
+ // 从span context获取trace ID
+ spanContext := span.Context()
+ if jaegerSpanContext, ok := spanContext.(interface{ TraceID() string }); ok {
+ return jaegerSpanContext.TraceID()
+ }
+
+ // 如果无法获取trace ID,生成一个UUID作为fallback
+ return uuid.New().String()
+}
+
// CreatePhoto 创建照片
func (s *photoServiceImpl) CreatePhoto(ctx context.Context, req dto.CreatePhotoRequest) (*dto.PhotoResponse, error) {
+ // 创建span用于链路追踪
+ span, ctx := opentracing.StartSpanFromContext(ctx, "PhotoService.CreatePhoto")
+ defer span.Finish()
+
+ // 获取trace ID
+ traceID := s.getTraceID(span)
+ logger := s.logger.WithField("trace_id", traceID)
+
// 参数验证
if err := req.Validate(); err != nil {
+ span.SetTag("error", true)
+ span.LogKV("event", "validation_error", "error", err.Error())
+ logger.WithError(err).Error("Invalid request parameters")
return nil, fmt.Errorf("invalid request: %w", err)
}
@@ -549,7 +603,9 @@ func (s *photoServiceImpl) CreatePhoto(ctx context.Context, req dto.CreatePhotoR
// 保存照片到数据库
if err := s.photoRepo.Create(ctx, tx, photo); err != nil {
- s.logger.WithError(err).Error("Failed to save photo to database")
+ span.SetTag("error", true)
+ span.LogKV("event", "database_error", "error", err.Error())
+ logger.WithError(err).Error("Failed to save photo to database")
return nil, fmt.Errorf("failed to save photo: %w", err)
}
@@ -590,6 +646,15 @@ func (s *photoServiceImpl) CreatePhoto(ctx context.Context, req dto.CreatePhotoR
// 清除相关缓存
s.invalidateCache(ctx, "photos:*", "categories:*", "stats:*")
+ // 记录成功日志
+ span.SetTag("photo_id", photo.ID.Value())
+ span.SetTag("photo_title", photo.Title)
+ logger.WithFields(logrus.Fields{
+ "photo_id": photo.ID.Value(),
+ "photo_title": photo.Title,
+ "photo_slug": photo.Slug,
+ }).Info("Photo created successfully")
+
return dto.NewPhotoResponse(photo), nil
}
```
@@ -1267,6 +1332,7 @@ type Config struct {
Storage StorageConfig `mapstructure:"storage"`
JWT JWTConfig `mapstructure:"jwt"`
Logger LoggerConfig `mapstructure:"logger"`
+ Tracing TracingConfig `mapstructure:"tracing"`
Upload UploadConfig `mapstructure:"upload"`
Image ImageConfig `mapstructure:"image"`
}
@@ -1367,6 +1433,18 @@ type LoggerConfig struct {
Compress bool `mapstructure:"compress"`
}
+// TracingConfig 链路追踪配置
+type TracingConfig struct {
+ Enabled bool `mapstructure:"enabled"`
+ ServiceName string `mapstructure:"service_name"`
+ Jaeger struct {
+ Endpoint string `mapstructure:"endpoint"`
+ Username string `mapstructure:"username"`
+ Password string `mapstructure:"password"`
+ } `mapstructure:"jaeger"`
+ SamplingRate float64 `mapstructure:"sampling_rate"`
+}
+
// UploadConfig 上传配置
type UploadConfig struct {
MaxFileSize int64 `mapstructure:"max_file_size"`
@@ -1486,6 +1564,12 @@ func setDefaults() {
viper.SetDefault("logger.max_age", 30)
viper.SetDefault("logger.compress", true)
+ // 链路追踪默认配置
+ viper.SetDefault("tracing.enabled", true)
+ viper.SetDefault("tracing.service_name", "photography-backend")
+ viper.SetDefault("tracing.jaeger.endpoint", "http://localhost:14268/api/traces")
+ viper.SetDefault("tracing.sampling_rate", 1.0)
+
// 上传默认配置
viper.SetDefault("upload.max_file_size", 52428800) // 50MB
viper.SetDefault("upload.allowed_types", []string{"image/jpeg", "image/png", "image/raw"})
@@ -1812,6 +1896,18 @@ services:
networks:
- photography-network
+ # Jaeger链路追踪
+ jaeger:
+ image: jaegertracing/all-in-one:latest
+ ports:
+ - "16686:16686"
+ - "14268:14268"
+ environment:
+ - COLLECTOR_OTLP_ENABLED=true
+ restart: unless-stopped
+ networks:
+ - photography-network
+
# Nginx反向代理(可选)
nginx:
image: nginx:alpine
diff --git a/docs/v1/backend/运维监控方案.md b/docs/v1/backend/运维监控方案.md
new file mode 100644
index 0000000..b2991db
--- /dev/null
+++ b/docs/v1/backend/运维监控方案.md
@@ -0,0 +1,1320 @@
+# 摄影作品集网站 - 日志管理方案
+
+## 🎯 方案概述
+
+这是一个**简单实用的日志管理方案**,专注于日志收集和问题修复。日志查看功能集成到管理后台,提供友好的Web界面。
+
+### 设计原则
+- **集成化**: 日志查看功能集成到管理后台
+- **用户友好**: 提供美观易用的Web界面
+- **问题导向**: 专注于快速定位和修复问题
+- **低维护成本**: 几乎零维护的方案
+- **渐进式**: 后续可以根据需要扩展
+
+## 📋 核心组件
+
+### 日志管理架构
+```
+┌─────────────────────────────────────────────────────────────┐
+│ 后端应用日志 │
+│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
+│ │ 错误日志 │ │ 访问日志 │ │ 业务日志 │ │
+│ │ (JSON格式) │ │ (HTTP日志) │ │ (操作日志) │ │
+│ └─────────────┘ └─────────────┘ └─────────────┘ │
+├─────────────────────────────────────────────────────────────┤
+│ 管理后台日志模块 │
+│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
+│ │ 日志查看器 │ │ 实时监控 │ │ 统计分析 │ │
+│ │ (Web界面) │ │ (自动刷新) │ │ (图表展示) │ │
+│ └─────────────┘ └─────────────┘ └─────────────┘ │
+├─────────────────────────────────────────────────────────────┤
+│ API接口层 │
+│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
+│ │ 日志查询API │ │ 统计API │ │ 搜索API │ │
+│ │(/api/logs) │ │(/api/stats) │ │(/api/search)│ │
+│ └─────────────┘ └─────────────┘ └─────────────┘ │
+└─────────────────────────────────────────────────────────────┘
+```
+
+## 🔧 技术选择
+
+### 日志方案
+- **日志存储**: 本地文件 (JSON格式)
+- **日志轮转**: lumberjack.v2
+- **日志查看**: 管理后台Web界面
+- **Trace ID**: 集成OpenTracing (已在架构文档中添加)
+- **权限控制**: 基于管理后台的用户权限
+
+### 集成方式
+- **前端**: 管理后台的日志管理模块
+- **后端**: Gin路由提供日志查询API
+- **认证**: 复用管理后台的登录认证
+- **权限**: 仅管理员可访问日志功能
+
+## 📝 日志配置
+
+### 1. 应用日志配置
+
+#### 日志配置文件 (config.yaml)
+```yaml
+# config/config.yaml
+logger:
+ level: "info"
+ format: "json"
+ output: "file"
+ filename: "/app/logs/app.log"
+ max_size: 100 # MB
+ max_age: 7 # days
+ compress: true
+
+# 可选:如果需要链路追踪
+tracing:
+ enabled: true
+ service_name: "photography-backend"
+ jaeger:
+ endpoint: "http://localhost:14268/api/traces"
+ sampling_rate: 1.0
+```
+
+### 2. 日志格式标准化
+
+#### 统一的日志格式
+```json
+{
+ "timestamp": "2024-01-15T10:30:00Z",
+ "level": "info",
+ "message": "Photo created successfully",
+ "service": "photography-backend",
+ "trace_id": "abc123def456",
+ "request_id": "req-789",
+ "user_id": "user-123",
+ "operation": "create_photo",
+ "photo_id": 1001,
+ "duration": 0.5,
+ "error": null
+}
+```
+
+### 3. 日志分类
+
+#### 三种核心日志类型
+```bash
+# 日志目录结构
+logs/
+├── app.log # 应用日志 (所有级别)
+├── error.log # 错误日志 (ERROR级别)
+└── access.log # HTTP访问日志
+```
+
+#### 日志级别使用
+```go
+// 日志级别使用指南
+logger.Info("正常业务操作") // 记录重要的业务操作
+logger.Warn("需要关注的情况") // 记录警告信息
+logger.Error("错误情况") // 记录错误信息
+logger.Debug("调试信息") // 开发调试用
+```
+
+## 🔍 问题诊断工具
+
+### 1. 命令行工具
+
+#### 快速查看错误
+```bash
+#!/bin/bash
+# scripts/check-errors.sh
+
+echo "📋 最近的错误日志 (最近1小时):"
+tail -f /app/logs/app.log | grep -i error | jq -r '.timestamp + " " + .message'
+
+echo "📊 错误统计:"
+grep -i error /app/logs/app.log | jq -r '.message' | sort | uniq -c | sort -nr | head -10
+```
+
+#### 按trace ID查找日志
+```bash
+#!/bin/bash
+# scripts/find-by-trace.sh
+
+TRACE_ID=$1
+if [ -z "$TRACE_ID" ]; then
+ echo "用法: ./find-by-trace.sh "
+ exit 1
+fi
+
+echo "🔍 查找 trace ID: $TRACE_ID"
+grep "$TRACE_ID" /app/logs/app.log | jq -r '.timestamp + " [" + .level + "] " + .message'
+```
+
+#### 实时监控错误
+```bash
+#!/bin/bash
+# scripts/monitor-errors.sh
+
+echo "🚨 实时错误监控 (按Ctrl+C退出):"
+tail -f /app/logs/app.log | grep --line-buffered -i error | while read line; do
+ echo -e "\033[31m[ERROR]\033[0m $(echo "$line" | jq -r '.timestamp + " " + .message')"
+done
+```
+
+### 2. 管理后台日志模块
+
+#### 日志查看组件 (React/Vue)
+```html
+
+
+
+ 📋 日志查看器
+
+
+
+
+
+
+
+
+
+```
+
+#### 管理后台日志API接口
+集成到现有的管理后台后端:
+
+```go
+// internal/api/handlers/admin/logs_handler.go
+package admin
+
+import (
+ "bufio"
+ "encoding/json"
+ "net/http"
+ "os"
+ "strconv"
+ "strings"
+
+ "github.com/gin-gonic/gin"
+ "photography-backend/pkg/middleware"
+)
+
+// LogEntry 日志条目
+type LogEntry struct {
+ Timestamp string `json:"timestamp"`
+ Level string `json:"level"`
+ Message string `json:"message"`
+ TraceID string `json:"trace_id,omitempty"`
+ UserID string `json:"user_id,omitempty"`
+ Operation string `json:"operation,omitempty"`
+}
+
+// LogHandler 日志处理器
+type LogHandler struct {
+ logFile string
+}
+
+// NewLogHandler 创建日志处理器
+func NewLogHandler(logFile string) *LogHandler {
+ return &LogHandler{logFile: logFile}
+}
+
+// GetLogs 获取日志列表
+// @Summary 获取系统日志
+// @Description 获取系统日志列表,支持过滤和搜索
+// @Tags 日志管理
+// @Accept json
+// @Produce json
+// @Param level query string false "日志级别" Enums(error,warn,info,debug)
+// @Param search query string false "搜索关键词"
+// @Param trace_id query string false "Trace ID"
+// @Param lines query int false "返回行数" default(100)
+// @Success 200 {object} LogListResponse
+// @Failure 401 {object} ErrorResponse
+// @Failure 403 {object} ErrorResponse
+// @Failure 500 {object} ErrorResponse
+// @Router /admin/api/logs [get]
+func (h *LogHandler) GetLogs(c *gin.Context) {
+ // 获取参数
+ levelFilter := c.Query("level")
+ searchFilter := c.Query("search")
+ traceID := c.Query("trace_id")
+ lines := 100
+ if l := c.Query("lines"); l != "" {
+ if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 && parsed <= 1000 {
+ lines = parsed
+ }
+ }
+
+ // 读取日志文件
+ logs, err := h.readLogs(lines, levelFilter, searchFilter, traceID)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{
+ "error": "读取日志失败",
+ "details": err.Error(),
+ })
+ return
+ }
+
+ c.JSON(http.StatusOK, gin.H{
+ "code": 0,
+ "message": "success",
+ "data": gin.H{
+ "logs": logs,
+ "total": len(logs),
+ },
+ })
+}
+
+// GetLogStats 获取日志统计
+// @Summary 获取日志统计信息
+// @Description 获取各级别日志的统计数据
+// @Tags 日志管理
+// @Accept json
+// @Produce json
+// @Success 200 {object} LogStatsResponse
+// @Failure 401 {object} ErrorResponse
+// @Failure 403 {object} ErrorResponse
+// @Failure 500 {object} ErrorResponse
+// @Router /admin/api/logs/stats [get]
+func (h *LogHandler) GetLogStats(c *gin.Context) {
+ stats := map[string]int{
+ "total": 0,
+ "error": 0,
+ "warn": 0,
+ "info": 0,
+ "debug": 0,
+ }
+
+ file, err := os.Open(h.logFile)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{
+ "error": "读取日志文件失败",
+ "details": err.Error(),
+ })
+ return
+ }
+ defer file.Close()
+
+ scanner := bufio.NewScanner(file)
+ for scanner.Scan() {
+ line := scanner.Text()
+ if line == "" {
+ continue
+ }
+
+ stats["total"]++
+
+ // 解析日志级别
+ var entry LogEntry
+ if err := json.Unmarshal([]byte(line), &entry); err == nil {
+ if count, exists := stats[entry.Level]; exists {
+ stats[entry.Level] = count + 1
+ }
+ }
+ }
+
+ c.JSON(http.StatusOK, gin.H{
+ "code": 0,
+ "message": "success",
+ "data": stats,
+ })
+}
+
+// readLogs 读取日志文件
+func (h *LogHandler) readLogs(maxLines int, levelFilter, searchFilter, traceFilter string) ([]LogEntry, error) {
+ file, err := os.Open(h.logFile)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+
+ var lines []string
+ scanner := bufio.NewScanner(file)
+ for scanner.Scan() {
+ lines = append(lines, scanner.Text())
+ }
+
+ // 从后往前读取最新的日志
+ start := len(lines) - maxLines*2 // 预读更多行用于过滤
+ if start < 0 {
+ start = 0
+ }
+
+ var logs []LogEntry
+ for i := start; i < len(lines) && len(logs) < maxLines; i++ {
+ line := lines[i]
+ if line == "" {
+ continue
+ }
+
+ var entry LogEntry
+ if err := json.Unmarshal([]byte(line), &entry); err != nil {
+ continue // 跳过无法解析的行
+ }
+
+ // 应用过滤条件
+ if levelFilter != "" && entry.Level != levelFilter {
+ continue
+ }
+ if searchFilter != "" {
+ searchLower := strings.ToLower(searchFilter)
+ if !strings.Contains(strings.ToLower(entry.Message), searchLower) &&
+ !strings.Contains(strings.ToLower(entry.TraceID), searchLower) &&
+ !strings.Contains(strings.ToLower(entry.Operation), searchLower) {
+ continue
+ }
+ }
+ if traceFilter != "" && !strings.Contains(entry.TraceID, traceFilter) {
+ continue
+ }
+
+ logs = append(logs, entry)
+ }
+
+ // 反转数组,让最新的日志在前面
+ for i, j := 0, len(logs)-1; i < j; i, j = i+1, j-1 {
+ logs[i], logs[j] = logs[j], logs[i]
+ }
+
+ return logs, nil
+}
+
+// RegisterLogRoutes 注册日志相关路由
+func RegisterLogRoutes(r *gin.RouterGroup, logFile string) {
+ logHandler := NewLogHandler(logFile)
+
+ // 需要管理员权限的路由组
+ adminGroup := r.Group("")
+ adminGroup.Use(middleware.RequireAuth()) // 需要登录
+ adminGroup.Use(middleware.RequireAdmin()) // 需要管理员权限
+
+ {
+ adminGroup.GET("/logs", logHandler.GetLogs)
+ adminGroup.GET("/logs/stats", logHandler.GetLogStats)
+ }
+}
+```
+
+#### 管理后台前端集成
+在管理后台中添加日志管理页面:
+
+```javascript
+// admin/src/pages/Logs/LogViewer.jsx
+import React, { useState, useEffect } from 'react';
+import {
+ Card,
+ Table,
+ Select,
+ Input,
+ Button,
+ Tag,
+ Space,
+ Statistic,
+ Row,
+ Col,
+ message
+} from 'antd';
+import {
+ ReloadOutlined,
+ SearchOutlined,
+ ExclamationCircleOutlined,
+ WarningOutlined,
+ InfoCircleOutlined,
+ BugOutlined
+} from '@ant-design/icons';
+import { adminApi } from '../../services/api';
+
+const { Option } = Select;
+const { Search } = Input;
+
+const LogViewer = () => {
+ const [logs, setLogs] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [filters, setFilters] = useState({
+ level: '',
+ search: '',
+ trace_id: '',
+ lines: 100,
+ });
+ const [stats, setStats] = useState({});
+ const [autoRefresh, setAutoRefresh] = useState(false);
+
+ // 日志级别配置
+ const levelConfig = {
+ error: { color: 'red', icon: },
+ warn: { color: 'orange', icon: },
+ info: { color: 'blue', icon: },
+ debug: { color: 'default', icon: },
+ };
+
+ // 获取日志数据
+ const fetchLogs = async () => {
+ setLoading(true);
+ try {
+ const response = await adminApi.get('/logs', { params: filters });
+ setLogs(response.data.data.logs || []);
+ } catch (error) {
+ message.error('获取日志失败');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ // 获取统计数据
+ const fetchStats = async () => {
+ try {
+ const response = await adminApi.get('/logs/stats');
+ setStats(response.data.data || {});
+ } catch (error) {
+ console.error('获取统计数据失败', error);
+ }
+ };
+
+ useEffect(() => {
+ fetchLogs();
+ fetchStats();
+ }, [filters]);
+
+ // 自动刷新
+ useEffect(() => {
+ let interval;
+ if (autoRefresh) {
+ interval = setInterval(() => {
+ fetchLogs();
+ fetchStats();
+ }, 5000);
+ }
+ return () => interval && clearInterval(interval);
+ }, [autoRefresh, filters]);
+
+ // 表格列配置
+ const columns = [
+ {
+ title: '时间',
+ dataIndex: 'timestamp',
+ key: 'timestamp',
+ width: 180,
+ render: (timestamp) => new Date(timestamp).toLocaleString(),
+ },
+ {
+ title: '级别',
+ dataIndex: 'level',
+ key: 'level',
+ width: 80,
+ render: (level) => {
+ const config = levelConfig[level] || levelConfig.info;
+ return (
+
+ {level.toUpperCase()}
+
+ );
+ },
+ },
+ {
+ title: 'Trace ID',
+ dataIndex: 'trace_id',
+ key: 'trace_id',
+ width: 120,
+ render: (traceId) => traceId ? (
+
+ ) : '-',
+ },
+ {
+ title: '消息',
+ dataIndex: 'message',
+ key: 'message',
+ ellipsis: true,
+ render: (message, record) => {
+ // 高亮搜索关键词
+ if (filters.search && message.toLowerCase().includes(filters.search.toLowerCase())) {
+ const regex = new RegExp(`(${filters.search})`, 'gi');
+ const parts = message.split(regex);
+ return parts.map((part, index) =>
+ part.toLowerCase() === filters.search.toLowerCase() ?
+ {part} : part
+ );
+ }
+ return message;
+ },
+ },
+ {
+ title: '操作',
+ dataIndex: 'operation',
+ key: 'operation',
+ width: 120,
+ render: (operation) => operation || '-',
+ },
+ ];
+
+ return (
+
+ {/* 统计卡片 */}
+
+
+
+
+
+
+
+
+ }
+ />
+
+
+
+
+ }
+ />
+
+
+
+
+ }
+ />
+
+
+
+
+ {/* 过滤控件 */}
+
+
+
+
+ setFilters(prev => ({ ...prev, search: e.target.value }))}
+ prefix={}
+ />
+
+ setFilters(prev => ({ ...prev, trace_id: e.target.value }))}
+ />
+
+
+
+ }
+ onClick={fetchLogs}
+ loading={loading}
+ >
+ 刷新
+
+
+
+
+
+
+ {/* 日志表格 */}
+
+ `${record.timestamp}-${index}`}
+ pagination={{
+ showSizeChanger: false,
+ showQuickJumper: true,
+ showTotal: (total) => `共 ${total} 条日志`,
+ }}
+ scroll={{ x: 800 }}
+ />
+
+
+ );
+};
+
+export default LogViewer;
+```
+
+## 🚀 集成到管理后台
+
+### 1. 后端集成步骤
+
+#### 在main.go中注册日志路由
+```go
+// cmd/server/main.go
+func main() {
+ // ... 其他初始化代码
+
+ // 创建Gin引擎
+ r := gin.Default()
+
+ // 注册API路由
+ apiGroup := r.Group("/api")
+ {
+ // 其他API路由...
+ }
+
+ // 注册管理后台路由
+ adminGroup := r.Group("/admin/api")
+ {
+ // 注册日志管理路由
+ admin.RegisterLogRoutes(adminGroup, "logs/app.log")
+
+ // 其他管理后台路由...
+ }
+
+ r.Run(":8080")
+}
+```
+
+#### 添加权限中间件
+```go
+// pkg/middleware/auth.go
+package middleware
+
+import (
+ "net/http"
+ "github.com/gin-gonic/gin"
+)
+
+// RequireAuth 需要登录认证
+func RequireAuth() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ // 检查JWT token或session
+ token := c.GetHeader("Authorization")
+ if token == "" {
+ c.JSON(http.StatusUnauthorized, gin.H{
+ "error": "未授权访问",
+ })
+ c.Abort()
+ return
+ }
+
+ // 验证token并获取用户信息
+ // ... token验证逻辑
+
+ c.Next()
+ }
+}
+
+// RequireAdmin 需要管理员权限
+func RequireAdmin() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ // 检查用户是否是管理员
+ userRole := c.GetString("user_role")
+ if userRole != "admin" {
+ c.JSON(http.StatusForbidden, gin.H{
+ "error": "需要管理员权限",
+ })
+ c.Abort()
+ return
+ }
+
+ c.Next()
+ }
+}
+```
+
+### 2. 前端集成步骤
+
+#### 在管理后台路由中添加日志管理
+```javascript
+// admin/src/router/index.js
+import LogViewer from '../pages/Logs/LogViewer.jsx';
+
+const routes = [
+ // 其他路由...
+ {
+ path: '/admin',
+ component: AdminLayout,
+ children: [
+ {
+ path: 'logs',
+ component: LogViewer,
+ meta: {
+ title: '日志管理',
+ requireAuth: true,
+ requireAdmin: true
+ }
+ }
+ // 其他子路由...
+ ]
+ }
+];
+```
+
+#### 在管理后台菜单中添加日志入口
+```javascript
+// admin/src/layout/AdminLayout.jsx
+const menuItems = [
+ {
+ key: 'dashboard',
+ icon: ,
+ label: '仪表盘',
+ path: '/admin/dashboard'
+ },
+ {
+ key: 'photos',
+ icon: ,
+ label: '照片管理',
+ path: '/admin/photos'
+ },
+ {
+ key: 'logs',
+ icon: ,
+ label: '日志管理',
+ path: '/admin/logs'
+ },
+ // 其他菜单项...
+];
+```
+
+### 3. 配置更新
+
+#### 更新应用配置文件
+```yaml
+# config/config.yaml
+app:
+ name: "photography-backend"
+ version: "1.0.0"
+ environment: "production"
+
+logger:
+ level: "info"
+ format: "json"
+ output: "file"
+ filename: "logs/app.log"
+ max_size: 100
+ max_age: 7
+ compress: true
+
+tracing:
+ enabled: true
+ service_name: "photography-backend"
+ sampling_rate: 1.0
+
+admin:
+ log_access: true # 启用日志访问功能
+ log_retention_days: 30 # 日志保留天数
+```
+
+### 4. 一键部署脚本
+
+#### 部署管理后台日志功能
+```bash
+#!/bin/bash
+# deploy-admin-logs.sh
+
+echo "🚀 部署管理后台日志功能..."
+
+# 创建必要目录
+mkdir -p logs
+mkdir -p admin/src/pages/Logs
+
+# 确保日志文件存在
+touch logs/app.log
+chmod 664 logs/app.log
+
+# 创建日志轮转配置
+cat > /etc/logrotate.d/photography-backend << 'EOF'
+/path/to/photography/logs/*.log {
+ daily
+ rotate 30
+ compress
+ delaycompress
+ missingok
+ notifempty
+ create 664 app app
+ postrotate
+ systemctl reload photography-backend
+ endscript
+}
+EOF
+
+# 设置权限
+chown -R app:app logs/
+chmod 755 logs/
+
+echo "✅ 管理后台日志功能部署完成!"
+echo ""
+echo "📋 访问方式:"
+echo " 1. 登录管理后台: http://localhost:8080/admin"
+echo " 2. 进入日志管理页面"
+echo " 3. 使用管理员账号访问"
+echo ""
+echo "🔧 API地址:"
+echo " 日志列表: GET /admin/api/logs"
+echo " 日志统计: GET /admin/api/logs/stats"
+```
+
+## 🔧 故障排查流程
+
+### 1. 问题诊断步骤
+```bash
+# 快速诊断脚本
+#!/bin/bash
+# scripts/quick-diagnosis.sh
+
+echo "🔍 快速问题诊断"
+
+# 1. 检查服务状态
+echo "1. 检查服务状态..."
+docker-compose ps
+
+# 2. 检查最近错误
+echo "2. 最近10条错误日志..."
+tail -n 1000 logs/app.log | grep -i error | tail -10 | jq -r '.timestamp + " " + .message'
+
+# 3. 检查磁盘空间
+echo "3. 检查磁盘空间..."
+df -h
+
+# 4. 检查日志文件大小
+echo "4. 检查日志文件大小..."
+ls -lh logs/
+
+# 5. 检查内存使用
+echo "5. 检查内存使用..."
+docker stats --no-stream photography-backend
+
+echo "✅ 诊断完成"
+```
+
+### 2. 常见问题解决
+
+#### 问题1:无法找到错误原因
+```bash
+# 1. 获取完整的错误上下文
+grep -B 5 -A 5 "error_message" logs/app.log
+
+# 2. 按时间范围查找
+grep "2024-01-15T10:" logs/app.log | grep -i error
+```
+
+#### 问题2:需要追踪特定用户的操作
+```bash
+# 按用户ID查找
+grep "user-123" logs/app.log | jq -r '.timestamp + " " + .message'
+
+# 按操作类型查找
+grep "create_photo" logs/app.log | jq -r '.timestamp + " " + .message'
+```
+
+#### 问题3:日志文件太大
+```bash
+# 手动轮转日志
+mv logs/app.log logs/app.log.old
+sudo systemctl restart photography-backend
+
+# 或者使用logrotate
+sudo logrotate -f /etc/logrotate.d/photography-backend
+```
+
+## 🎯 总结
+
+这个集成到管理后台的日志方案提供了:
+
+### ✨ 核心特性
+- **🏢 集成化管理** - 完全集成到管理后台,统一的用户体验
+- **🔐 权限控制** - 基于管理后台的认证和授权体系
+- **📊 专业界面** - 使用Ant Design组件,美观且专业
+- **🔍 强大搜索** - 支持关键词、级别、Trace ID多维度过滤
+- **⏰ 实时监控** - 自动刷新功能,实时观察系统状态
+- **📈 统计分析** - 直观的统计卡片,快速了解系统健康状况
+
+### 🚀 部署简单
+1. 运行部署脚本: `./deploy-admin-logs.sh`
+2. 访问管理后台: `http://localhost:8080/admin`
+3. 登录管理员账号,进入日志管理页面
+
+### 💡 使用场景
+- **错误排查**: 管理员快速定位和分析错误日志
+- **性能监控**: 通过Trace ID追踪完整请求链路
+- **运营监控**: 实时观察系统运行状态
+- **历史分析**: 搜索和分析历史日志数据
+- **团队协作**: 多个管理员可以同时查看和分析日志
+
+### 🔧 技术优势
+- **集成性**: 完全集成到现有管理后台
+- **安全性**: 基于管理后台的权限控制
+- **专业性**: 使用成熟的UI组件库
+- **可扩展**: 易于添加新的日志分析功能
+- **用户友好**: 直观的界面,无需学习成本
+
+### 🔐 权限控制
+- **认证**: 需要管理后台登录
+- **授权**: 仅管理员可访问日志功能
+- **安全**: API接口有完整的权限验证
+
+### 📋 API接口
+- `GET /admin/api/logs` - 获取日志列表
+- `GET /admin/api/logs/stats` - 获取日志统计
+
+这就是最适合生产环境的日志管理方案 - **专业、安全、易用**!
\ No newline at end of file