开发文档

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

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