前言
实现一个web服务并不是想象的那么难,但我们需要清楚一些消息在后端是如何传递的。今天为大家讲解一个简单的注册服务后端是如何实现的。希望对大家有所帮助。话不多讲,开始表演。
且停下思考两分钟
注册服务日常可见,一个网站需要用户来支撑。想一想随便一个网站他们的注册界面是怎么样的。用户名,密码当然还有用于防止恶意程序的验证码。但最终后端注册服务需要的数据就是用户名(username)、密码(password)。对于我们的http中的报文,我们有http框架Hertz为我们处理http报文中的数据。而对于报文中的数据我们可以GORM将我们的数据映射到我们的数据库。思路基本清晰,开始实现。
IDL定义
参数的定义
message douyin_user_register_request {
string username = 1[(api.form)="username", (api.body)="username", (api.vd)="len($)>0&&len($)<30"]; // 注册用户名,最长32个字符
string password = 2[(api.form)="password", (api.body)="password", (api.vd)="len($)>0&&len($)<30"]; // 密码,最长32个字符
}
message douyin_user_register_response {
int32 status_code = 1; // 状态码,0-成功,其他值-失败
optional string status_msg = 2; // 返回状态描述
int64 user_id = 3; // 用户id
string token = 4; // 用户鉴权token
}
这里将定义的参数与http请求体里的参数进行绑定,可以省去重新调用方法PostForm手动获取参数的麻烦。
你可以参考 这里 了解更多关于注解的信息。
方法的定义
service UserHandler {
rpc UserRegister(douyin_user_register_request) returns(douyin_user_register_response){
option (api.post) = "douyin/user/register";
//注册成功返回id 和token
}
}
我们使用 POST 注解定义了 POST 方法和路由,hz 将根据定义的路由生成对应的路由组,Handler 框架以及中间件框架等。
hz 生成代码
生成
注example.com/m换成当前项目模块名,idl/user.proto是为当前相对文件路径
hz new -module example.com/m -I idl -idl idl/user.proto
整理 & 拉取依赖
go mod tidy
更新
hz update -idl idl/user.proto
生成文件结构如下
hertz_gorm
├── biz
| ├── dal // Logic code that interacts with the database
│ ├── handler // Main logical code that handles HTTP requests
| ├── model // Go struct corresponding to the database table
| ├── router // Middleware and mapping of routes to handlers
├── go.mod // go.mod
├── idl // thift idl
├── main.go // Initialize and start the server
├── router.go // Sample route registration
├── router_gen.go // Route registration
├── docker-compose.yml // docker-compose.yml
├── Makefile // Makefile
GORM操作数据库
配置GORM
要在数据库中使用 GORM,你首先需要使用驱动程序连接数据库并配置 GORM,如 biz/dal/mysql/init.go 所示。
// biz/dal/mysql/init.go
package mysql
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
var dsn = "root:123456@tcp(127.0.0.1:3306)/go_db"
var DB *gorm.DB
func Init() {
var err error
DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
SkipDefaultTransaction: true,
PrepareStmt: true,
Logger: logger.Default.LogMode(logger.Info),
})
if err != nil {
panic(err)
}
}
这里我们通过 DSN 连接 MySQL 数据库,并维护一个全局数据库操作对象 DB。
在GORM配置方面,由于本项目不涉及同时操作多张表,我们可以将 SkipDefaultTransaction 配置为 true 来跳过默认事务,并通过PrepareStmt 启用缓存以提高效率。
我们还使用了默认的 logger,以便我们可以清楚地看到 GORM 为我们生成的 SQL。
操作MySQL
//biz/dal/mysql/user.go
package mysql
type User struct {
ID int64 `json:"id"`
UserName string `json:"user_name"`
Password string `json:"password"`
}
func (User) TableName() string {
return "users"
}
func CreateUser(user *User) (int64, error) {
err := DB.Create(&user).Error
if err != nil {
return 0, err
}
return user.ID, nil
}
先创建User结构体,用于接收传递的参数。这里结构体里的参数可以增加更多用户的属性,这里只为了简化实例。
在CreateUser方法里使用DB对象的Create方法将user记录插入到数据库中。
HTTP请求处理
//biz/handler/usercode/user_handler.go
// UserRegister .
// @router douyin/user/register [POST]
func UserRegister(ctx context.Context, c *app.RequestContext) {
var err error
var user_id int64
var req usercode.DouyinUserRegisterRequest
err = c.BindAndValidate(&req)
if err != nil {
s := err.Error()
c.String(consts.StatusBadRequest, err.Error())
c.JSON(consts.StatusBadRequest, &usercode.DouyinUserRegisterResponse{
StatusCode: consts.StatusBadRequest,
StatusMsg: &s,
})
return
}
user_id, err = mysql.CreateUser(&mysql.User{
UserName: req.Username,
Password: req.Password,
})
if err != nil {
s := err.Error()
c.JSON(consts.StatusOK, &usercode.DouyinUserRegisterResponse{
StatusCode: consts.StatusBadRequest,
StatusMsg: &s,
})
return
}
resp := new(usercode.DouyinUserRegisterResponse)
resp.UserId = user_id
resp.StatusCode = 0
statusMsg := "注册成功"
resp.StatusMsg = &statusMsg
c.JSON(consts.StatusOK, resp)
}
由于我们在 protobuf IDL中使用了api 注解,BindAndValidate 将为我们完成参数绑定和验证。所有有效的参数都会被注入到DouyinUserRegisterRequest中,这非常方便。
如果出现错误,我们可以使用 JSON 方法以 JSON 格式返回数据。无论是 DouyinUserRegisterResponse 还是业务代码,我们都可以直接使用hz生成的代码。
之后,我们可以通过在 dal层调用CreateUser来插入一个新用户到 MySQL 中,传入封装好的参数。
如果出现错误,就像一开始那样,返回包含错误代码和信息的JSON。否则,返回正确的服务代码,表示成功创建了用户。
运行
go build miniTikTok
./miniTikTok.exe
结果
请求
响应
结言
hertz官方文档有许多有意思的案例,感兴趣的小伙伴可以去看一看。