Skip to content

Latest commit

 

History

History
293 lines (195 loc) · 8.37 KB

04-用户服务.md

File metadata and controls

293 lines (195 loc) · 8.37 KB

四、用户中心服务

本项目地址 : https://github.com/Mikaelemmmm/go-zero-looklook

1、用户中心业务架构图

image-20220428105545020

2、依赖关系

usercenter-api(用户中心api)、usercenter-rpc(用户中心rpc)

我们看项目usercenter/cmd/api/desc/usercenter.api ,所有的用户api对外的http方法都在这里面

这里面有4个业务注册、登陆、获取用户信息、微信小程序授权

3、注册来举例

1、注册api服务

我们在写api服务代码的时候是先要在usercenter.api中定义好service中的方法,然后在desc/user中写request、response,这样拆分开的好处是不那么臃肿

1、在usercenter.api中定义注册方法如下

//用户模块v1版本的接口
@server(
	prefix: usercenter/v1
	group: user
)
service usercenter {
  @doc "注册"
	@handler register
	post /user/register (RegisterReq) returns (RegisterResp)
  
  .....
}

2、在app/usercenter/cmd/api/desc/user/user.api中定义RegisterReq\RegisterResp

type (
	RegisterReq {
		Mobile   string `json:"mobile"`
		Password string `json:"password"`
	}
	RegisterResp {
		AccessToken  string `json:"accessToken"`
		AccessExpire int64  `json:"accessExpire"`
		RefreshAfter int64  `json:"refreshAfter"`
	}
)

3、goctl生成api代码

1)命令行进入app/usercenter/cmd/api/desc目录下。

2)去项目目录下deploy/script/gencode/gen.sh中,复制如下一条命令,在命令行中执行(命令行要切换到app/usercenter/cmd/api/desc目录)

$ goctl api go -api *.api -dir ../  -style=goZero

4、打开app/usercenter/cmd/api/internal/logic/user/register.go文件

image-20220118123507065

这里就很容易了,直接调用user的rpc服务即可

这里有个小技巧,很多同学感觉rpc服务返回的字段跟api定义差不多,每次都要手动去复制很麻烦,那么go有没有像java一样的BeanCopyUtils.copy 这种工具呢?答案肯定是有的,可以看上面的代码copier.Copy ,这个库是gorm作者的另一款新作,是不是很兴奋。 那我们继续看看调用后端的rpc是什么样子的。

