这是我参与「第五届青训营」伴学笔记创作活动的第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)
}