goframe框架签到系统项目(路由管理、中间件、获取请求参数、返回响应、配置管理、日志、错误处理、数据校验、数据库ORM、缓存、解决go mod tidy运行后也还是红线)
文章目录
路由管理
只需记两种,其他了解皆即可。
第一种
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse)
group.Bind(
hello.NewV1(),
// 注册book相关路由
// 请求方法和路径是根据 请求结构体里的 g.Meta 标签自动生成的
book.NewV1(),
)
})
给服务器注册一个路由(第二种)
s.BindHandler("/login", func(r *ghttp.Request) {
r.Response.WriteJsonExit(map[string]string{
"name": "Simon",
"value": "gf_demo",
})
})

指定 GET 方法访问 /login(第二种)
s.BindHandler("GET:/login", func(r *ghttp.Request) {
r.Response.WriteJsonExit(map[string]string{
"name": "Simon",
"value": "gf_demo",
})
})

用本地 18000 转发到服务器 8000
C:\Users\lenovo>ssh -L 18000:127.0.0.1:8000 root@47.83.151.229
Welcome to Ubuntu 22.04.5 LTS (GNU/Linux 5.15.0-161-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/pro
System information as of Fri Dec 19 03:28:18 PM CST 2025
System load: 0.06 Processes: 144
Usage of /: 24.8% of 39.01GB Users logged in: 1
Memory usage: 59% IPv4 address for eth0: 172.23.104.251
Swap usage: 0%
* Strictly confined Kubernetes makes edge and IoT secure. Learn how MicroK8s
just raised the bar for easy, resilient and secure K8s cluster deployment.
https://ubuntu.com/engage/secure-kubernetes-at-the-edge
Expanded Security Maintenance for Applications is not enabled.
18 updates can be applied immediately.
To see these additional updates run: apt list --upgradable
5 additional security updates can be applied with ESM Apps.
Learn more about enabling ESM Apps service at https://ubuntu.com/esm
New release '24.04.3 LTS' available.
Run 'do-release-upgrade' to upgrade to it.
2 updates could not be installed automatically. For more details,
see /var/log/unattended-upgrades/unattended-upgrades.log
Welcome to Alibaba Cloud Elastic Compute Service !
Last login: Fri Dec 19 15:27:15 2025 from 111.55.204.163
root@GoLang:~#

中间件
原理洋葱模型
- 处理前
- 业务处理handler
- 业务处理后
几个常用的中间件要记住

鉴权(Authorization)
就是:判断“你有没有权限做这件事”。
它和认证(Authentication)容易混,但不一样:

package cmd
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gcmd"
"gf_demo/internal/controller/book"
"gf_demo/internal/controller/hello"
)
func MiddlewareAuth(r *ghttp.Request) {
// 在请求处理之前进行鉴权
// 根据请求是否携带某些token,来判断是否是有效请求
token := r.Get("token")
if token.String() == "123" {
// “放行”,继续执行后面的流程(下一个中间件 / 业务 handler / controller)。
r.Middleware.Next()
return
}
// 鉴权失败,返回403状态码
r.Response.WriteStatus(403)
}
var (
Main = gcmd.Command{
Name: "main",
Usage: "main",
Brief: "start http server",
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
s := g.Server()
// s.Use() // 注册全局中间件
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse) // 分组注册中间件
group.Bind(
hello.NewV1(),
// 注册book相关路由
// 请求方法和路径是根据 请求结构体里的 g.Meta 标签自动生成的
book.NewV1(),
)
})
s.Use(MiddlewareAuth)
s.BindHandler("GET:/index", func(r *ghttp.Request) {
r.Response.WriteJsonExit(map[string]string{
"name": "index",
})
})
// 给服务器绑定一个路由:当有人用 GET 方法访问 /login 时,就返回一段固定的 JSON,并立刻结束请求
// s.BindHandler("GET:/login", func(r *ghttp.Request) {
// // 把你传进去的数据转换成 JSON, 写到 HTTP 响应里,立刻终止后续处理(不再继续执行后面的 handler / 中间件链)
// r.Response.WriteJsonExit(map[string]string{
// "name": "Simon",
// "value": "gf_demo",
// })
// })
s.Run()
return nil
},
}
)


获取请求参数
在 api 目录下定义请求参数结构体内嵌 g.Meta。

返回响应
使用通用格式响应中间件。
group.Middleware(ghttp.MiddlewareHandlerResponse)
配置
官方文档:https://goframe.org/docs/core/gcfg
基本用法:
g.Cfg().Get(ctx, "viewpath")
g.Cfg().Get(ctx, "database.default.0.role") // 支持层级访问

