微服務從代碼到k8s部署應有盡有系列(四、用戶中心)

我們用一個系列來講解從需求到上線、從代碼到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 支持我們!

微信交流羣

關注『微服務實踐』公衆號並點擊 交流羣 獲取社區羣二維碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章