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

 上一篇文章我们讲解了在goframe框架中app/system/index/internal/api目录的context文件

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

本篇文章我们讲解一下app/system/index/internal/service目录下的context文件

package api

import (
	"focus/app/model"
	"focus/app/system/index/internal/define"
	"focus/app/system/index/internal/service"
	"focus/library/response"

	"github.com/gogf/gf/net/ghttp"
)

// 内容管理
var Content = contentApi{}

type contentApi struct{}

// @summary 展示创建内容页面
// @tags    前台-内容
// @produce html
// @router  /content/create [GET]
// @success 200 {string} html "页面HTML"
func (a *contentApi) Create(r *ghttp.Request) {
	var (
		req *define.ContentCreateReq
	)
	if err := r.Parse(&req); err != nil {
		service.View.Render500(r, model.View{
			Error: err.Error(),
		})
	}
	service.View.Render(r, model.View{
		ContentType: req.Type,
	})
}

// @summary 创建内容
// @description 客户端AJAX提交,客户端
// @tags    前台-内容
// @produce json
// @param   entity body define.ContentDoCreateReq true "请求参数" required
// @router  /content/do-create [POST]
// @success 200 {object} define.ContentCreateOutput "请求结果"
func (a *contentApi) DoCreate(r *ghttp.Request) {
	var (
		req *define.ContentDoCreateReq
	)
	if err := r.ParseForm(&req); err != nil {
		response.JsonExit(r, 1, err.Error())
	}
	if res, err := service.Content.Create(r.Context(), req.ContentCreateInput); err != nil {
		response.JsonExit(r, 1, err.Error())
	} else {
		response.JsonExit(r, 0, "", res)
	}
}

// @summary 展示修改内容页面
// @tags    前台-内容
// @produce html
// @param   id query int true "问答ID"
// @router  /content/update [GET]
// @success 200 {string} html "页面HTML"
func (a *contentApi) Update(r *ghttp.Request) {
	var (
		req *define.ContentUpdateReq
	)
	if err := r.Parse(&req); err != nil {
		service.View.Render500(r, model.View{
			Error: err.Error(),
		})
	}
	if getDetailRes, err := service.Content.GetDetail(r.Context(), req.Id); err != nil {
		service.View.Render500(r)
	} else {
		service.View.Render(r, model.View{
			ContentType: getDetailRes.Content.Type,
			Data:        getDetailRes,
		})
	}
}

// @summary 修改内容
// @tags    前台-内容
// @produce json
// @param   entity body define.ContentDoUpdateReq true "请求参数" required
// @router  /content/do-update [POST]
// @success 200 {object} response.JsonRes "请求结果"
func (a *contentApi) DoUpdate(r *ghttp.Request) {
	var (
		req *define.ContentDoUpdateReq
	)
	if err := r.ParseForm(&req); err != nil {
		response.JsonExit(r, 1, err.Error())
	}
	if err := service.Content.Update(r.Context(), req.ContentUpdateInput); err != nil {
		response.JsonExit(r, 1, err.Error())
	} else {
		response.JsonExit(r, 0, "")
	}
}

// @summary 删除内容
// @tags    前台-内容
// @produce json
// @param   id formData int true "内容ID"
// @router  /content/do-delete [POST]
// @success 200 {object} response.JsonRes "请求结果"
func (a *contentApi) DoDelete(r *ghttp.Request) {
	var (
		req *define.ContentDoDeleteReq
	)
	if err := r.ParseForm(&req); err != nil {
		response.JsonExit(r, 1, err.Error())
	}
	if err := service.Content.Delete(r.Context(), req.Id); err != nil {
		response.JsonExit(r, 1, err.Error())
	} else {
		response.JsonExit(r, 0, "")
	}
}

// @summary 采纳回复
// @tags    前台-内容
// @produce json
// @param   entity body define.ContentAdoptReplyReq true "请求参数" required
// @router  /content/adopt-reply [POST]
// @success 200 {object} response.JsonRes "请求结果"
func (a *contentApi) AdoptReply(r *ghttp.Request) {
	var (
		req *define.ContentAdoptReplyReq
	)
	if err := r.ParseForm(&req); err != nil {
		response.JsonExit(r, 1, err.Error())
	}
	if err := service.Content.AdoptReply(r.Context(), req.Id, req.ReplyId); err != nil {
		response.JsonExit(r, 1, err.Error())
	} else {
		response.JsonExit(r, 0, "")
	}
}

定义了一个名为 `contentService` 的结构体,并为其实现了一系列与内容管理相关的方法。 以下是对每个方法的简要解释:

1. `GetList`:根据输入的条件查询内容列表,包括分页、排序、类型筛选、栏目筛选、用户权限等,并关联查询相关的分类和用户信息。

2. `Search`:根据输入的关键词和其他条件搜索内容列表,包括分页、排序、类型筛选、栏目筛选等,并进行搜索统计和关联查询相关的分类和用户信息。

