中文资讯 掘金 AI Sat, 01 Aug 2026 正文 ok 239 words

从 PHP 到 AI + Golang,程序员自救转型手记(四十六):再次优化基类

英文标题:从 PHP 到 AI + Golang,程序员自救转型手记(四十六):再次优化基类

这是一个系列 Blog,作者将以一个 PHP 全栈工程师的身份,利用 AI 工具(TRAE、claude code、codex等):从零开始学习 golang 语言,并最终完成实战项目。
打开原文相关 3 · 机会 0
media3.2 KB media589 B media12.9 KB

中文

这是一个系列 Blog,作者将以一个 PHP 全栈工程师的身份,利用 AI 工具(TRAE、claude code、codex、deepseek、豆包等):从零开始学习 golang 语言,并最终完成 ai-go-admin(github | gitee)开源项目的制作,全程记录分享。

在上一期,我们进行了 “前端远程下拉输入组件”,本期将完成:再次优化基类

再次优化基类

基类严格来讲不是 go 里边的概念,在本 blog 中使用它范指 基控制器、基服务、基仓储

列表数据有序保证

很多项目都没有做这个,甚至都不知道这个概念,这其实是一个数据库(含 PostgreSQL、MySQL)使用的小坑,在未指定排序子句 ORDER BY 时:数据库并不保证排序,即先查到哪行就输出哪行且不保证多次相同查询中的数据输出顺序

也就是说,只要是列表查询,开发者必需传递 ORDER BY 子句,如果不传递,你看的有序只是临时的,下次未必还是这个顺序,这在分页等场景可能是灾难性的。

实践中,我们以用户传递的排序字段优先,如果没有传递,默认读取主键字段做有序保证,且就算有传递排序字段,依旧以主键字段托底 即可(防止用户排序字段值有一样的,此时又会丢失有序保证)。

基控制器封装 BuildSerOpts 方法,用于构建服务层选项数据

比如 Get 方法,现在组装服务层选项是这样写的:

entity, err := h.svc.Get(c, service.Options{
    Omit:       h.cfg.Omit.Get,
    Select:     h.cfg.Select.Get,
    PrimaryKey: c.Param("pk"),
})

增加 BuildSerOpts 方法后,可以简化为:

opts := h.BuildSerOpts(c, "Get", Request{
    PrimaryKeyValue: c.Param("pk"),
})

entity, err := h.svc.Get(c, opts)

即:无需再单独传递 Omit 和 Select 选项;而且在 List 方法中收益更大,代码可以由原来的 7 行改为单行:opts := h.BuildSerOpts(c, "List", req.Request)。

基类增加自定义扩展参数解析函数

目前,我们可以为基类配置 OmitFields(各动作的 Omit 黑名单字段)、SelectFields(设置各动作的 Select 白名单字段)、Adapter(数据适配器)。

以上都是固定的参数,但在实际业务场景中,很多时候需要能够向底层传递一些 自定义的扩展参数,规划如下:

  1. 仓储层与自定义扩展参数无关,最多传递到服务层即可

  2. 服务层额外接受一个 Extension 的 Options,类型为 any,增加参数后完整的 Options 如下:

    type Options struct {
        OmitFields       []string 
        SelectFields     []string 
        Wheres           []Where  
        SortField        string   
        SortOrder        string   
        Page             int      
        Limit            int      
        PrimaryKeyValue  string   
        PrimaryKeyValues []string 
        Extension        any      
    }
    
  3. 控制器层不仅仅是接受一个 Extension any,更合理的方式是接受一个函数 ExtensionResolver func(c *gin.Context) any,可以将它称之为 扩展数据解析器,函数返回值将被赋值给服务层的 Extension any(直接于之前增加的 BuildSerOpts(构建服务层选项数据)方法中调用一下解析器再赋值即可)。

以上是需求描述,也是给 AI 的提示词(Blog 会比实际发送的更加详细很多),AI 帮忙实现以上需求后,我特意实际使用了一下:

比如我们需要向服务层传递一个自定义的 AdminSession 参数,又不想重写控制器层的方法,首先在服务层定义好结构体(字段列表和类型完全自定义):

type AuthAdminRuleExtension struct {
	AdminSession *dto.AdminSession
}

控制器 handler.NewHandler 时,额外使用 WithExtension 传递一个 扩展数据解析器 即可:

func NewAuthAdminRuleHandler(svc *svcAuth.AuthAdminRuleService) *AuthAdminRuleHandler {
	return &AuthAdminRuleHandler{
		Handler: handler.NewHandler(svc,
            
			handler.WithExtension(func(c *gin.Context) any {
				return &svcAuth.AuthAdminRuleExtension{
                    
					AdminSession: middleware.GetAdmin(c),
				}
			}),
		),
		svc: svc,
	}
}

服务层,读取和使用扩展数据:

extension, ok := opts.Extension.(*AuthAdminRuleExtension)
if !ok || extension.AdminSession == nil {
    return nil, errors.New("参数错误,缺少 AdminSession 扩展数据")
}

Create 默认忽略传递的主键字段入库

