优雅使用GoFrame共享变量Context示例详解

目录
  • 前言摘要
  • Context是什么?
    • 为什么需要Context?
    • Context是如何实现共享变量的?
    • 如何使用?
  • 一、结构定义
    • 介绍
  • 二、逻辑封装
  • 三、上下文变量注入
  • 四、上下文变量使用
    • 方法定义
    • Context对象获取
    • 自定义Key-Value
  • 五、注意问题
  • 总结

前言摘要

昨天和同事merge代码又遇到了很多冲突,发现之前有些方法写的参数不规范,没有传入Context,不方便进行链路追踪。他在review项目代码,基本把项目中的方法都加了Context参数。

今天就为大家介绍一下Context的使用,告诉大家Context是什么?怎么用?为什么要用Context以及使用中的小技巧和注意问题。

Context是什么?

Context指的是标准库的context.Context,是一个接口对象,常用于异步IO控制以及上下文流程变量的传递。

本文将要介绍的是Context如何优雅的在业务流程中进行变量的传递,以及为什么需要要进行变量的传递。

为什么需要Context?

Go的执行流程中,特别是HTTP/RPC执行流程中,不存在”全局变量”获取请求参数的方式,只有通过上下文Context变量传递到后续流程的方法中。

Context是如何实现共享变量的?

Context上下文变量即包含了所有需要传递的共享变量。

并且该Context中的共享变量是事先约定的,并且往往存储为对象指针形式。

如何使用?

通过Context上下文共享变量非常简单,下面通过示例带大家了解一下如何传递和使用通用的共享变量。

一、结构定义

上下文对象中往往存储一些需要共享的变量,这些变量通常使用结构化的对象来存储,以方便维护。

例如,我们在model定义一个上下文中的共享变量:

const (
	// 上下文变量存储键名,前后端系统共享
	ContextKey = "ContextKey"
)
// 请求上下文结构
type Context struct {
	Session *ghttp.Session // 当前Session管理对象
	User    *ContextUser   // 上下文用户信息
	Data    g.Map          // 自定KV变量,业务模块根据需要设置,不固定
}
// 请求上下文中的用户信息
type ContextUser struct {
	Id       uint   // 用户ID
	Passport string // 用户账号
	Nickname string // 用户名称
	Avatar   string // 用户头像
}

介绍

model.ContextKey常量表示存储在context.Context上下文变量中的键名,该键名用于从传递的context.Context变量中存储/获取业务自定义的共享变量。

model.Context结构体中的Session表示当前请求的Session对象,在GoFrame框架中每个HTTP请求对象中都会有一个空的Session对象,该对象采用了懒初始化设计,只有在真正执行读写操作时才会初始化。

model.Context结构体中的User表示当前登录的用户基本信息,只有在用户登录后才有数据,否则是nil

model.Context结构体中的Data属性用于存储自定义的KV变量,因此一般来说开发者无需再往context.Context上下文变量中增加自定义的键值对,而是直接使用model.``Context对象的这个Data属性即可。

二、逻辑封装

由于该上下文对象也是和业务逻辑相关的,因此我们需要通过service对象将上下文变量封装起来以方便其他模块使用。

// 上下文管理服务
var Context = new(contextService)
type contextService struct{}
// 初始化上下文对象指针到上下文对象中,以便后续的请求流程中可以修改。
func (s *contextService) Init(r *ghttp.Request, customCtx *model.Context) {
	r.SetCtxVar(model.ContextKey, customCtx)
}
// 获得上下文变量,如果没有设置,那么返回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
}
// 将上下文信息设置到上下文请求中,注意是完整覆盖
func (s *contextService) SetUser(ctx context.Context, ctxUser *model.ContextUser) {
	s.Get(ctx).User = ctxUser
}

Tips

在架构设计中,在哪个场景下设置Context是非常关键的。

上下文的变量必须在请求一开始便注入到请求流程中,以便于其他方法调用,所以在中间件中来实现是非常优雅的选择。

我们来看下面的介绍:

三、上下文变量注入

HTTP请求中我们可以使用GoFrame的中间件来实现。

GRPC请求中我们也可以使用拦截器来实现。

service层的middleware管理对象中,我们可以这样来定义:

