Bootstrap

goframe项目详解(基于开源项目 易理解)(四)

上一篇文章我们介绍了goframe中的 app目录下的model文件夹

goframe项目详解(基于开源项目 易理解)(三)-CSDN博客

本篇文章我们来介绍goframe项目的service文件夹和shared文件夹

在 GoFrame 项目中,

service 文件夹通常用于存放业务逻辑相关的代码。

包含了对数据的处理、业务规则的实现、与其他模块的交互协调等逻辑。

咱们说service,一般service层是干啥的

 service 层中设置菜单通常会涉及以下一些常见的操作和目的:

  1. 数据验证和处理:

    • 检查菜单数据的完整性和有效性,例如菜单名称是否合法、链接是否有效等。
    • 处理菜单数据的格式转换,以适应存储或其他模块的需求。
  2. 与数据存储交互:

    • 将菜单数据保存到数据库中,包括创建新的菜单记录、更新现有菜单的信息。
    • 从数据库中读取菜单数据,用于构建菜单结构或进行其他操作。
  3. 权限控制:

    • 根据用户的权限设置确定哪些菜单对当前用户可见或可操作。
  4. 菜单排序和组织:

    • 按照特定的规则对菜单进行排序,例如按照优先级、使用频率等。
    • 构建菜单的层次结构,处理父菜单和子菜单之间的关系。
  5. 缓存管理:

    • 将常用的菜单数据缓存起来,以提高访问性能。
  6. 与其他服务或模块集成:

    • 将菜单数据传递给前端展示模块,或者与其他相关的业务逻辑模块进行交互,以协同完成更复杂的业务功能。

总之,在 service 层中处理菜单设置是为了实现与菜单相关的各种业务逻辑,并提供统一、可靠和可维护的菜单管理功能。

shared 文件夹可能用于存放一些被多个模块或功能共享的代码、文件或资源。

例如,可能会存放一些通用的工具函数、常量、配置文件、公共的模型或数据结构等,这些可以被不同的业务模块或功能所复用。

一般来说这个share文件夹可有可无

在goframe项目中常见的目录是

/
├── app
│   ├── api
│   ├── dao
│   ├── model
│   └── service
├── boot
├── config
├── docker
├── document
├── i18n
├── library
├── packed
├── public
├── router
├── template
├── dockerfile
├── go.mod
└── main.go
  • app:业务逻辑层,存放所有业务逻辑相关的代码。
    • api:业务接口,接收/解析用户输入参数的入口/接口层。
    • dao:数据访问,包含最基础的数据库 curd 方法。
    • model:结构模型,管理数据实体对象以及输入与输出数据结构定义。
    • service:逻辑封装,实现特定的业务需求,可供不同的包调用。
  • boot初始化包,用于项目初始化参数设置。
  • config配置管理,存放所有的配置文件。
  • docker:镜像文件,存放 docker 镜像相关依赖文件、脚本文件等。
  • document:项目文档。
  • i18n:国际化配置文件目录。
  • library:公共库包,封装公共的功能,不包含业务需求实现。
  • packed:打包目录,将资源文件打包的 go 文件存放在这里,boot 包初始化时会自动调用。
  • public静态目录,仅有该目录下的文件才能对外提供静态服务访问。
  • router路由注册,用于路由的统一注册管理。
  • template模板文件,存放 mvc 模板文件
  • dockerfile:镜像描述,云原生时代用于编译生成 docker 镜像的描述文件。
  • go.mod:依赖管理,使用 go module 包管理的依赖描述文件。
  • main.go:入口文件,程序入口。

咱们先看service目录下的Menu.go

在这之前,先说菜单是干啥的,菜单是路由吗?

菜单主要是为用户提供一个直观的导航方式,帮助用户快速找到应用的不同功能或页面。菜单通常以列表、图标等形式展示在应用的界面上,每个菜单项可能对应一个特定的功能、页面或操作。

路由则是用于定义应用中不同 URL 与处理函数之间的映射关系当用户在浏览器中输入一个 URL 时,路由机制会根据预先定义的规则将请求分发到相应的处理函数中,从而实现相应的功能并返回页面内容。

