GoFrame 框架設(shè)計-Context 業(yè)務(wù)流程共享變量

2022-03-28 16:55 更新

?Context?指的是標準庫的?context.Context?,是一個接口對象,常用于異步?IO?控制以及上下文流程變量的傳遞。本文將要介紹的,是如何使用?Context?傳遞流程間共享變量。

在Go的執(zhí)行流程中,特別是?HTTP/RPC?執(zhí)行流程中,不存在”全局變量”獲取請求參數(shù)的方式,只有將上下文?Context?變量傳遞到后續(xù)流程的方法中,而?Context?上下文變量即包含了所有需要傳遞的共享變量。并且該?Context?中的共享變量應當是事先約定的,并且往往存儲為對象指針形式。

通過?Context?上下文共享變量非常簡單,以下我們通過一個項目中的示例來展示如何在實戰(zhàn)化項目中傳遞和使用通用的共享變量。

一、結(jié)構(gòu)定義

上下文對象中往往存儲一些需要共享的變量,這些變量通常使用結(jié)構(gòu)化的對象來存儲,以方便維護。例如,我們在?model?定義一個上下文中的共享變量:

const (
	// 上下文變量存儲鍵名,前后端系統(tǒng)共享
	ContextKey = "ContextKey"
)

// 請求上下文結(jié)構(gòu)
type Context struct {
	Session *ghttp.Session // 當前Session管理對象
	User    *ContextUser   // 上下文用戶信息
	Data    g.Map          // 自定KV變量,業(yè)務(wù)模塊根據(jù)需要設(shè)置,不固定
}

// 請求上下文中的用戶信息
type ContextUser struct {
	Id       uint   // 用戶ID
	Passport string // 用戶賬號
	Nickname string // 用戶名稱
	Avatar   string // 用戶頭像
}

其中:

  1. ?model.ContextKey?常量表示存儲在?context.Context?上下文變量中的鍵名,該鍵名用于從傳遞的?context.Context?變量中存儲/獲取業(yè)務(wù)自定義的共享變量。
  2. ?model.Context?結(jié)構(gòu)體中的?Session?表示當前請求的?Session?對象,在?GoFrame?框架中每個?HTTP?請求對象中都會有一個空的?Session?對象,該對象采用了懶初始化設(shè)計,只有在真正執(zhí)行讀寫操作時才會初始化。
  3. ?model.Context?結(jié)構(gòu)體中的?User?表示當前登錄的用戶基本信息,只有在用戶登錄后才有數(shù)據(jù),否則是?nil?。
  4. ?model.Context?結(jié)構(gòu)體中的?Data?屬性用于存儲自定義的?KV?變量,因此一般來說開發(fā)者無需再往?context.Context?上下文變量中增加自定義的鍵值對,而是直接使用?model.Context?對象的這個?Data?屬性即可。詳見后續(xù)介紹。

二、邏輯封裝

由于該上下文對象也是和業(yè)務(wù)邏輯相關(guān)的,因此我們需要通過?service?對象將上下文變量封裝起來以方便其他模塊使用。

// 上下文管理服務(wù)
var Context = new(contextService)

type contextService struct{}

// 初始化上下文對象指針到上下文對象中,以便后續(xù)的請求流程中可以修改。
func (s *contextService) Init(r *ghttp.Request, customCtx *model.Context) {
	r.SetCtxVar(model.ContextKey, customCtx)
}

// 獲得上下文變量,如果沒有設(shè)置,那么返回nil
func (s *contextService) Get(ctx context.Context) *model.Context {
	value := ctx.Value(model.ContextKey)
	if value == nil {
		return nil
	}
	if localCtx, ok := value.(*model.Context); ok {
		return localCtx
	}
	return nil
}

// 將上下文信息設(shè)置到上下文請求中,注意是完整覆蓋
func (s *contextService) SetUser(ctx context.Context, ctxUser *model.ContextUser) {
	s.Get(ctx).User = ctxUser
}

三、上下文變量注入

上下文的變量必須在請求一開始便注入到請求流程中,以便于其他方法調(diào)用。在?HTTP?請求中我們可以使用?GoFrame?的中間件來實現(xiàn)。在?GRPC?請求中我們也可以使用攔截器來實現(xiàn)。在?service?層的?middleware?管理對象中,我們可以這樣來定義:

// 自定義上下文對象
func (s *middlewareService) Ctx(r *ghttp.Request) {
	// 初始化,務(wù)必最開始執(zhí)行
	customCtx := &model.Context{
		Session: r.Session,
		Data:    make(g.Map),
	}
	service.Context.Init(r, customCtx)
	if userEntity := Session.GetUser(r.Context()); userEntity != nil {
		customCtx.User = &model.ContextUser{
			Id:       userEntity.Id,
			Passport: userEntity.Passport,
			Nickname: userEntity.Nickname,
			Avatar:   userEntity.Avatar,
		}
	}
	// 將自定義的上下文對象傳遞到模板變量中使用
	r.Assigns(g.Map{
		"Context": customCtx,
	})
	// 執(zhí)行下一步請求邏輯
	r.Middleware.Next()
}

該中間件初始化了用戶執(zhí)行流程共享的對象,并且存儲到?context.Context?變量中的對象是指針類型?*model.Context?。這樣任何一個地方獲取到這個指針,既可以獲取到里面的數(shù)據(jù),也能夠直接修改里面的數(shù)據(jù)。

其中,如果?Session?中存在用戶登錄后的存儲信息,那么也會將需要共享的用戶基本信息寫入到?*model.Context?中。

四、上下文變量使用

方法定義

約定俗成的,方法定義的第一個輸入?yún)?shù)往往預留給?context.Context?類型參數(shù)使用,以便接受上下文變量,特別是?service?層的方法。例如:

// 執(zhí)行用戶登錄
func (s *userService) Login(ctx context.Context, loginReq *define.UserServiceLoginReq) error {
    ...
}

// 查詢內(nèi)容列表
func (s *contentService) GetList(ctx context.Context, r *define.ContentServiceGetListReq) (*define.ContentServiceGetListRes, error) {
    ...
}

// 創(chuàng)建回復內(nèi)容
func (s *replyService) Create(ctx context.Context, r *define.ReplyServiceCreateReq) error {
    ...
}

此外,約定俗成的,方法的最后一個返回參數(shù)往往是?error?類型。如果您確定此方法內(nèi)部永不會產(chǎn)生?error?,那么可以忽略。

Context對象獲取

通過?service?中封裝的以下方法,將?context.Context?上下文變量傳遞進去即可。?context.Context?上下文變量在?GoFrame?框架的?HTTP?請求中可以通過?r.Context()?方法獲取,在?GRPC?請求中,編譯生成的?pb?文件中執(zhí)行方法的第一個參數(shù)即固定是?context.Context?。

service.Context.Get(ctx)

自定義Key-Value

通過以下方式設(shè)置/獲取自定義的?key-value?鍵值對。

// 設(shè)置自定義鍵值對
service.Context.Get(ctx).Data[key] = value

...

// 獲取自定義鍵值對
service.Context.Get(ctx).Data[key]

五、注意事項

上下文變量只傳遞必須的鏈路參數(shù)數(shù)據(jù),不要什么參數(shù)都往里面塞。特別是一些方法參數(shù)傳參的數(shù)據(jù),別往里面塞,而應當顯示傳遞方法參數(shù)。

上下文變量僅用作運行時臨時使用,不可持久化存儲長期使用。例如將?ctx?序列化后存儲到數(shù)據(jù)庫,并再下一次請求中讀取出來反序列化使用是錯誤做法。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號