从 PHP 到 AI + Golang,程序员自救转型手记(四十六):再次优化基类
这是一个系列 Blog,作者将以一个 PHP 全栈工程师的身份,利用 AI 工具(TRAE、claude code、codex、deepseek、豆包等):从零开始学习 golang 语言,并最终完成 ai-go-admin(github | gitee)开源项目的制作,全程记录分享。
在上一期,我们进行了 “前端远程下拉输入组件”,本期将完成:再次优化基类
再次优化基类
列表数据有序保证
很多项目都没有做这个,甚至都不知道这个概念,这其实是一个数据库(含 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(数据适配器)。
以上都是固定的参数,但在实际业务场景中,很多时候需要能够向底层传递一些 自定义的扩展参数,规划如下:
仓储层与自定义扩展参数无关,最多传递到服务层即可
服务层额外接受一个
Extension的Options,类型为any,增加参数后完整的Options如下:复制代码// Options 通用服务操作选项// 每个方法所需要的选项都可以在此找到,但并非每个方法都会使用全部的选项type Options struct { OmitFields []string // 排除出入库字段,会传递给仓储层的 Omit 方法 SelectFields []string // 选择出入库字段,会传递给仓储层的 Select 方法 Wheres []Where // 查询条件,用于构建 WhereScopes,然后传递给仓储层的 Scopes 方法 SortField string // 排序字段,用于构建 OrderScope SortOrder string // 排序方式 Page int // 页码,用于构建 PaginateScope Limit int // 每页条数 PrimaryKeyValue string // 主键值,目前可供 Get、Update 方法获取数据行 PrimaryKeyValues []string // 主键切片,目前可供 Delete 方法批量删除行 Extension any // 任意自定义扩展参数}控制器层不仅仅是接受一个
Extension any,更合理的方式是接受一个函数ExtensionResolver func(c *gin.Context) any,可以将它称之为扩展数据解析器,函数返回值将被赋值给服务层的Extension any(直接于之前增加的BuildSerOpts(构建服务层选项数据)方法中调用一下解析器再赋值即可)。
以上是需求描述,也是给 AI 的提示词(Blog 会比实际发送的更加详细很多),AI 帮忙实现以上需求后,我特意实际使用了一下:
比如我们需要向服务层传递一个自定义的 AdminSession 参数,又不想重写控制器层的方法,首先在服务层定义好结构体(字段列表和类型完全自定义):
复制代码// AuthAdminRuleExtension 规则列表扩展参数type AuthAdminRuleExtension struct {AdminSession *dto.AdminSession}控制器 handler.NewHandler 时,额外使用 WithExtension 传递一个 扩展数据解析器 即可:
复制代码// NewAuthAdminRuleHandler 创建菜单和权限规则管理控制器实例func NewAuthAdminRuleHandler(svc *svcAuth.AuthAdminRuleService) *AuthAdminRuleHandler {return &AuthAdminRuleHandler{Handler: handler.NewHandler(svc, // 传递 `扩展数据解析器`handler.WithExtension(func(c *gin.Context) any {return &svcAuth.AuthAdminRuleExtension{// 传递 AdminSessionAdminSession: middleware.GetAdmin(c),}}),),svc: svc,}}服务层,读取和使用扩展数据:
复制代码// 从控制器传来的 `管理员信息` 扩展数据extension, ok := opts.Extension.(*AuthAdminRuleExtension)if !ok || extension.AdminSession == nil {return nil, errors.New("参数错误,缺少 AdminSession 扩展数据")}// opts.xxxx 参数,基本每个方法都有接受// 此时就可以使用 extension.AdminSession 了Create 默认忽略传递的主键字段入库
我们已经设计了 OmitFields 选项,用于配置忽略入库的字段,但是执行创建动作时,主键字段总是应该被忽略,我们在很早之前就写好了获取当前模型主键的方法,这里直接配合它,在 未设定 OmitFields 选项时,默认忽略掉主键字段。
-
08.03
时光杂货店批发市场有什么用 时光杂货店批发市场作用详细说明
-
08.03
时光杂货店商铺系统如何玩 时光杂货店商铺系统入门攻略
-
08.03
时光杂货店人才培养做法有哪些 时光杂货店人才培养攻略
-
08.03
时光杂货店哪个产业收益最高 时光杂货店产业收益解析
-
08.03
时光杂货店微氪入门指南 时光杂货店微氪如何玩
-
08.03
时光杂货店推广如何玩 时光杂货店推广玩法分享
-
-
- 通义灵码安装后为什么打不开?
- 08.03
-
-
-
- 鸣潮穗穗值得培养吗 穗穗配队声骸推荐
- 08.03
-
-
-
下载
- |
-
-
下载
- 《行尸走肉第一章》免安装中文汉化硬盘版下载
- 单机|436 MB
- 一款以动作冒险为主题的游戏
-
-
下载
- 《街头霸王X铁拳》免安装中文汉化硬盘版下载
- 单机|111MB
- 一款非常好玩的格斗游戏
-
-
下载
- |
-
-
下载
- 《暗黑破坏神3》免安装繁体中文正式版下载
- 单机|7630 MB
- 一款以角色扮演为主题的游戏
-
-
下载
- 《马克思佩恩3》免安装硬盘版下载
- 单机|27033 MB
- 一款以第三人称射击为主题的游戏