开发文档
This commit is contained in:
@ -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
|
||||
|
||||
1320
docs/v1/backend/运维监控方案.md
Normal file
1320
docs/v1/backend/运维监控方案.md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user