package hello
import (
"context"
"fmt"
"github.com/gogf/gf/v2/frame/g"
v1 "gf_demo/api/hello/v1"
)
func (c *ControllerV1) Hello(ctx context.Context, req *v1.HelloReq) (res *v1.HelloRes, err error) {
fmt.Println(g.Cfg().Get(ctx, "name"))
fmt.Println(g.Cfg().Get(ctx, "info.version"))
g.RequestFromCtx(ctx).Response.Writeln("Hello World!")
return
}

# https://goframe.org/docs/web/server-config-file-template
server:
address: ":8000"
openapiPath: "/api.json"
swaggerPath: "/swagger"
# https://goframe.org/docs/core/glog-config
logger:
level : "all"
stdout: true
# https://goframe.org/docs/core/gdb-config-file
database:
default:
link: "mysql:gf:GfPass!123@tcp(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=true&loc=Local"
name: "Simon"
info:
version: "1.0.0"


默认配置文件目录:
会自动扫描工作目录下的 config 和 manifest/config
默认文件修改

默认目录修改

日志
官方文档:https://goframe.org/docs/core/glog
日志配置:
logger:
path: "/var/log/" # 日志文件路径。默认为空,表示关闭,仅输出到终端
file: "{Y-m-d}.log" # 日志文件格式。默认为"{Y-m-d}.log"
prefix: "" # 日志内容输出前缀。默认为空
level: "all" # 日志输出级别
timeFormat: "2006-01-02T15:04:05" # 自定义日志输出的时间格式,使用Golang标准的时间格式配置
ctxKeys: [] # 自定义Context上下文变量名称,自动打印Context的变量到日志中。默认为空
header: true # 是否打印日志的头信息。默认true
stdout: true # 日志是否同时输出到终端。默认true
rotateSize: 0 # 按照日志文件大小对文件进行滚动切分。默认为0,表示关闭滚动切分特性
rotateExpire: 0 # 按照日志文件时间间隔对文件滚动切分。默认为0,表示关闭滚动切分特性
rotateBackupLimit: 0 # 按照切分的文件数量清理切分文件,当滚动切分特性开启时有效。默认为0,表示不备份,切分则删除
rotateBackupExpire: 0 # 按照切分的文件有效期清理切分文件,当滚动切分特性开启时有效。默认为0,表示不备份,切分则删除
rotateBackupCompress: 0 # 滚动切分文件的压缩比(0-9)。默认为0,表示不压缩
rotateCheckInterval: "1h" # 滚动切分的时间检测间隔,一般不需要设置。默认为1小时
stdoutColorDisabled: false # 关闭终端的颜色打印。默认开启
writerColorEnable: false # 日志文件是否带上颜色。默认false,表示不带颜色

基本用法:
g.Log().Debug(ctx, gtime.Datetime())
g.Log().Info(ctx, gtime.Datetime())
输出 JSON 格式
g.Log().Debug(ctx, g.Map{"uid": 100, "name": "john"})
测试 Log().Infof 、 Log().Debug
# https://goframe.org/docs/web/server-config-file-template
server:
address: ":8000"
openapiPath: "/api.json"
swaggerPath: "/swagger"
# https://goframe.org/docs/core/glog-config
logger:
path: "log/" # 日志文件路径。默认为空,表示关闭,仅输出到终端
file: "gf_demo_{Y-m-d}.log" # 日志文件格式。默认为"{Y-m-d}.log"
prefix: "" # 日志内容输出前缀。默认为空
level: "all" # 日志输出级别
timeFormat: "2006-01-02T15:04:05" # 自定义日志输出的时间格式,使用Golang标准的时间格式配置
ctxKeys: [] # 自定义Context上下文变量名称,自动打印Context的变量到日志中。默认为空
header: true # 是否打印日志的头信息。默认true
stdout: true # 日志是否同时输出到终端。默认true
rotateSize: "100M" # 按照日志文件大小对文件进行滚动切分。默认为0,表示关闭滚动切分特性
rotateExpire: 0 # 按照日志文件时间间隔对文件滚动切分。默认为0,表示关闭滚动切分特性
rotateBackupLimit: 10 # 按照切分的文件数量清理切分文件,当滚动切分特性开启时有效。默认为0,表示不备份,切分则删除
rotateBackupExpire: 0 # 按照切分的文件有效期清理切分文件,当滚动切分特性开启时有效。默认为0,表示不备份,切分则删除
rotateBackupCompress: 0 # 滚动切分文件的压缩比(0-9)。默认为0,表示不压缩
rotateCheckInterval: "1h" # 滚动切分的时间检测间隔,一般不需要设置。默认为1小时
stdoutColorDisabled: false # 关闭终端的颜色打印。默认开启
writerColorEnable: false # 日志文件是否带上颜色。默认false,表示不带颜色
# https://goframe.org/docs/core/gdb-config-file
database:
default:
link: "mysql:gf:GfPass!123@tcp(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=true&loc=Local"
name: "Simon"
info:
version: "1.0.0"