在很多 Web 应用中,菜单中的每个选项可能会对应一个特定的路由,当用户点击菜单选项时,应用会根据对应的路由进行页面跳转或执行相应的操作。但菜单的设计不仅仅取决于路由,还会考虑用户体验、业务逻辑和功能分类等因素。

总的来说,菜单是为了方便用户操作和导航,而路由是实现页面跳转和功能处理的技术机制,菜单和路由相互配合,以提供一个流畅和易用的 Web 应用体验。

package service

import (
	"errors"
	"focus/app/model"
)

type MenuService struct{}

func (s MenuService) SetTopMenus(menus []*model.MenuItem) error {
	// 在这里执行设置顶部菜单的逻辑,例如将菜单数据保存到数据库或其他存储中
	// 示例:模拟一个错误
	if len(menus) == 0 {
		return errors.New("菜单列表不能为空")
	}
	return nil
}

func (s MenuService) GetTopMenus() []*model.MenuItem {
	// 在这里执行获取顶部菜单的逻辑,例如从数据库或其他存储中读取
	// 示例:返回一个固定的菜单列表
	defaultTopMenus := []*model.MenuItem{
		{
			Name: "首页",
			Url:  "/",
		},
		{
			Name: "主题",
			Url:  "/topic",
		},
	}
	return defaultTopMenus
}

定义了一个名为 MenuService 的结构体,并为其实现了两个方法SetTopMenus 和 GetTopMenus 。

  • SetTopMenus 方法用于设置顶部菜单它接收一个菜单切片,如果菜单列表为空则返回一个错误。
  • GetTopMenus 方法用于获取顶部菜单,这里模拟返回了一个固定的默认菜单列表。

import (
	"errors"
	"focus/app/model"
)
  • errors 包:提供了创建和处理错误值的功能。
  • "focus/app/model":项目中model自定义的一个包,可能包含与数据模型相关的定义和操作。

通过导入这些包,后续的代码就可以使用其中定义的函数、类型和常量等。

type MenuService struct{}

func (s MenuService) SetTopMenus(menus []*model.MenuItem) error {
	// 在这里执行设置顶部菜单的逻辑,例如将菜单数据保存到数据库或其他存储中
	// 示例:模拟一个错误
	if len(menus) == 0 {
		return errors.New("菜单列表不能为空")
	}
	return nil
}

定义了 MenuService 结构体类型,并为其定义了一个 SetTopMenus 方法

这是一个名为 `SetTopMenus` 的方法,属于 `MenuService` 类型。 它接受一个参数 `menus`,类型是 `[]*model.MenuItem` ,即一个指向 `model.MenuItem` 类型的指针切片。 方法的返回值类型是 `error` ,表示该方法可能在执行过程中返回一个错误对象。

Q:为什么type MenuService struct{}要定义成空结构体?

  1. 强调方法的归属:通过将方法与特定的结构体关联,即使结构体本身没有数据字段,也能清晰地表明这些方法是属于 MenuService 这个“服务”的一部分,增强代码的组织性和可读性。

  2. 未来的扩展性:虽然当前结构体没有字段,但可能在后续的开发中,根据需求添加与菜单服务相关的状态或配置信息。

  3. 统一编程风格:在项目中,如果其他类似的服务结构体也采用这种方式,为了保持风格的一致性,也会这样定义。

  4. 避免不必要的数据存储:有时,服务方法可能不需要依赖于结构体中的具体数据,仅通过输入参数就能完成所需的操作,此时不需要在结构体中存储数据。这个还有一篇博客专门用来介绍原因:为什么go语言中service目录下的的结构体有时要定义成空结构体-CSDN博客

我们讲解细一点,这个是什么

看model文件夹下menu.go

package model

// 菜单数据结构
type MenuItem struct {
	Name   string      // 显示名称
	Url    string      // 链接地址
	Icon   string      // 图标,可能是class,也可能是iconfont
	Target string      // 打开方式: 空, _blank
	Active bool        // 是否被选中
	Items  []*MenuItem // 子级菜单
}

这还是一个嵌套结构体