我们已经设计了 OmitFields 选项,用于配置忽略入库的字段,但是执行创建动作时,主键字段总是应该被忽略,我们在很早之前就写好了获取当前模型主键的方法,这里直接配合它,在 未设定 OmitFields 选项时,默认忽略掉主键字段。

English / 原文

这是一个系列 Blog,作者将以一个 PHP 全栈工程师的身份,利用 AI 工具(TRAE、claude code、codex、deepseek、豆包等):从零开始学习 golang 语言,并最终完成 ai-go-admin(github | gitee)开源项目的制作,全程记录分享。

在上一期,我们进行了 “前端远程下拉输入组件”,本期将完成:再次优化基类

再次优化基类

基类严格来讲不是 go 里边的概念,在本 blog 中使用它范指 基控制器、基服务、基仓储

列表数据有序保证

很多项目都没有做这个,甚至都不知道这个概念,这其实是一个数据库(含 PostgreSQL、MySQL)使用的小坑,在未指定排序子句 ORDER BY 时:数据库并不保证排序,即先查到哪行就输出哪行且不保证多次相同查询中的数据输出顺序

也就是说,只要是列表查询,开发者必需传递 ORDER BY 子句,如果不传递,你看的有序只是临时的,下次未必还是这个顺序,这在分页等场景可能是灾难性的。

实践中,我们以用户传递的排序字段优先,如果没有传递,默认读取主键字段做有序保证,且就算有传递排序字段,依旧以主键字段托底 即可(防止用户排序字段值有一样的,此时又会丢失有序保证)。

基控制器封装 BuildSerOpts 方法,用于构建服务层选项数据

比如 Get 方法,现在组装服务层选项是这样写的:

entity, err := h.svc.Get(c, service.Options{
    Omit:       h.cfg.Omit.Get,
    Select:     h.cfg.Select.Get,
    PrimaryKey: c.Param("pk"),
})

增加 BuildSerOpts 方法后,可以简化为:

opts := h.BuildSerOpts(c, "Get", Request{
    PrimaryKeyValue: c.Param("pk"),
})

entity, err := h.svc.Get(c, opts)

即:无需再单独传递 Omit 和 Select 选项;而且在 List 方法中收益更大,代码可以由原来的 7 行改为单行:opts := h.BuildSerOpts(c, "List", req.Request)。

基类增加自定义扩展参数解析函数

目前,我们可以为基类配置 OmitFields(各动作的 Omit 黑名单字段)、SelectFields(设置各动作的 Select 白名单字段)、Adapter(数据适配器)。

以上都是固定的参数,但在实际业务场景中,很多时候需要能够向底层传递一些 自定义的扩展参数,规划如下:

  1. 仓储层与自定义扩展参数无关,最多传递到服务层即可

  2. 服务层额外接受一个 Extension 的 Options,类型为 any,增加参数后完整的 Options 如下:

    type Options struct {
        OmitFields       []string 
        SelectFields     []string 
        Wheres           []Where  
        SortField        string   
        SortOrder        string   
        Page             int      
        Limit            int      
        PrimaryKeyValue  string   
        PrimaryKeyValues []string 
        Extension        any      
    }
    
  3. 控制器层不仅仅是接受一个 Extension any,更合理的方式是接受一个函数 ExtensionResolver func(c *gin.Context) any,可以将它称之为 扩展数据解析器,函数返回值将被赋值给服务层的 Extension any(直接于之前增加的 BuildSerOpts(构建服务层选项数据)方法中调用一下解析器再赋值即可)。

以上是需求描述,也是给 AI 的提示词(Blog 会比实际发送的更加详细很多),AI 帮忙实现以上需求后,我特意实际使用了一下:

比如我们需要向服务层传递一个自定义的 AdminSession 参数,又不想重写控制器层的方法,首先在服务层定义好结构体(字段列表和类型完全自定义):

type AuthAdminRuleExtension struct {
	AdminSession *dto.AdminSession
}

控制器 handler.NewHandler 时,额外使用 WithExtension 传递一个 扩展数据解析器 即可:

func NewAuthAdminRuleHandler(svc *svcAuth.AuthAdminRuleService) *AuthAdminRuleHandler {
	return &AuthAdminRuleHandler{
		Handler: handler.NewHandler(svc,
            
			handler.WithExtension(func(c *gin.Context) any {
				return &svcAuth.AuthAdminRuleExtension{
                    
					AdminSession: middleware.GetAdmin(c),
				}
			}),
		),
		svc: svc,
	}
}

服务层,读取和使用扩展数据:

extension, ok := opts.Extension.(*AuthAdminRuleExtension)
if !ok || extension.AdminSession == nil {
    return nil, errors.New("参数错误,缺少 AdminSession 扩展数据")
}

Create 默认忽略传递的主键字段入库

我们已经设计了 OmitFields 选项,用于配置忽略入库的字段,但是执行创建动作时,主键字段总是应该被忽略,我们在很早之前就写好了获取当前模型主键的方法,这里直接配合它,在 未设定 OmitFields 选项时,默认忽略掉主键字段。