package hello
import (
"context"
"fmt"
"github.com/gogf/gf/v2/frame/g"
v1 "gf_demo/api/hello/v1"
)
func (c *ControllerV1) Hello(ctx context.Context, req *v1.HelloReq) (res *v1.HelloRes, err error) {
fmt.Println(g.Cfg().Get(ctx, "name"))
fmt.Println(g.Cfg().Get(ctx, "info.version"))
g.Log().Infof(ctx, "Hello %s", "Simon")
g.Log().Debug(ctx, "Debug Log")
g.RequestFromCtx(ctx).Response.Writeln("Hello World!")
return
}




测试 JSON 格式
package hello
import (
"context"
"fmt"
"github.com/gogf/gf/v2/frame/g"
v1 "gf_demo/api/hello/v1"
)
func (c *ControllerV1) Hello(ctx context.Context, req *v1.HelloReq) (res *v1.HelloRes, err error) {
fmt.Println(g.Cfg().Get(ctx, "name"))
fmt.Println(g.Cfg().Get(ctx, "info.version"))
g.Log().Infof(ctx, "Hello %s", "Simon")
g.Log().Debug(ctx, "Debug Log")
g.Log().Debug(ctx, g.Map{
"uid": 100,
"name": "john",
})
g.RequestFromCtx(ctx).Response.Writeln("Hello World!")
return
}



测试 Handler
package hello
import (
"context"
"encoding/json"
"fmt"
"os"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/glog"
"github.com/gogf/gf/v2/text/gstr"
v1 "gf_demo/api/hello/v1"
)
// JsonOutputsForLogger is for JSON marshaling in sequence.
// 自定义的“日志 JSON 模板”
// {"time":"...","level":"...","content":"..."}
type JsonOutputsForLogger struct {
Time string `json:"time"`
Level string `json:"level"`
Content string `json:"content"`
}
// LoggingJsonHandler is a example handler for logging JSON format content.
// 日志处理器(Handler)
var LoggingJsonHandler glog.Handler = func(ctx context.Context, in *glog.HandlerInput) {
jsonForLogger := JsonOutputsForLogger{
Time: in.TimeFormat,
// 用 gstr.Trim(in.LevelFormat, "[]") 把 [] 去掉,变成 INFO
Level: gstr.Trim(in.LevelFormat, "[]"),
// 把内容前后空格去掉。
Content: gstr.Trim(in.ValuesContent()), // 2.7以上版本用in.ValuesContent()
}
// 把结构体转成 JSON 字节
jsonBytes, err := json.Marshal(jsonForLogger)
// 如果转 JSON 失败,就把错误写到 stderr
if err != nil {
_, _ = os.Stderr.WriteString(err.Error())
return
}
// 把 JSON 写入日志缓冲区
in.Buffer.Write(jsonBytes)
in.Buffer.WriteString("\n")
// 继续走后续处理链
in.Next(ctx)
}
func (c *ControllerV1) Hello(ctx context.Context, req *v1.HelloReq) (res *v1.HelloRes, err error) {
fmt.Println(g.Cfg().Get(ctx, "name"))
fmt.Println(g.Cfg().Get(ctx, "info.version"))
g.Log().SetHandlers(LoggingJsonHandler)
g.Log().Infof(ctx, "Hello %s", "Simon")
g.Log().Debug(ctx, "Debug Log")
g.Log().Debug(ctx, g.Map{
"uid": 100,
"name": "john",
})
g.RequestFromCtx(ctx).Response.Writeln("Hello World!")
return
}



错误处理
官方文档:https://goframe.org/docs/core/gerror
err = errors.New("sql error")
err = gerror.Wrap(err, "adding failed")
err = gerror.Wrap(err, "api calling failed")
数据校验
官方文档:https://goframe.org/docs/core/gvalid
- 内置数据校验规则
- 常用数据校验方法
- 如何自定义校验规则
- I18n 特性
补充:框架自动从请求参数解析数据并对 req 对象赋值。



测试 demo
package main
import (
"context"
"fmt"
"github.com/gogf/gf/v2/util/gvalid"
)
type User struct {
// uid = 属性别名 ✅(用于错误信息里显示的字段名)
Uid int `v:"uid @integer|min:1#|请输入用户ID"`
Name string `v:"name @required|length:6,30#请输入用户名称|用户名称长度非法"`
Pass1 string `v:"password1@required|password3"`
Pass2 string `v:"password2@required|password3|same:Pass1#|密码格式不合法|两次密码不一致,请重新输入"`
}
func main() {
// 先把数据加载到结构体,然后进行数据校验
data2 := User{
Uid: 1,
Name: "张三",
Pass1: "123456",
Pass2: "1234567890",
}
err := gvalid.New().Data(data2).Run(context.Background())
fmt.Printf("%+v\n", err)
}