3. `GetDetail`:根据给定的 ID 查询内容详情,并获取关联的用户信息。

4. `Create`:创建新的内容,并处理用户 ID 的设置和 HTML 代码的过滤。

5. `Update`:在事务中更新内容,处理 HTML 代码的过滤和权限检查。

6. `Delete`:在事务中根据用户权限删除内容及相关的回复。

7. `AddViewCount`:在事务中增加内容的浏览次数。

8. `AddReplyCount`:在事务中增加内容的回复次数。

9. `AdoptReply`:在事务中采纳指定的回复。

10. `UnacceptedReply`:在事务中取消采纳回复。

这些方法提供了对内容数据的各种操作和管理功能,并通过数据库操作和事务来保证数据的一致性和安全性。

import (
	"context"
	"focus/app/dao"
	"focus/app/model"
	"focus/app/shared"
	"focus/app/system/index/internal/define"

	"github.com/gogf/gf/database/gdb"
	"github.com/gogf/gf/encoding/ghtml"
	"github.com/gogf/gf/frame/g"
	"github.com/gogf/gf/util/gutil"
)

这段代码是用 Go 语言编写的导入(`import`)部分。 它引入了多个包和模块,用于在后续的代码中使用其中定义的类型、函数和方法。 以下是对每个导入的简要说明:

- `"context"`:用于处理上下文相关的操作,例如设置超时、传递取消信号等

- `"focus/app/dao"`:包含与数据访问对象(DAO)相关的代码,用于与数据库进行交互

- `"focus/app/model"`:包含数据模型的定义

- `"focus/app/shared"`:包含一些共享的功能或数据

- `"focus/app/system/index/internal/define"`:包含特定模块的定义。

接下来的几个包:

- `"github.com/gogf/gf/database/gdb"`:是与数据库操作相关的库

- `"github.com/gogf/gf/encoding/ghtml"`:用于 HTML 编码和解码相关的操作。

- `"github.com/gogf/gf/frame/g"`:是一个框架相关的核心包。

- `"github.com/gogf/gf/util/gutil"`:包含一些实用工具函数。

var Content = contentService{}

type contentService struct{}

 定义了一个名为 Content 的变量,并将其初始化为 contentService 类型的空结构体实例。

type contentService struct{} 定义了一个名为 contentService 的结构体类型,这个结构体目前没有任何字段

通过将 Content 定义为这种结构体类型的变量,后续可以对其调用在 contentService 结构体上定义的方法来执行相关的服务操作。

就是相当于var num int = 10 num是变量

问题:这里为什么要定义这个变量?

定义 `var Content = contentService{}` 这样的变量通常是为了创建一个 `contentService` 类型的实例,以便后续在代码的其他部分调用这个实例所关联的方法来执行与内容服务相关的操作。

通过将相关的操作和功能封装在 `contentService` 结构体及其方法中,并通过这个全局变量来访问和使用这些功能,可以使代码更具组织性、可读性和可维护性。

例如,可以在其他函数中通过 `Content.GetList` 、 `Content.Create` 等方式来调用 `contentService` 结构体中定义的方法。

如果不定义变量直接使用结构体也是可以的,但通常不太方便。 例如,您每次需要使用其方法时,都需要创建一个新的结构体实例:

package main

import "fmt"

type ContentService struct{}

func (s ContentService) SomeMethod() {
    fmt.Println("Hello from SomeMethod")
}

func main() {
    var s ContentService
    s.SomeMethod()
}

而如果像前面提到的定义一个全局变量 `Content` ,那么在代码的任何位置都可以直接使用 `Content.SomeMethod()` ,无需每次都重新创建实例,使用起来更加方便和简洁。 不过具体是否定义变量还是每次直接创建新实例,取决于具体的应用场景和需求。

接下来我们看第一个方法:

// 查询内容列表
func (s *contentService) GetList(ctx context.Context, input define.ContentGetListInput) (output *define.ContentGetListOutput, err error) {
	var (
		m = dao.Content.Ctx(ctx)
	)
	output = &define.ContentGetListOutput{
		Page: input.Page,
		Size: input.Size,
	}
	// 默认查询topic
	if input.Type != "" {
		m = m.Where(dao.Content.C.Type, input.Type)
	} else {
		m = m.Where(dao.Content.C.Type, model.ContentTypeTopic)
	}
	// 栏目检索
	if input.CategoryId > 0 {
		idArray, err := Category.GetSubIdList(ctx, input.CategoryId)
		if err != nil {
			return output, err
		}
		m = m.Where(dao.Content.C.CategoryId, idArray)
	}
	// 管理员可以查看所有文章
	if input.UserId > 0 && !User.IsAdminShow(ctx, input.UserId) {
		m = m.Where(dao.Content.C.UserId, input.UserId)
	}
	// 分配查询
	listModel := m.Page(input.Page, input.Size)
	// 排序方式
	switch input.Sort {
	case model.ContentSortHot:
		listModel = listModel.OrderDesc(dao.Content.C.ViewCount)
	case model.ContentSortActive:
		listModel = listModel.OrderDesc(dao.Content.C.UpdatedAt)
	default:
		listModel = listModel.OrderDesc(dao.Content.C.Id)
	}
	// 执行查询
	var list []*model.Content
	if err := listModel.Scan(&list); err != nil {
		return output, err
	}
	// 没有数据
	if len(list) == 0 {
		return output, nil
	}
	output.Total, err = m.Count()
	if err != nil {
		return output, err
	}
	// Content
	if err := listModel.ScanList(&output.List, "Content"); err != nil {
		return output, err
	}
	// Category
	err = dao.Category.
		Fields(model.ContentListCategoryItem{}).
		Where(dao.Category.C.Id, gutil.ListItemValuesUnique(output.List, "Content", "CategoryId")).
		ScanList(&output.List, "Category", "Content", "id:CategoryId")
	if err != nil {
		return output, err
	}
	// User
	err = dao.User.
		Fields(model.ContentListUserItem{}).
		Where(dao.User.C.Id, gutil.ListItemValuesUnique(output.List, "Content", "UserId")).
		ScanList(&output.List, "User", "Content", "id:UserId")
	if err != nil {
		return output, err
	}
	return
}

这段 Go 代码定义了一个名为 `GetList` 的方法,属于 `contentService` 结构体。

这个方法的主要功能是根据给定的上下文和输入参数从数据库中查询内容列表,并进行一系列的条件筛选、排序、关联查询等操作,最后返回查询结果和可能出现的错误。 以下是对代码的详细解释:

1. 首先创建了一个与数据库表 `Content` 相关的查询对象 `m` ,并设置了一些初始条件。

2. 根据输入参数 `input` 的不同值,添加更多的查询条件,如内容类型、栏目 ID、用户 ID 等。

3. 进行分页设置,并根据排序规则对结果进行排序

4. 执行查询并将结果存储在 `list` 切片中

5. 如果查询结果为空,直接返回

6. 获取查询结果的总数

7. 进行关联查询,获取与内容相关的分类和用户信息

总的来说,这个方法实现了一个较为复杂的内容列表查询功能,考虑了多种条件和关联数据的获取。

func (s *contentService) GetList(ctx context.Context, input define.ContentGetListInput) (output *define.ContentGetListOutput, err error)

这段 Go 语言代码定义了一个名为 GetList 的方法,它属于 *contentService 类型

方法的参数包括:

  • ctx context.Context:一个 context.Context 类型的参数,用于在不同的操作之间传递上下文信息,例如设置超时、传递取消信号等。
  • input define.ContentGetListInput:一个自定义类型 define.ContentGetListInput 的参数,可能包含获取列表的相关输入条件。

方法的返回值包括:

  • output *define.ContentGetListOutput:一个指向自定义类型 define.ContentGetListOutput 的指针,可能用于返回获取到的列表数据或相关输出信息。
  • err error:一个错误类型的值,用于返回可能发生的错误。

其中define.ContentGetListInput 和define.ContentGetListOutput都是自定义的结构体。

问题:ctx context.Context是什么?

ctx context.Context 是 Go 语言中用于表示上下文的变量。context.Context 是 Go 语言在 1.7 版本中引入标准库的一个接口,它定义了以下几个方法:

  • deadline():返回上下文被取消的时间,即完成工作的截止日期。如果没有设置截止日期,则返回 ok == false
  • done():返回一个通道(<-chan struct{}),当当前工作完成或者上下文被取消后,该通道会被关闭。多次调用 done 方法会返回同一个通道。
  • err():返回上下文结束的原因。只有在 done 返回的通道被关闭时,才会返回非空的值。如果上下文被取消,会返回 canceled 错误;如果上下文超时,会返回 deadlineExceeded 错误。
  • value(key interface{}):从上下文获取与指定键对应的值。对于同一个上下文,多次调用 value 并传入相同的键会返回相同的结果。该方法可用于传递请求特定的数据。

context.Context 的主要作用是在不同的协程或函数之间同步请求特定的数据、取消信号以及处理请求的截止日期等信息。它允许在一次请求经过的所有协程或函数间传递取消信号及共享数据,以达到父协程对子协程的管理和控制的目的。

在实际使用中,通常会创建一个具体的 Context 对象,例如使用 context.Background() 创建一个顶级的、无截止时间且不能取消的上下文;或使用 context.WithCancel(parent)context.WithTimeout(parent, duration)context.WithDeadline(parent, deadline) 等方法基于父上下文创建可取消、带超时或截止日期的子上下文。