SetTopMenus 方法用于设置顶部菜单,它接收一个 []*model.MenuItem 类型的切片参数 menus 。方法内部会检查传入的菜单切片是否为空,如果为空则返回一个表示错误的 errors.New("菜单列表不能为空") ,否则返回 nil 表示操作成功,没有错误。

我们接下来往下看:

func (s MenuService) GetTopMenus() []*model.MenuItem {
	// 在这里执行获取顶部菜单的逻辑,例如从数据库或其他存储中读取
	// 示例:返回一个固定的菜单列表
	defaultTopMenus := []*model.MenuItem{
		{
			Name: "首页",
			Url:  "/",
		},
		{
			Name: "主题",
			Url:  "/topic",
		},
	}
	return defaultTopMenus
}

定义了一个名为 GetTopMenus 的方法,属于 MenuService 类型。该方法返回一个包含两个固定菜单项的切片

GetTopMenus 方法的作用是获取顶部菜单的信息。

在这个示例中,由于没有实际从数据库或其他存储中读取菜单数据,而是直接返回了一个固定的默认菜单列表。但在实际应用中,这个方法通常会包含从数据库或其他持久化存储中读取顶部菜单数据的逻辑,并将其以 []*model.MenuItem 的形式返回给调用方使用。

接下来我们看share文件夹

package shared

import (
	"context"
	"focus/app/model"
	"github.com/gogf/gf/frame/g"
	"github.com/gogf/gf/net/ghttp"
)

// 上下文管理服务
var Context = contextShared{}

type contextShared struct{}

// 初始化上下文对象指针到上下文对象中,以便后续的请求流程中可以修改。
func (s *contextShared) Init(r *ghttp.Request, customCtx *model.Context) {
	r.SetCtxVar(model.ContextKey, customCtx)
}

// 获得上下文变量,如果没有设置,那么返回nil
func (s *contextShared) 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 *contextShared) SetUser(ctx context.Context, ctxUser *model.ContextUser) {
	s.Get(ctx).User = ctxUser
}

// 将上下文信息设置到上下文请求中,注意是完整覆盖
func (s *contextShared) SetData(ctx context.Context, data g.Map) {
	s.Get(ctx).Data = data
}

代码定义了一个名为 contextShared 的结构体和相关的方法,用于处理 ghttp 请求的上下文相关操作。

  • Init 方法用于在 ghttp.Request 中设置自定义的上下文 customCtx 
  • Get 方法用于从给定的上下文 ctx 中获取之前设置的 model.Context 类型的值,如果未设置则返回 nil 。
  • SetUser 方法用于设置上下文的用户信息。
  • SetData 方法用于设置上下文的数据。
import (
	"context"
	"focus/app/model"
	"github.com/gogf/gf/frame/g"
	"github.com/gogf/gf/net/ghttp"
)

 先看import导入的包

import "context" 导入了 Go 语言标准库中的 context 包。

context 包主要用于在不同的 Goroutine 之间传递上下文信息,包括截止时间、取消信号和其他请求范围的值,它可以帮助控制和协调多个并发操作,例如在超时、取消操作或传递特定的上下文数据时进行相应的处理。

github.com/gogf/gf/frame/g是 GoFrame 框架中的一个包,GoFrame 是一款模块化、高性能、生产级的 Go 基础开发框架。该包提供了许多常用的基础开发模块和 Web 服务开发的核心组件,例如:

  • 缓存:用于缓存数据,以提高数据访问速度。
  • 日志:支持记录应用程序的运行日志。
  • 队列:实现数据的队列结构,可用于异步处理等场景。
  • 数组、集合、容器:提供各种数据结构的实现。
  • 定时器:用于定时执行任务。
  • 命令行:方便进行命令行工具的开发。
  • 内存锁:实现内存级别的锁定机制。
  • 对象池:管理对象的创建和复用,提高性能。
  • 配置管理:集中管理应用程序的配置信息。
  • 资源管理:有效管理各种资源。
  • 数据校验:对输入数据进行校验。
  • 数据编码:处理数据的编码和解码。
  • 定时任务:支持定时触发执行的任务。
  • 数据库 ORM:提供对象关系映射,方便操作数据库。
  • TCP/UDP 组件:用于网络通信中的 TCP 和 UDP 协议。
  • 进程管理/通信:管理进程以及进程间的通信。
  • Web 服务相关组件:如 router(路由)、cookiesession(会话管理)、middleware(中间件)、服务注册、模板引擎等,支持热重启、热更新、域名绑定、TLS/HTTPS、rewrite 等特性。