测试 demo2
package main
import (
"context"
"fmt"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/util/gvalid"
)
type User struct {
// uid = 属性别名 ✅(用于错误信息里显示的字段名)
Uid int `v:"uid @integer|min:1#|请输入用户ID"`
Name string `v:"name @required|length:6,30#请输入用户名称|用户名称长度非法"`
Pass1 string `v:"password1@required|password3"`
Pass2 string `v:"password2@required|password3|same:Pass1#|密码格式不合法|两次密码不一致,请重新输入"`
}
func main() {
// 先把数据加载到结构体,然后进行数据校验
data2 := User{
Uid: 1,
Name: "张三",
Pass1: "123456",
Pass2: "1234567890",
}
err := gvalid.New().Data(data2).Run(context.Background())
fmt.Printf("%+v\n", err)
// Current 方法用于获取当前层级的错误信息,通过 error 接口对象返回
fmt.Printf("%v\n", gerror.Current(err))
}

数据库 ORM(ORM 的全称是 Object-Relational Mapping,中文一般叫 对象关系映射。)
官方文档:https://goframe.org/docs/core/gdb
导入数据库驱动

通用方法:
lastInsertId, err := g.DB().Model("book").Ctx(ctx).Data(g.Map{
"title": req.Title,
"price": req.Price,
"status": v1.StatusAvailable,
}).InsertAndGetId()
最佳实践:
lastInsertId, err := dao.Book.Ctx(ctx).Data(do.Book{
Title: req.Title,
Price: req.Price,
Status: v1.StatusAvailable,
}).InsertAndGetId()
测试 demo
package book
import (
"context"
_ "github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
// dao 和 do:分别是数据访问对象(DAO)和数据库操作模型(DO)。dao.Book 用于执行与数据库交互的操作,do.Book 是数据库表的模型映射
v1 "gf_demo/api/book/v1"
_ "gf_demo/internal/dao"
"gf_demo/internal/model/do"
)
func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) {
// 1. 解析请求参数并对请求参数进行校验
// 2. 实现创建图书的业务逻辑
// dao.Book.Ctx(ctx):调用 dao 中 Book 对象的上下文方法,表示在当前上下文中执行数据库操作。
// .Data(do.Book{...}):这里的 do.Book 是将传入的 req.Title 和 req.Price 以及默认的 Status(StatusAvailable)放入一个 do.Book 结构体中。do.Book 是用于与数据库表映射的模型,包含了书籍的字段。
// .InsertAndGetId():插入新的书籍记录到数据库,并返回插入数据的 ID(lastInsertId)。如果插入失败,err 会包含错误信息
// lastInsertId, err := dao.Book.Ctx(ctx).Data(do.Book{
// Title: req.Title,
// Price: req.Price,
// Status: v1.StatusAvailable, // 默认可用状态
// }).InsertAndGetId()
// 等价于 INSERT INTO book (title, price, status) VALUES (?, ?, ?);
// g.DB(): 获取默认的数据库对象(连接池/操作入口)
// .Model("book"): 指定要操作的表是 book
// 把 Go 的 context 传进来。作用常见有:超时控制(ctx 超时就取消 DB 操作)、链路追踪/日志关联、请求结束自动取消等
// .Data(do.Book{...}): 指定“要插入的数据”
// .InsertAndGetId(): 执行插入,并返回 插入记录的主键ID(通常是自增 ID)
lastInsertId, err := g.DB().Model("book").Ctx(ctx).Data(do.Book{
Title: req.Title,
Price: req.Price,
Status: v1.StatusAvailable, // 默认可用状态
}).InsertAndGetId()
if err != nil {
return nil, gerror.Wrap(err, "failed to create book")
}
// 3. 返回创建结果
return &v1.CreateRes{
Id: lastInsertId, // 假设创建的图书ID为1
}, nil
// 创建了一个自定义错误,通常用于未实现的功能或者接口。
// return nil, gerror.NewCode(gcode.CodeNotImplemented)
}


