写点什么

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

作者:万俊峰Kevin
  • 2022 年 4 月 05 日
  • 本文字数:3208 字

    阅读完需:约 11 分钟

我们用一个系列来讲解从需求到上线、从代码到 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


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

微信交流群

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

发布于: 刚刚阅读数: 2
用户头像

保持简单 2017.10.24 加入

go-zero作者

评论

发布
暂无评论
微服务从代码到k8s部署应有尽有系列(四、用户中心)_微服务_万俊峰Kevin_InfoQ写作平台