github.com/gogf/gf/net/ghttp包提供了强大的 HTTP 服务器和简单的客户端实现。以下是该包中的一些主要内容:

  • 常量和变量:可能包含一些与 HTTP 相关的常量或全局变量。
  • 函数
    • buildParams:用于构建参数。
    • middlewareCors:设置 CORS 中间件。
    • middlewareHandlerResponse:处理响应的中间件。
    • middlewareJSONBody:处理 JSON 体的中间件。
    • restartAllServer:根据给定的上下文和新的可执行文件路径等重启所有服务器。
    • shutdownAllServer:根据上下文关闭所有服务器。
    • startPprofServer:启动性能分析服务器。
    • supportedMethods:返回支持的 HTTP 方法列表。
    • wait:等待某些操作完成或条件满足。
  • 结构体类型
    • Cookie:用于操作 Cookie。提供了获取、设置、检查、删除 Cookie 等方法,例如 getCookiesetcontainsremove 等。
    • CookieOptions:可能用于配置 Cookie 的相关选项。
    • DefaultHandlerResponse:默认的处理响应结构体。
    • Domain:代表域名相关的操作。可以绑定处理程序、中间件、状态处理程序等,例如 bindHandlerbindHookHandlerbindMiddleware 等方法。
var Context = contextShared{}

type contextShared struct{}

var Context = contextShared{} 定义了一个名为 Context 的变量,并将其初始化为 contextShared 类型的一个空实例。

type contextShared struct{} 则定义了一个名为 contextShared 的空结构体类型。

这种空结构体在某些情况下可以用于标记、事件通知或者作为一种简单的组合类型,具体用途取决于后续为其定义的方法的功能。

问题:为什么要定义 var Context = contextShared{}?

这个在后面的system目录下会用到

一般这样定义的目的有:

  1. 提供一个统一的访问点:通过定义这个全局变量,可以在整个应用的不同部分以一致的方式访问和使用上下文管理的功能,而无需在每个需要的地方重新创建或传递相关的对象。

  2. 简化代码使用:使得获取和操作上下文的代码更加简洁和直观,不需要每次都创建新的 contextShared 实例。

  3. 共享状态和配置:可以在这个对象中存储和共享一些全局的上下文相关的状态或配置信息,方便在整个应用中进行统一管理和修改。

  4. 封装复杂性:将与上下文管理相关的复杂逻辑封装在 contextShared 结构体及其方法中,通过 Context 变量来暴露给外部使用,隐藏了内部实现的细节。

  5. 提高代码的可维护性和可读性:使得上下文管理的相关操作集中在一个明确的位置,方便后续的维护和理解代码的功能。

func (s *contextShared) Init(r *ghttp.Request, customCtx *model.Context) {
	r.SetCtxVar(model.ContextKey, customCtx)
}

这段代码定义了 `contextShared` 结构体的 `Init` 方法。

该方法接收一个 `*ghttp.Request` 类型的参数 `r` 和一个 `*model.Context` 类型的参数 `customCtx`

其功能是使用 `r.SetCtxVar` 方法将 `customCtx` 存储在请求的上下文中,键为 `model.ContextKey` 。

这样,在后续处理该请求的流程中,可以通过相应的方式获取和使用这个存储的上下文信息。

问题:为什么这上面的类型和参数都要用*指针(*p代表什么?指针p指向的值)

对于 r *ghttp.Request:使用指针可以避免在函数内部复制整个 ghttp.Request 对象,从而减少内存开销。因为 ghttp.Request 可能是一个比较大的结构体,如果直接传递值,会导致较大的复制成本。通过传递指针,函数可以直接操作原始的请求对象,而无需复制它。

