文章目录
- 注册流程
- 登录流程
- 用户认证流程
- [分布式 ID 生成器](#分布式 ID 生成器)
-
- [分布式 ID 的特点](#分布式 ID 的特点)
- 可能会问的问题
-
- [1. 为什么不直接使用数据库主键 ID 做用户 ID?](#1. 为什么不直接使用数据库主键 ID 做用户 ID?)
- [2. 为什么不使用 UUID(Universally Unique Identifier)/ GUID(Globally Unique Identifier)?](#2. 为什么不使用 UUID(Universally Unique Identifier)/ GUID(Globally Unique Identifier)?)
- [snowflake 算法介绍](#snowflake 算法介绍)
-
- [SnowFlake 算法在同一毫秒内最多可以生成多少个全局唯一 ID 呢?](#SnowFlake 算法在同一毫秒内最多可以生成多少个全局唯一 ID 呢?)
- [snowflake 的 Go 实现](#snowflake 的 Go 实现)
-
- [1. bwmarrin/snowflake](#1. bwmarrin/snowflake)
- [2. sonyflake](#2. sonyflake)
- 为用户注册实现层添加雪花算法
- 抽离业务逻辑到service层
- 测试
注册流程
提交注册信息 → 参数校验 → 入库 → 注册成功
登录流程
提交登录信息 → 参数校验 → 查询数据库 → 登陆成功 → 返回 Token
用户认证流程
在请求中携带 token → 后端认证中间件校验 → 校验成功 → 返回数据
分布式 ID 生成器
分布式 ID 的特点
- 全局唯一性:不能出现有重复的 ID 标识,这是基本要求。
- 递增性:确保生成 ID 对于用户或业务是递增的。
- 高可用性:确保任何时候都能生成正确的 ID。
- 高性能:在高并发的环境下依然表现良好。
不仅仅是用于用户 ID,实际互联网中有很多场景都需要能够生成类似 MySQL 自增 ID 这样不断增大,同时又不会重复的 ID,以支持业务中的高并发场景。
比较典型的场景有:
- 电商促销时短时间内会有大量的订单涌入到系统,比如每秒 10w+;
- 明星出轨时微博短时间内会产生大量的相关微博转发和评论消息。
在这些业务场景下将数据插入数据库之前,我们需要给这些订单和消息先分配一个唯一 ID,然后再保存到数据库中。对这个 ID 的要求是希望其中能带有一些时间信息,这样即使我们后端的系统对消息进行了分库分表,也能够以时间顺序对这些消息进行排序。
可能会问的问题
uid: 781261132115
uid: 78943213101
req → uid → uid % 100 →
- userinfo_15
- userinfo_01
1. 为什么不直接使用数据库主键 ID 做用户 ID?
使用数据库自增 ID 作为用户 ID,无法满足超大用户量规模和分布式架构的需求。
分布式 ID 生成系统本身是一个高度可靠、容错的分布式服务。
2. 为什么不使用 UUID(Universally Unique Identifier)/ GUID(Globally Unique Identifier)?

snowflake 算法介绍
雪花算法,它是 Twitter 开源的由 64 位整数组成的分布式 ID,性能较高,并且在单机上递增。

通常一个 64 位的 ID 会被划分为以下几个部分:

SnowFlake 算法在同一毫秒内最多可以生成多少个全局唯一 ID 呢?
同一毫秒的 ID 数量 = 1024 × 4096 = 4194304
snowflake 的 Go 实现
1. bwmarrin/snowflake
github.com/bwmarrin/snowflake 是一个相当轻量化的 snowflake Go 实现。
产生的 64 位 ID 结构如下所示:

安装依赖
go
go get github.com/bwmarrin/snowflake
使用示例:
go
package main
import (
"fmt"
"time"
"github.com/bwmarrin/snowflake"
)
func main() {
// 设置自定义的系统起始时间戳
startTime, _ := time.Parse(time.DateOnly, "2025-01-01")
snowflake.Epoch = startTime.UnixMilli()
// 创建 1 号节点,集群中不能使用重复的 node 序号
node, err := snowflake.NewNode(1)
if err != nil {
fmt.Println(err)
return
}
// 生成一个 snowflake ID
id := node.Generate()
// 打印不同展示形式的 ID
fmt.Printf("Int64 ID: %d\n", id)
fmt.Printf("String ID: %s\n", id)
fmt.Printf("Base2 ID: %s\n", id.Base2())
fmt.Printf("Base64 ID: %s\n", id.Base64())
}
2. sonyflake
sonyflake 是 Sony 公司开源的一个项目,基本思路和 snowflake 差不多,不过位分配上稍有不同:

这个库生成的 ID 中时间戳只用了 39 个 bit,但时间的单位变成了 10ms,
所以理论上比 41 位表示的时间还要久(174 年)。
Sequence ID 和之前的定义一致,Machine ID 其实就是 Node ID。
安装依赖:
go
go get github.com/sony/sonyflake/v2
sonyflake 库有以下配置参数:
go
type Settings struct {
BitsSequence int
BitsMachineID int
TimeUnit time.Duration
StartTime time.Time
MachineID func() (int, error)
CheckMachineID func(int) bool
}
其中:
- StartTime 选项和我们之前的 Epoch 差不多,如果不设置的话,
默认是从 2014-09-01 00:00:00 +0000 UTC 开始。 - MachineID 可以由用户自定义的函数,如果用户不定义的话,
会默认将本机 IP 的低 16 位作为 machine id。 - CheckMachineID 是由用户提供的检查 MachineID 是否冲突的函数。
使用示例:
go
package main
import (
"fmt"
"github.com/sony/sonyflake/v2"
"time"
)
func main() {
st, _ := time.Parse(time.DateOnly, "2025-01-01")
settings := sonyflake.Settings{
StartTime: st,
}
sonyFlake, err := sonyflake.New(settings)
if err != nil {
panic(err)
}
id, err := sonyFlake.NextID()
fmt.Printf("id:%v err:%v\n", id, err)
}
为用户注册实现层添加雪花算法
go
package userinfo
import (
"context"
"time"
"github.com/gogf/gf/crypto/gmd5"
_ "github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
v1 "backend/api/userinfo/v1"
"backend/internal/dao"
"backend/internal/model/entity"
"github.com/sony/sonyflake/v2"
)
const (
defaultAvatar = "https://avatars.githubusercontent.com/u/51045272?v=4"
)
// 建议:在包级别初始化一次(比如 init() 或 main 启动时)
var sf *sonyflake.Sonyflake
func init() {
st, err := time.Parse(time.DateOnly, "2025-11-01") // 用过去时间
if err != nil {
panic(err) // 这里只在启动阶段 panic 可以接受
}
sf, err = sonyflake.New(sonyflake.Settings{StartTime: st})
if err != nil {
panic(err)
}
}
// 用户注册流程
func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (*v1.CreateRes, error) {
// 参数校验(框架已经做完了)
// 创建用户,入库
// st, _ := time.Parse(time.DateOnly, "2025-11-01")
// settings := sonyflake.Settings{
// StartTime: st,
// }
// sonyFlake, err := sonyflake.New(settings)
// if err != nil {
// panic(err)
// }
// userID, err := sonyFlake.NextID() // id
// fmt.Printf("id:%v err:%v\n", userID, err)
userID, err := sf.NextID()
if err != nil {
g.Log().Errorf(ctx, "生成用户ID失败: %v", err)
return nil, gerror.Wrap(err, "生成用户ID失败")
}
// 拿一条记录/返回给业务/插入一整条数据 → 用 entity.Userinfo
// 写查询条件、更新字段(部分更新) → 用 do.Userinfo
newUserInfo := entity.Userinfo{
UserId: uint64(userID), // 使用雪花算法生成唯一ID
Username: req.Username,
// Password: req.Password, // 是不是需要对用户输入的密码进行加密
Password: gmd5.MustEncryptString(req.Password), // 对字符串 s 做 MD5, 返回 MD5 的十六进制字符串
// 练习可用;生产换 bcrypt
Email: req.Email,
Avatar: defaultAvatar, // 简化注册流程,一般使用默认头像,后续支持用户在个人中心上传头像
}
// id, err := dao.Userinfo.Ctx(ctx).InsertAndGetId(newUserInfo)
_, err = dao.Userinfo.Ctx(ctx).Insert(newUserInfo)
if err != nil {
g.Log().Errorf(ctx, "创建用户失败:%v", err)
return nil, gerror.Wrap(err, "创建用户失败")
}
// 返回结果
return &v1.CreateRes{
UserId: uint64(userID),
Username: newUserInfo.Username,
}, nil
}



抽离业务逻辑到service层
go
package userinfo
import (
// "backend/internal/dao"
"backend/internal/model"
// "backend/internal/model/entity"
"context"
// "time"
// "github.com/gogf/gf/crypto/gmd5"
// "github.com/gogf/gf/v2/errors/gerror"
// "github.com/gogf/gf/v2/frame/g"
// "github.com/sony/sonyflake/v2"
)
// 把用户服务抽象成一个接口, 列出来所需要实现的方法
type UserInfoService interface {
Create(ctx context.Context, input *model.CreateUserInput) (*model.CreateUserOutput, error)
}

go
package impl
import (
"backend/internal/dao"
"backend/internal/model"
"backend/internal/model/entity"
"backend/internal/service/userinfo"
"context"
"time"
"github.com/gogf/gf/crypto/gmd5"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/sony/sonyflake/v2"
)
// 用户相关业务逻辑
// 定义一个结构体, 实现UserInfoService接口
type UserInfo struct{}
// func New() *UserInfo {
// return &UserInfo{}
// }
func New() userinfo.UserInfoService {
return &UserInfo{}
}
// 建议:在包级别初始化一次(比如 init() 或 main 启动时)
var sf *sonyflake.Sonyflake
const (
defaultAvatar = "https://avatars.githubusercontent.com/u/51045272?v=4"
)
func init() {
st, err := time.Parse(time.DateOnly, "2025-11-01") // 用过去时间
if err != nil {
panic(err) // 这里只在启动阶段 panic 可以接受
}
sf, err = sonyflake.New(sonyflake.Settings{StartTime: st})
if err != nil {
panic(err)
}
}
// Create 创建用户
// func (u *UserInfo) Create(ctx context.Context, username, password, email string) error {
func (u *UserInfo) Create(ctx context.Context, input *model.CreateUserInput) (*model.CreateUserOutput, error) {
// 1. 判断用户名是否已经存在(根据用户名查重)
exist, err := dao.Userinfo.Ctx(ctx).
Where(dao.Userinfo.Columns().Username, input.Username).
Exist()
if err != nil {
g.Log().Errorf(ctx, "查询用户是否存在失败: %v", err)
return nil, err
}
if exist {
return nil, gerror.New("用户已存在")
}
// 2. 生成唯一 id
userID, err := sf.NextID()
if err != nil {
g.Log().Errorf(ctx, "生成用户ID失败: %v", err)
return nil, gerror.Wrap(err, "生成用户ID失败")
}
// 3. 创建用户
// 创建用户,入库
newUserInfo := entity.Userinfo{
UserId: uint64(userID), // 使用雪花算法生成唯一ID
Username: input.Username,
// Password: req.Password, // 是不是需要对用户输入的密码进行加密
Password: gmd5.MustEncryptString(input.Password), // 对字符串 s 做 MD5, 返回 MD5 的十六进制字符串
// 练习可用;生产换 bcrypt
Email: input.Email,
Avatar: defaultAvatar, // 简化注册流程,一般使用默认头像,后续支持用户在个人中心上传头像
}
// id, err := dao.Userinfo.Ctx(ctx).InsertAndGetId(newUserInfo)
_, err = dao.Userinfo.Ctx(ctx).Insert(newUserInfo)
if err != nil {
g.Log().Errorf(ctx, "创建用户失败:%v", err)
return nil, gerror.Wrap(err, "创建用户失败")
}
// 4. 返回结果
return &model.CreateUserOutput{
UserId: uint64(userID),
Username: input.Username,
}, nil
}

go
package model
// 定义 controller 层 与 service 层 之间交互的数据
// gf 框架推荐使用 input/output 结构体封装交互的数据
type CreateUserInput struct {
Username string `json:"username"`
Password string `json:"password"`
Email string `json:"email"`
}
type CreateUserOutput struct {
UserId uint64 `json:"userId"`
Username string `json:"username"`
}

go
package userinfo
import (
"context"
_ "time"
"backend/internal/model"
_ "github.com/gogf/gf/crypto/gmd5"
_ "github.com/gogf/gf/v2/errors/gcode"
_ "github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
v1 "backend/api/userinfo/v1"
_ "backend/internal/dao"
_ "backend/internal/model/entity"
_ "github.com/sony/sonyflake/v2"
)
// const (
// defaultAvatar = "https://avatars.githubusercontent.com/u/51045272?v=4"
// )
// // 建议:在包级别初始化一次(比如 init() 或 main 启动时)
// var sf *sonyflake.Sonyflake
// func init() {
// st, err := time.Parse(time.DateOnly, "2025-11-01") // 用过去时间
// if err != nil {
// panic(err) // 这里只在启动阶段 panic 可以接受
// }
// sf, err = sonyflake.New(sonyflake.Settings{StartTime: st})
// if err != nil {
// panic(err)
// }
// }
// 用户注册流程
func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (*v1.CreateRes, error) {
// 参数校验(框架已经做完了)
// st, _ := time.Parse(time.DateOnly, "2025-11-01")
// settings := sonyflake.Settings{
// StartTime: st,
// }
// sonyFlake, err := sonyflake.New(settings)
// if err != nil {
// panic(err)
// }
// userID, err := sonyFlake.NextID() // id
// fmt.Printf("id:%v err:%v\n", userID, err)
// userID, err := sf.NextID()
// if err != nil {
// g.Log().Errorf(ctx, "生成用户ID失败: %v", err)
// return nil, gerror.Wrap(err, "生成用户ID失败")
// }
// 拿一条记录/返回给业务/插入一整条数据 → 用 entity.Userinfo
// 写查询条件、更新字段(部分更新) → 用 do.Userinfo
// newUserInfo := entity.Userinfo{
// UserId: uint64(userID), // 使用雪花算法生成唯一ID
// Username: req.Username,
// // Password: req.Password, // 是不是需要对用户输入的密码进行加密
// Password: gmd5.MustEncryptString(req.Password), // 对字符串 s 做 MD5, 返回 MD5 的十六进制字符串
// // 练习可用;生产换 bcrypt
// Email: req.Email,
// Avatar: defaultAvatar, // 简化注册流程,一般使用默认头像,后续支持用户在个人中心上传头像
// }
// // id, err := dao.Userinfo.Ctx(ctx).InsertAndGetId(newUserInfo)
// _, err = dao.Userinfo.Ctx(ctx).Insert(newUserInfo)
// if err != nil {
// g.Log().Errorf(ctx, "创建用户失败:%v", err)
// return nil, gerror.Wrap(err, "创建用户失败")
// }
// 返回结果
// return &v1.CreateRes{
// UserId: uint64(userID),
// Username: newUserInfo.Username,
// }, nil
// 参数校验(框架已经做完了)
input := model.CreateUserInput{
Username: req.Username,
Password: req.Password,
Email: req.Email,
}
// 调用service层的逻辑
output, err := c.svc.Create(ctx, &input)
if err != nil {
g.Log().Errorf(ctx, "Create user failed: %v", err)
return nil, err
}
// 返回结果
return &v1.CreateRes{
UserId: output.UserId,
Username: output.Username,
}, nil
}

go
// =================================================================================
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
// =================================================================================
package userinfo
import (
"backend/api/userinfo" // api层的user包
srvUser "backend/internal/service/userinfo" // service层的user包
"backend/internal/service/userinfo/impl"
)
type ControllerV1 struct {
// 用户信息服务
svc srvUser.UserInfoService
}
func NewV1() userinfo.IUserinfoV1 {
return &ControllerV1{
svc: impl.New(),
}
}

测试


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