测试 demo2
package book
import (
"context"
_ "github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
// dao 和 do:分别是数据访问对象(DAO)和数据库操作模型(DO)。dao.Book 用于执行与数据库交互的操作,do.Book 是数据库表的模型映射
v1 "gf_demo/api/book/v1"
_ "gf_demo/internal/dao"
)
func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) {
// 1. 解析请求参数并对请求参数进行校验
// 2. 实现创建图书的业务逻辑
// dao.Book.Ctx(ctx):调用 dao 中 Book 对象的上下文方法,表示在当前上下文中执行数据库操作。
// .Data(do.Book{...}):这里的 do.Book 是将传入的 req.Title 和 req.Price 以及默认的 Status(StatusAvailable)放入一个 do.Book 结构体中。do.Book 是用于与数据库表映射的模型,包含了书籍的字段。
// .InsertAndGetId():插入新的书籍记录到数据库,并返回插入数据的 ID(lastInsertId)。如果插入失败,err 会包含错误信息
// lastInsertId, err := dao.Book.Ctx(ctx).Data(do.Book{
// Title: req.Title,
// Price: req.Price,
// Status: v1.StatusAvailable, // 默认可用状态
// }).InsertAndGetId()
// 等价于 INSERT INTO book (title, price, status) VALUES (?, ?, ?);
// g.DB(): 获取默认的数据库对象(连接池/操作入口)
// .Model("book"): 指定要操作的表是 book
// 把 Go 的 context 传进来。作用常见有:超时控制(ctx 超时就取消 DB 操作)、链路追踪/日志关联、请求结束自动取消等
// .Data(do.Book{...}): 指定“要插入的数据”
// .InsertAndGetId(): 执行插入,并返回 插入记录的主键ID(通常是自增 ID)
// lastInsertId, err := g.DB().Model("book").Ctx(ctx).Data(do.Book{
// Title: req.Title,
// Price: req.Price,
// Status: v1.StatusAvailable, // 默认可用状态
// }).InsertAndGetId()
lastInsertId, err := g.DB().Model("book").Ctx(ctx).Data(g.Map{
"title": req.Title,
"price": req.Price,
"status": v1.StatusAvailable, // 默认可用状态
}).InsertAndGetId()
if err != nil {
return nil, gerror.Wrap(err, "failed to create book")
}
// 3. 返回创建结果
return &v1.CreateRes{
Id: lastInsertId, // 假设创建的图书ID为1
}, nil
// 创建了一个自定义错误,通常用于未实现的功能或者接口。
// return nil, gerror.NewCode(gcode.CodeNotImplemented)
}


缓存
注意!一定要记得注册 Redis 驱动!!!
import "github.com/gogf/gf/contrib/nosql/redis/v2"
基于内存的缓存
// 内存缓存
cache := gcache.New()
cache.Set(ctx, "key1", "value1", time.Minute*10)
// 返回的是 gvar.Var 类型
if v, err := cache.Get(ctx, "key1"); err != nil {
return nil, err
} else {
fmt.Println(v.String())
}
// 使用gcache包提供的全局cache对象
// gcache.Set(ctx, "key2", "value2", time.Minute*10)


使用gcache包提供的全局cache对象
gcache.Set(ctx, "key2", "value2", time.Minute * 10)
基于 Redis 的缓存
// 基于redis的缓存
redisCache := gcache.New()
// 拿到redis client: gredis.Redis
redisCache.SetAdapter(gcache.NewAdapterRedis(g.Redis())) // 设置Redis适配器
redisCache.Set(ctx, "key2", "value2", time.Minute*10)
// 返回的是 gvar.Var 类型
if v, err := redisCache.Get(ctx, "key2"); err != nil {
return nil, err
} else {
fmt.Println(v.String())
}
给 ORM 设置基于 Redis 的缓存
g.DB().GetCache().SetAdapter(gcache.NewAdapterRedis(g.Redis()))
g.Redis() 是 goframe 框架给我们自动初始化好的全局 Redis Client 对象。
对应的配置文件 manifest/config/config.yaml 中的:
# Redis 配置示例
redis:
# 单实例配置示例1
default:
address: 127.0.0.1:6379
db: 0

导入Redis驱动

