feat: update dockerfile
This commit is contained in:
61
pkg/cors/cors.go
Normal file
61
pkg/cors/cors.go
Normal file
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"gitea.com/bitwsd/core/common/log"
|
||||
"gitea.com/bitwsd/document_ai/pkg/log"
|
||||
)
|
||||
|
||||
// RetryConfig 重试配置
|
||||
|
||||
30
pkg/log/log_config.go
Normal file
30
pkg/log/log_config.go
Normal file
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
152
pkg/log/logger.go
Normal file
152
pkg/log/logger.go
Normal file
@@ -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...)
|
||||
}
|
||||
|
||||
75
pkg/middleware/access.go
Normal file
75
pkg/middleware/access.go
Normal file
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
19
pkg/middleware/requestid.go
Normal file
19
pkg/middleware/requestid.go
Normal file
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
Reference in New Issue
Block a user