对于 customCtx *model.Context传递指针的主要目的通常是为了在函数内部修改该上下文对象。如果不使用指针,函数内部对 customCtx 的修改将不会反映到原始的对象上,因为函数内部操作的只是 customCtx 的副本。而通过传递指针,函数可以直接修改指针所指向的实际 model.Context 对象,从而在函数外部也能看到这些修改。

另外,在 Go 语言中,指针的使用相对较为安全和直观。只要确保在使用指针时,所指向的对象是有效的(不为 nil,就可以避免出现空指针异常等问题。同时,Go 语言的编译器和运行时也会进行一些检查和优化,以确保指针的正确使用。

这样的设计可以提高程序的性能和灵活性,特别是当需要在函数内部修改请求或上下文对象的状态时,使用指针是一种常见且有效的方式。但需要注意,使用指针时要小心处理可能出现的空指针情况,以增强程序的稳定性。

问题:*ghttp.Request是什么?

*ghttp.Request是 GoFrame 框架中用于表示 HTTP 请求的对象。它继承了底层的 http.Request对象,并包含了会话相关的 cookie 和 session 对象(每个请求都会有两个独立的 cookie 和 session 对象)。此外,每个请求还有一个唯一的 id(请求 id,全局唯一),用以标识每一个请求。该对象的成员还包含一个与当前请求对应的返回输出对象指针 response,用于数据的返回。

通过 *ghttp.Request 对象,可以方便地获取请求中的各种信息,例如请求参数、请求头、请求体等。它提供了一系列方法来获取不同类型的请求参数,如 getgetQuerygetForm 等方法用于获取字符串类型的参数;getIntgetFloat 等方法用于获取特定类型的数值参数;getStruct 方法用于将请求参数绑定到指定的结构体对象上;getBody 或 getBodyString 方法用于获取客户端提交的原始数据等。

例如,可以使用 r.Get("key") 来获取指定键名的参数值,其中 r 是一个 *ghttp.Request 对象。

在 GoFrame 框架中,依靠 ghttp.Request 对象实现请求输入的处理,以便在 Web 应用中对客户端发送的 HTTP 请求进行解析和操作。

问题:r.SetCtxVar(model.ContextKey, customCtx) 这行代码是干啥的?

“Ctx”常见的是“context”的缩写,意思是“上下文”“环境”或“背景”等。

r.SetCtxVar(model.ContextKey, customCtx) 的作用是将 customCtx 这个值与指定的键 model.ContextKey 关联,并存储在请求的上下文中。

说白了,r.SetCtxVar(model.ContextKey, customCtx)的作用是将与model.ContextKey对应的变量设置为customCtx的值。r就是ghttp.Request,在后续的请求处理流程中,可以通过相应的方法(如GetCtxVar)根据键来获取之前设置的上下文变量的值,以便在不同的函数或模块中共享和使用这个变量所代表的信息。

在许多编程框架中(例如 GoFrame 框架),会使用上下文(context)来在请求处理过程中传递和共享相关的数据或状态。通过SetCtxVar方法,可以将一个特定的键值对关联到当前请求的上下文中。

这样,在后续处理这个请求的过程中,可以通过相应的方法获取到这个存储在上下文中的值并进行使用

咱们再回去看一下model包下的context.go文件

// ==========================================================================
// This is auto-generated by gf cli tool. Fill this file as you wish.
// ==========================================================================

package model

import (
	"github.com/gogf/gf/frame/g"
	"github.com/gogf/gf/net/ghttp"
)

const (
	// 上下文变量存储键名,前后端系统共享
	ContextKey = "ContextKey"
	// 默认管理员ID
	DefaultAdminId = 1
)

// 请求上下文结构
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 // 用户头像
	IsAdmin  bool   // 是否是管理员
}

定义了一些与请求上下文相关的模型结构和常量。

  • ContextKey 常量用于标识请求上下文中存储数据的键。
  • DefaultAdminId 常量表示默认的管理员 ID。

Context 结构体包含了当前的会话管理对象 Session、用户信息 User 和自定义的键值对数据 Data

