feat: zap日志配置
This commit is contained in:
@@ -7,3 +7,16 @@ redis:
|
|||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
password: sundynix
|
password: sundynix
|
||||||
port: 6379
|
port: 6379
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
zap:
|
||||||
|
director: log
|
||||||
|
encode-level: LowercaseColorLevelEncoder
|
||||||
|
format: console
|
||||||
|
level: debug
|
||||||
|
log-in-console: true
|
||||||
|
prefix: '[sundynix-go]'
|
||||||
|
retention-day: 5
|
||||||
|
show-line: true
|
||||||
|
stacktrace-key: stacktrace
|
||||||
@@ -7,3 +7,16 @@ redis:
|
|||||||
host: 192.168.100.127
|
host: 192.168.100.127
|
||||||
password: sundynix
|
password: sundynix
|
||||||
port: 6379
|
port: 6379
|
||||||
|
|
||||||
|
|
||||||
|
# zap日志配置
|
||||||
|
zap:
|
||||||
|
director: log
|
||||||
|
encode-level: LowercaseColorLevelEncoder
|
||||||
|
format: console
|
||||||
|
level: info
|
||||||
|
log-in-console: true
|
||||||
|
prefix: '[sundynix-go]'
|
||||||
|
retention-day: -1
|
||||||
|
show-line: true
|
||||||
|
stacktrace-key: stacktrace
|
||||||
@@ -3,4 +3,5 @@ package config
|
|||||||
type Server struct {
|
type Server struct {
|
||||||
Mysql Mysql `mapstructure:"mysql" json:"mysql" yaml:"mysql"`
|
Mysql Mysql `mapstructure:"mysql" json:"mysql" yaml:"mysql"`
|
||||||
Redis Redis `mapstructure:"redis" json:"redis" yaml:"redis"`
|
Redis Redis `mapstructure:"redis" json:"redis" yaml:"redis"`
|
||||||
|
Zap Zap `mapstructure:"zap" json:"zap" yaml:"zap"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Zap struct {
|
||||||
|
Level string `mapstructure:"level" json:"level" yaml:"level"`
|
||||||
|
Prefix string `mapstructure:"prefix" json:"prefix" yaml:"prefix"`
|
||||||
|
Format string `mapstructure:"format" json:"format" yaml:"format"`
|
||||||
|
Director string `mapstructure:"director" json:"director" yaml:"director"`
|
||||||
|
EncodeLevel string `mapstructure:"encode-level" json:"encode-level" yaml:"encode-level"`
|
||||||
|
StacktraceKey string `mapstructure:"stacktrace-key" json:"stacktrace-key" yaml:"stacktrace-key"`
|
||||||
|
ShowLine bool `mapstructure:"show-line" json:"show-line" yaml:"show-line"`
|
||||||
|
LogInConsole bool `mapstructure:"log-in-console" json:"log-in-console" yaml:"log-in-console"`
|
||||||
|
RetentionDay int `mapstructure:"retention-day" json:"retention-day" yaml:"retention-day"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Levels 返回一个基于 Zap 实例中配置的日志级别的 zapcore.Level 切片。
|
||||||
|
// 该切片从配置的日志级别开始,包含所有更高严重性级别,直到 FatalLevel。
|
||||||
|
// 如果无法解析配置的日志级别,则默认使用 DebugLevel。
|
||||||
|
//
|
||||||
|
// 返回值:
|
||||||
|
// - []zapcore.Level: 包含从配置的日志级别(或解析失败时的 DebugLevel)到 FatalLevel 的所有日志级别的切片。
|
||||||
|
func (c *Zap) Levels() []zapcore.Level {
|
||||||
|
// 初始化一个容量为 7 的空切片,用于存储日志级别。
|
||||||
|
levels := make([]zapcore.Level, 0, 7)
|
||||||
|
|
||||||
|
// 解析配置的日志级别。如果解析失败,则默认使用 DebugLevel。
|
||||||
|
level, err := zapcore.ParseLevel(c.Level)
|
||||||
|
if err != nil {
|
||||||
|
level = zapcore.DebugLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从解析的(或默认的)日志级别开始,迭代到 FatalLevel,并将每个级别追加到切片中。
|
||||||
|
for ; level <= zapcore.FatalLevel; level++ {
|
||||||
|
levels = append(levels, level)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回填充好的日志级别切片
|
||||||
|
return levels
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encoder 返回一个 zapcore.Encoder,用于编码日志记录。
|
||||||
|
func (c *Zap) Encoder() zapcore.Encoder {
|
||||||
|
config := zapcore.EncoderConfig{
|
||||||
|
TimeKey: "time",
|
||||||
|
NameKey: "name",
|
||||||
|
LevelKey: "level",
|
||||||
|
CallerKey: "caller",
|
||||||
|
MessageKey: "message",
|
||||||
|
StacktraceKey: c.StacktraceKey,
|
||||||
|
LineEnding: zapcore.DefaultLineEnding,
|
||||||
|
EncodeTime: func(t time.Time, encoder zapcore.PrimitiveArrayEncoder) {
|
||||||
|
encoder.AppendString(c.Prefix + t.Format("2006-01-02 15:04:05"))
|
||||||
|
},
|
||||||
|
EncodeLevel: c.LevelEncoder(),
|
||||||
|
EncodeCaller: zapcore.FullCallerEncoder,
|
||||||
|
EncodeDuration: zapcore.SecondsDurationEncoder,
|
||||||
|
}
|
||||||
|
if c.Format == "json" {
|
||||||
|
return zapcore.NewJSONEncoder(config)
|
||||||
|
}
|
||||||
|
return zapcore.NewConsoleEncoder(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Zap) LevelEncoder() zapcore.LevelEncoder {
|
||||||
|
switch {
|
||||||
|
case c.EncodeLevel == "LowercaseLevelEncoder":
|
||||||
|
return zapcore.LowercaseLevelEncoder
|
||||||
|
case c.EncodeLevel == "LowercaseColorLevelEncoder":
|
||||||
|
return zapcore.LowercaseColorLevelEncoder
|
||||||
|
case c.EncodeLevel == "CapitalLevelEncoder":
|
||||||
|
return zapcore.CapitalLevelEncoder
|
||||||
|
case c.EncodeLevel == "CapitalColorLevelEncoder":
|
||||||
|
return zapcore.CapitalColorLevelEncoder
|
||||||
|
default:
|
||||||
|
return zapcore.LowercaseLevelEncoder
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
+44
@@ -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
|
||||||
|
}
|
||||||
@@ -2,11 +2,13 @@ package global
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
"go.uber.org/zap"
|
||||||
"sundynix-go/config"
|
"sundynix-go/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 全局变量 加载在内存中
|
// 全局变量 加载在内存中
|
||||||
var (
|
var (
|
||||||
Viper *viper.Viper
|
Viper *viper.Viper
|
||||||
|
Logger *zap.Logger
|
||||||
Config *config.Server
|
Config *config.Server
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
2025-04-25 12:55:18 [35mdebug[0m /Users/blizzard/sourceCode/GolandProjects/src/sundynix-go/main.go:18 this is debug log
|
||||||
|
[sundynix-go]2025-04-25 12:57:15 [35mdebug[0m /Users/blizzard/sourceCode/GolandProjects/src/sundynix-go/main.go:18 this is debug log
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
2025-04-25 12:53:32 [31merror[0m /Users/blizzard/sourceCode/GolandProjects/src/sundynix-go/main.go:18 this is error log
|
||||||
|
2025-04-25 12:53:43 [31merror[0m /Users/blizzard/sourceCode/GolandProjects/src/sundynix-go/main.go:19 this is error log
|
||||||
|
2025-04-25 12:55:18 [31merror[0m /Users/blizzard/sourceCode/GolandProjects/src/sundynix-go/main.go:19 this is error log
|
||||||
|
[sundynix-go]2025-04-25 12:57:15 [31merror[0m /Users/blizzard/sourceCode/GolandProjects/src/sundynix-go/main.go:19 this is error log
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
2025-04-25 12:53:14 [34minfo[0m /Users/blizzard/sourceCode/GolandProjects/src/sundynix-go/main.go:17 this is debug log
|
||||||
|
2025-04-25 12:53:32 [34minfo[0m /Users/blizzard/sourceCode/GolandProjects/src/sundynix-go/main.go:17 this is debug log
|
||||||
|
2025-04-25 12:53:43 [34minfo[0m /Users/blizzard/sourceCode/GolandProjects/src/sundynix-go/main.go:17 this is debug log
|
||||||
|
2025-04-25 12:55:18 [34minfo[0m /Users/blizzard/sourceCode/GolandProjects/src/sundynix-go/main.go:17 this is debug log
|
||||||
|
[sundynix-go]2025-04-25 12:57:15 [34minfo[0m /Users/blizzard/sourceCode/GolandProjects/src/sundynix-go/main.go:17 this is debug log
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"go.uber.org/zap"
|
||||||
"sundynix-go/core"
|
"sundynix-go/core"
|
||||||
"sundynix-go/global"
|
"sundynix-go/global"
|
||||||
)
|
)
|
||||||
@@ -9,6 +9,13 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
//初始化viper
|
//初始化viper
|
||||||
global.Viper = core.Viper()
|
global.Viper = core.Viper()
|
||||||
fmt.Println(global.Config.Mysql)
|
//初始化zap
|
||||||
fmt.Println(global.Config.Redis)
|
global.Logger = core.Zap()
|
||||||
|
//替换zap
|
||||||
|
zap.ReplaceGlobals(global.Logger)
|
||||||
|
|
||||||
|
global.Logger.Info("this is debug log")
|
||||||
|
global.Logger.Debug("this is debug log")
|
||||||
|
global.Logger.Error("this is error log")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PathExist(path string) (bool, error) {
|
||||||
|
stat, err := os.Stat(path)
|
||||||
|
if err == nil {
|
||||||
|
if stat.IsDir() {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, errors.New("存在同名文件")
|
||||||
|
}
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user