# 打印输出



 



 











 


















import "fmt"

// Println 中间有空格 Print 没有
fmt.Println("go", "python", "php", "javascript") // go python php javascript
fmt.Print("go", "python", "php", "javascript") // gopythonphpjavascript

// Println 会自动换行,Print 不会
func main() {
	fmt.Println("hello")
	fmt.Println("world")
	// hello
	// world
	
	fmt.Print("hello")
	fmt.Print("world")
	// helloworld
}

// Printf 是格式化输出
a := 10
b := 20
c := 30
fmt.Println("a=", a, ",b=", b, ",c=", c) // a= 10 ,b= 20 ,c= 30
fmt.Printf("a=%d,b=%d,c=%d", a, b, c)   // a=10,b=20,c=30

// Sprintf 拼接字符串,默认转为字符串
host := "127.0.0.1"
port := 6379
addr := fmt.Sprintf("%s:%d", host, port)
fmt.Println(addr)

// Errorf 错误信息
err := fmt.Error("用户名格式不正确: %s", "aaaa")
if err != nil {
	panic(err)
}

# %v 和 %s 的区别

  • %v 是通用占位符,什么都能打,但格式不一定是你想要的(如字节切片会输出数字数组)
  • %s 是字符串专用,用于字符串/字节切片,用在其他类型上会报错(或者产生 %!s(...) 形式的错误)
s := "hello"
b := []byte("world")
num := 123

fmt.Printf("%v\n", s)   // 输出: hello
fmt.Printf("%s\n", s)   // 输出: hello

fmt.Printf("%v\n", b)   // 输出: [119 111 114 108 100] 
fmt.Printf("%s\n", b)   // 输出: world              

fmt.Printf("%v\n", num) // 输出: 123
fmt.Printf("%s\n", num) // 输出: %!s(int=123) 错误!%s 不能用于 int

# 时间处理

  • 当前时间
now := time.Now() // 获取当前时间
fmt.Printf("当前时间:%v\n", now)
fmt.Printf("年:%v\n", now.Year())
fmt.Printf("月:%v\n", now.Month())
fmt.Printf("日:%v\n", now.Day())
fmt.Printf("时:%v\n", now.Hour())
fmt.Printf("分:%02d\n", now.Minute())
fmt.Printf("秒:%02d\n", now.Second())
fmt.Printf("星期:%v\n", now.Weekday())
fmt.Printf("纳秒:%v\n", now.Nanosecond())
fmt.Printf("时间戳:%v\n", now.Unix())
  • Format 方法格式化输出日期字符串
now := time.Now()
// 格式化的模板为 Go 的出生时间 2006 年 1 月 2 号 15 点 04 分Mon Jan
// 24 小时制
fmt.Println(now.Format("2006-01-02 15:04:05"))
// 12 小时制
fmt.Println(now.Format("2006-01-02 03:04:05"))
fmt.Println(now.Format("2006/01/02 15:04"))
fmt.Println(now.Format("15:04 2006/01/02"))
fmt.Println(now.Format("2006/01/02"))
fmt.Println(now.Format("06")) // 返回的是年份的后两位
  • 时间戳转换为日期字符串
func unixToTime1(timestamp int64) {
	timeObj := time.Unix(timestamp, 0) // 将时间戳转为时间格式
	year := timeObj.Year()             // 年
	month := timeObj.Month()           // 月
	day := timeObj.Day()               // 日
	hour := timeObj.Hour()             // 小时
	minute := timeObj.Minute()         // 分钟
	second := timeObj.Second()         // 秒
	fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second)
}
func unixToTime2(timestamp int64) {
	timeObj := time.Unix(timestamp, 0) // 将时间戳转为时间格式
	fmt.Println(t.Format("2006-01-02 03:04:05")) // 日期格式化输出
}
func main() {
	unixToTime1(1587880013)
	unixToTime2(1587880013)
}
  • 日期字符串转换成时间戳
t1 := "2019-01-08 13:50:30"
timeTemplate := "2006-01-02 15:04:05" // 常规类型
stamp, _ := time.ParseInLocation(timeTemplate , t1, time.Local)
fmt.Println(stamp.Unix())
  • 时间操作函数
now := time.Now()
later := now.Add(time.Hour) // 当前时间加 1 小时后的时间
fmt.Println("1小时后的时间:", later)

difference := later.Sub(now)
fmt.Println("时间差:", difference.Hours())

later.Before(now) // 判断 later 是否在 now 之前
fmt.Println("later 是否在 now 之前:", later.Before(now))
  • 定时器
