diff --git a/Dockerfile b/Dockerfile index 438efbe..7ae85d6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,6 @@ # Build stage -FROM registry.cn-beijing.aliyuncs.com/bitwsd/golang AS builder +FROM golang:1.20-alpine AS builder + WORKDIR /app @@ -7,10 +8,10 @@ WORKDIR /app COPY . . # Build binary -RUN CGO_ENABLED=0 GOOS=linux go build -mod=vendor -o main ./main.go +RUN CGO_ENABLED=0 GOOS=linux go build -mod=vendor -o doc_ai ./main.go # Runtime stage -FROM registry.cn-beijing.aliyuncs.com/bitwsd/alpine +FROM alpine:latest # Set timezone RUN apk add --no-cache tzdata && \ @@ -21,7 +22,7 @@ RUN apk add --no-cache tzdata && \ WORKDIR /app # Copy binary from builder -COPY --from=builder /app/main . +COPY --from=builder /app/doc_ai . # Copy config files COPY config/config_*.yaml ./config/ @@ -34,7 +35,7 @@ RUN mkdir -p /data/formula && \ EXPOSE 8024 # Set entrypoint -ENTRYPOINT ["./main"] +ENTRYPOINT ["./doc_ai"] # Default command (can be overridden) CMD ["-env", "prod"] \ No newline at end of file diff --git a/api/v1/task/handler.go b/api/v1/task/handler.go index d96752d..a3a2f89 100644 --- a/api/v1/task/handler.go +++ b/api/v1/task/handler.go @@ -3,7 +3,7 @@ package task import ( "net/http" - "gitea.com/bitwsd/core/common/log" + "gitea.com/bitwsd/document_ai/pkg/log" "gitea.com/bitwsd/document_ai/internal/model/task" "gitea.com/bitwsd/document_ai/internal/service" "gitea.com/bitwsd/document_ai/pkg/common" diff --git a/api/v1/user/handler.go b/api/v1/user/handler.go index c068127..8fb63e6 100644 --- a/api/v1/user/handler.go +++ b/api/v1/user/handler.go @@ -3,7 +3,7 @@ package user import ( "net/http" - "gitea.com/bitwsd/core/common/log" + "gitea.com/bitwsd/document_ai/pkg/log" "gitea.com/bitwsd/document_ai/config" model "gitea.com/bitwsd/document_ai/internal/model/user" "gitea.com/bitwsd/document_ai/internal/service" diff --git a/config/config.go b/config/config.go index 4e8aade..d7c23f3 100644 --- a/config/config.go +++ b/config/config.go @@ -1,7 +1,7 @@ package config import ( - "gitea.com/bitwsd/core/common/log" + "gitea.com/bitwsd/document_ai/pkg/log" "github.com/spf13/viper" ) diff --git a/go.mod b/go.mod index a920b06..7eddce1 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module gitea.com/bitwsd/document_ai go 1.20 require ( - gitea.com/bitwsd/core v0.0.0-20241128075635-8d72a929b914 github.com/alibabacloud-go/darabonba-openapi v0.2.1 github.com/alibabacloud-go/dysmsapi-20170525/v2 v2.0.18 github.com/alibabacloud-go/tea v1.1.19 @@ -13,7 +12,9 @@ require ( github.com/gin-gonic/gin v1.10.0 github.com/google/uuid v1.6.0 github.com/redis/go-redis/v9 v9.7.0 + github.com/rs/zerolog v1.33.0 github.com/spf13/viper v1.19.0 + gopkg.in/natefinch/lumberjack.v2 v2.2.1 gorm.io/driver/mysql v1.5.7 gorm.io/gorm v1.25.12 ) @@ -54,7 +55,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect - github.com/rs/zerolog v1.33.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect @@ -76,6 +76,5 @@ require ( golang.org/x/time v0.5.0 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 1f22c0d..2af57c4 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -gitea.com/bitwsd/core v0.0.0-20241128075635-8d72a929b914 h1:3aRCeiuq/PWMr2yjEN9Y5NusfmpdMKiO4i/5tM5qc34= -gitea.com/bitwsd/core v0.0.0-20241128075635-8d72a929b914/go.mod h1:hbEUo3t/AFGCnQbxwdG4oiw2IHdlRgK02cqd0yicP1Y= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= github.com/alibabacloud-go/darabonba-openapi v0.1.18/go.mod h1:PB4HffMhJVmAgNKNq3wYbTUlFvPgxJpTzd1F5pTuUsc= diff --git a/internal/service/recognition_service.go b/internal/service/recognition_service.go index be98d3b..6a93226 100644 --- a/internal/service/recognition_service.go +++ b/internal/service/recognition_service.go @@ -11,11 +11,11 @@ import ( "strings" "time" - "gitea.com/bitwsd/core/common/log" "gitea.com/bitwsd/document_ai/config" "gitea.com/bitwsd/document_ai/internal/model/formula" "gitea.com/bitwsd/document_ai/internal/storage/cache" "gitea.com/bitwsd/document_ai/internal/storage/dao" + "gitea.com/bitwsd/document_ai/pkg/log" "gitea.com/bitwsd/document_ai/pkg/common" "gitea.com/bitwsd/document_ai/pkg/constant" diff --git a/internal/service/task.go b/internal/service/task.go index 3f9b20c..e861736 100644 --- a/internal/service/task.go +++ b/internal/service/task.go @@ -5,7 +5,7 @@ import ( "errors" "strings" - "gitea.com/bitwsd/core/common/log" + "gitea.com/bitwsd/document_ai/pkg/log" "gitea.com/bitwsd/document_ai/internal/model/task" "gitea.com/bitwsd/document_ai/internal/storage/dao" "gorm.io/gorm" diff --git a/internal/service/user_service.go b/internal/service/user_service.go index 01d1af8..140608d 100644 --- a/internal/service/user_service.go +++ b/internal/service/user_service.go @@ -6,7 +6,7 @@ import ( "fmt" "math/rand" - "gitea.com/bitwsd/core/common/log" + "gitea.com/bitwsd/document_ai/pkg/log" "gitea.com/bitwsd/document_ai/internal/storage/cache" "gitea.com/bitwsd/document_ai/internal/storage/dao" "gitea.com/bitwsd/document_ai/pkg/sms" diff --git a/main.go b/main.go index 5076b23..b962566 100644 --- a/main.go +++ b/main.go @@ -10,9 +10,9 @@ import ( "syscall" "time" - "gitea.com/bitwsd/core/common/cors" - "gitea.com/bitwsd/core/common/log" - "gitea.com/bitwsd/core/common/middleware" + "gitea.com/bitwsd/document_ai/pkg/cors" + "gitea.com/bitwsd/document_ai/pkg/log" + "gitea.com/bitwsd/document_ai/pkg/middleware" "gitea.com/bitwsd/document_ai/api" "gitea.com/bitwsd/document_ai/config" "gitea.com/bitwsd/document_ai/internal/storage/cache" diff --git a/pkg/cors/cors.go b/pkg/cors/cors.go new file mode 100644 index 0000000..faaafd2 --- /dev/null +++ b/pkg/cors/cors.go @@ -0,0 +1,61 @@ +package cors + +import ( + "strconv" + "strings" + + "github.com/gin-gonic/gin" +) + +type Config struct { + AllowOrigins []string + AllowMethods []string + AllowHeaders []string + ExposeHeaders []string + AllowCredentials bool + MaxAge int +} + +func DefaultConfig() Config { + return Config{ + AllowOrigins: []string{"*"}, + AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, + AllowHeaders: []string{"Origin", "Content-Type", "Accept"}, + ExposeHeaders: []string{"Content-Length"}, + AllowCredentials: true, + MaxAge: 86400, // 24 hours + } +} + +func Cors(config Config) gin.HandlerFunc { + return func(c *gin.Context) { + origin := c.Request.Header.Get("Origin") + + // 检查是否允许该来源 + allowOrigin := "*" + for _, o := range config.AllowOrigins { + if o == origin { + allowOrigin = origin + break + } + } + + c.Header("Access-Control-Allow-Origin", allowOrigin) + c.Header("Access-Control-Allow-Methods", strings.Join(config.AllowMethods, ",")) + c.Header("Access-Control-Allow-Headers", strings.Join(config.AllowHeaders, ",")) + c.Header("Access-Control-Expose-Headers", strings.Join(config.ExposeHeaders, ",")) + c.Header("Access-Control-Max-Age", strconv.Itoa(config.MaxAge)) + + if config.AllowCredentials { + c.Header("Access-Control-Allow-Credentials", "true") + } + + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(204) + return + } + + c.Next() + } +} + diff --git a/pkg/httpclient/client.go b/pkg/httpclient/client.go index f3779e9..d508ed0 100644 --- a/pkg/httpclient/client.go +++ b/pkg/httpclient/client.go @@ -10,7 +10,7 @@ import ( "net/http" "time" - "gitea.com/bitwsd/core/common/log" + "gitea.com/bitwsd/document_ai/pkg/log" ) // RetryConfig 重试配置 diff --git a/pkg/log/log_config.go b/pkg/log/log_config.go new file mode 100644 index 0000000..02d7da2 --- /dev/null +++ b/pkg/log/log_config.go @@ -0,0 +1,30 @@ +package log + +var ( + maxSize = 100 // MB + outputPath = "/app/logs/app.log" +) + +type LogConfig struct { + AppName string `yaml:"appName"` // 应用名称 + Level string `yaml:"level"` // debug, info, warn, error + Format string `yaml:"format"` // json, console + OutputPath string `yaml:"outputPath"` // 日志文件路径 + MaxSize int `yaml:"maxSize"` // 单个日志文件最大尺寸,单位MB + MaxAge int `yaml:"maxAge"` // 日志保留天数 + MaxBackups int `yaml:"maxBackups"` // 保留的旧日志文件最大数量 + Compress bool `yaml:"compress"` // 是否压缩旧日志 +} + +func DefaultLogConfig() *LogConfig { + return &LogConfig{ + Level: "info", + Format: "json", + OutputPath: outputPath, + MaxSize: maxSize, + MaxAge: 7, + MaxBackups: 3, + Compress: true, + } +} + diff --git a/pkg/log/logger.go b/pkg/log/logger.go new file mode 100644 index 0000000..fba9a3a --- /dev/null +++ b/pkg/log/logger.go @@ -0,0 +1,152 @@ +package log + +import ( + "context" + "fmt" + "os" + "path/filepath" + "runtime" + "time" + + "github.com/rs/zerolog" + "gopkg.in/natefinch/lumberjack.v2" +) + +type LogType string + +const ( + TypeAccess LogType = "access" + TypeBusiness LogType = "business" + TypeError LogType = "error" +) + +var ( + logger zerolog.Logger +) + +// Setup 初始化日志配置 +func Setup(conf LogConfig) error { + // 确保日志目录存在 + if err := os.MkdirAll(filepath.Dir(conf.OutputPath), 0755); err != nil { + return fmt.Errorf("create log directory failed: %v", err) + } + + // 配置日志轮转 + writer := &lumberjack.Logger{ + Filename: conf.OutputPath, + MaxSize: conf.MaxSize, // MB + MaxAge: conf.MaxAge, // days + MaxBackups: conf.MaxBackups, + Compress: conf.Compress, + } + + // 设置日志级别 + level, err := zerolog.ParseLevel(conf.Level) + if err != nil { + level = zerolog.InfoLevel + } + zerolog.SetGlobalLevel(level) + + // 初始化logger 并添加 app_name + logger = zerolog.New(writer).With(). + Timestamp(). + Str("app_name", conf.AppName). // 添加 app_name + Logger() + + return nil +} + +// log 统一的日志记录函数 +func log(ctx context.Context, level zerolog.Level, logType LogType, kv ...interface{}) { + if len(kv)%2 != 0 { + kv = append(kv, "MISSING") + } + + event := logger.WithLevel(level) + + // 添加日志类型 + event.Str("type", string(logType)) + + // 添加请求ID + if reqID, exists := ctx.Value("request_id").(string); exists { + event.Str("request_id", reqID) + } + + // 添加调用位置 + if pc, file, line, ok := runtime.Caller(2); ok { + event.Str("caller", fmt.Sprintf("%s:%d %s", filepath.Base(file), line, runtime.FuncForPC(pc).Name())) + } + + // 处理key-value对 + for i := 0; i < len(kv); i += 2 { + key, ok := kv[i].(string) + if !ok { + continue + } + value := kv[i+1] + + switch v := value.(type) { + case error: + event.AnErr(key, v) + case int: + event.Int(key, v) + case int64: + event.Int64(key, v) + case float64: + event.Float64(key, v) + case bool: + event.Bool(key, v) + case time.Duration: + event.Dur(key, v) + case time.Time: + event.Time(key, v) + case []byte: + event.Bytes(key, v) + case string: + event.Str(key, v) + default: + event.Interface(key, v) + } + } + + event.Send() +} + +// Debug 记录调试日志 +func Debug(ctx context.Context, kv ...interface{}) { + log(ctx, zerolog.DebugLevel, TypeBusiness, kv...) +} + +// Info 记录信息日志 +func Info(ctx context.Context, kv ...interface{}) { + log(ctx, zerolog.InfoLevel, TypeBusiness, kv...) +} + +// Warn 记录警告日志 +func Warn(ctx context.Context, kv ...interface{}) { + log(ctx, zerolog.WarnLevel, TypeError, kv...) +} + +// Error 记录错误日志 +func Error(ctx context.Context, kv ...interface{}) { + log(ctx, zerolog.ErrorLevel, TypeError, kv...) +} + +func Fatal(ctx context.Context, kv ...interface{}) { + // 获取错误堆栈 + buf := make([]byte, 4096) + n := runtime.Stack(buf, false) + + // 添加堆栈信息到kv + newKv := make([]interface{}, 0, len(kv)+2) + newKv = append(newKv, kv...) + newKv = append(newKv, "stack", string(buf[:n])) + + log(ctx, zerolog.FatalLevel, TypeError, newKv...) +} + +// Access 记录访问日志 +func Access(ctx context.Context, kv ...interface{}) { + log(ctx, zerolog.InfoLevel, TypeAccess, kv...) +} + diff --git a/pkg/middleware/access.go b/pkg/middleware/access.go new file mode 100644 index 0000000..b13e772 --- /dev/null +++ b/pkg/middleware/access.go @@ -0,0 +1,75 @@ +package middleware + +import ( + "bytes" + "io" + "strings" + "time" + + "gitea.com/bitwsd/document_ai/pkg/log" + "github.com/gin-gonic/gin" +) + +const ( + maxBodySize = 1024 * 500 // 500KB 限制 +) + +// 自定义 ResponseWriter 来捕获响应 +type bodyWriter struct { + gin.ResponseWriter + body *bytes.Buffer +} + +func (w *bodyWriter) Write(b []byte) (int, error) { + w.body.Write(b) + return w.ResponseWriter.Write(b) +} + +func AccessLog() gin.HandlerFunc { + return func(c *gin.Context) { + start := time.Now() + path := c.Request.URL.Path + raw := c.Request.URL.RawQuery + + // 处理请求体 + var reqBody string + if c.Request.Body != nil && (c.Request.Method == "POST" || c.Request.Method == "PUT") { + // 读取并限制请求体大小 + bodyBytes, _ := io.ReadAll(io.LimitReader(c.Request.Body, maxBodySize)) + c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) // 重新设置 body + reqBody = string(bodyBytes) + } + + // 设置自定义 ResponseWriter + var responseBody string + if !strings.Contains(c.GetHeader("Accept"), "text/event-stream") { + bw := &bodyWriter{body: &bytes.Buffer{}, ResponseWriter: c.Writer} + c.Writer = bw + } + + c.Next() + + // 获取响应体(非 SSE) + if writer, ok := c.Writer.(*bodyWriter); ok { + responseBody = writer.body.String() + if len(responseBody) > maxBodySize { + responseBody = responseBody[:maxBodySize] + "... (truncated)" + } + } + + // 记录访问日志 + log.Access(c.Request.Context(), + "request_id", c.GetString("request_id"), + "method", c.Request.Method, + "path", path, + "query", raw, + "ip", c.ClientIP(), + "user_agent", c.Request.UserAgent(), + "status", c.Writer.Status(), + "duration", time.Since(start), + "request_body", reqBody, + "response_body", responseBody, + ) + } +} + diff --git a/pkg/middleware/requestid.go b/pkg/middleware/requestid.go new file mode 100644 index 0000000..242ddf6 --- /dev/null +++ b/pkg/middleware/requestid.go @@ -0,0 +1,19 @@ +package middleware + +import ( + "github.com/gin-gonic/gin" + "github.com/google/uuid" +) + +func RequestID() gin.HandlerFunc { + return func(c *gin.Context) { + requestID := c.Request.Header.Get("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + } + c.Request.Header.Set("X-Request-ID", requestID) + c.Set("request_id", requestID) + c.Next() + } +} + diff --git a/pkg/oss/policy.go b/pkg/oss/policy.go index 12fa3bd..8551893 100644 --- a/pkg/oss/policy.go +++ b/pkg/oss/policy.go @@ -12,8 +12,8 @@ import ( "strings" "time" - "gitea.com/bitwsd/core/common/log" "gitea.com/bitwsd/document_ai/config" + "gitea.com/bitwsd/document_ai/pkg/log" "github.com/aliyun/aliyun-oss-go-sdk/oss" ) diff --git a/pkg/utils/routine.go b/pkg/utils/routine.go index 9126a8a..5ed9ca1 100644 --- a/pkg/utils/routine.go +++ b/pkg/utils/routine.go @@ -3,7 +3,7 @@ package utils import ( "context" - "gitea.com/bitwsd/core/common/log" + "gitea.com/bitwsd/document_ai/pkg/log" ) func SafeGo(fn func()) {