解决go mod tidy运行后也还是红线
让 VSCode 的 gopls 重新加载(BrokenImport 常靠这个解决)
在 VSCode:Ctrl + Shift + P
输入:Go: Restart Language Server(重启语言服务)
再执行:Go: Clear Cache(如果有这个选项)
保存文件,看看红线还在不在
连接Redis
使用 github.com/redis/go-redis/v9 包自行连接 Redis
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// you can obtain one at https://github.com/gogf/gf.
package main
import (
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
"github.com/redis/go-redis/v9"
)
// main demonstrates basic Redis operations using GoFrame
// It initializes a Redis client and performs simple SET/GET operations
func main() {
// Get the initialization context
ctx := gctx.GetInitCtx()
// Initialize Redis client with error handling
redisClient, err := NewRedisClient()
if err != nil {
panic(err)
}
// Demonstrate basic Redis operations
redisClient.Set(ctx, "key", "value", 0)
redisClient.Get(ctx, "key")
g.Log().Info(ctx, "key:", redisClient.Get(ctx, "key").Val())
}
// RedisConfig defines the configuration structure for Redis connection
type RedisConfig struct {
Address string // Redis server address in format "host:port"
Password string // Redis server password, empty if no password is set
}
// NewRedisClient creates and initializes a new Redis client using configuration from config.yaml
// Returns the initialized client and any error encountered during initialization
func NewRedisClient() (*redis.Client, error) {
var (
err error
ctx = gctx.GetInitCtx()
config *RedisConfig
)
// Load Redis configuration from config.yaml
err = g.Cfg().MustGet(ctx, "redis").Scan(&config)
if err != nil {
return nil, err
}
if config == nil {
return nil, gerror.New("redis config not found")
}
g.Log().Debugf(ctx, "Redis Config: %s", config)
// Initialize Redis client with the loaded configuration
redisClient := redis.NewClient(&redis.Options{
Addr: config.Address,
Password: config.Password,
})
// Test the connection by sending a PING command
err = redisClient.Ping(ctx).Err()
return redisClient, err
}
设置缓存:
// Create redis client object.
redis, err := gredis.New(redisConfig)
if err != nil {
panic(err)
}
// Create redis cache adapter and set it to cache object.
cache.SetAdapter(gcache.NewAdapterRedis(redis))
依赖注入
- 使用 github.com/samber/do 包进行依赖管理
- utility/injection/redis.go 声明注入 Redis Client 的函数。
package injection
import (
"context"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/redis/go-redis/v9"
"github.com/samber/do"
)
// injectRedis 注入 Redis.
func injectRedis(ctx context.Context, injector *do.Injector) {
do.Provide(injector, func(i *do.Injector) (*redis.Client, error) {
type RedisConfig struct {
Address string
Password string
}
var (
err error
config *RedisConfig
)
err = g.Cfg().MustGet(ctx, "redis").Scan(&config)
if err != nil {
return nil, err
}
if config == nil {
return nil, gerror.New("redis config not found")
}
g.Log().Debugf(ctx, "Redis Config: %s", config)
svc := redis.NewClient(&redis.Options{
Addr: config.Address,
Password: config.Password,
})
SetupShutdownHelper(injector, svc, func(svc *redis.Client) error {
return svc.Close()
})
return svc, nil
})
}
utility/injection/injection.go 定义依赖注入器,并注入 Redis Client
package injection
import (
"context"
"fmt"
"reflect"
"github.com/gogf/gf/v2/frame/g"
"github.com/samber/do"
)
var defaultInjector *do.Injector
// MustInvoke invokes the function with the default injector and panics if any error
// occurs.
func MustInvoke[T any]() T {
return do.MustInvoke[T](defaultInjector)
}
// Invoke invokes the function with the default injector.
func Invoke[T any]() (T, error) {
return do.Invoke[T](defaultInjector)
}
// SetupDefaultInjector initializes the default injector with the given context.
func SetupDefaultInjector(ctx context.Context) *do.Injector {
if defaultInjector != nil {
return defaultInjector
}
injector := do.NewWithOpts(&do.InjectorOpts{})
injectRedis(ctx, injector)
defaultInjector = injector
return defaultInjector
}
// ShutdownDefaultInjector shuts down the default injector.
func ShutdownDefaultInjector() {
if defaultInjector != nil {
if err := defaultInjector.Shutdown(); err != nil {
g.Log().Debugf(context.Background(), "ShutdownDefaultInjector: %+v", err)
}
defaultInjector = nil
}
}
// SetupShutdownHelper sets up a shutdown helper.
func SetupShutdownHelper[T any](injector *do.Injector, service T, onShutdown func(service T) error) {
do.Provide(injector, func(i *do.Injector) (ShutdownHelper[T], error) {
g.Log().Debugf(context.Background(), "NewShutdownHelper: %s", reflect.TypeOf(service))
return NewShutdownHelper(service, onShutdown), nil
})
do.MustInvoke[ShutdownHelper[T]](injector)
}
// SetupShutdownHelperNamed sets up a shutdown helper with a name.
func SetupShutdownHelperNamed[T any](injector *do.Injector, service T, name string,
onShutdown func(service T) error) {
name = fmt.Sprintf("ShutdownHelper:%s", name)
do.ProvideNamed(injector, name, func(i *do.Injector) (ShutdownHelper[T], error) {
g.Log().Debugf(
context.Background(),
"NewShutdownHelper: %s, %s",
reflect.TypeOf(service), name,
)
return NewShutdownHelperNamed(service, name, onShutdown), nil
})
do.MustInvokeNamed[ShutdownHelper[T]](injector, name)
}
// ShutdownHelper is a helper struct for shutdown.
type ShutdownHelper[T any] struct {
name string
service T
onShutdown func(service T) error
}
// NewShutdownHelper creates a new ShutdownHelper.
func NewShutdownHelper[T any](service T, onShutdown func(service T) error) ShutdownHelper[T] {
return ShutdownHelper[T]{
service: service,
onShutdown: onShutdown,
}
}
// NewShutdownHelperNamed creates a new ShutdownHelper with a name.
func NewShutdownHelperNamed[T any](service T, name string, onShutdown func(service T) error) ShutdownHelper[T] {
return ShutdownHelper[T]{
name: name,
service: service,
onShutdown: onShutdown,
}
}
// Shutdown shuts down the service.
func (h ShutdownHelper[T]) Shutdown() error {
g.Log().Debugf(
context.Background(),
"ShutdownHelper Shutdown: %s, %s",
reflect.TypeOf(h.service), h.name,
)
return h.onShutdown(h.service)
}
在 internal/cmd/cmd.go 的程序入口处添加依赖注入。
injection.SetupDefaultInjector(ctx)
defer injection.ShutdownDefaultInjector()
在需要使用的地方(取出 redis client)。
func New() *Service {
return &Service{
rc: injection.MustInvoke[*redis.Client](), // 取出 redis client
}
}
测试连接Redis小demo
package book
import (
"context"
"fmt"
"time"
// dao 和 do:分别是数据访问对象(DAO)和数据库操作模型(DO)。dao.Book 用于执行与数据库交互的操作,do.Book 是数据库表的模型映射
v1 "gf_demo/api/book/v1"
"gf_demo/internal/dao"
"gf_demo/internal/model/do"
"gf_demo/internal/model/entity"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcache"
)
func (c *ControllerV1) GetList(ctx context.Context, req *v1.GetListReq) (res *v1.GetListRes, err error) {
// 获取图书列表的业务逻辑
// 结构体的字段会使用其 零值:
// List 字段的零值是 nil,因为 List 是一个切片类型,切片的零值是 nil
// res = &v1.GetListRes{}
// 内存缓存
cache := gcache.New()
// 把 key1 存起来,10 分钟后自动过期删除
cache.Set(ctx, "key1", "value1", time.Minute*10)
// 返回的是 gvar.Var类型
// if v, err := cache.Get(ctx, "key1"); err != nil {
// return nil, err
// } else {
// fmt.Println(v.String())
// }
v, err := cache.Get(ctx, "key1")
if err != nil {
return nil, err
}
if v == nil || v.IsNil() {
fmt.Println("缓存不存在或已过期")
return nil, nil
}
fmt.Println(v.String())
// 使用gcache包提供的全局cache对象
// gcache.Set(ctx, "key2", "value2", time.Minute * 10)
// 基于redis的缓存
redisCache := gcache.New()
// 拿到redis client: gredis.Redis
redisCache.SetAdapter(gcache.NewAdapterRedis(g.Redis())) // 设置Redis适配器
redisCache.Set(ctx, "key2", "value2", time.Minute*10)
v, err = redisCache.Get(ctx, "key2")
if err != nil {
return nil, err
}
if v == nil || v.IsNil() {
fmt.Println("Redis缓存不存在或已过期")
return nil, nil
}
fmt.Println(v.String())
res = &v1.GetListRes{
List: make([]*entity.Book, 0), // 初始化为长度为 0 的切片
}
// Where(...) 负责筛选条件,Scan(...) 负责把数据库结果“抄写”进你的变量里。
// 这是“按条件过滤”:只查 status = req.Status 的记录
// 等价于 SQL 的:SELECT * FROM book WHERE status = ?
// (? 就是 req.Status)
// 把查询出来的“多行记录”填充到 res.List 这个切片里
err = dao.Book.Ctx(ctx).
Where(do.Book{
Status: req.Status,
}).Scan(&res.List)
return res, err
}


