路由管理

只需记两种,其他了解皆即可。

第一种

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:~#

在这里插入图片描述

中间件

原理洋葱模型
在这里插入图片描述

  1. 处理前
  2. 业务处理handler
  3. 业务处理后

几个常用的中间件要记住

在这里插入图片描述

鉴权(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

  1. 内置数据校验规则
  2. 常用数据校验方法
  3. 如何自定义校验规则
  4. 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
}


在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

之后我会持续更新,如果喜欢我的文章,请记得一键三连哦,点赞关注收藏,你的每一个赞每一份关注每一次收藏都将是我前进路上的无限动力 !!!↖(▔▽▔)↗感谢支持!

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