1
0

feat: 实现分层架构,包含 domain、service、repository 和 pkg 层

- 新增 domain 层:model、provider、route、stats 实体
- 新增 service 层:models、providers、routing、stats 业务逻辑
- 新增 repository 层:models、providers、stats 数据访问
- 新增 pkg 工具包:errors、logger、validator
- 新增中间件:CORS、logging、recovery、request ID
- 新增数据库迁移:初始 schema 和索引
- 新增单元测试和集成测试
- 新增规范文档:config-management、database-migration、error-handling、layered-architecture、middleware-system、request-validation、structured-logging、test-coverage
- 移除 config 子包和 model_router(已迁移至分层架构)
This commit is contained in:
2026-04-16 00:47:20 +08:00
parent 915b004924
commit f18904af1e
77 changed files with 5727 additions and 1257 deletions

View File

@@ -0,0 +1,109 @@
package logger
import (
"os"
"path/filepath"
"time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// Config 日志配置
type Config struct {
Level string // 日志级别: debug, info, warn, error
Path string // 日志文件目录,为空则仅输出到 stdout
MaxSize int // 单个日志文件最大尺寸 (MB)
MaxBackups int // 保留的旧日志文件最大数量
MaxAge int // 保留旧日志文件的最大天数
Compress bool // 是否压缩旧日志文件
}
// New 根据配置创建 zap.Logger
// 如果 Path 为空,仅输出到 stdout
// 如果 Path 已设置,同时输出到 stdout 和文件(文件使用 JSON 格式stdout 使用 console 格式)
func New(cfg Config) (*zap.Logger, error) {
level, err := parseLevel(cfg.Level)
if err != nil {
return nil, err
}
// stdout encoder — console 格式
stdoutEncoder := zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
TimeKey: "ts",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
FunctionKey: zapcore.OmitKey,
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.CapitalColorLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.StringDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
})
stdoutCore := zapcore.NewCore(
stdoutEncoder,
zapcore.AddSync(os.Stdout),
level,
)
// 仅 stdout 模式
if cfg.Path == "" {
return zap.New(stdoutCore, zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel)), nil
}
// 文件 encoder — JSON 格式
fileEncoder := zapcore.NewJSONEncoder(zapcore.EncoderConfig{
TimeKey: "ts",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
FunctionKey: zapcore.OmitKey,
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.StringDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
})
rotateWriter := newRotateWriter(cfg)
fileCore := zapcore.NewCore(
fileEncoder,
zapcore.AddSync(rotateWriter),
level,
)
core := zapcore.NewTee(stdoutCore, fileCore)
return zap.New(core, zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel)), nil
}
// parseLevel 将字符串解析为 zapcore.Level
func parseLevel(s string) (zapcore.Level, error) {
switch s {
case "debug":
return zapcore.DebugLevel, nil
case "info":
return zapcore.InfoLevel, nil
case "warn":
return zapcore.WarnLevel, nil
case "error":
return zapcore.ErrorLevel, nil
default:
return zapcore.InfoLevel, nil
}
}
// logFileName 生成当日日志文件名: nex-YYYY-MM-DD.log
func logFileName() string {
return "nex-" + time.Now().Format("2006-01-02") + ".log"
}
// logFilePath 拼接完整日志文件路径
func logFilePath(dir string) string {
return filepath.Join(dir, logFileName())
}