草庐IT

可能这就是做开源项目的意义吧!

王中阳Go 2023-03-28 原文

本文重点

  1. 使用goframe v2最新版的最佳实践
  2. 列表取值slice容量初始化,避免scan动态扩容
  3. slice的延迟初始化
  4. 更新操作注意的问题
老规则:我把详细步骤已经整理好,大家可以参考这个步骤进行开发,更欢迎提优化建议。

取值列表优化

下方代码示例是项目之前的列表取值写法,和官方示例focus-single写法一样,思路如下:

  1. 获得*gdb.Model对象,方便后续调用
  2. 实例化返回结构体
  3. 分页查询
  4. 执行查询和赋值(只是为了查询有无数据,并没有赋值到响应结构体中)
  5. 无数据判断
  6. 再查询count,获得数据个数
  7. 把查询到的结果赋值到响应结构体中
每段代码都写清楚了注释,这么写能实现功能,但是性能不够好,还有优化空间:

// GetList 查询内容列表
func (s *sAdmin) GetList(ctx context.Context, in model.AdminGetListInput) (out *model.AdminGetListOutput, err error) {
//1.获得*gdb.Model对象,方便后续调用
var (
m = dao.AdminInfo.Ctx(ctx)
)
//2. 实例化返回结构体
out = &model.AdminGetListOutput{
Page: in.Page,
Size: in.Size,
}
//3. 分页查询
listModel := m.Page(in.Page, in.Size)
//4. 执行查询和赋值(只是为了查询有无数据,并没有赋值到响应结构体中)
var list []*entity.AdminInfo
if err := listModel.Scan(&list); err != nil {
return out, err
}
//5.无数据判断
if len(list) == 0 {
return out, nil
}
//6. 再查询count,获得数据个数
out.Total, err = m.Count()
if err != nil {
return out, err
}
//7. 把查询到的结果赋值到响应结构体中
if err := listModel.Scan(&out.List); err != nil {
return out, err
}
return
}
整理一下上面代码的问题:

  1. 步骤4没有必要,可以直接查询count,如果count为0直接返回;否则再执行查询赋值操作
  2. 上述这种写法有个问题:当没有查询到数据时,list值为null,但是我们期望的返回值为空数组[]

  1. 还有就是slice的容量初始化下会更好,scan期间不会有扩容行为
  2. 再者就是延迟slice的初始化,如果前面出错,就没有必要实例化列表了
我们优化一下代码,优化后的代码如下,也写了详细的注释:

  1. 获得*gdb.Model对象,方便后续调用
  2. 实例化响应结构体
  3. 分页查询
  4. 再查询count,判断有无数据
  5. 延迟初始化list切片 确定有数据,再按期望大小初始化切片容量
  6. 把查询到的结果赋值到响应结构体中
// GetList 查询内容列表
func (s *sAdmin) GetList(ctx context.Context, in model.AdminGetListInput) (out *model.AdminGetListOutput, err error) {
//1. 获得*gdb.Model对象,方便后续调用
m := dao.AdminInfo.Ctx(ctx)
//2. 实例化响应结构体
out = &model.AdminGetListOutput{
Page: in.Page,
Size: in.Size,
}
//3. 分页查询
listModel := m.Page(in.Page, in.Size)
//4. 再查询count,判断有无数据
out.Total, err = m.Count()
if err != nil || out.Total == 0 {
//解决空数据返回[] 而不是返回nil的问题
out.List = make([]model.AdminGetListOutputItem, 0, 0)
return out, err
}
//5. 延迟初始化list切片 确定有数据,再按期望大小初始化切片容量
out.List = make([]model.AdminGetListOutputItem, 0, in.Size)
//6. 把查询到的结果赋值到响应结构体中
if err := listModel.Scan(&out.List); err != nil {
return out, err
}
return
}
优化代码之后,无数据的list返回格式和预期一样为[]:

这是有数据的返回结果示例:

以上优化记录已经同步到GitHub,欢迎查看、复刻经验:

​https://github.com/wangzhongyang007/goframe-shop-v2/commit/ee020ea96616c30cb5bce5f7ab24417ad56e1a67​

上面的例子很简单,就是普通的查询数据,也没有搜索条件,也不涉及到模型关联。

关联查询取值

咱们再举一个复杂点的例子,带大家进阶一下,我就直接安排优化后的代码了:

  1. 定义全局通用的查询语句
  2. 实例化响应结构体
  3. 翻页查询
  4. 优先查询count,报错或者无数据则直接返回
  5. 延迟初始化list 确定有数据再按期望大小,实例化切片的容量
  6. 进一步优化:根据传入参数区分查询对应的关联模型
// GetList 查询内容列表
func (*sCollection) GetList(ctx context.Context, in model.CollectionListInput) (out *model.CollectionListOutput, err error) {
//1. 定义全局通用的查询语句
userId := gconv.Uint(ctx.Value(consts.CtxUserId))
m := dao.CollectionInfo.Ctx(ctx).Where(dao.CollectionInfo.Columns().Type, in.Type).
Where(dao.CollectionInfo.Columns().UserId, userId)
//2. 实例化响应结构体
out = &model.CollectionListOutput{
Page: in.Page,
Size: in.Size,
}
//3. 翻页查询
listModel := m.Page(in.Page, in.Size)
//4. 优先查询count,报错或者无数据则直接返回
out.Total, err = listModel.Count()
if err != nil || out.Total == 0 {
out.List = make([]model.CollectionListOutputItem, 0, 0)
return out, err
}
//5. 延迟初始化list 确定有数据再按期望大小,实例化切片的容量
out.List = make([]model.CollectionListOutputItem, 0, in.Size)
//6. 进一步优化:根据传入参数区分查询对应的关联模型
if in.Type == consts.CollectionTypeGoods {
if err := listModel.With(model.GoodsItem{}).Scan(&out.List); err != nil {
return out, err
}
} else if in.Type == consts.CollectionTypeArticle {
if err := listModel.With(model.ArticleItem{}).Scan(&out.List); err != nil {
return out, err
}
} else {
if err := listModel.WithAll().Scan(&out.List); err != nil {
return out, err
}
}
return
}
上面的示例使用了with模型关联,核心思路是:根据传入参数区分查询对应的关联模型,不做无效查询。