// 使用 time.NewTicker(时间间隔)来设置定时器
ticker := time.NewTicker(time.Second) //定义一个 1 秒间隔的定时器
n := 0
for i := range ticker.C {
	fmt.Println(i) //每秒都会执行的任务
	n++
	if n > 5 {
		ticker.Stop()
		return
	}
}

// time.Sleep(time.Second) 来实现定时器
for {
	time.Sleep(time.Second)
	fmt.Println("我在定时执行任务")
}

# 文件操作

# 文件基本操作

  • 打开和关闭文件
// os.Open()以只读方式打开一个文件,返回一个*File 和一个 err
// 操作完成文件对象以后一定要记得关闭文件
file, err := os.Open("./go.mod")
if err != nil {
	fmt.Println("open file failed!, err:", err)
	return
}
fmt.Println(file)  // &{0xc000078780}
// 为了防止文件忘记关闭,我们通常使用 defer 注册文件关闭语句
defer file.Close()
  • 创建文件
// 在当前目录创建(或清空)一个名为 "example.txt" 的文件
file, err := os.Create("example.txt")
if err != nil {
	// 如果创建失败,例如父目录不存在或没有写入权限,则记录错误并退出
	log.Fatal("创建文件失败: ", err)
}
// defer 语句确保在函数返回前文件被正确关闭,避免资源泄漏
defer file.Close()
log.Println("文件 example.txt 创建成功")
  • 创建并写入文件
// 直接写入全部内容(会覆盖原有内容)
data := []byte("Hello, Go!\n第二行内容")
err := os.WriteFile("example.txt", data, 0644)
if err != nil {
	panic(err)
}

// 以追加模式打开并写入
f, err := os.OpenFile("example.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
	panic(err)
}
defer f.Close()
_, err = f.WriteString("\n追加一行")
if err != nil {
	panic(err)
}
  • 读取文件
// 一次性读取整个文件
content, err := os.ReadFile("example.txt")
if err != nil {
	panic(err)
}
fmt.Println(string(content))

// 按行读取(使用 bufio)
f, err := os.Open("example.txt")
if err != nil {
	panic(err)
}
defer f.Close()

scanner := bufio.NewScanner(f)
for scanner.Scan() {
	fmt.Println("行:", scanner.Text())
}
if err := scanner.Err(); err != nil {
	panic(err)
}
  • 删除文件
err := os.Remove("example.txt")
if err != nil {
    panic(err)
}
  • 重命名
err := os.Rename("old.txt", "new.txt")
if err != nil {
    panic(err)
}

# 目录操作

  • 创建目录
// 创建单层目录
err := os.Mkdir("mydir", 0755)
if err != nil {
	panic(err)
}

// 创建多层目录
err = os.MkdirAll("path/to/deep/dir", 0755)
if err != nil {
	panic(err)
}
  • 删除目录
// 删除空目录
err := os.Remove("mydir")
if err != nil {
	panic(err)
}

// 递归删除整个目录树
err = os.RemoveAll("path/to/deep")
if err != nil {
	panic(err)
}
  • 读取目录内容
entries, err := os.ReadDir(".")
if err != nil {
	panic(err)
}
for _, entry := range entries {
	fmt.Printf("名称: %s, 是否目录: %v\n", entry.Name(), entry.IsDir())
	// 如需获取详细信息,可调用 entry.Info()
	info, _ := entry.Info()
	fmt.Printf("  大小: %d, 修改时间: %v\n", info.Size(), info.ModTime())
}
  • 遍历目录树(递归)
err := filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
	if err != nil {
		return err
	}
	fmt.Printf("路径: %s, 是否为目录: %v\n", path, d.IsDir())
	return nil
})
if err != nil {
	panic(err)
}

# 路径操作

  • 路径拼接、拆分
// 平台无关的路径拼接
fullPath := filepath.Join("dir", "subdir", "file.txt")
fmt.Println(fullPath) // 输出: dir/subdir/file.txt (Windows 下为 dir\subdir\file.txt)

// 获取目录和文件名
dir, file := filepath.Split("dir/subdir/file.txt")
fmt.Printf("目录: %q, 文件: %q\n", dir, file) // 目录: "dir/subdir/", 文件: "file.txt"

// 获取绝对路径
abs, err := filepath.Abs(".")
if err != nil {
    panic(err)
}
fmt.Println("绝对路径:", abs)
  • 检查路径是否存在
func pathExists(path string) (bool, error) {
    _, err := os.Stat(path)
    if err == nil {
        return true, nil
    }
    if os.IsNotExist(err) {
        return false, nil
    }
    return false, err
}