// 自定义上下文对象
func (s *middlewareService) Ctx(r *ghttp.Request) {
	// 初始化,务必最开始执行
	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,
	})
	// 执行下一步请求逻辑
	r.Middleware.Next()
}

这个中间件初始化了用户执行流程共享的对象,并且存储到context.Context变量中的对象是指针类型*model.Context

这样做的好处是:任何一个地方获取到这个指针,不仅可以获取到里面的数据,而且能够直接修改里面的数据。

TIPS

如果Session中存在用户登录后的存储信息,那么也会将需要共享的用户基本信息写入到*model.Context中。

四、上下文变量使用

方法定义

方法定义的第一个输入参数往往预留给context.Context类型参数使用,以便接受上下文变量,特别是service层的方法。

例如:

// 执行用户登录
func (s *userService) Login(ctx context.Context, loginReq *define.UserServiceLoginReq) error {
    ...
}
// 查询内容列表
func (s *contentService) GetList(ctx context.Context, r *define.ContentServiceGetListReq) (*define.ContentServiceGetListRes, error) {
    ...
}
// 创建回复内容
func (s *replyService) Create(ctx context.Context, r *define.ReplyServiceCreateReq) error {
    ...
}

TIPS

另外一个好习惯是:方法的最后一个返回参数往往是error类型。如果确定方法内部永不会产生error,那么可以忽略。

Context对象获取

通过service中封装的以下方法,将context.Context上下文变量传递进去即可。

context.Context上下文变量在GoFrame框架的HTTP请求中可以通过r.Context()方法获取。

GRPC请求中,编译生成的pb文件中执行方法的第一个参数即固定是context.Context

service.Context.Get(ctx)

自定义Key-Value

我们可以通过以下方式设置/获取自定义的key-value键值对。

// 设置自定义键值对
service.Context.Get(ctx).Data[key] = value
...go
// 获取自定义键值对
service.Context.Get(ctx).Data[key]

五、注意问题

  • 上下文变量只传递必须的链路参数数据,不要什么参数都往里面塞。特别是一些方法参数、传参的数据,千万不能往上下文里面塞,而应当显示的传递方法参数。
  • 上下文变量仅用作运行时临时使用,不可做持久化存储长期使用。

总结

这篇文章详细的为大家介绍了GoFrame上下文对象Context的知识点:

Context的作用:在业务流程中进行变量的共享。

Context的结构定义、逻辑封装、如何在中间件中注入、如何通过Context设置值和取值、Context如何自定义key-value、以及在项目开发中使用的注意问题,更多关于GoFrame共享变量Context的资料请关注我们其它相关文章!

(0)