更新操作

使用OmitEmpty,更新操作过滤空值,比如:

func (*sAddress) Update(ctx context.Context, in model.UpdateAddressInput) (err error) {
if _, err = dao.AddressInfo.Ctx(ctx).Data(in).OmitEmpty().Where(dao.AddressInfo.Columns().Id, in.Id).Update(); err != nil {
return err
}
return nil
}
我开发过程中原本没有使用OmitEmpty(),忽略了这个问题,感谢这位朋友提的建议。

开源项目地址:

做开源项目这件事,从没想过一蹴而就,想得一直是越来越好,投入长期精力:​https://github.com/wangzhongyang007/goframe-shop-v2​

本文转载自微信公众号「 程序员升级打怪之旅」,作者「王中阳Go」,可以通过以下二维码关注。

转载本文请联系「 程序员升级打怪之旅」公众号。

有关可能这就是做开源项目的意义吧!的更多相关文章

  1. ruby - 如何以所有可能的方式将字符串拆分为长度最多为 3 的连续子字符串? - 2

    我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123

  2. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  3. ruby-on-rails - 项目升级后 Pow 不会更改 ruby​​ 版本 - 2

    我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby​​版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby​​版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘

  4. ruby-on-rails - 新 Rails 项目 : 'bundle install' can't install rails in gemfile - 2

    我已经像这样安装了一个新的Rails项目:$railsnewsite它执行并到达:bundleinstall但是当它似乎尝试安装依赖项时我得到了这个错误Gem::Ext::BuildError:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcheckingforlibkern/OSAtomic.h...yescreatingMakefilemake"DESTDIR="cleanmake"DESTDIR="

  5. Ruby 从大范围中获取第 n 个项目 - 2

    假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit

  6. 旋转矩阵的几何意义 - 2

    点向量坐标矩阵的几何意义介绍旋转矩阵的几何含义之前,先介绍一下点向量坐标矩阵的几何含义点:在一维空间下就是一个标量,如同一条直线上,以任意某一个位置为0点,以一定的尺度间隔为1,2,3...,相反方向为-1,-2,-3...;如此就形成了一维坐标系,这时候任何一个点都可以用一个数值表示,如点p1=5,即即从原点出发沿着x轴正方向移动5个尺度;点p2=-3,负方向移动3个尺度;     在一维坐标系上过原点做垂直于一维坐标系的直线,则形成了二维坐标系,此时描述一个点需要两个数值来表示点p3=(3,2),即从原点出发沿着x轴正方向移动3个尺度,在此基础上沿着y轴正方向移动两个尺度的位置就是点p3。

  7. ruby - 如何在 Ruby 字符串中插入项目符号字符? - 2

    我正在尝试创建一个带有项目符号字符的Ruby1.9.3字符串。str="•"+"helloworld"但是,当我输入它时,我收到有关非ASCII字符的语法错误。我该怎么做? 最佳答案 你可以把Unicode字符放在那里。str="\u2022"+"helloworld" 关于ruby-如何在Ruby字符串中插入项目符号字符?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/1195

  8. ruby - 在 Rails 项目中测试本地版本的 gem - 2

    我的Rails站点使用了一个确实不是很好的gem。每次我需要做一些新的事情时,我最终不得不花费与向实际Rails项目添加代码一样多的时间来为gem添加功能。但我不介意,我将我的Gemfile设置为指向我的gem的GitHub分支(我尝试提交PR,但维护者似乎已经下台)。问题是我真的没有找到一种合理的方法来测试我添加到gem的新东西。在railsc中测试它会特别好,但我能想到的唯一方法是a)更改~/.rvm/gems/.../foo。rb,这看起来不对或者b)升级版本,推送到Github,然后运行​​bundleup,这除了耗时之外显然是一场灾难,因为我不确定我所做的promise是否正

  9. Ruby Regex,获取所有可能的匹配项(不截断字符串) - 2

    我遇到了ruby​​正则表达式的问题。我需要找到所有(可能重叠的)匹配项。这是问题的简化:#Simpleexample"Hey".scan(/../)=>["He"]#Actualresults#Withoverlappingmatchestheresultshouldbe=>["He"],["ey"]我尝试执行并获得所有结果的正则表达式如下所示:"aaaaaa".scan(/^(..+)\1+$/)#Thislooksformultiplesof(here)"a"biggerthanonethat"fills"theentirestring."aa"*3=>true,"aaa"*2=

  10. ruby - 合并 nanoc 中的项目 - 2

    我一直在尝试使用nanoc用于生成静态网站。我需要组织一个复杂的排列页面,我想让我的内容保持干燥。包含或合并的概念在nanoc系统中如何运作?我已阅读文档,但似乎找不到我想要的内容。例如:我如何获取两个部分内容项并将它们合并到一个新的内容项中。在staticmatic您可以在您的页面中执行以下操作。=partial('partials/shared/navigation')类似的约定在nanoc中如何运作? 最佳答案 这里是nanoc的作者。在nanoc中,部分是布局。因此,您可以拥有layouts/partials/shared/

随机推荐