goframe框架签到系统项目(BITFIELD 命令详解、Redis Key 设计、goframe 框架教程、安装MySQL)

文章目录

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


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

相关推荐
Lucas555555552 小时前
现代C++四十不惑:AI时代系统软件的基石与新征程
开发语言·c++·人工智能
川贝枇杷膏cbppg2 小时前
Redis 的 AOF
java·数据库·redis
⑩-2 小时前
SpringCloud-Nacos 配置中心实战
后端·spring·spring cloud
吃喝不愁霸王餐APP开发者2 小时前
Java后端系统对接第三方外卖API时的幂等性设计与重试策略实践
java·开发语言
TG:@yunlaoda360 云老大2 小时前
如何在华为云国际站代理商控制台进行SFS Turbo的性能与容量核查?
服务器·网络·数据库·华为云
写代码的【黑咖啡】2 小时前
深入理解 Python 中的模块(Module)
开发语言·python
ytttr8732 小时前
MATLAB基于LDA的人脸识别算法实现(ORL数据库)
数据库·算法·matlab
wuk9983 小时前
matlab为地图进行四色着色
开发语言·matlab
_MyFavorite_3 小时前
cl报错+安装 Microsoft Visual C++ Build Tools
开发语言·c++·microsoft