相关推荐

  • GoFrame框架数据校验之校验结果Error接口对象

    目录 基本介绍 方法示例 注意问题 方法详解 进阶 注意问题 总结 上一篇文章介绍了 GoFrame数据校验之校验对象 | 校验结构体 ,得到了大家积极的反馈. 再接再厉,这篇总结分享:GoFrame数据校验之校验结果总结分享. 基本介绍 校验结果为一个Error接口对象.当数据规则校验成功时,校验方法返回的结果为nil. 当数据规则校验失败时,返回的该对象是包含结构化的层级map,包含多个字段及其规则及对应错误信息,以便于接收端能够准确定位错误规则. 方法示例 首先我们看一下Error的数据结

  • GoFrame框架gredis优雅的取值和类型转换

    目录 取值方法 不推荐 推荐 DoVar示例 ReceiveVar示例 HashSet示例 小技巧 总结 上一篇文章为大家介绍了 GoFrame gcache使用实践 | 缓存控制 淘汰策略 ,得到了大家积极的反馈. 后续几篇文章再接再厉,仍然为大家介绍GoFrame框架缓存相关的知识点,以及自己项目使用中的一些总结思考,GoFrame框架下文简称gf. 今天重点为大家介绍:GoFrame gredis 如何优雅的取值和类型转换 取值方法 不推荐 通过客户端方法Do/Receive获取的数据都是

  • GoFrame基于性能测试得知grpool使用场景

    目录 前言摘要 先说结论 测试性能代码 运行结果 总结 前言摘要 之前写了一篇 grpool goroutine池详解 | 协程管理 收到了大家积极的反馈,今天这篇来做一下grpool的性能测试分析,让大家更好的了解什么场景下使用grpool比较好. 先说结论 grpool相比于goroutine更节省内存,但是耗时更长: 原因也很简单:grpool复用了协程,减少了协程的创建和销毁,减少了内存消耗:也因为协程的复用,总的goroutine数量更少,导致耗时更多. 测试性能代码 开启for循环,

  • GoFrame框架数据校验之校验对象校验结构体

    目录 前言摘要 基本概念 方法介绍 简要说明 注意问题TIPS 链式操作 示例1:单数据校验 简单示例 进阶示例 进阶示例打印结果 示例2:Map数据校验 示例3:Struct数据校验 总结 前言摘要 这篇文章将会为大家介绍GoFrame数据校验中校验对象的知识点,包括:Validator对象常用方法的介绍.单数据校验.校验Map.校验结构体的示例. 基本概念 数据校验组件提供了数据校验对象:用于数据校验统一的配置管理,支持我们便捷的进行链式操作. 方法介绍 type Validator fun

  • GoFrame错误处理常用方法及错误码使用示例

    目录 前言 错误创建 New/Newf Wrap/Wrapf NewSkip/NewSkipf 错误码使用 错误码相关方法概览 NewCode/NewCodef 示例代码 WrapCode/WrapCodef 示例代码 NewCodeSkip/NewCodeSkipf 获取error中的错误码接口 总结 前言 这篇文章将为大家介绍:GoFrame 错误处理的常用方法&错误码的使用.如何自定义错误对象.如何忽略部分堆栈信息.如何自定义错误码的返回.如何获取error对象中的错误码. 错误创建 Ne

  • GoFrame 框架缓存查询结果的示例详解

    目录 查询缓存 相关方法: 缓存对象 缓存适配(Redis缓存) 使用示例 数据表结构 示例代码 小技巧 运行结果分析 总结 后续几篇文章再接再厉,仍然为大家介绍GoFrame框架缓存相关的知识点,以及自己项目使用中的一些总结思考,GoFrame框架下文简称gf. 今天重点为大家介绍:GoFrame 如何优雅的缓存查询结果 查询缓存 gdb支持对查询结果的缓存处理,常用于多读少写的查询缓存场景,并支持手动的缓存清理. 需要注意的是,查询缓存仅支持链式操作,且在事务操作下不可用. 相关方法: fu

  • 优雅使用GoFrame共享变量Context示例详解

    目录 前言摘要 Context是什么? 为什么需要Context? Context是如何实现共享变量的? 如何使用? 一.结构定义 介绍 二.逻辑封装 三.上下文变量注入 四.上下文变量使用 方法定义 Context对象获取 自定义Key-Value 五.注意问题 总结 前言摘要 昨天和同事merge代码又遇到了很多冲突,发现之前有些方法写的参数不规范,没有传入Context,不方便进行链路追踪.他在review项目代码,基本把项目中的方法都加了Context参数. 今天就为大家介绍一下Cont

  • Go如何优雅的使用字节池示例详解

    目录 背景 为何需要字节池 最简单的方式 测试 不预先申请空间 预先申请空间 字节数组池 测试结果 更优雅的方式 测试 直接使用Buffer bytes.Buffer池 测试结果 限制池大小 测试 固定大小字节池 测试结果 总结 总结 背景 在某些场景下,我们可能会大量的使用字节数组,比如IO操作.编解码,如果不进行优化,大量的申请和释放字节数组会造成一定的性能损耗,因此有必要复用字节数组. 为何需要字节池 在 Go 语言编程中,在从 io.Reader 中读取数据时,我们都要创建一个字节切片

  • python编程中简洁优雅的推导式示例详解

    目录 1. 列表推导式 增加条件语句 多重循环 更多用法 2. 字典推导式 3. 集合推导式 4. 元组推导式 Python语言有一种独特的推导式语法,相当于语法糖的存在,可以帮助你在某些场合写出较为精简酷炫的代码.但没有它,也不会有太多影响.Python语言有几种不同类型的推导式. 1. 列表推导式 列表推导式是一种快速生成列表的方式.其形式是用方括号括起来的一段语句,如下例子所示: lis = [x * x for x in range(1, 10)] print(lis) 输出 [1, 4

  • GoFrame实现顺序性校验示例详解

    目录 引言 基本介绍 无序的原因 顺序校验 总结 引言 在上一篇文章中 GoFrame数据校验之校验结果 | Error接口对象 ,关于顺序与非顺序性校验没有做充分的介绍. 这篇文章填上之前留的坑,我们以map校验举例: 基本介绍 我们通过上一篇文章了解到:Error接口对象的方法,其顺序性只有使用顺序校验规则时有效,否则返回的结果是随机的. 即使我们使用FirstItem, FirstString()等其他方法获取校验结果也是一样,返回的校验结果不固定. 无序的原因 因为校验的规则我们传递的是

  • nginx pod hook钩子优雅关闭示例详解

    目录 一.系统环境 二.前言 三.pod hook(pod钩子) 四.如何优雅的关闭nginx pod 一.系统环境 服务器版本 docker软件版本 Kubernetes(k8s)集群版本 CPU架构 CentOS Linux release 7.4.1708 (Core) Docker version 20.10.12 v1.21.9 x86_64 Kubernetes集群架构:k8scloude1作为master节点,k8scloude2,k8scloude3作为worker节点 服务器

  • .NET Core利用 AsyncLocal 实现共享变量的代码详解

    目录 简介 AsyncLocal 解读 总结 简介 我们如果需要整个程序共享一个变量,我们仅需将该变量放在某个静态类的静态变量上即可(不满足我们的需求,静态变量上,整个程序都是固定值).我们在Web 应用程序中,每个Web 请求服务器都为其分配了一个独立线程,如何实现用户,租户等信息隔离在这些独立线程中.这就是今天要说的线程本地存储.针对线程本地存储 .NET 给我们提供了两个类 ThreadLocal 和 AsyncLocal.我们可以通过查看以下例子清晰的看到两者的区别: [TestClas

  • Flutter之 ListView组件使用示例详解

    目录 ListView的默认构造函数定义 默认构造函数 ListView.builder ListView.separated 固定高度列表 ListView 原理 实例:无限加载列表 添加固定列表头 总结 ListView的默认构造函数定义 ListView是最常用的可滚动组件之一,它可以沿一个方向线性排布所有子组件,并且它也支持列表项懒加载(在需要时才会创建).我们看看ListView的默认构造函数定义: ListView({ ... //可滚动widget公共参数 Axis scrollD

  • react后台系统最佳实践示例详解

    目录 一.中后台系统的技术栈选型 1. 要做什么 2. 要求 3. 技术栈怎么选 二.hooks时代状态管理库的选型 context redux recoil zustand MobX 三.hooks的使用问题与解决方案 总结 一.中后台系统的技术栈选型 本文主要讲三块内容:中后台系统的技术栈选型.hooks时代状态管理库的选型以及hooks的使用问题与解决方案. 1. 要做什么 我们的目标是搭建一个适用于公司内部中后台系统的前端项目最佳实践. 2. 要求 由于业务需求比较多,一名开发人员需要负

  • kotlin android extensions 插件实现示例详解

    目录 前言 原理浅析 总体结构 源码分析 插件入口 配置编译器插件传参 编译器插件接收参数 注册各种Extension IrGenerationExtension ExpressionCodegenExtension StorageComponentContainerContributor ClassBuilderInterceptorExtension PackageFragmentProviderExtension 总结 前言 kotlin-android-extensions 插件是 Ko

  • ChatGPT编程秀之最小元素的设计示例详解

    目录 膨胀的野心与现实的窘境 新时代,新思路 总结一下 膨胀的野心与现实的窘境 上一节随着我能抓openai的列表之后,我的野心开始膨胀,既然我们写了一个框架,可以开始写面向各网站的爬虫了,为什么只面向ChatGPT呢?几乎所有的平台都是这么个模式,一个列表,然后逐个抓取.那我能不能把这个能力泛化呢?可不可以设计一套机制,让所有的抓取功能都变得很简单呢?我抽取一系列的基础能力,而不管抓哪个网站只需要复用这些能力就可以快速的开发出爬虫.公司内的各种平台都是这么想的对吧? 那么我们就需要进行设计建模

随机推荐