函数签名中包含 ctx context.Context 参数时,函数就可以根据上下文的变化做出相应的响应,例如通过监听 done 通道来判断是否收到取消信号,通过 err 方法获取取消的原因,或者通过 value 方法获取共享的数据等。这样可以方便地管理并发任务,例如在请求需要提前结束或超时时,及时停止相关的子协程或操作,避免资源浪费,同时也可以在不同的协程之间共享必要的数据。

import (
    "context"
    "fmt"
    "time"
)

func doSomething(ctx context.Context) error {
    // 开始一个耗时操作前,检查是否有取消信号
    select {
    case <-ctx.Done():
        return ctx.Err()
    default:
    }

    // 进行实际操作...
    // 如果操作可能持续很长时间,应在循环或长阻塞调用中不断检查
    for {
        select {
        case <-ctx.Done():
            return ctx.Err() // 当接收到取消信号时,立即返回错误
            // 其他处理逻辑
        }
    }
}

func main() {
    // 创建一个带超时的 context
    ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
    defer cancel() // 当超时或手动取消后,释放资源

    go func() {
        // 在另一个 goroutine 中使用这个 context
        if err := doSomething(ctx); err!= nil {
            log.Println("操作被取消:", err)
        }
    }()

    // 等待 goroutine 执行
    select {
    case <-ctx.Done():
        fmt.Println("操作超时")
    }
}

 我们接着往下看

	var (
		m = dao.Content.Ctx(ctx)
	)

 问题:dao.Content.Ctx是什么?还记得我们的dao目录吗?

// =================================================================================
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
// =================================================================================

package dao

import (
	"focus/app/dao/internal"
)

// contentDao is the manager for logic model data accessing and custom defined data operations functions management.
// You can define custom methods on it to extend its functionality as you wish.
type contentDao struct {
	*internal.ContentDao
}

var (
	// Content is globally public accessible object for table gf_content operations.
	Content contentDao
)

func init() {
	Content = contentDao{
		internal.NewContentDao(),
	}
}

// Fill with you ideas below.

主要定义了一个名为 contentDao 的结构体,并初始化了一个全局可访问的 Content 对象

以下是对代码的详细解释:

  1. 代码开头的注释说明了这是由 GoFrame CLI 工具自动生成的,并且鼓励开发者根据自己的需求填充后续的内容。

  2. contentDao 结构体包含了一个指向 internal.ContentDao 的指针。

  3. Content 变量是 contentDao 类型的全局对象,并通过调用 internal.NewContentDao() 进行初始化。

  4. init 函数在包被初始化时执行,完成了对 Content 对象的初始化操作。

这段代码为对 gf_content 表的操作提供了一个基础的框架和全局访问对象,以便在其他代码中方便地使用相关的数据操作功能。

然后我们回来看刚才那个方法:

var (
		m = dao.Content.Ctx(ctx)
	)

dao.Content 是之前定义的某个对象或类型。

Ctx(ctx) 是 dao.Content 上的一个方法或函数,它接收一个参数 ctx ,然后将其返回的值赋给变量 m 

咱们回头一看!这个dao包下的Content没有Ctx方法啊,咱们一看,是不是有继承关系?一看还有个嵌套结构体,嵌套结构体,你还记得嵌套结构体有什么用吗?是不是类似java的继承?咱们来复习一下吧

Go 语言中的嵌套结构体与 Java 中的继承有一些相似之处,但也有重要的区别。

相似点:

- 都可以实现代码复用,使一个结构体能够访问和使用另一个结构体的成员和方法(在 Java 中是继承父类的成员和方法)。

不同点:

- 在 Go 中,

嵌套结构体只是将一个结构体嵌入到另一个结构体中,不会像 Java 的继承那样有严格的继承层次和规则。被嵌套的结构体的字段和方法不会自动提升到嵌套结构体的级别,需要通过显式的引用访问。

- Java 的继承有单继承的限制(一个类只能直接继承一个父类),而 Go 语言的嵌套结构体可以嵌套多个结构体。 总的来说,虽然有一些相似性,但 Go 语言的嵌套结构体和 Java 的继承在概念和使用方式上还是有明显差异的。

言归正传,找interal目录下的Content发现也没有Ctx这个方法,奇怪了?ctx这个方法是啥?

咱们看看之前internal包下content下的代码

// ==========================================================================
// Code generated by GoFrame CLI tool. DO NOT EDIT.
// ==========================================================================

package internal

import (
	"github.com/gogf/gf/database/gdb"
	"github.com/gogf/gf/frame/g"
	"github.com/gogf/gf/frame/gmvc"
)

// ContentDao is the manager for logic model data accessing and custom defined data operations functions management.
type ContentDao struct {
	gmvc.M                // M is the core and embedded struct that inherits all chaining operations from gdb.Model.
	C      contentColumns // C is the short type for Columns, which contains all the column names of Table for convenient usage.
	DB     gdb.DB         // DB is the raw underlying database management object.
	Table  string         // Table is the underlying table name of the DAO.
}

