文章目录
- [BITFIELD 命令详解](#BITFIELD 命令详解)
-
- 一、概述
- 二、核心特性
-
- [1. 多字段操作](#1. 多字段操作)
- [2. 类型支持(u16)](#2. 类型支持(u16))
- [3. 溢出控制](#3. 溢出控制)
- [3. 原子性](#3. 原子性)
- 三、语法与常用子命令
- 四、示例
-
- [1. 存储用户积分(16 位无符号整数)](#1. 存储用户积分(16 位无符号整数))
- [2. 多字段操作(同时处理多个用户)](#2. 多字段操作(同时处理多个用户))
- [3. 溢出控制](#3. 溢出控制)
- 五、应用场景
-
- [1. 精确计数器](#1. 精确计数器)
- [2. 状态压缩存储](#2. 状态压缩存储)
- [3. 时间序列数据](#3. 时间序列数据)
- [4. 用户画像标签](#4. 用户画像标签)
- 六、性能与注意事项
- [BITFIELD 示例代码](#BITFIELD 示例代码)
- [Redis Key 设计](#Redis Key 设计)
- [Bitmap 和 BITFIELD 的关系](#Bitmap 和 BITFIELD 的关系)
- [goframe 框架教程](#goframe 框架教程)
-
- [goframe 介绍](#goframe 介绍)
- gf
- 安装
- 验证安装成功
- 创建项目模板
- 目录结构
- 运行项目模板
- 开发示例
-
- [安装 MySQL](#安装 MySQL)
- [设置 MySQL 密码](#设置 MySQL 密码)
- [设置 MySQL 新账户](#设置 MySQL 新账户)
- 1、创建数据表
- [2、修改配置文件,生成 dao/do/entity 代码。](#2、修改配置文件,生成 dao/do/entity 代码。)
- [3、编写 API 定义](#3、编写 API 定义)
- [4、生成 controller 层代码](#4、生成 controller 层代码)
- `controller路由实现代码`
- `api接口抽象文件`
- 5、编写接口的业务逻辑
- 创建(/root/proj/proj2/goframeProj/gf_demo/internal/controller/book/book_v1_create.go)
- 删除(/root/proj/proj2/goframeProj/gf_demo/internal/controller/book/book_v1_delete.go)
- 更新(/root/proj/proj2/goframeProj/gf_demo/internal/controller/book/book_v1_update.go)
- 查单个(/root/proj/proj2/goframeProj/gf_demo/internal/controller/book/book_v1_get_one.go)
- 查列表(/root/proj/proj2/goframeProj/gf_demo/internal/controller/book/book_v1_get_list.go)
- 6、完善配置和路由
- [`6.1.1、在 main.go 文件 import 导入 MySQL 驱动`](#
6.1.1、在 main.go 文件 import 导入 MySQL 驱动) - `6.1.2、在项目根目录下执行下面的命令`
- [`6.2 添加业务配置 manifest/config/config.yaml`](#
6.2 添加业务配置 manifest/config/config.yaml) - [`6.3 internal/cmd/cmd.go 添加路由`](#
6.3 internal/cmd/cmd.go 添加路由) - 7、启动服务
- [下载安装 VSCode 的 Postman 插件](#下载安装 VSCode 的 Postman 插件)
- [测试 GET](#测试 GET)
- API定义修改(/root/proj/proj2/goframeProj/gf_demo/api/book/v1/book.go)
- 让返回data为切片
- [测试 POST](#测试 POST)
- [测试 PUT](#测试 PUT)
- [测试 DELETE](#测试 DELETE)
BITFIELD 命令详解
一、概述
BITFIELD 是 Redis 3.2 版本引入的高级位操作命令,用于对 Bitmap(位图) 执行复杂的位级操作。它允许在一个命令中对多个位范围进行读写,支持不同的整数类型(如 8 位、16 位、32 位整数),并提供了溢出控制和原子性操作,适合处理需要精确控制的位级数据。
二、核心特性
1. 多字段操作
在单个命令中对同一个键的多个位范围进行读写,减少网络开销。
2. 类型支持(u16)
支持有符号整数(i8、i16、i32、i64)和无符号整数(u8、u16、u32、u64),满足不同精度需求。


3. 溢出控制
处理数值溢出时,可选择:
- WRAP:环绕(如 255+1=0)。
- SAT:饱和(如 255+1=255)。
- FAIL:失败(返回 NULL)。
3. 原子性
所有操作在单个命令中执行,保证原子性,无需使用事务。
三、语法与常用子命令
bash
BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment]
[OVERFLOW WRAP|SAT|FAIL]
常用子命令:

四、示例
1. 存储用户积分(16 位无符号整数)
go
# 初始化用户 1001 的积分为 100(从位偏移 0 开始,使用 16 位无符号整数)
BITFIELD user:score:1001 SET u16 0 100
# 增加 50 积分
BITFIELD user:score:1001 INCRBY u16 0 50
# 获取当前积分
BITFIELD user:score:1001 GET u16 0
2. 多字段操作(同时处理多个用户)
go
# 为用户 1001 设置积分为 100,用户 1002 设置为 200
# 假设每个用户使用 16 位,用户 1002 的偏移量为 16
BITFIELD users:scores SET u16 0 100 SET u16 16 200


3. 溢出控制
go
# 从 counter 键的第 0 位开始,读取一个 u8 整数
# 将该值增加 250
# 应用饱和溢出处理,确保结果不超过类型范围
BITFIELD counter OVERFLOW SAT INCRBY u8 0 250
# 结果:255(饱和策略,不会溢出到 256)
五、应用场景
1. 精确计数器
统计点赞数、访问量,避免计数器溢出。
示例:用 INCRBY i32 0 1 实现点赞计数。
2. 状态压缩存储
将多个布尔状态(如用户权限)压缩到一个 Bitmap 中。
示例:用 u8 存储 8 种权限状态。
3. 时间序列数据
存储每日活跃用户数、订单量等,按天递增偏移量。
示例:每天使用新的 32 位整数存储数据。
4. 用户画像标签
用不同位表示用户属性(如性别、年龄段、兴趣)。
示例:BITFIELD user:1001 SET u1 0 1 SET u1 1 0 表示男性、非会员。
六、性能与注意事项

BITFIELD 示例代码
go
package main
import (
"context"
"fmt"
"time"
"github.com/redis/go-redis/v9"
)
// 拉取 go-redis v9 依赖
// go get github.com/redis/go-redis/v9
// 整理依赖(推荐)
// go mod tidy
// bitmap 示例代码
func main() {
// 初始化一个 Redis 客户端,并指定 Redis 服务所在的地址和端口。在后续的操作中,可以通过 rdb 对象与 Redis 服务器进行通信。
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
defer rdb.Close()
ctx := context.Background()
// bitmapDemo(ctx, rdb)
bitFieldDemo(ctx, rdb)
}
// bitmap 示例
func bitmapDemo(ctx context.Context, rdb *redis.Client) {
key := "test:bitmap"
// setbit
// 把 test:bitmap 这张表里的 第 0 个位置 设成 1
// SETBIT 返回的是"设置之前这个位置的值"
// 第一次设置返回值一定是0(之前是没开)
val1 := rdb.SetBit(ctx, key, 0, 1).Val()
fmt.Printf("setbit ret: %v\n", val1)
// getbit
val2 := rdb.GetBit(ctx, key, 0).Val()
fmt.Printf("getbit ret: %v\n", val2)
// bitcount:统计有多少个 bit 是 1
// Start:0, End:-1:表示从第 0 个字节开始到最后一个字节(-1 是末尾)
val3 := rdb.BitCount(ctx, key, &redis.BitCount{Start: 0, End: -1}).Val()
fmt.Printf("bitcount ret: %v\n", val3)
// bitop
key2 := "test:bitmap:2"
rdb.SetBit(ctx, key2, 2, 1)
// offset: 0 1 2
// 1 0 0
// 0 0 1
key3 := "test:bitmap:3"
// BITOP AND:对两个 Bitmap 做按位与,结果写入 key3
val4 := rdb.BitOpAnd(ctx, key3, key, key2).Val()
fmt.Printf("bitop ret: %v\n", val4)
val5 := rdb.BitCount(ctx, key3, &redis.BitCount{Start: 0, End: -1}).Val()
fmt.Printf("bitcount ret: %v\n", val5)
// bitpos: 找"第一个等于 1 的 bit 在哪里"
val7 := rdb.BitPos(ctx, key2, 1).Val()
fmt.Printf("bitpos ret: %v\n", val7)
rdb.SetBit(ctx, key2, 0, 1)
rdb.SetBit(ctx, key2, 1, 1)
val6 := rdb.BitCount(ctx, key2, &redis.BitCount{Start: 0, End: -1}).Val()
fmt.Printf("get ret: %v\n", val6)
// Bitmap 在 Redis 里,本质还是 String,但它不是"给人看的字符串"
// val8 := rdb.Get(ctx, key2).Val()
// fmt.Printf("val8: %v\n", val8)
// 只有在你存的是 普通字符串 / 数字字符串 时:GET 才"合理"
rdb.Set(ctx, "score", "100", 0)
val9 := rdb.Get(ctx, "score").Val() // "100"
fmt.Printf("val9: %v\n", val9)
}
func bitFieldDemo(ctx context.Context, rdb *redis.Client) {
key := "test:checkin:2026" // 存储2026年整年的打卡情况
// 假设现在是 2026-05
// 5.1 打卡
// 1.1 0
// 1.2 1
// 1.3 2
// ...
// 12.31 365
// 计算出5.1号是今年的第几天,索引位就是天数-1
t, _ := time.Parse("2006-01-02", "2026-05-01")
offset := t.YearDay() - 1 // 5.1
offset52 := offset + 1 // 5.2
offset53 := offset + 2 // 5.3
rdb.SetBit(ctx, key, int64(offset52), 1) // 5.2打卡
rdb.SetBit(ctx, key, int64(offset53), 1) // 5.3打卡
// 5.1(bit[120]):0(没设置它)
// 5.2(bit[121]):1
// 5.3(bit[122]):1
// 查看5月的打卡情况
// 从5.1 到 5.31的数据读取出来
// 5.1 到 5.31 共多少天?
// 用多大的整数能存下31天
// 从 offset=120 开始,读 31 个 bit
ret := rdb.BitField(ctx, key, "GET", "u31", offset, "GET", "u4", offset).Val()
fmt.Printf("ret:%v\n", ret)
fmt.Printf("bet:%031b\n", ret[0])
fmt.Printf("bet:%04b\n", ret[1])
}
bash
root@GoLang:~/proj/proj2/goframeProj# go run main.go
ret:[805306368 6]
bet:0110000000000000000000000000000
bet:0110
Redis Key 设计
1、用户签到记录(Bitmap)
记录一个用户年度签到记录,key 格式:
bash
user:checkins:daily:userID:年份

一个 key 存储用户一年的签到记录。
- 方便使用 bitcount 统计年度累计签到天数。
- 每年之后将 Redis 中的签到数据持久化入 DB。
2、用户补签记录(Bitmap)
因为产品设计用户只能补签当月的日期,所以这里使用月份维度的 Redis Key。一个用户每月补签记录,key格式:
bash
user:checkins:retro:userID:月份

一个 key 存储用户当月的补签记录。
- 既能记录当月补签日期,又能方便的计算当月补签次数。
- 每个月后将 Redis 中的补签数据持久化入 DB。
3、如何计算当月连续签到天数?
当月每日签到 bitmap |(逻辑或) 当月补签 bitmap = 当月签到 bitmap

逐位遍历当月签到 bitmap 后判断可得出当月连续签到天数。
Bitmap 和 BITFIELD 的关系
Bitmap 是数据的"存储方式"(一串 bit);BITFIELD 是在这串 bit 上"按字段读写整数"的高级操作命令。

goframe 框架教程
goframe 介绍
大而全的Go框架。
官方文档内容非常详细&齐全,👉 https://goframe.org/
通常不会存在 100% 符合你需求的框架。
gf
gf 是 goframe 框架提供的开发工具,为开发者提供了便捷的开发指令简化开发工作,提供了例如工程脚手架、代码自动生成、工具及框架更新等实用命令。
安装
bash
go install github.com/gogf/gf/cmd/gf/v2@latest
验证安装成功
bash
root@GoLang:~/proj/proj2/goframeProj# gf -v
v2.9.6
Welcome to GoFrame!
Env Detail:
Go Version: go1.25.4 linux/amd64
GF Version(go.mod):
CLI Detail:
Installed At: /root/go/bin/gf
Built Go Version: go1.25.4
Built GF Version: v2.9.6
Others Detail:
Docs: https://goframe.org
Now : 2025-12-17T14:48:50+08:00
root@GoLang:~/proj/proj2/goframeProj#
创建项目模板
bash
root@GoLang:~/proj/proj2/goframeProj# gf init gf_demo -u
该命令创建一个工程脚手架目录,项目名称是 demo,其中的 -u 参数用于指定是否更新项目中使用的 goframe 框架为最新版本。
目录结构
bash
.
├── Makefile
├── README.MD
├── api
│ └── hello
│ ├── hello.go
│ └── v1
│ └── hello.go
├── go.mod
├── go.sum
├── hack
│ ├── config.yaml
│ ├── hack-cli.mk
│ └── hack.mk
├── internal
│ ├── cmd
│ │ └── cmd.go
│ ├── consts
│ │ └── consts.go
│ ├── controller
│ │ └── hello
│ │ ├── hello.go
│ │ ├── hello_new.go
│ │ └── hello_v1_hello.go
│ ├── dao
│ ├── logic
│ ├── model
│ │ ├── do
│ │ └── entity
│ ├── packed
│ │ └── packed.go
│ └── service
├── main.go
├── manifest
│ ├── config
│ │ └── config.yaml
│ ├── deploy
│ │ └── kustomize
│ │ ├── base
│ │ │ ├── deployment.yaml
│ │ │ ├── kustomization.yaml
│ │ │ └── service.yaml
│ │ └── overlays
│ │ └── develop
│ │ ├── configmap.yaml
│ │ ├── deployment.yaml
│ │ └── kustomization.yaml
│ ├── docker
│ │ ├── Dockerfile
│ │ └── docker.sh
│ ├── i18n
│ └── protobuf
├── resource
│ ├── public
│ │ ├── html
│ │ ├── plugin
│ │ └── resource
│ │ ├── css
│ │ ├── image
│ │ └── js
│ └── template
└── utility


运行项目模板
bash
root@GoLang:~/proj/proj2/goframeProj# cd gf_demo && gf run main.go

开发示例
使用 gf 开发一个 book 增删改查。
安装 MySQL
bash
root@GoLang:~/proj/proj2/goframeProj/gf_demo# sudo apt update
root@GoLang:~/proj/proj2/goframeProj/gf_demo# sudo apt install mysql-server
安装后服务通常会自动启动;也可以检查/启动:
bash
sudo systemctl status mysql
sudo systemctl start mysql
设置 MySQL 密码
给 root 设置/修改密码(能 sudo mysql 进入的情况最常见)
① 先进入 MySQL(不用密码)
bash
sudo mysql
② 在 MySQL 里修改 root 密码
bash
ALTER USER 'root'@'localhost' IDENTIFIED BY 'StrongPassword!123';
FLUSH PRIVILEGES;
exit;
然后用密码登录测试:
bash
mysql -u root -p
设置 MySQL 新账户
bash
mysql> CREATE USER IF NOT EXISTS 'gf'@'localhost' IDENTIFIED BY 'GfPass!123';
Query OK, 0 rows affected (0.01 sec)
mysql> CREATE USER IF NOT EXISTS 'gf'@'127.0.0.1' IDENTIFIED BY 'GfPass!123';
Query OK, 0 rows affected (0.01 sec)
mysql> GRANT ALL PRIVILEGES ON demo.* TO 'gf'@'localhost';
Query OK, 0 rows affected (0.00 sec)
mysql> GRANT ALL PRIVILEGES ON demo.* TO 'gf'@'127.0.0.1';
Query OK, 0 rows affected (0.01 sec)
mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)
mysql> exit;
Bye
root@GoLang:~/proj/proj2/goframeProj/gf_demo#
1、创建数据表
bash
root@GoLang:~/proj/proj2/goframeProj/gf_demo# mysql
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.44-0ubuntu0.22.04.2 (Ubuntu)
Copyright (c) 2000, 2025, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> show tables;
ERROR 1046 (3D000): No database selected
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
4 rows in set (0.00 sec)
mysql> CREATE DATABASE demo DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
Query OK, 1 row affected (0.01 sec)
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| demo |
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.00 sec)
mysql> USE demo;
Database changed
mysql> show tables;
Empty set (0.00 sec)
mysql> CREATE TABLE `book` (
-> `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'book id',
-> `title` varchar(45) DEFAULT NULL COMMENT 'title',
-> `price` int unsigned DEFAULT NULL COMMENT 'price',
-> `status` tinyint DEFAULT NULL COMMENT 'book status',
-> PRIMARY KEY (`id`)
-> ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Query OK, 0 rows affected, 1 warning (0.03 sec)
启动数据库,输入下面的建表语句,创建 book 表。
sql
CREATE TABLE `book` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'book id',
`title` varchar(45) DEFAULT NULL COMMENT 'title',
`price` int unsigned DEFAULT NULL COMMENT 'price',
`status` tinyint DEFAULT NULL COMMENT 'book status',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
bash
mysql> show tables;
+----------------+
| Tables_in_demo |
+----------------+
| book |
+----------------+
1 row in set (0.00 sec)
2、修改配置文件,生成 dao/do/entity 代码。
2.1 修改 hack/config.yaml 文件中的数据库连接信息。
yaml
# CLI tool, only in development environment.
# https://goframe.org/docs/cli
gfcli:
gen:
dao:
- link: "mysql:gf:GfPass!123@tcp(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=true&loc=Local"
descriptionTag: true
docker:
build: "-a amd64 -s linux -p temp -ew"
tagPrefixes:
- my.image.pub/my-app
2.2 执行下面的命令生成 dao/do/entity 代码
bash
gf gen dao

⽣成代码如下图:


3、编写 API 定义
在 api 目录下新建 book 目录用来保存相关 API 定义。
考虑到后续接口的迭代,通常创建类似 v1 的版本号目录。
新增 api/book/v1/book.go 文件,添加以下增删改查 API 相关的请求和响应结构体定义。
go
package v1
// book 相关的增删改查的请求参数结构体和返回响应的结构体
import (
"gf_demo/internal/model/entity"
"github.com/gogf/gf/v2/frame/g"
)
// CreateReq 创建书籍请求结构体
type CreateReq struct {
g.Meta `path:"/books" method:"post" tags:"book" summary:"创建书籍"`
Title string `p:"title" v:"required|length:1,45#书名必须填写|书名长度必须满足1-45字" dc:"书籍标题"`
Price int `p:"price" v:"required|min:1#价格必须填写|价格不能小于1" dc:"书籍价格"`
}
// CreateRes 创建书籍响应结构体
type CreateRes struct {
// mime 是 MIME Type(媒体类型) 的简称,用来告诉客户端"这段数据是什么格式"
g.Meta `mime:"application/json"`
// json:"id":序列化成 JSON 时字段名用小写 id, 字段类型:64 位整数。
Id int64 `json:"id" dc:"book id"`
}
// DeleteReq 删除书籍请求结构体
type DeleteReq struct {
g.Meta `path:"/books/{id}" method:"delete" tags:"book" summary:"删除书籍"`
Id int64 `p:"id" v:"required|min:1#ID必须传|ID不能小于1" dc:"书籍ID"`
}
// DeleteRes 删除书籍响应结构体
type DeleteRes struct{}
// Status 书籍状态枚举
type Status = int8
const (
StatusAvailable Status = 0 // 上架
StatusDisable Status = 1 // 下架
)
// UpdateReq 更新书籍请求结构体
type UpdateReq struct {
// 这表示 API 请求的路径(路由)。books/{id} 是一个包含路径参数的 URL 模板,{id} 是路径中的占位符,表示书籍的唯一 ID。
// 例如,当你访问 PUT /books/123 时,123 就是书籍的 ID
g.Meta `path:"/books/{id}" method:"put" tags:"book" summary:"更新书籍"`
Id int64 `p:"id" v:"required|min:1#ID必须传|ID不能小于1" dc:"书籍ID"`
Title string `p:"title" v:"length:1,45#书名长度必须满足1-45字" dc:"书籍标题"`
Price int `p:"price" v:"min:1#价格不能小于1" dc:"书籍价格"`
Status Status `p:"status" v:"in:0,1#状态只能是上架或下架" dc:"书籍状态"`
}
// UpdateRes 更新书籍响应结构体
type UpdateRes struct{}
// GetOneReq 获取单个用户请求结构体
type GetOneReq struct {
g.Meta `path:"/books/{id}" method:"get" tags:"book" summary:"获取书籍"`
Id int64 `p:"id" v:"required|min:1#ID必须传|ID不能小于1" dc:"书籍ID"`
}
type GetOneRes struct {
// 使用生成的 model 结构体
*entity.Book `json:"book"`
}
// GetListReq 获取书籍列表请求结构体
type GetListReq struct {
g.Meta `path:"/books" method:"get" tags:"book" summary:"获取书籍列表"`
Status Status `p:"status" v:"in:0,1#状态只能是上架或下架" dc:"书籍状态"`
}
type GetListRes struct {
// 使用生成的 model 结构体
List []*entity.Book `json:"list" dc:"book list"`
}


4、生成 controller 层代码
具体见官方文档说明
执行下面的命令,根据上一步定义的API,生成 controller 层代码。
bash
root@GoLang:~/proj/proj2/goframeProj/gf_demo# gf gen ctrl
generated: /root/proj/proj2/goframeProj/gf_demo/api/book/book.go
generated: /root/proj/proj2/goframeProj/gf_demo/internal/controller/book/book.go
generated: /root/proj/proj2/goframeProj/gf_demo/internal/controller/book/book_new.go
generated: /root/proj/proj2/goframeProj/gf_demo/internal/controller/book/book_v1_create.go
generated: /root/proj/proj2/goframeProj/gf_demo/internal/controller/book/book_v1_delete.go
generated: /root/proj/proj2/goframeProj/gf_demo/internal/controller/book/book_v1_update.go
generated: /root/proj/proj2/goframeProj/gf_demo/internal/controller/book/book_v1_get_one.go
generated: /root/proj/proj2/goframeProj/gf_demo/internal/controller/book/book_v1_get_list.go
generated: /root/proj/proj2/goframeProj/gf_demo/api/hello/hello.go
done!

controller路由实现代码

api接口抽象文件

5、编写接口的业务逻辑
通常,我们在 controller 层实现 API 参数解析和校验的逻辑,将业务逻辑写在 logic 层或 service 层。
但是像本示例中比较简单的逻辑,可以直接在 controller 层实现。
创建(/root/proj/proj2/goframeProj/gf_demo/internal/controller/book/book_v1_create.go)
go
package book
import (
"context"
_ "github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
// 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()
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)
}
删除(/root/proj/proj2/goframeProj/gf_demo/internal/controller/book/book_v1_delete.go)
go
package book
import (
"context"
// dao 和 do:分别是数据访问对象(DAO)和数据库操作模型(DO)。dao.Book 用于执行与数据库交互的操作,do.Book 是数据库表的模型映射
v1 "gf_demo/api/book/v1"
"gf_demo/internal/dao"
)
func (c *ControllerV1) Delete(ctx context.Context, req *v1.DeleteReq) (res *v1.DeleteRes, err error) {
// 删除图书的业务逻辑
_, err = dao.Book.Ctx(ctx).WherePri(req.Id).Delete()
return nil, err
// return nil, gerror.NewCode(gcode.CodeNotImplemented)
}
更新(/root/proj/proj2/goframeProj/gf_demo/internal/controller/book/book_v1_update.go)
go
package book
import (
"context"
// 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) Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error) {
// 更新书籍的业务逻辑
_, err = dao.Book.Ctx(ctx).Data(do.Book{
Title: req.Title,
Price: req.Price,
Status: req.Status,
}).WherePri(req.Id).Update()
return nil, err
// return nil, gerror.NewCode(gcode.CodeNotImplemented)
}
查单个(/root/proj/proj2/goframeProj/gf_demo/internal/controller/book/book_v1_get_one.go)
go
package book
import (
"context"
// dao 和 do:分别是数据访问对象(DAO)和数据库操作模型(DO)。dao.Book 用于执行与数据库交互的操作,do.Book 是数据库表的模型映射
v1 "gf_demo/api/book/v1"
"gf_demo/internal/dao"
)
func (c *ControllerV1) GetOne(ctx context.Context, req *v1.GetOneReq) (res *v1.GetOneRes, err error) {
// 查询单个图书的业务逻辑
res = &v1.GetOneRes{} // ✅ 关键:先分配内存
// Scan 的作用是:把数据库查到的一行记录,填充到你给它的变量里
err = dao.Book.Ctx(ctx).
WherePri(req.Id).
Scan(&res.Book) // 查询到的数据赋值给res.Book变量
return res, err
// return nil, gerror.NewCode(gcode.CodeNotImplemented)
}
查列表(/root/proj/proj2/goframeProj/gf_demo/internal/controller/book/book_v1_get_list.go)
go
package book
import (
"context"
// 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) GetList(ctx context.Context, req *v1.GetListReq) (res *v1.GetListRes, err error) {
// 获取图书列表的业务逻辑
res = &v1.GetListRes{}
// 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
}
6、完善配置和路由
6.1 添加数据库驱动
6.1.1、在 main.go 文件 import 导入 MySQL 驱动
go
package main
import (
_ "gf_demo/internal/packed"
_ "github.com/gogf/gf/contrib/drivers/mysql/v2" // 导入MySQL驱动
"github.com/gogf/gf/v2/os/gctx"
"gf_demo/internal/cmd"
)
func main() {
cmd.Main.Run(gctx.GetInitCtx())
}
6.1.2、在项目根目录下执行下面的命令
bash
root@GoLang:~/proj/proj2/goframeProj/gf_demo# go mod tidy
go: finding module for package github.com/gogf/gf/contrib/drivers/mysql/v2
go: found github.com/gogf/gf/contrib/drivers/mysql/v2 in github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.6
root@GoLang:~/proj/proj2/goframeProj/gf_demo#
安装依赖
6.2 添加业务配置 manifest/config/config.yaml
yaml
# 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"

6.3 internal/cmd/cmd.go 添加路由
go
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"
)
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.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse)
group.Bind(
hello.NewV1(),
// 注册book相关路由
// 请求方法和路径是根据 请求结构体里的 g.Meta 标签自动生成的
book.NewV1(),
)
})
s.Run()
return nil
},
}
)


7、启动服务
在项目根目录下执行 go run main.go 启动程序。
打开 http://127.0.0.1:8000/swagger/ 即可查看完整 API 文档。
bash
root@GoLang:~/proj/proj2/goframeProj/gf_demo# go run main.go
2025-12-19T09:51:10.630+08:00 [INFO] pid[158974]: http server started listening on [:8000]
2025-12-19T09:51:10.630+08:00 [INFO] swagger ui is serving at address: http://127.0.0.1:8000/swagger/
2025-12-19T09:51:10.630+08:00 [INFO] openapi specification is serving at address: http://127.0.0.1:8000/api.json
----------|--------|-------------|----------------------------------------------------------|----------------------------------
| ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE |
----------|--------|-------------|----------------------------------------------------------|----------------------------------
| :8000 | ALL | /api.json | github.com/gogf/gf/v2/net/ghttp.(*Server).openapiSpec | |
----------|--------|-------------|----------------------------------------------------------|----------------------------------
| :8000 | GET | /books | gf_demo/internal/controller/book.(*ControllerV1).GetList | ghttp.MiddlewareHandlerResponse |
----------|--------|-------------|----------------------------------------------------------|----------------------------------
| :8000 | POST | /books | gf_demo/internal/controller/book.(*ControllerV1).Create | ghttp.MiddlewareHandlerResponse |
----------|--------|-------------|----------------------------------------------------------|----------------------------------
| :8000 | DELETE | /books/{id} | gf_demo/internal/controller/book.(*ControllerV1).Delete | ghttp.MiddlewareHandlerResponse |
----------|--------|-------------|----------------------------------------------------------|----------------------------------
| :8000 | GET | /books/{id} | gf_demo/internal/controller/book.(*ControllerV1).GetOne | ghttp.MiddlewareHandlerResponse |
----------|--------|-------------|----------------------------------------------------------|----------------------------------
| :8000 | PUT | /books/{id} | gf_demo/internal/controller/book.(*ControllerV1).Update | ghttp.MiddlewareHandlerResponse |
----------|--------|-------------|----------------------------------------------------------|----------------------------------
| :8000 | GET | /hello | gf_demo/internal/controller/hello.(*ControllerV1).Hello | ghttp.MiddlewareHandlerResponse |
----------|--------|-------------|----------------------------------------------------------|----------------------------------
| :8000 | ALL | /swagger/* | github.com/gogf/gf/v2/net/ghttp.(*Server).swaggerUI | HOOK_BEFORE_SERVE |
----------|--------|-------------|----------------------------------------------------------|----------------------------------
打开 http://127.0.0.1:8000/swagger/ 即可查看完整 API 文档。

下载安装 VSCode 的 Postman 插件

测试 GET


API定义修改(/root/proj/proj2/goframeProj/gf_demo/api/book/v1/book.go)
go
// GetListReq 获取书籍列表请求结构体
type GetListReq struct {
g.Meta `path:"/books" method:"get" tags:"book" summary:"获取书籍列表"`
Status Status `p:"status" v:"required|in:0,1#status必传|状态只能是上架或下架" dc:"书籍状态"`
}



让返回data为切片
go
package book
import (
"context"
// 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"
)
func (c *ControllerV1) GetList(ctx context.Context, req *v1.GetListReq) (res *v1.GetListRes, err error) {
// 获取图书列表的业务逻辑
// 结构体的字段会使用其 零值:
// List 字段的零值是 nil,因为 List 是一个切片类型,切片的零值是 nil
// res = &v1.GetListRes{}
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
}
重启服务 go run main.go

测试 POST

bash
mysql> select * from book;
+----+-------------------------+-------+--------+
| id | title | price | status |
+----+-------------------------+-------+--------+
| 1 | 《Redis高手心法》 | 100 | 0 |
+----+-------------------------+-------+--------+
1 row in set (0.00 sec)


测试 PUT

测试 DELETE

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