ContextUser 结构体包含了用户的一些基本信息,如 Id(用户 ID)、Passport(用户账号)、Nickname(用户名称)、Avatar(用户头像)和 IsAdmin(是否是管理员)。

什么是请求上下文?

请求上下文可以理解为在处理一次 HTTP 请求的过程中,维持一段程序正常运行所需要的外部变量的值的集合,它包含了与特定请求相关的信息和数据。

在 Web 应用中,当客户端发送一个请求到服务器时,服务器需要处理这个请求并生成相应的响应。在这个过程中,有很多与请求相关的信息是需要被多个函数或模块访问和使用的,例如请求的方法(GET、POST 等)、请求头、请求参数、用户会话信息等

为了避免将这些请求相关的信息作为参数在各个函数之间传递,造成函数接口的复杂和混乱,许多 Web 框架(如 Flask)采用了请求上下文的机制。通过请求上下文,这些与请求相关的信息被集中存储在一个特定的上下文中,使得在处理请求的代码中可以方便地访问和使用这些信息,而无需显式地传递大量参数。

例如,在上述代码示例中,request 就是请求上下文的对象,它封装了 HTTP 请求的内容,通过 request 可以获取请求的相关数据,如请求地址、请求方式、Cookie 等。session 则用于记录请求会话中的信息,例如用户信息。

请求上下文通常存放在一个类似于栈的数据结构中,以支持多个请求的并发处理。当一个新的请求到来时,相应的请求上下文被压入栈中;当请求处理完成后,该请求上下文从栈中弹出。这样可以确保每个请求都有自己独立的上下文,不会相互干扰。

在具体的框架中,请求上下文的实现和包含的具体信息可能会有所不同,但总体目的都是为了方便在处理请求的过程中管理和使用与请求相关的各种数据和状态。在使用框架进行 Web 开发时,了解和正确使用框架提供的请求上下文机制,可以更高效地处理请求并实现各种功能。

如果使用的是特定的框架,建议参考该框架的文档以获取更详细和准确的关于请求上下文的信息,以及如何在该框架中具体地使用请求上下文来处理请求。

我们接着往下看:

func (s *contextShared) 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
}

 这段代码定义了 `contextShared` 结构体的 `Get` 方法,

用于从给定的 `context.Context` 中获取与 `model.ContextKey` 关联的值,并将其转换为 `*model.Context` 类型。 具体步骤如下:

1. 通过 `ctx.Value(model.ContextKey)` 从上下文 `ctx` 中获取与指定键 `model.ContextKey` 关联的值,并将其存储在 `value` 变量中。

2. 如果 `value` 为 `nil`,说明上下文没有设置该键对应的值,直接返回 `nil` 。

3. 如果 `value` 可以成功转换为 `*model.Context` 类型(通过 `ok` 标志判断),则返回该值;否则,也返回 `nil` 。 总的来说,这个方法用于从给定的上下文对象中尝试获取特定类型的上下文信息,如果获取不到或类型不匹配则返回 `nil` 。

详解:

*model.Context 是返回值,将参数 ctx 转换为 *model.Context 类型的原因是:通过这种方式可以从给定的 context.Context 对象中获取与特定键关联的、事先约定好的上下文数据(在这里就是 *model.Context 类型的数据)

context.Context 通常用于在不同的函数或协程之间传递上下文信息,包括一些共享的数据、截止时间、取消信号等。而这里的 model.Context 是自定义的一个结构体,用于存储特定的上下文相关变量。

在代码中,使用 ctx.Value(model.ContextKey) 尝试从 ctx 中获取与 model.ContextKey 对应的 value。如果获取到的值不为 nil,并且可以成功转换为 *model.Context 类型,那么就返回这个转换后的 *model.Context 对象。

这样做的好处是可以在整个请求处理流程中,方便地通过传递和访问 context.Context 对象来获取和使用特定的上下文信息,而不需要将所有相关的数据作为单独的参数在各个函数之间传递,提高了代码的简洁性和可维护性。

例如,在一个 Web 请求处理中,可能在请求开始时将一些必要的上下文信息(如用户信息、请求相关的配置等)存储在 context.Context 中,然后在后续的不同函数或方法中,通过这种方式获取并使用这些上下文信息,而无需频繁地传递大量参数。

