gozero 实战 User Login
在上一篇 《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》
 
 
                     
                     
                    