青训营开发问题及解决方法、设计思路(三)

261 阅读4分钟

这是我参与「第五届青训营」伴学笔记创作活动的第16天

本文记录在青训营大项目开发的过程中遇到了一些问题和BUG,方便以后自己查阅以及其他网友参考,第二部分介绍部分服务的设计思路。

一、问题和BUG

1、go build github.com/chenzhuoyu/iasm/x86_64: /usr/local/go/pkg/tool/linux_amd

  • 原因:内存不够
  • 解决方法:虚拟机则关机加内存到4G,真实物理机则重启试试。

2、/usr/local/go/pkg/tool/linux_amd64/link: fingerprint mismatch: 11xxxxxx/11xxxxxxx has fea4f7c1601b679e51, import from 22xxxxxxx/22xxxxxxx expecting 3d868e08f8c60f7d7b3e18

  • 原因:由于module中import的版本不一致导致的。
  • 解决方法:找到依赖的自定义module库(22xxxxxxx/22xxxxxxx)的目录,执行 go mod verify 解决

二、 用户注册和登录设计思路

1、数据库层

数据库层使用gorm库和mysql数据库。

我们首先定义User数据模型,数据模型对应数据库的表和column(gorm库中如果没有设置表名TableName() 函数的话,结构体名的复数等于表名,字段名等于column名)。

type User struct {
   gorm.Model
   Username      string `json:"username"`
   Password      string `json:"password"`
   FollowCount   int64  `json:"follow_count"`
   FollowerCount int64  `json:"follower_count"`
   //Deleted        gorm.DeletedAt
}

定义UserInfoResp结构体,用于接收查询用户的返回结果,由于查询限制字段和存入字段不一致,因此才会另外声明一个结构体。

type UserInfoResp struct {
   FollowCount   int64  `json:"follow_count"`   // 关注总数
   FollowerCount int64  `json:"follower_count"` // 粉丝总数
   ID            int64  `json:"id"`             // 用户id,和db.user不同
   IsFollow      bool   `json:"is_follow"`      // true-已关注,false-未关注
   Username      string `json:"name"`           // 用户名称,需要改成name字段来返回,与user结构体不同,
}

查询用户信息 by userName

func QueryUser(ctx context.Context, userName string) ([]*User, error) {
   res := make([]*User, 0)
   if err := DB.WithContext(ctx).Where("username = ?", userName).Find(&res).Error; err != nil {
      return nil, err
   }
   return res, nil
}

查询用户信息 by userIDs。我们设置了一个灵活的查询方法,这里我们可以批量传入userID查询多个用户,也可以单个查询。

func MGetUsers(ctx context.Context, userIDs []int64) ([]*User, error) {
   res := make([]*User, 0)
   if len(userIDs) == 0 {
      return res, nil
   }

   if err := DB.WithContext(ctx).Where("id in ?", userIDs).Find(&res).Error; err != nil {
      return nil, err
   }
   return res, nil
}

2、User服务层

(1、用户注册

首先我们通过请求的username查询是否有该用户,如果存在用户的话,则返回用户已存在错误UserAlreadyExistErr。然后如果没有的话,我们通过一些哈希算法,加密用户请求的密码req.Password,然后调用数据库的创建用户函数。然后再用jwt中间键生成token,返回用户id和token。

func (s *CreateUserService) CreateUser(req *user.UserRegisterRequest) (int64, string, error) {
   users, err := db.QueryUser(s.ctx, req.Username)
   var token string
   if err != nil {
      return 0, "", err
   }
   if len(users) != 0 {
      return 0, "", erren.UserAlreadyExistErr
   }
   h := hash.New()
   if _, err = io.WriteString(h, req.Password); err != nil {
      return 0, "", err
   }
   password := fmt.Sprintf("%x", h.Sum(nil))
   userid, err := db.CreateUser(s.ctx, []*db.User{{
      Username: req.Username,
      Password: password,
   }})
   if err != nil {
      return 0, "", err
   }
   if token, err = middleware.GenToken(req.Username, int64(userid)); err != nil {
      return 0, "", err
   }
   return int64(userid), token, nil
}

(2、查询用户信息

首先我们调用数据库的查询用户函数,通过传入的userID来查询是否有该用户,如果没有,则返回用户不存在错误UserNotExistErr。如果有,则继续解析token。然后通过token和对方的userID来查询是否已关注该用户。E然后再通过结构体传参,来把是否关注isFollow参数传入结构体然后返回。

func (s *UserService) User(req *user.UserRequest) (*user.User, error) {
   users, err := db.MGetUsers(s.ctx, []int64{req.UserId})
   if err != nil {
      return nil, err
   }
   if len(users) == 0 {
      return nil, erren.UserNotExistErr
   }
   claims, err := middleware.ParseToken(req.Token)
   if err != nil {
      return nil, err
   }
   isFollow, err := db.QueryIsFollow(s.ctx, claims.ID, req.UserId)
   if err != nil {
      return nil, err
   }
   u := user.User{Id: int64(users[0].ID), FollowCount: users[0].FollowCount, FollowerCount: users[0].FollowerCount, IsFollow: isFollow, Name: users[0].Username}
  
   return &u, nil
}

3、API层

(1、 User 查询用户信息

首先我们使用c中的c.ShouldBind把请求中的UserId、Token请求参数绑定到结构体中 (这里注意,c.ShouldBind或bind有个关键,要在结构体的字段的tag打上标签uri:和form:不然会解析不到,详见juejin.cn/post/719928… 第三章)。然后判断userID和Token的有效性。然后创建上下文ctx。调用rpc模块传递上下文和请求到user服务中。等待响应resp回来之后再调用发送响应函数SendResponse传递到路由响应中。

func User(c *gin.Context) {
   var request user.UserRequest
   if err := c.ShouldBind(&request); err != nil {
      BadResponse(c, err)
      return
   }

   if request.UserId <= 0 || len(request.Token) == 0 {
      BadResponse(c, erren.ParamErr)
      return
   }

   // gin 貌似没有配套上下文参数,暂时手动创建
   ctx := context.Background()
   resp, err := rpc.User(ctx, &request)
   if err != nil {
      BadResponse(c, erren.ConvertErr(err))
      return
   }
   SendResponse(c, resp)
}

(2、 用户注册 首先我们使用c中的c.ShouldBind把请求中的Username、Password请求参数绑定到结构体中。然后判断Username、Password的有效性。然后创建上下文ctx。调用rpc模块传递上下文和请求到LoginUser服务中。等待响应resp回来之后再调用发送响应函数SendResponse传递到路由响应中。。

func LoginUser(c *gin.Context) {
   var request user.UserLoginRequest
   if err := c.ShouldBind(&request); err != nil {
      BadResponse(c, err)
      return
   }

   if len(request.Username) == 0 || len(request.Password) == 0 {
      BadResponse(c, erren.ParamErr)
      return
   }

   // gin 貌似没有配套上下文参数,暂时手动创建
   ctx := context.Background()
   resp, err := rpc.LoginUser(ctx, &request)
   if err != nil {
      BadResponse(c, erren.ConvertErr(err))
      return
   }
   SendResponse(c, resp)
}