微服務從程式碼到k8s部署應有盡有系列(四、使用者中心)

語言: CN / TW / HK

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

微信交流群

關注『微服務實踐』公眾號並點選 交流群 獲取社群群二維碼。