6

微服务从代码到k8s部署应有尽有系列(四、用户中心)

 2 years ago
source link: https://my.oschina.net/kevwan/blog/5438193
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

我们用一个系列来讲解从需求到上线、从代码到k8s部署、从日志到监控等各个方面的微服务完整实践。

整个项目使用了go-zero开发的微服务,基本包含了go-zero以及相关go-zero作者开发的一些中间件,所用到的技术栈基本是go-zero项目组的自研组件,基本是go-zero全家桶了。

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

一、用户中心业务架构图

二、依赖关系

usercenter-api(用户中心api) 依赖 identity-rpc(授权认证rpc)、usercenter-rpc(用户中心rpc)

usercenter-rpc(用户中心rpc)依赖 identity-rpc(授权中心rpc)

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

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

三、注册举例

1、注册api服务

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

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

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

b、在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"`
	}
)

c、goctl生成api代码

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

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

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

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

这里就很容易了,直接调用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目录)

    $  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写逻辑代码

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

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

    在logic中直接使用

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

    在usercenter-rpc注册成功之后,需要请求token给前端登陆,直接请求identity-rpc颁发该用户的token

    identity-rpc中如下

    message GenerateTokenReq {
      int64 userId = 1;
    }
    message GenerateTokenResp {
      string accessToken = 1;
      int64  accessExpire = 2;
      int64  refreshAfter = 3;
    }
    
    
    service identity{
      //生成token,只针对用户服务开放访问
      rpc generateToken(GenerateTokenReq) returns(GenerateTokenResp);
      .....
    }
    

    generatetokenlogic.go

    // GenerateToken 生成token,只针对用户服务开放访问.
    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)
    	}
    
    	//存入redis
    	userTokenKey := fmt.Sprintf(globalkey.CacheUserTokenKey, in.UserId)
    	err = l.svcCtx.RedisClient.Setex(userTokenKey, accessToken, int(accessExpire))
    	if err != nil {
    		return nil, errors.Wrapf(ErrGenerateTokenError, "SetnxEx err userId:%d, err:%v", in.UserId, err)
    	}
    
    	return &pb.GenerateTokenResp{
    		AccessToken:  accessToken,
    		AccessExpire: now + accessExpire,
    		RefreshAfter: now + accessExpire/2,
    	}, nil
    }
    

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

四、业务获取登陆用户id

当我们在获取用户信息,或者下单等场景下总要获取登陆用户的id,前一篇我们讲到,我们在授权identity服务中校验完token,解析出来的userId会放到header中返回给nginx的authReuest

在文件app/identity/cmd/api/internal/handler/verify/tokenHandler.go

nginx通过authRequest然后访问后端的服务时候,会把header内容传递给后端服务,因为我们在nginx中配置了如下

那这样的话,我们在后端服务就可以拿到这个userId了,比如我们现在访问usercenter/v1/user/detail获取当前登陆用户信息

ok,可以看到我们通过 ctxdata.GetUidFromCtx(l.ctx)就可以拿到,为什么这么神奇呢?我们点开看看这个方法

实际上就是从ctx中拿到的userId,是不是很奇怪,我们明明在nignx就放在了header中,你在go的业务代码中为什么能通过ctx拿到?

1、【小技巧】middleware

当nginx在header中携带了x-user就是userId来访问后端服务的时候,我们后端服务在启动时main函数会加载一个全局中间件,比如usercenter-api中的main

app/usercenter/cmd/api/usercenter.go

这里定义了全局中间件,只要有请求到我们usercenter-ap某个方法之前,都会先进入全局中间件中,中间件具体内容如下

所以是不是一下就明白了,在请求我们usercenter/v1/user/detail时候,会先进入这个中间件,在这个中间件内,我们通过nginx的header中的X-User拿到解析后的userId放到ctx中,那继续进入到usercenter/v1/user/detail时候,我们是不是就可以通过ctx直接取出来在业务中用啦,一切真相大白。

同样其他用户中心服务登陆、获取登陆用户信息、小程序授权登陆都是一个道理,这里就不再啰嗦了,自行看代码即可

【注】小程序授权登陆,记得修改配置文件,这里的配置文件是假的,改成自己的

https://github.com/zeromicro/go-zero

https://gitee.com/kevwan/go-zero

欢迎使用 go-zerostar 支持我们!

微信交流群

关注『微服务实践』公众号并点击 交流群 获取社区群二维码。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK