我們用一個系列來講解從需求到上線、從程式碼到k8s部署、從日誌到監控等各個方面的微服務完整實踐。
整個專案使用了go-zero開發的微服務,基本包含了go-zero以及相關go-zero作者開發的一些中介軟體,所用到的技術棧基本是go-zero專案組的自研元件,基本是go-zero全家桶了。
實戰專案地址:https://github.com/Mikaelemmm...
一、使用者中心業務架構圖
二、依賴關係
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-zero
並 star 支援我們!
微信交流群
關注『微服務實踐』公眾號並點選 交流群 獲取社群群二維碼。