本文目录
- 一、Web的CLD分层架构、
- 二、注册业务流程
- 三、登录业务流程
- 四、Token认证模式
- 基于Cookie和Seesion的认证模式
- 五、分布式ID生成与雪花算法
一、Web的CLD分层架构、
一般Web项目可以看作是CLD架构,也就是图中所示,Controller
+Logic
+DAO
层,主要如下图所示。
二、注册业务流程
首先在main.go
中注册一个全局的路由。
在SetupRouter
中找注册业务的路由,然后进入controller
层的SignupHandler
方法。
接着看看SignupHandler
的处理流程。
前端发来一个字符串,我们封装在models
里面。
binding:"required":
这是用于数据验证的标签。required 表示该字段是必填的,如果用户没有提交 username,验证会失败。
eqfield=Password
: 表示确认密码必须与 Password 字段的值相等。如果用户提交的密码和确认密码不一致,验证会失败。
来看处理请求参数错的代码:
首先尝试将请求的 JSON 数据绑定到结构体 fo 中,如果绑定过程中出现错误,会记录一条错误日志并进一步判断错误类型。如果错误不是 validator.ValidationErrors
类型,说明是普通的参数错误,直接返回一个通用的参数错误响应;但如果错误是 validator.ValidationErrors
类型,则会将验证错误翻译成更易读的错误消息,并返回带有具体错误信息的响应。
然后接下来就是开始注册了,也就是对应的当我们处理路由,进行完参数校验之后,就开始处理逻辑Service/Logic
了。
来看看Logic.SighnUp
的代码。
首先检查指定用户名是否已经存在于数据库中。它通过执行 SQL 查询统计用户名匹配的记录数,如果查询失败则直接返回错误;如果查询成功且记录数大于零,说明用户已存在,函数返回一个自定义错误提示“用户已存在”;如果记录数为零,则表示用户不存在,函数返回 nil,表示检查通过。
然后就是生成唯一的用户ID,并且创建一个User实例,然后存进数据库中。
生成唯一的用户ID是通过雪花算法来的。如下所示。
在最开始的main.go
函数中就会初始化了。
下面是对应的算法。
time.Parse 函数用于将字符串解析为 time.Time 类型的时间对象,而 “2006-01-02” 是 Go 语言中时间格式化的标准模板字符串。
基于索尼雪花算法(Sonyflake)的全局唯一 ID 生成器的初始化和使用逻辑。Init 函数用于初始化 Sonyflake,它接收一个机器 ID 并设置为全局变量 sonyMachineID,同时定义了一个起始时间(2022年2月9日)作为 Sonyflake 的基准时间。通过 sonyflake.Settings 配置结构体,将起始时间和机器 ID 设置传递给 Sonyflake,并尝试创建一个新的 Sonyflake 实例。如果初始化成功,sonyFlake 将被赋值为这个实例,否则返回错误。
最后就是向数据库插入数据了。
这里我们对密码进行了md5加密再存进系统中去。
首先创建一个新的 MD5 哈希对象 h。MD5 是一种广泛使用的哈希算法,它将任意长度的输入数据转换为一个固定长度(128 位)的哈希值。然后将一个名为 secret 的字符串转换为字节切片,并写入到哈希对象中。secret 通常用于为哈希过程添加额外的安全性,类似于“盐”(salt)的作用,防止直接对原始数据进行哈希。
h.Sum(data)
将 data 写入到哈希对象 h 中,并计算最终的哈希值。Sum 方法会返回一个字节切片,表示哈希结果。hex.EncodeToString
将哈希结果(字节切片)转换为十六进制字符串。这是因为哈希值通常是一个二进制字节序列,而十六进制字符串更易于存储和传输。
三、登录业务流程
登录功能也是一样的,首先前端通过HTTP发送一个登录请求,然后后端的Controller是服务的入口,处理路由,进行参数校验,然后请求转发。接着继续交给Logic层,然后Logic层负责处理业务逻辑,最后到DAO层进行数据存储的相关功能。
通过POST请求到login中。
然后转到LoginHandler
中。
把前端传来的参数绑定到定义好的LoginForm表单数据模型
中,然后进行参数校验和解析。
在这里我们对错误进行了封装,首先来看看code
业务状态码的封装,后续直接调用即可。
if err := c.ShouldBindJSON(&u); err != nil {
// 请求参数有误,直接返回响应
zap.L().Error("Login with invalid param", zap.Error(err))
// 判断err是不是 validator.ValidationErrors类型的errors
errs, ok := err.(validator.ValidationErrors)
if !ok {
// 非validator.ValidationErrors类型错误直接返回
ResponseError(c, CodeInvalidParams) // 请求参数错误
return
}
// validator.ValidationErrors类型错误则进行翻译
ResponseErrorWithMsg(c, CodeInvalidParams, removeTopStruct(errs.Translate(trans)))
return
}
如果 err 不是 validator.ValidationErrors
类型,说明这是一个非验证错误(例如 JSON 格式错误或绑定失败)。此时直接调用 ResponseError
函数,返回状态码 CodeInvalidParams
(请求参数错误),并结束函数执行。
如果 err 是 validator.ValidationErrors 类型,则调用 errs.Translate(trans) 方法将验证错误翻译成用户可读的错误信息。trans 是一个翻译器,用于支持国际化。removeTopStruct 函数用于移除错误消息中可能包含的结构体名称,使错误信息更简洁。然后调用 ResponseErrorWithMsg 函数返回状态码 CodeInvalidParams,并附带具体的错误信息。
这里是李文周老师的对validator库参数校验若干实用技巧:https://www.liwenzhou.com/posts/Go/validator-usages/
然后看看Login的函数逻辑。
然后在DAO层的login进行数据库登录验证。
四、Token认证模式
基于Cookie和Seesion的认证模式
HTTP是无状态协议,一次请求结束之后,下次在发送请求,服务器就不知道是谁发来的,这里需要注意的是,同一个IP不代表同一个用户,在Web应用中,用户的认证和鉴权非常重要。
接下来先讲讲Cookie Session模式。
流程图很明显了,但是存在一定的问题。
服务端需要存储session,并且由于Sesion需要经常快速查找,通常存储在内存或内存数据库中,同时在线用户较多时需要占用大量的服务器资源。
当需要扩展时,创建Session的服务器可能不是验证Session的服务器,所以还需要将所有Session单独存储并共享。
由于客户端使用Cookie存储SessionID,在跨域场景下需要进行兼容性处理,同时这种方式也难以防范CSRF攻击。
这个时候可以通过Token进行处理,比如下面的流程实例。
基于Token的无状态会话管理方式,就是服务端可以不再存储信息,甚至是不存储session。客户端来保存token,然后访问需要认证的接口时在URL参数或者HTTP请求的Header头部中加入Token,服务端就可以通过解码Token进行授权了,然后返回给客户端需要的数据。
五、分布式ID生成与雪花算法
主要就是四个特点,然后后面对系统分库分表了之后,也可以以时间顺序对消息进行排序。
然后就是雪花算法。
在项目中我们用了索尼的雪花算法,思路和索尼算法不同,但是位的分配上稍有不同。
所以其实现代码比较简单,就如上面流程梳理的图所示。