// ContentColumns defines and stores column names for table gf_content.
type contentColumns struct {
	Id             string // 自增ID
	Key            string // 唯一键名,用于程序硬编码,一般不常用
	Type           string // 内容模型: topic, ask, article等,具体由程序定义
	CategoryId     string // 栏目ID
	UserId         string // 用户ID
	AdoptedReplyId string // 采纳的回复ID,问答模块有效
	Title          string // 标题
	Content        string // 内容
	Sort           string // 排序,数值越低越靠前,默认为添加时的时间戳,可用于置顶
	Brief          string // 摘要
	Thumb          string // 缩略图
	Tags           string // 标签名称列表,以JSON存储
	Referer        string // 内容来源,例如github/gitee
	Status         string // 状态 0: 正常, 1: 禁用
	ReplyCount     string // 回复数量
	ViewCount      string // 浏览数量
	ZanCount       string // 赞
	CaiCount       string // 踩
	CreatedAt      string // 创建时间
	UpdatedAt      string // 修改时间
}

// NewContentDao creates and returns a new DAO object for table data access.
func NewContentDao() *ContentDao {
	columns := contentColumns{
		Id:             "id",
		Key:            "key",
		Type:           "type",
		CategoryId:     "category_id",
		UserId:         "user_id",
		AdoptedReplyId: "adopted_reply_id",
		Title:          "title",
		Content:        "content",
		Sort:           "sort",
		Brief:          "brief",
		Thumb:          "thumb",
		Tags:           "tags",
		Referer:        "referer",
		Status:         "status",
		ReplyCount:     "reply_count",
		ViewCount:      "view_count",
		ZanCount:       "zan_count",
		CaiCount:       "cai_count",
		CreatedAt:      "created_at",
		UpdatedAt:      "updated_at",
	}
	return &ContentDao{
		C:     columns,
		M:     g.DB("default").Model("gf_content").Safe(),
		DB:    g.DB("default"),
		Table: "gf_content",
	}
}

以下是对 `m = dao.Content.Ctx(ctx)` 这种用法更详细的解释:

首先,我们来看 `dao.Content` 的定义。在 `internal` 包中,`ContentDao` 结构体被定义。 `ContentDao` 结构体包含了几个重要的部分:

重点看

type ContentDao struct {
	gmvc.M                // M is the core and embedded struct that inherits all chaining operations from gdb.Model.
	C      contentColumns // C is the short type for Columns, which contains all the column names of Table for convenient usage.
	DB     gdb.DB         // DB is the raw underlying database management object.
	Table  string         // Table is the underlying table name of the DAO.
}

- `gmvc.M`:这是一个核心的嵌入式结构体,从 `gdb.Model` 继承了一系列的链式操作

- `C`:一个 `contentColumns` 类型的成员,用于方便地管理和使用表的列名

- `DB`:`gdb.DB` 类型,代表底层的数据库管理对象

- `Table`:指定了所关联的表的名称

当我们执行 `dao.Content.Ctx(ctx)` 时: 这意味着我们正在对 `dao.Content` 所关联的那个基于 `gdb.Model` 的部分调用 `Ctx` 方法。

`gdb.Model` 的 `Ctx` 方法被设计用于设置当前数据库操作的上下文。 当我们将 `ctx`(`context.Context` 类型)作为参数传递给 `Ctx` 方法时:

在方法内部,可能会进行一系列基于这个上下文的设置和调整。

例如:

1. 可能会根据 `ctx` 中的信息来设置数据库连接的超时时间。如果 `ctx` 中指定了一个最大等待时间,那么 `Ctx` 方法可以确保数据库操作在这个时间内完成,否则采取相应的超时处理策略,比如取消操作或返回错误

2. 可能会用于管理数据库事务的属性。如果 `ctx` 与一个正在进行的事务相关联,`Ctx` 方法可以确保后续的数据库操作在这个事务的范围内执行,保证数据的一致性和完整性。

3. 还可能会将特定的标识或信息与这个上下文关联起来,以便在数据库操作的日志记录或跟踪中使用。这有助于在出现问题时进行更精确的故障排查和性能分析。

最后,`Ctx` 方法返回一个新的 `*Model` ,这个返回值被赋给了变量 `m` 。 通过这样的操作,我们为后续基于 `m` 的数据库操作设置了特定的上下文环境,使得这些操作能够在我们期望的条件和约束下进行,从而实现更灵活、可控和可靠的数据库交互。 

总的来说吧,这个ctx怎么找到的找dao.Content 发现嵌套结构体internal.Content,还继续找,发现嵌套结构体上还有个嵌套结构体gdb.Model这就找到了ctx方法了,那么这个ctx方法是干啥的呢?

m = dao.Content.Ctx(ctx) 的作用是为 dao.Content 所关联的数据库操作设置特定的上下文 ctx ,并将设置好上下文后的模型对象返回并赋值给变量 m 。

