本项目地址 : https://github.com/Mikaelemmmm/go-zero-looklook
usercenter-api(用户中心api)、usercenter-rpc(用户中心rpc)
我们看项目usercenter/cmd/api/desc/usercenter.api ,所有的用户api对外的http方法都在这里面
这里面有4个业务注册、登陆、获取用户信息、微信小程序授权
我们在写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文件
这里就很容易了,直接调用user的rpc服务即可
这里有个小技巧,很多同学感觉rpc服务返回的字段跟api定义差不多,每次都要手动去复制很麻烦,那么go有没有像java一样的BeanCopyUtils.copy 这种工具呢?答案肯定是有的,可以看上面的代码copier.Copy ,这个库是gorm作者的另一款新作,是不是很兴奋。 那我们继续看看调用后端的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
在logic中直接使用
由于项目支持小程序、手机号,小程序注册不需要密码,所以在处理密码时候做了个处理,手机号注册就要传递密码,小程序注册就不需要传递密码,至于手机号注册密码不能为空要在手机号注册时候的api服务自己判断
在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服务
在文件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)就可以拿到