# 其他I/O操作包

  • bufio
# bufio 包实现了带缓冲区的读写,是对文件读写的封装
  • ioutil
# ioutil库包含在io目录下,它的主要作用是作为一个工具包,里面有一些比较实用的函数

# 日志服务

日志级别:Debug < Info < Warn < Error < Fatal

import (
	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
	"github.com/rifflock/lfshook"
	"github.com/sirupsen/logrus"
)

type LogFormatter struct{}

// 格式详情
func (s *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
	timestamp := time.Now().Format("2006-01-02 15:04:05.000")
	var file string
	var len int
	if entry.Caller != nil {
		// 数提取路径的最后一个元素,即文件名,例如:F:/project/src/main.go → main.go
		file = filepath.Base(entry.Caller.File)
		// 获取调用日志函数时的代码行号
		len = entry.Caller.Line
	}

	reqid := utils.GetRequestId() // [reqid:main-container]
	msg := fmt.Sprintf("%s [reqid:%v][%s:%d][%s] %s\n", 
	                   timestamp, reqid, file, len, 
					   strings.ToUpper(entry.Level.String()), entry.Message)
	return []byte(msg), nil
}

// 日志自定义格式
type EmptyFormatter struct{}

// 格式详情
func (s *EmptyFormatter) Format(entry *logrus.Entry) ([]byte, error) {
	return []byte{}, nil
}

var (
	logFilePath = "./log" //文件存储路径
	logFileName = "app.log"

	Logger *logrus.Logger
)

func init() {
	os.Mkdir("./log", 0755)
	// 日志文件
	fileName := path.Join(logFilePath, logFileName)
	// 以创建、只写、追加模式打开文件 ./log/app.log,权限 0644。如果文件不存在则创建
	file, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	if err != nil {
		panic("打开/写入文件失败" + err.Error())
	}
	// 创建一个新的 logrus.Logger 实例,赋值给全局变量 Logger
	Logger = logrus.New()
	// 日志级别
	Logger.SetLevel(logrus.DebugLevel)
	// 设置输出目标为刚刚打开的 app.log 文件
	// 即使设置了 Logger.Out = file
	// 但由于使用了 EmptyFormatter,实际上不会向 file 写入任何内容
	// 所有日志都通过 hook 机制写入轮转文件
	Logger.Out = file
	// 启用报告调用者信息,这样每条日志的 entry.Caller 才能获取文件名和行号
	Logger.SetReportCaller(true)
	// 设置日志格式为 EmptyFormatter,即通过 Logger.Out 输出时不会产生任何内容
	// 这是为了避免直接写入 app.log 文件,转而通过下面的 hook 写入带轮转的日志文件
	Logger.SetFormatter(&EmptyFormatter{})
	// 用于日志文件的自动切割和轮转
	logWriter, err := rotatelogs.New(
		// 分割后的文件名称
		fileName+".%Y%m%d.log",
		// 生成软链,指向最新日志文件
		// 让你可以用固定的文件名访问不断变化的最新日志文件,无需关心具体的日期后缀
		rotatelogs.WithLinkName(fileName),
		// 设置最大保存时间(7天)
		rotatelogs.WithMaxAge(7*24*time.Hour), //以hour为单位的整数
		// 设置日志切割时间间隔(1小时),实际是每天生成一个文件
		rotatelogs.WithRotationTime(1*time.Hour),
	)
	if err != nil {
		panic(err)
	}
	// 为每个日志级别指定写入目标。这里所有级别都使用同一个 logWriter
	writerMap := lfshook.WriterMap{
		logrus.InfoLevel:  logWriter,
		logrus.FatalLevel: logWriter,
		logrus.DebugLevel: logWriter,
		logrus.WarnLevel:  logWriter,
		logrus.ErrorLevel: logWriter,
		logrus.PanicLevel: logWriter,
	}

	// 调用 Logger.Infof()及以上级别 就会触发所有已注册的 Hook
	// fshook.NewHook() 是lfshook 提供的 Hook 实现,专门用于将不同级别的日志写入不同的文件
	Logger.AddHook(lfshook.NewHook(writerMap, &LogFormatter{}))
}

log 包提供了一个预定义的 logger,可以通过调用函数Print系列(Print|Printf]PrintIn) 、Fatal系列(Fatal|Fatalf|Fatalln)、和Panic系列(Panic|Panicf|Panicln)来使用。

# print系列

单纯打印日志

// 2022/08/01 14:11:26 this is a log
log.Print("this is a log") 