通过设置上下文,可能会影响后续基于这个模型进行的数据库操作的行为,例如控制操作的超时时间、关联事务、添加跟踪标识等。这样可以使数据库操作更加灵活、可控,并适应不同的业务场景和需求。赋值给 m 是为了方便后续使用这个设置好上下文的模型对象进行具体的数据库查询、更新等操作。

接着往下看:

	output = &define.ContentGetListOutput{
		Page: input.Page,
		Size: input.Size,
	}

这段代码创建了一个 `define.ContentGetListOutput` 类型的指针,并对其部分字段进行了初始化。 具体来说,它将 `input` 中的 `Page` 和 `Size` 值分别赋给了新创建的输出对象的相应字段。这通常是在处理数据输出的场景中,根据输入的参数来构建输出的数据结构。

问题:这段代码在传入参数的时候没有用*表示指针指向的值,而是在初始化的时候用使用&表示指针,那么能不能再参数给他改成*,在下面定义变量的时候实现&

func (s *contentService) GetList(ctx context.Context, input define.ContentGetListInput) (output *define.ContentGetListOutput, err error) {
	var (
		m = dao.Content.Ctx(ctx)
	)
	output = &define.ContentGetListOutput{
		Page: input.Page,
		Size: input.Size,
	}

答案:不建议这样修改。

define.ContentGetListInput 前面加 * 会将其变为指针类型。通常,如果 input 在函数内部不需要被修改,或者不是为了节省拷贝成本,没有必要将其设为指针

而 output = &define.ContentGetListOutput{...} 这里创建并返回一个指向新结构体实例的指针。如果去掉 & ,则是创建一个值类型的结构体实例并赋值给 output 。这取决于代码的具体逻辑和需求,如果在其他地方需要通过指针来操作和修改这个输出结构体,或者为了避免结构体值的拷贝,使用指针是合适的。

咱们看第一个if语句

	if input.Type != "" {
		m = m.Where(dao.Content.C.Type, input.Type)
	} else {
		m = m.Where(dao.Content.C.Type, model.ContentTypeTopic)
	}

这段代码的作用是根据 `input.Type` 的值来设置查询条件。

如果 `input.Type` 不为空字符串,就使用 `input.Type` 作为查询条件中的值;

如果 `input.Type` 是空字符串,就使用 `model.ContentTypeTopic` 作为查询条件中的值。

通过这种方式,可以根据不同的输入情况来灵活地构建数据库查询条件。

m = m.Where(dao.Content.C.Type, input.Type)

 m 的基础上添加一个新的查询条件

具体来说,它指定了在 dao.Content 的 C.Type 字段上进行条件匹配匹配的值为 input.Type 。这通常是在数据库查询操作中用于筛选数据的一部分逻辑。

Where 通常是数据库操作中的一个方法,用于设置查询条件。

它的作用是根据指定的列(在这个例子中是 dao.Content.C.Type )和给定的值( input.Type )来过滤数据,以便从数据库中获取符合这些条件的数据。

m = m.Where(dao.Content.C.Type, model.ContentTypeTopic)

即按照 dao.Content.C.Type 这个字段的值等于 model.ContentTypeTopic 来筛选数据。通过连续调用这样的条件设置方法,可以逐步构建出复杂的数据库查询条件

这个就是按照类型查询

咱们看下一个if语句:

	if input.CategoryId > 0 {
		idArray, err := Category.GetSubIdList(ctx, input.CategoryId)
		if err != nil {
			return output, err
		}
		m = m.Where(dao.Content.C.CategoryId, idArray)
	}

这段代码的作用是:如果输入的 `CategoryId` 大于 0 ,就通过 `Category.GetSubIdList` 方法获取子类别 ID 列表,并将其用于设置查询条件

具体来说,如果 `input.CategoryId` 满足条件,会尝试获取其对应的子类别 ID 列表并存放在 `idArray` 中。如果获取过程中出现错误,直接返回当前的输出和错误。如果没有错误,就使用获取到的子类别 ID 列表为 `dao.Content.C.CategoryId` 字段设置查询条件,以进一步筛选数据。

问题:在go语言方法的参数中传参什么时候需要传入ctx参数?

在以下几种常见的情况下,通常需要传入 `context.Context` 作为参数:

1. 涉及到可能会被取消、超时或携带其他相关上下文信息的操作,例如在网络请求、数据库操作、长时间运行的任务等场景中。通过 `ctx` 可以控制操作的超时时间,或者在操作被取消时及时停止并释放相关资源。

2. 当需要在不同的函数或方法之间传递一些与请求或操作相关的通用上下文数据,比如身份验证信息、跟踪标识、日志记录的相关设置等。

3. 在涉及到并发操作或异步操作时,`ctx` 可以用于协调和控制多个并发任务的执行。

4. 如果您的应用使用了分布式追踪系统,`ctx` 可以用于携带追踪上下文信息,以便在整个系统中跟踪请求的流程和性能。

总之,只要操作可能需要对其执行过程进行更精细的控制、需要传递一些通用的上下文信息,或者与其他可能被取消/超时的操作进行协调时,就可能需要传入 `context.Context` 。

我们接着往下看:

	// 管理员可以查看所有文章
	if input.UserId > 0 && !User.IsAdminShow(ctx, input.UserId) {
		m = m.Where(dao.Content.C.UserId, input.UserId)
	}

这段代码的逻辑是:

首先判断输入的用户 ID(input.UserId)是否大于 0 。然后,通过调用 User.IsAdminShow(ctx, input.UserId) 来检查该用户是否为管理员。

如果用户 ID 大于 0 但不是管理员,就对查询对象 m 设置条件,只筛选出用户 ID 等于 input.UserId 的文章。

这实现了只有非管理员用户被限制只能查看自己的文章,而管理员用户不受此限制,可以查看所有文章的功能逻辑。

问题:dao.Content.C.UserId中的C是什么?

dao.Content.C.UserId 可能表示在 dao.Content 这个数据访问对象(DAO)中,通过 C 这个字段(是一个包含列信息的结构体或其他相关结构)来获取名为 UserId 的列信息。

咱们回头看internal包下的Content

type ContentDao struct {
	gmvc.M                // M is the core and embedded struct that inherits all chaining operations from gdb.Model.
	C      contentColumns // C is the short type for Columns, which contains all the column names of Table for convenient usage.
	DB     gdb.DB         // DB is the raw underlying database management object.
	Table  string         // Table is the underlying table name of the DAO.
}

在这段代码中,C 是一个 categoryColumns 类型的字段。

它是用于定义和操作与“类别”相关的数据库表中的列信息,以便更方便地在数据库操作中引用和处理这些列。

categoryColumns 通常是一个自定义的类型,用于集中管理和操作与“类别”相关的数据库表中的列信息。

它的具体用途和使用方式取决于其内部的定义和实现。

一般来说,它可能包含了一些列名的常量或者与列操作相关的方法,以便在数据库操作中更清晰、便捷地引用和处理特定的列。

例如,可能有获取特定列名的方法,或者基于这些列进行条件构建、数据提取等操作的辅助方法。

	// 分配查询
	listModel := m.Page(input.Page, input.Size)

这段代码使用之前构建的查询对象 `m` 进行分页查询。

`input.Page` 表示要获取的页码,`input.Size` 表示每页的记录数量。

m.Page(input.Page, input.Size)` 会根据指定的页码和每页记录数从数据库中获取相应的数据,并将结果存储在 `listModel` 中。

接着往下看:

// 排序方式
	switch input.Sort {
	case model.ContentSortHot:
		listModel = listModel.OrderDesc(dao.Content.C.ViewCount)
	case model.ContentSortActive:
		listModel = listModel.OrderDesc(dao.Content.C.UpdatedAt)
	default:
		listModel = listModel.OrderDesc(dao.Content.C.Id)
	}

这段代码根据输入的 input.Sort 的值来决定查询结果的排序方式。

“ContentSortHot” 可译为 “热门内容排序”
“ContentSortActive” 可译为 “活跃内容排序”

  • 如果 input.Sort 的值是 model.ContentSortHot,则按照 dao.Content.C.ViewCount 字段降序排列(即浏览量从高到低)。
  • 如果 input.Sort 的值是 model.ContentSortActive,则按照 dao.Content.C.UpdatedAt 字段降序排列(即更新时间从新到旧)。
  • 如果 input.Sort 的值不是上述两种情况,就按照 dao.Content.C.Id 字段降序排列。

这些都来自哪里?在internal目录下

type contentColumns struct {
	Id             string // 自增ID
	Key            string // 唯一键名,用于程序硬编码,一般不常用
	Type           string // 内容模型: topic, ask, article等,具体由程序定义
	CategoryId     string // 栏目ID
	UserId         string // 用户ID
	AdoptedReplyId string // 采纳的回复ID,问答模块有效
	Title          string // 标题
	Content        string // 内容
	Sort           string // 排序,数值越低越靠前,默认为添加时的时间戳,可用于置顶
	Brief          string // 摘要
	Thumb          string // 缩略图
	Tags           string // 标签名称列表,以JSON存储
	Referer        string // 内容来源,例如github/gitee
	Status         string // 状态 0: 正常, 1: 禁用
	ReplyCount     string // 回复数量
	ViewCount      string // 浏览数量
	ZanCount       string // 赞
	CaiCount       string // 踩
	CreatedAt      string // 创建时间
	UpdatedAt      string // 修改时间
}

咱们接着往下看:


	// 执行查询
	var list []*model.Content
	if err := listModel.Scan(&list); err != nil {
		return output, err
	}

作用是执行之前构建好的查询模型 listModel ,并将查询结果扫描到 list 切片中。如果在扫描过程中发生错误(通过 err 表示),就返回当前的输出 output 和这个错误

if err := listModel.Scan(&list);

执行 listModel 的扫描操作,并将结果存储到 &list 中。如果在扫描过程中出现错误,就将错误赋值给 err 。

Scan 方法通常用于从数据库查询结果中读取数据并填充到指定的变量或数据结构中。

问题:为什么listModel能调用Scan 方法?

因为 listModel 依赖 gdb.Model ,并且 gdb.Model 或其相关类型定义了 Scan 方法,那么 listModel 就能够调用这个方法。这意味着 listModel 可能继承了或包含了与 gdb.Model 相关的功能和方法,从而能够使用 Scan 来处理查询结果的映射。


	// 没有数据
	if len(list) == 0 {
		return output, nil
	}
	output.Total, err = m.Count()
	if err != nil {
		return output, err
	}

我们接着往下看

这段代码首先检查 `list` 的长度,如果长度为 0 (即没有数据),则直接返回 `output` 且不附带错误(`nil`)。

如果 `list` 不为空,就通过 `m.Count()` 计算数据的总数并将结果存储到 `output.Total` 中。如果在计算总数的过程中发生错误,就返回 `output` 和这个错误。

	// Content
	if err := listModel.ScanList(&output.List, "Content"); err != nil {
		return output, err
	}

使用 listModel 的 ScanList 方法将查询结果扫描到 output.List 中,并且指定了扫描的类型为 "Content" 。如果在扫描过程中出现错误,就返回 output 和这个错误。

进行这一步 `listModel.ScanList(&output.List, "Content")` 通常是为了将从数据库查询到的数据填充到 `output.List` 中,以便后续在程序中对这些数据进行处理、展示或进行其他逻辑操作。

指定 `"Content"` 可能是为了表明正在处理的是与 "Content" 相关的数据类型或类别,这有助于在代码的逻辑中更清晰地标识和区分不同类型的数据处理。

我们接着往下看

	err = dao.Category.
		Fields(model.ContentListCategoryItem{}).
		Where(dao.Category.C.Id, gutil.ListItemValuesUnique(output.List, "Content", "CategoryId")).
		ScanList(&output.List, "Category", "Content", "id:CategoryId")
	if err != nil {
		return output, err
	}

这段代码的作用是在 `dao.Category` 中进行一系列的数据库操作。

首先通过 `Fields(model.ContentListCategoryItem{})` 指定要获取的字段。然后使用 `Where` 子句根据 `dao.Category.C.Id` 的值进行筛选,筛选的值来自于 `output.List` 中 `"Content"` 对应的 `"CategoryId"` 的唯一值列表。 接着使用 `ScanList` 方法将查询结果扫描到 `output.List` 中,并指定了一些相关的参数 `"Category"` 、 `"Content"` 、 `"id:CategoryId"` 。

如果在这个过程中出现错误(`err` 不为 `nil`),就返回 `output` 和这个错误。 这样做的目的通常是为了获取与特定条件匹配的分类数据,并与已有的 `output.List` 中的内容进行关联或整合,以满足特定的业务需求。

Fields(model.ContentListCategoryItem{}) 通常用于指定要从数据库中获取的字段

在这里,model.ContentListCategoryItem{} 是一个定义了特定字段结构的对象,通过将其传递给 Fields 方法,来告知数据库操作只获取与该结构中定义的字段相关的数据

问题:Fields 方法是干啥的?

Fields 方法通常用于指定在数据库查询操作中要获取的字段。通过调用 Fields 并传入特定的参数(例如您这里的 model.ContentListCategoryItem{} ),可以限制返回的结果只包含指定的这些字段,而不是获取所有字段,从而提高查询效率和减少不必要的数据传输。

我们接着往下看:

	// User
	err = dao.User.
		Fields(model.ContentListUserItem{}).
		Where(dao.User.C.Id, gutil.ListItemValuesUnique(output.List, "Content", "UserId")).
		ScanList(&output.List, "User", "Content", "id:UserId")
	if err != nil {
		return output, err
	}
	return
}

这段代码是在对 dao.User 进行数据库操作。

首先通过 Fields(model.ContentListUserItem{}) 指定要获取的用户字段。然后使用 Where 子句根据 dao.User.C.Id 的值进行筛选,筛选的值来自于 output.List 中 "Content" 对应的 "UserId" 的唯一值列表。

接着使用 ScanList 方法将查询结果扫描到 output.List 中,并指定了相关的参数 "User" 、 "Content" 、 "id:UserId" 。

ScanList(&output.List, "User", "Content", "id:UserId") 这个方法调用的作用可能是将查询到的数据扫描并填充到 output.List 中。

参数 "User" 表示正在处理的是与用户相关的数据类型。

"Content"是某种数据类型或上下文的标识。

"id:UserId" 是在指定数据映射时,将数据库中的 id 字段与 UserId 进行关联或匹配。

如果整个过程中没有错误,就正常结束返回。

  • 22
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值