测试全局Redis缓存
CacheOption
package cmd
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gcache"
"github.com/gogf/gf/v2/os/gcmd"
"gf_demo/internal/controller/book"
"gf_demo/internal/controller/hello"
)
// func MiddlewareAuth(r *ghttp.Request) {
// // 在请求处理之前进行鉴权
// // 根据请求是否携带某些token,来判断是否是有效请求
// token := r.Get("token")
// if token.String() == "123" {
// // “放行”,继续执行后面的流程(下一个中间件 / 业务 handler / controller)。
// r.Middleware.Next()
// return
// }
// // 鉴权失败,返回403状态码
// r.Response.WriteStatus(403)
// }
var (
Main = gcmd.Command{
Name: "main",
Usage: "main",
Brief: "start http server",
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
s := g.Server()
// s.Use() // 注册全局中间件
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse) // 分组注册中间件
group.Bind(
hello.NewV1(),
// 注册book相关路由
// 请求方法和路径是根据 请求结构体里的 g.Meta 标签自动生成的
book.NewV1(),
)
})
// s.Use(MiddlewareAuth)
// s.BindHandler("GET:/index", func(r *ghttp.Request) {
// r.Response.WriteJsonExit(map[string]string{
// "name": "index",
// })
// })
// 给服务器绑定一个路由:当有人用 GET 方法访问 /login 时,就返回一段固定的 JSON,并立刻结束请求
// s.BindHandler("GET:/login", func(r *ghttp.Request) {
// // 把你传进去的数据转换成 JSON, 写到 HTTP 响应里,立刻终止后续处理(不再继续执行后面的 handler / 中间件链)
// r.Response.WriteJsonExit(map[string]string{
// "name": "Simon",
// "value": "gf_demo",
// })
// })
g.DB().GetCache().SetAdapter(gcache.NewAdapterRedis(g.Redis()))
s.Run()
return nil
},
}
)