// 2022/08/01 14:11:26 this is a log: 100
log.Printf("this is a log: %d", 100)

name := "zhang san"
age := 20
// 2022/08/01 14:11:26 zhangsan  20
log.Println(name, " ", age)

# panic系列

打印日志,抛出panic异常

defer fmt.Println("发生了 panic误!")
log.Print("this is a log")
log.Panic("this is a panic log")
fmt.Println("运行结束...")

// 2022/08/01 14:12:13 this is a log
// 2022/08/01 14:12:13 this is a panic log
// 发生了panic错误!
// panic: this is a panic log

# fatal系列

打印日志,强制结束程序(os.Exit(1)),defer函数不会执行

defer fmt.Println("defer... ")
log.Print("this is a log")
log.Fatal("this is a fatal log")
fmt.Println("运行结束... ")

// 2022/08/01 14:12:45 this is a log
// 2022/08/01 14:12:45 this is a fatal log

# 网络请求

# 网址解析

// 这是 URL 标准规范(RFC 3986) 中定义的固定语法
https://username:password@host
        └────┬─┘ └───┬───┘
             用户名   密码
u, _ := url.Parse("https://username:password@host")
name := u.User.Username()
password, _ := u.User.Password()

# JSON 序列化 jsoniter

  • json 的高性能替代品 jsoniter
// 可无缝替换 encoding/json 的性能升级包
// 它拥有完全兼容的 API,通过优化反射和内存管理,能显著提升序列化/反序列化速度

import (
	jsoniter "github.com/json-iterator/go"
	"github.com/json-iterator/go/extra"
)

func init() {
	// 用于注册 jsoniter 的模糊解码器
	// 默认情况下,JSON 反序列化要求类型严格匹配
	// 会自动将字符串 "25" 转换为整数 25 
	extra.RegisterFuzzyDecoders()
}

// 使用兼容标准库的配置初始化
var json = jsoniter.ConfigCompatibleWithStandardLibrary

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
    jsonStr := `{"name":"Alice", "age":30}`
    var user User
    err := json.Unmarshal([]byte(jsonStr), &user)
	// 或者不需要兼容标准库的配置初始化
	// err := jsoniter.Unmarshal([]byte(jsonStr), &user)
	
    if err != nil { panic(err) }
    println(user.Name) // 输出 Alice
}
  • 嵌套结构的可以使用 gjson
// go get -u github.com/tidwall/gjson
json := `{
  "user":{
     "first.name": "Janet",
     "last.name": "Prichard"
   }
}`
user := gjson.Get(json, "user")
fmt.Println(user.Get(gjson.Escape("first.name")))
fmt.Println(user.Get(gjson.Escape("last.name")))

# 表达式计算 govaluate

  • 主要作用是将你提供的字符串表达式 expr 进行“编译”,转换成一个可以反复执行的“表达式对象”
  • 可以把它理解为在 Go 程序中实现了一个轻量级的、类似于 eval 的功能

# 工作流程可以分为两步

  • 调用NewEvaluableExpression()将表达式转为一个表达式对象
expression, err := govaluate.NewEvaluableExpression(expr)
  • 调用表达式对象的Evaluate方法,传入参数,返回表达式的值
result, err := expression.Evaluate(params)

# 代码示例

  • 对于没有参数的表达式,Evaluate 方法传入 nil














 







import (
    "fmt"
    "log"
    "github.com/Knetic/govaluate"
)

func main() {
    // 1. 解析并编译表达式
    expr, err := govaluate.NewEvaluableExpression("10 > 0")
    if err != nil {
        log.Fatal("表达式语法错误: ", err)
    }

    // 2. 计算表达式的值
    result, err := expr.Evaluate(nil)
    if err != nil {
        log.Fatal("计算表达式出错: ", err)
    }

    fmt.Println(result) // 输出: true
}
  • 带参数和表达式复用











 




 



import (
    "fmt"
    "github.com/Knetic/govaluate"
)

func main() {
    // 1. 定义一个带参数 a 和 b 的表达式
    expr, _ := govaluate.NewEvaluableExpression("a * b")

    // 2. 准备第一组参数并计算
    params1 := map[string]interface{}{"a": 2, "b": 4}
    result1, _ := expr.Evaluate(params1)
    fmt.Println(result1) // 输出: 8

    // 3. 复用同一个表达式对象,传入第二组参数计算
    params2 := map[string]interface{}{"a": 5, "b": 6}
    result2, _ := expr.Evaluate(params2)
    fmt.Println(result2) // 输出: 30
}