开发文档

This commit is contained in:
xujiang
2025-07-09 14:32:52 +08:00
parent 73197d8da8
commit 180fbd2ae9
7 changed files with 5856 additions and 3 deletions

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff