在上一篇 《go-zero 实战 - User API Gateway》 中,我们通过定义 user.api
文件中的内容对 API 类型进行了声明,并通过 goctl api 命令,一键快速生成一个 api
服务,如果仅仅是启动一个 go-zero
的 api
演示项目,你甚至都不用编码,就可以完成一个 api
服务开发及正常运行。
本篇将在上一篇的基础上完成以下工作内容:
user
,并定义表结构,以便存储用户基本信息;创建本地数据库表 user
,并定义表结构
在本机 mysql
中创建 foodguides
数据库,并新建 user
表。
CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户Id',
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名称',
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户密码',
`email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户邮箱',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `name_index` (`name`),
UNIQUE KEY `email_index` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
执行如下 SQL
语句新增一条数据:
INSERT INTO `foodguides`.`user`(`id`, `name`, `password`, `email`) VALUES (1, 'RenPanPan', '809161', 'renpanpan1990@163.com');
使用 goctl model 生成 mysql CRUD 代码
在 usermanage
下创建 model
文件夹。
$ mkdir -p usermanage/model
$ cd usermanage/model
在 model
下新建 user.sql
文件,将上述创建 user
表的 SQL 语句复制到 user.sql
文件中。
在 model
目录下执行如下命令生成 CRUD
代码:
$ goctl model mysql ddl --src user.sql --dir .
当你看到 Done.
输出则代表生成成功了,接下来我们来看一下生成的代码内容:
➜ model:
# 列出当前目录下的文件
$ ls
user.sql usermodel.go usermodel_gen.go vars.go
# 查看目录树
$ tree
.
├── user.sql
├── usermodel.go // CRUD 代码
├── usermodel_gen.go
└── vars.go // 定义常量和变量
完成用户登录接口的逻辑
编辑 usermanage/api/etc
下的 user-api.yaml
文件,追加如下内容:
DataSource: root:123456@tcp(127.0.0.1:9528)/foodguides?&parseTime=True&loc=Local
AccessSecret: ad879037-d3fd-tghj-112d-6bfc35d54b7d
AccessExpire: 86400
Tips
如何配置 DataSource?
编辑 usermanage/api/internal/config
下的 config.go
文件,新增 DataSource
, AccessSecret
, AccessExpire
三个变量:
type Config struct {
rest.RestConf
DataSource string
AccessSecret string
AccessExpire int64
}
编辑 usermanage/api/internal/svc
下的 serviceContext.go
文件,新增 Model
变量 ,新增实例化代码:
type ServiceContext struct {
Config config.Config
Model model.UserModel
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
Model: model.NewUserModel(sqlx.NewMysql(c.DataSource)),
}
}
编辑 usermanage/api/internal/logic
下的 loginlogic.go
文件, 修改 Login
方法如下所示:
func (l *LoginLogic) Login(req *types.LoginRequest) (*types.LoginResponse, error) {
res, err := l.svcCtx.Model.FindOneByEmail(l.ctx, req.Email)
if err == nil {
if req.Password == res.Password {
now := time.Now().Unix()
accessExpire := l.svcCtx.Config.AccessExpire
jwtToken, err := renpanpan.GetJwtToken(l.svcCtx.Config.AccessSecret, now, accessExpire, strconv.FormatInt(res.Id, 10))
if err != nil {
return nil, err
}
token := types.JwtToken{
AccessToken: jwtToken,
AccessExpire: now + accessExpire,
RefreshAfter: now + accessExpire/2,
}
response := types.UserReply{
Id: res.Id,
Username: res.Name,
Email: res.Email,
JwtToken: token,
}
return &types.LoginResponse{UserReply: response}, nil
} else {
return nil, errors.New("密码错误")
}
}
return nil, err
}
为了简单起见,在 Login
方法中,我只是简单比较了数据库存储的密码是否和请求传的密码参数是否相等(真正的项目此处逻辑应该包含密码加密验证的等相关代码),当两者相等时,即认为用户登录成功,我们将为用户生成一个 token
返回给客户端。
注意:上面的代码可能会因为找不到 renpanpan
类型的 GetJwtToken
方法而报错,我们将在接下来的修改 Response
返回格式中实现它,GetJwtToken
方法的实现可参考 官网示例。
修改 Response
返回格式
我希望客户端接口请求数据的返回格式是这样子的:
{
"code": 1,
"msg": "",
"data": {
"id": 1,
"username": "",
"email": "renpanpan1990@163.com.com",
"accessToken": "user-login-token",
"accessExpire": 1611470394,
"refreshAfter": 1611427194
}
}
目前如果需要实现这种格式响应,有 2 种做法:
go-zero
扩展包来实现我们用做法 1 来演示一下,做法 2 可参考官方文档的演示实例 HTTP 扩展。
在 Foodguides
文件夹下新增名为 renpanpan
文件夹,并创建 renpanpan.go
文件,新增如下代码:
package renpanpan
import "github.com/golang-jwt/jwt/v4"
type HttpResponse struct {
Code int `json:"code"`
Message string `json:"msg"`
Data interface{} `json:"result"`
}
func SuccessResponse(resData interface{}, message string) HttpResponse {
return HttpResponse{Code: 1, Message: message, Data: resData}
}
func FailureResponse(resData interface{}, message string, code int) HttpResponse {
return HttpResponse{Code: code, Message: message, Data: resData}
}
// GetJwtToken
// @secretKey: JWT 加解密密钥
// @iat: 时间戳
// @seconds: 过期时间,单位秒
// @payload: 数据载体
func GetJwtToken(secretKey string, iat, seconds int64, payload string) (string, error) {
claims := make(jwt.MapClaims)
claims["exp"] = iat + seconds
claims["iat"] = iat
claims["payload"] = payload
token := jwt.New(jwt.SigningMethodHS256)
token.Claims = claims
return token.SignedString([]byte(secretKey))
}
在 usermanage/api/internal/handler
下的 loginhandler.go
文件中,对 LoginHandler
方法做如下修改:
func LoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.LoginRequest
if err := httpx.Parse(r, &req); err != nil {
// httpx.ErrorCtx(r.Context(), w, err)
httpx.OkJson(w, renpanpan.FailureResponse(nil, err.Error(), 1000))
return
}
l := logic.NewLoginLogic(r.Context(), svcCtx)
resp, err := l.Login(&req)
if err != nil {
// httpx.ErrorCtx(r.Context(), w, err)
httpx.OkJson(w, renpanpan.FailureResponse(nil, err.Error(), 1000))
} else {
// httpx.OkJsonCtx(r.Context(), w, resp)
httpx.OkJson(w, renpanpan.SuccessResponse(resp, "登录成功"))
}
}
}
启动服务
运行如下命令以启动 user api
服务, 运行成功后,user api
则运行在本机的 8888
端口:
➜ FoodGuides:
$ go run usermanage/api/user.go -f usermanage/api/etc/user-api.yaml
Starting server at 0.0.0.0:8888...
我们用 Postman
尝试请求 /users/login
接口:
Postman
的 Body
选项中选择 raw
,并设置 JSON
内容为 {"email": "renpanpan1990@163.com","password": "809161"}
。至此,我们就完成了用户登录接口的搭建,下一篇我们将采用相同的方法搭建用户注册接口。
上一篇《go-zero 实战 - User API Gateway》
下一篇《go-zero 实战 - User Register》