package book
import (
"context"
"fmt"
"time"
// dao 和 do:分别是数据访问对象(DAO)和数据库操作模型(DO)。dao.Book 用于执行与数据库交互的操作,do.Book 是数据库表的模型映射
v1 "gf_demo/api/book/v1"
"gf_demo/internal/dao"
"gf_demo/internal/model/do"
"gf_demo/internal/model/entity"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcache"
)
func (c *ControllerV1) GetList(ctx context.Context, req *v1.GetListReq) (res *v1.GetListRes, err error) {
// 获取图书列表的业务逻辑
// 结构体的字段会使用其 零值:
// List 字段的零值是 nil,因为 List 是一个切片类型,切片的零值是 nil
// res = &v1.GetListRes{}
// 内存缓存
cache := gcache.New()
// 把 key1 存起来,10 分钟后自动过期删除
cache.Set(ctx, "key1", "value1", time.Minute*10)
// 返回的是 gvar.Var类型
// if v, err := cache.Get(ctx, "key1"); err != nil {
// return nil, err
// } else {
// fmt.Println(v.String())
// }
v, err := cache.Get(ctx, "key1")
if err != nil {
return nil, err
}
if v == nil || v.IsNil() {
fmt.Println("缓存不存在或已过期")
return nil, nil
}
fmt.Println(v.String())
// 使用gcache包提供的全局cache对象
// gcache.Set(ctx, "key2", "value2", time.Minute * 10)
// 基于redis的缓存
redisCache := gcache.New()
// 拿到redis client: gredis.Redis
redisCache.SetAdapter(gcache.NewAdapterRedis(g.Redis())) // 设置Redis适配器
redisCache.Set(ctx, "key2", "value2", time.Minute*10)
v, err = redisCache.Get(ctx, "key2")
if err != nil {
return nil, err
}
if v == nil || v.IsNil() {
fmt.Println("Redis缓存不存在或已过期")
return nil, nil
}
fmt.Println(v.String())
res = &v1.GetListRes{
List: make([]*entity.Book, 0), // 初始化为长度为 0 的切片
}
// Where(...) 负责筛选条件,Scan(...) 负责把数据库结果“抄写”进你的变量里。
// 这是“按条件过滤”:只查 status = req.Status 的记录
// 等价于 SQL 的:SELECT * FROM book WHERE status = ?
// (? 就是 req.Status)
// 把查询出来的“多行记录”填充到 res.List 这个切片里
// err = dao.Book.Ctx(ctx).
// Where(do.Book{
// Status: req.Status,
// }).Scan(&res.List)
// 默认使用内存缓存
// do.Book 是数据库表字段的映射模型(Data Object)。
// 这里等价于 SQL:WHERE status = ?(? 的值就是 req.Status)
// Scan 会把查询的多行结果扫描到 res.List 里(切片里每个元素是 *entity.Book)
// 去 book 表里查“状态 = req.Status”的书,把查到的结果塞进 res.List;并且把这次查询结果缓存 10 分钟,10 分钟内,如果有人再发起同样的查询,就直接从缓存拿结果,不再查数据库
// 这次查出来的结果,帮我存到缓存里,存 10 分钟。缓存这件事起个名字叫 book_list
err = dao.Book.Ctx(ctx).Cache(gdb.CacheOption{
Name: "book_list",
Duration: time.Minute * 10,
}).Where(do.Book{
// “只要 status 等于 req.Status 的那些书。”
// 等价 SQL:SELECT * FROM book WHERE status = ?
Status: req.Status,
}).Scan(&res.List)
return res, err
}





之后我会持续更新,如果喜欢我的文章,请记得一键三连哦,点赞关注收藏,你的每一个赞每一份关注每一次收藏都将是我前进路上的无限动力 !!!↖(▔▽▔)↗感谢支持!
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)