2、注册rpc服务
  • 定义protobuf文件

    我们在app/usercenter/cmd/rpc/pb中新建usercenter.proto,写入注册方法

    //req 、resp
    message RegisterReq {
      string mobile = 1;
      string nickname = 2;
      string password = 3;
      string authKey = 4;
      string authType = 5;
    }
    message RegisterResp {
      string accessToken = 1;
      int64  accessExpire = 2;
      int64  refreshAfter = 3;
    }
    
    //service
    service usercenter {
      rpc register(RegisterReq) returns(RegisterResp);
      ......
    }
  • 使用goctl生成代码,这里不需要自己手动敲

    1)命令行进入app/usercenter/cmd/rpc/pb目录下。

    2)去项目目录下deploy/script/gencode/gen.sh中,复制如下两条命令,在命令行中执行(命令行要切换到app/usercenter/cmd/rpc/pb目录)

    $  goctl rpc protoc *.proto --go_out=../ --go-grpc_out=../  --zrpc_out=../
    $  sed -i "" 's/,omitempty//g' *.pb.go
  • 打开app/usercenter/cmd/rpc/internal/logic/registerLogic.go写逻辑代码

    func (l *RegisterLogic) Register(in *usercenter.RegisterReq) (*usercenter.RegisterResp, error) {
    
    	user, err := l.svcCtx.UserModel.FindOneByMobile(l.ctx,in.Mobile)
    	if err != nil && err != model.ErrNotFound {
    		return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "mobile:%s,err:%v", in.Mobile, err)
    	}
    	if user != nil {
    		return nil, errors.Wrapf(ErrUserAlreadyRegisterError, "Register user exists mobile:%s,err:%v", in.Mobile, err)
    	}
    
    	var userId int64
    	if err := l.svcCtx.UserModel.Trans(l.ctx,func(ctx context.Context,session sqlx.Session) error {
    		user := new(model.User)
    		user.Mobile = in.Mobile
    		if len(user.Nickname) == 0 {
    			user.Nickname = tool.Krand( 8, tool.KC_RAND_KIND_ALL)
    		}
    		if len(in.Password) > 0 {
    			user.Password = tool.Md5ByString(in.Password)
    		}
    		insertResult, err := l.svcCtx.UserModel.Insert(ctx,session, user)
    		if err != nil {
    			return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "Register db user Insert err:%v,user:%+v", err, user)
    		}
    		lastId, err := insertResult.LastInsertId()
    		if err != nil {
    			return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "Register db user insertResult.LastInsertId err:%v,user:%+v", err, user)
    		}
    		userId = lastId
    
    		userAuth := new(model.UserAuth)
    		userAuth.UserId = lastId
    		userAuth.AuthKey = in.AuthKey
    		userAuth.AuthType = in.AuthType
    		if _, err := l.svcCtx.UserAuthModel.Insert(ctx,session, userAuth); err != nil {
    			return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "Register db user_auth Insert err:%v,userAuth:%v", err, userAuth)
    		}
    		return nil
    	}); err != nil {
    		return nil, err
    	}
    
    	//2、Generate the token, so that the service doesn't call rpc internally
    	generateTokenLogic :=NewGenerateTokenLogic(l.ctx,l.svcCtx)
    	tokenResp,err:=generateTokenLogic.GenerateToken(&usercenter.GenerateTokenReq{
    		UserId: userId,
    	})
    	if err != nil {
    		return nil, errors.Wrapf(ErrGenerateTokenError, "GenerateToken userId : %d", userId)
    	}
    
    	return &usercenter.RegisterResp{
    		AccessToken:  tokenResp.AccessToken,
    		AccessExpire: tokenResp.AccessExpire,
    		RefreshAfter: tokenResp.RefreshAfter,
    	}, nil
    }

    注册涉及到2张表,一个user表,一个user_auth表,user是存储用户基本信息的,user_auth是可以根据不同平台授权登陆的相关信息,所以这里涉及到本地事务,由于go-zero的事务要在model中才能使用,但是我在model中做了个处理,把它在model中暴露出来,就可以在logic中使用

    model中定义了Trans方法暴露事务给logic

    image-20220118122703035

    在logic中直接使用

    image-20220118122802624

    由于项目支持小程序、手机号,小程序注册不需要密码,所以在处理密码时候做了个处理,手机号注册就要传递密码,小程序注册就不需要传递密码,至于手机号注册密码不能为空要在手机号注册时候的api服务自己判断

    image-20220118122951910

    在usercenter-rpc注册成功之后,需要请求token给前端登陆

    	//2、Generate the token, so that the service doesn't call rpc internally
    	generateTokenLogic :=NewGenerateTokenLogic(l.ctx,l.svcCtx)
    	tokenResp,err:=generateTokenLogic.GenerateToken(&usercenter.GenerateTokenReq{
    		UserId: userId,
    	})
    	if err != nil {
    		return nil, errors.Wrapf(ErrGenerateTokenError, "GenerateToken userId : %d", userId)
    	}

    GenerateToken中如下

    func (l *GenerateTokenLogic) GenerateToken(in *pb.GenerateTokenReq) (*pb.GenerateTokenResp, error) {
    	now := time.Now().Unix()
    	accessExpire := l.svcCtx.Config.JwtAuth.AccessExpire
    	accessToken, err := l.getJwtToken(l.svcCtx.Config.JwtAuth.AccessSecret, now, accessExpire, in.UserId)
    	if err != nil {
    		return nil, errors.Wrapf(ErrGenerateTokenError, "getJwtToken err userId:%d , err:%v", in.UserId, err)
    	}
    
    	return &pb.GenerateTokenResp{
    		AccessToken:  accessToken,
    		AccessExpire: now + accessExpire,
    		RefreshAfter: now + accessExpire/2,
    	}, nil
    }
    
    func (l *GenerateTokenLogic) getJwtToken(secretKey string, iat, seconds, userId int64) (string, error) {
    
    	claims := make(jwt.MapClaims)
    	claims["exp"] = iat + seconds
    	claims["iat"] = iat
    	claims[ctxdata.CtxKeyJwtUserId] = userId
    	token := jwt.New(jwt.SigningMethodHS256)
    	token.Claims = claims
    	return token.SignedString([]byte(secretKey))
    }

    注册成功并去拿到token、token过期时间、置换token的时间给api服务

4、业务获取登陆用户id

在文件go-zero-looklook/common/ctxdata/ctxData.go

func GetUidFromCtx(ctx context.Context) int64 {
	var uid int64
	if jsonUid, ok := ctx.Value(CtxKeyJwtUserId).(json.Number); ok {
		if int64Uid, err := jsonUid.Int64(); err == nil {
			uid = int64Uid
		} else {
			logx.WithContext(ctx).Errorf("GetUidFromCtx err : %+v", err)
		}
	}
	return uid
}

ok,可以看到我们通过 ctxdata.GetUidFromCtx(l.ctx)就可以拿到