init: initial commit

This commit is contained in:
Blizzard
2026-02-06 14:44:06 +08:00
commit 3115b58cb2
133 changed files with 25889 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
package internal
const (
ConfigDefaultFile = "config-dev.yaml"
ConfigProdFile = "config-prod.yaml"
ConfigDebugFile = "config-debug.yaml"
ConfigReleaseFile = "config-release.yaml"
)
+161
View File
@@ -0,0 +1,161 @@
package internal
import (
"os"
"path/filepath"
"sync"
"time"
)
type Cutter struct {
level string // 日志级别
layout string //时间格式
formats []string //自定义参数
director string //日志文件夹
retentionDay int //保留天数
file *os.File //文件
mutex *sync.RWMutex // 读写锁
}
type CutterOption func(c *Cutter)
// 设置时间格式
func CutterWithLayout(layout string) CutterOption {
return func(c *Cutter) {
c.layout = layout
}
}
// 格式化参数
func CutterWithFormats(format ...string) CutterOption {
return func(c *Cutter) {
if len(format) > 0 {
c.formats = format
}
}
}
// NewCutter 创建一个新的 Cutter 实例,用于管理日志文件的切割和保留。
//
// 参数:
// - directory: 日志文件存储的目录路径。
// - level: 日志级别,用于标识日志的严重程度。
// - retentionDay: 日志文件保留的天数,超过该天数的日志文件将被删除。
// - options: 可选的 CutterOption 函数,用于对 Cutter 实例进行额外的配置。
//
// 返回值:
// - *Cutter: 返回一个初始化后的 Cutter 实例。
func NewCutter(directory string, level string, retentionDay int, options ...CutterOption) *Cutter {
// 初始化 Cutter 实例,设置日志级别、目录、保留天数以及互斥锁
rotate := &Cutter{
level: level,
director: directory,
retentionDay: retentionDay,
mutex: new(sync.RWMutex),
}
// 应用所有传入的 CutterOption 配置函数
for i := 0; i < len(options); i++ {
options[i](rotate)
}
return rotate
}
// Write 方法将给定的字节数据写入到日志文件中。该方法会确保日志文件的目录存在,并根据配置的格式生成文件名。
// 如果日志文件已经存在,数据将被追加到文件末尾。如果文件不存在,则会创建新文件。
// 该方法还会定期清理超过保留天数的日志文件夹。
//
// 参数:
// - bytes: 要写入的字节数据。
//
// 返回值:
// - n: 成功写入的字节数。
// - err: 如果发生错误,返回错误信息;否则返回 nil。
func (c *Cutter) Write(bytes []byte) (n int, err error) {
// 加锁以确保并发安全
c.mutex.Lock()
defer func() {
// 在函数结束时关闭文件并释放锁
if c.file != nil {
_ = c.file.Close()
c.file = nil
}
c.mutex.Unlock()
}()
// 生成日志文件名
length := len(c.formats)
values := make([]string, 0, 3+length)
values = append(values, c.director)
if c.layout != "" {
values = append(values, time.Now().Format(c.layout))
}
for i := 0; i < length; i++ {
values = append(values, c.formats[i])
}
values = append(values, c.level+".log")
filename := filepath.Join(values...)
// 确保日志文件所在的目录存在
directory := filepath.Dir(filename)
err = os.MkdirAll(directory, os.ModePerm)
if err != nil {
return 0, nil
}
// 清理超过保留天数的日志文件夹
err = removeNDaysFolders(c.director, c.retentionDay)
if err != nil {
return 0, err
}
// 打开或创建日志文件,并追加写入数据
c.file, err = os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return 0, err
}
// 将数据写入文件并返回写入的字节数
return c.file.Write(bytes)
}
// Sync 方法用于将当前文件的内容同步到磁盘,确保所有缓冲区的数据都写入磁盘。
// 该方法在调用时会先获取互斥锁,以确保在同步过程中不会有其他操作干扰。
// 如果当前 Cutter 实例中的文件对象不为 nil,则调用文件对象的 Sync 方法进行同步操作。
// 如果文件对象为 nil,则直接返回 nil,表示无需同步。
//
// 返回值:
// - error: 如果同步过程中发生错误,则返回该错误;否则返回 nil。
func (c *Cutter) Sync() error {
c.mutex.Lock()
defer c.mutex.Unlock()
// 如果文件对象存在,则调用其 Sync 方法进行同步
if c.file != nil {
return c.file.Sync()
}
// 文件对象不存在,直接返回 nil
return nil
}
// removeNDaysFolders 删除指定目录下,指定天数前的文件夹
func removeNDaysFolders(dir string, days int) error {
if days <= 0 {
return nil
}
cutoff := time.Now().AddDate(0, 0, -days)
return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() && info.ModTime().Before(cutoff) && path != dir {
err = os.RemoveAll(path)
if err != nil {
return err
}
}
return nil
})
}
+68
View File
@@ -0,0 +1,68 @@
package internal
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
"sundynix-go/global"
"time"
)
type ZapCore struct {
level zapcore.Level
zapcore.Core
}
// NewZapCore 创建一个 zapcore.Core
func NewZapCore(level zapcore.Level) *ZapCore {
entity := &ZapCore{level: level}
syncer := entity.WriteSyncer()
levelEnabler := zap.LevelEnablerFunc(func(l zapcore.Level) bool { return l == level })
entity.Core = zapcore.NewCore(global.Config.Zap.Encoder(), syncer, levelEnabler)
return entity
}
// WriteSyncer 创建一个 zapcore.WriteSyncer
func (z *ZapCore) WriteSyncer(formats ...string) zapcore.WriteSyncer {
cutter := NewCutter(
global.Config.Zap.Director,
z.level.String(),
global.Config.Zap.RetentionDay,
CutterWithLayout(time.DateOnly),
CutterWithFormats(formats...),
)
if global.Config.Zap.LogInConsole {
multiSyncer := zapcore.NewMultiWriteSyncer(os.Stdout, cutter)
return zapcore.AddSync(multiSyncer)
}
return zapcore.AddSync(cutter)
}
func (z *ZapCore) Enabled(level zapcore.Level) bool {
return z.level == level
}
func (z *ZapCore) With(fields []zapcore.Field) zapcore.Core {
return z.Core.With(fields)
}
func (z *ZapCore) Check(entry zapcore.Entry, check *zapcore.CheckedEntry) *zapcore.CheckedEntry {
if z.Enabled(entry.Level) {
return check.AddCore(entry, z)
}
return check
}
func (z *ZapCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
for i := 0; i < len(fields); i++ {
if fields[i].Key == "business" || fields[i].Key == "folder" || fields[i].Key == "directory" {
syncer := z.WriteSyncer(fields[i].String)
z.Core = zapcore.NewCore(global.Config.Zap.Encoder(), syncer, z.level)
}
}
return z.Core.Write(entry, fields)
}
func (z *ZapCore) Sync() error {
return z.Core.Sync()
}
+58
View File
@@ -0,0 +1,58 @@
package core
import (
"flag"
"fmt"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
"os"
"sundynix-go/core/internal"
"sundynix-go/global"
)
func Viper() *viper.Viper {
config := getConfigPath()
v := viper.New()
v.SetConfigFile(config)
v.SetConfigType("yaml")
err := v.ReadInConfig()
if err != nil {
panic(fmt.Errorf("fatal error config file: %w", err))
}
//监听配置文件变化并热加载
v.WatchConfig()
//监听配置文件变化事件
v.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("config file changed:", e.Name)
if err = v.Unmarshal(&global.Config); err != nil {
fmt.Println(err)
}
})
//将读取的配置信息反序列化到全局变量Conf中
if err = v.Unmarshal(&global.Config); err != nil {
panic(fmt.Errorf("fatal error unmarshal config: %w", err))
}
return v
}
// 获取配置文件路径 优先级: 命令行 > 环境变量 > 默认值
func getConfigPath() (config string) {
flag.StringVar(&config, "c", "", "choose config file")
flag.Parse()
// 命令行参数不为空 将值赋值于config
if config != "" {
fmt.Printf("正在使用命令行的 '-c' 参数传递的值, config 的路径为 %s\n", config)
return
}
_, err := os.Stat(config)
if err != nil || os.IsNotExist(err) {
config = internal.ConfigDefaultFile
fmt.Printf("配置文件路径不存在, 使用默认配置文件路径: %s\n", config)
}
return
}
+44
View File
@@ -0,0 +1,44 @@
package core
import (
"fmt"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
"sundynix-go/core/internal"
"sundynix-go/global"
"sundynix-go/utils"
)
// Zap 函数用于初始化并返回一个 zap.Logger 实例。
// 该函数会检查日志目录是否存在,如果不存在则创建该目录。
// 根据配置中的日志级别,创建对应的 zapcore.Core,并将它们合并为一个 zap.Logger。
// 如果配置中启用了显示行号,则会在日志中添加调用者信息。
// 返回值:
// - logger: 初始化后的 zap.Logger 实例,用于记录日志。
func Zap() (logger *zap.Logger) {
// 检查日志目录是否存在,如果不存在则创建
if ok, _ := utils.PathExist(global.Config.Zap.Director); !ok {
fmt.Printf("日志目录 %v 不存在,创建中...\n", global.Config.Zap.Director)
_ = os.Mkdir(global.Config.Zap.Director, os.ModePerm)
}
// 获取配置中的日志级别,并初始化对应的 zapcore.Core
levels := global.Config.Zap.Levels()
length := len(levels)
cores := make([]zapcore.Core, 0, length)
for i := 0; i < length; i++ {
core := internal.NewZapCore(levels[i])
cores = append(cores, core)
}
// 将所有的 zapcore.Core 合并为一个 zap.Logger
logger = zap.New(zapcore.NewTee(cores...))
// 如果配置中启用了显示行号,则添加调用者信息
if global.Config.Zap.ShowLine {
logger = logger.WithOptions(zap.AddCaller())
}
return logger
}