具体来说,在上述代码所在的场景中,可能在其他地方已经将 *model.Context 类型的相关数据设置到了 context.Context 中(使用了类似 ctx.SetValue(model.ContextKey, someModelContext) 的方式),这里的 Get 方法就是用于从当前的 context.Context 中获取之前设置的那个特定的 *model.Context 数据,以便后续的处理逻辑可以使用其中的信息。这样可以实现上下文信息在不同函数之间的共享和传递。

 *model.Context 是返回值,其实函数可以这样写:

func (s *contextShared) Get(ctx context.Context) (result *model.Context) {
    value := ctx.Value(model.ContextKey)
    if value == nil {
        return nil
    }
    if localCtx, ok := value.(*model.Context); ok {
        result = localCtx
        return
    }
    return nil
}

目前我们看到的是比较简介的

func (s *contextShared) 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 *contextShared) SetUser(ctx context.Context, ctxUser *model.ContextUser) {
	s.Get(ctx).User = ctxUser
}

 目的是将上下文信息设置到上下文请求中,注意是完整覆盖

定义了一个 SetUser 方法,用于设置 context.Context 中的用户信息。

在方法内部,它先通过 s.Get(ctx) 获取当前上下文中的 *model.Context 对象,然后将传入的 ctxUser 赋值给这个对象中的 User 字段。

func (s *contextShared) SetData(ctx context.Context, data g.Map) {
	s.Get(ctx).Data = data
}

定义了一个名为 SetData 的方法。

其功能是获取指定上下文中的 *model.Context 对象,并将传入的 data (类型为 g.Map )赋值给该对象中的 Data 字段,从而实现对上下文数据的设置。

回头看这4个方法:

  • Init 方法用于在 ghttp.Request 中设置自定义的上下文 customCtx ,并与特定的键 model.ContextKey 关联。
  • Get 方法从给定的 context.Context 中获取与特定键关联的值,并尝试将其转换为 *model.Context 类型,如果获取不到或类型不匹配则返回 nil 。
  • SetUser 方法获取当前上下文中的 *model.Context 对象,并设置其中的用户信息。
  • SetData 方法获取当前上下文中的 *model.Context 对象,并设置其中的数据部分。

整个 contextShared 结构体及其方法旨在方便地管理和操作与请求相关的上下文信息。

回头看context.go文件


package model

import (
	"github.com/gogf/gf/frame/g"
	"github.com/gogf/gf/net/ghttp"
)

const (
	// 上下文变量存储键名,前后端系统共享
	ContextKey = "ContextKey"
	// 默认管理员ID
	DefaultAdminId = 1
)

// 请求上下文结构
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 // 用户头像
	IsAdmin  bool   // 是否是管理员
}

model.Context   *ContextUser g.Map都能找到

总的来说,实现对请求上下文的管理和操作,提供了对请求上下文的初始化、获取和部分数据的更新操作,方便在整个请求处理流程中共享和管理相关的数据。

问题:请求上下文有什么用?是不是相当于session?

请求上下文的管理和操作与 Session 有相似之处,但也有一些区别。

请求上下文的管理和操作主要有以下用途:

  1. 数据共享和传递:在处理一个请求的不同阶段或不同的函数之间共享和传递必要的数据,避免了通过函数参数层层传递的繁琐。

  2. 状态管理:可以存储和管理请求的当前状态信息,例如用户身份、权限、操作流程的阶段等。

  3. 提高代码的可维护性和可读性:将与请求相关的信息集中管理,使得代码逻辑更加清晰,易于理解和维护

  4. 上下文切换:在处理并发请求时,能够确保每个请求都有其独立的上下文,互不干扰。

与 Session 相比:

Session 通常更侧重于在多个请求之间保持用户的相关状态信息,例如登录状态、用户偏好等,并且通常是基于会话 ID 来识别和关联不同请求属于同一个用户的会话。

请求上下文则更侧重于在单个请求的处理过程中管理和传递相关信息。

虽然它们有一些相似之处,但在具体的使用场景和功能重点上会有所不同。

;