西二Go组Work4总结
前前后后写了差不多一个星期,然后踩了一堆坑…
吐槽#
构式Proto#
感觉Hertz框架对Proto IDL的支持基本上是半残,主要遇到的问题:
proto3的
required参数校验失效,我试过自定义tag、在字段tag后面加required各种办法都法实现这个功能,因为proto3没用required关键字,所有的字段都是optional,所以我尝试改用proto2proto2的
required是正常的,但是生成出来的模型所有字段都是指针,无敌了,跑路了直接,构式proto支持无法正常生成
oneof的结构,因为业务数据要套一层data,所以就想直接写在oneof里,然后生成了一下发现直接成棍母了,石示例项目篡改生成的代码,最恶心的事情。当时参考了
hertz-examples的一些例子,头像上传接口的文件要塞在FileHeader里,我就找了一下发现他有个例子用bytes定义文件字段,生成模型有multipart.FileHeader字段,但是我自己写的时候他生成的是[]byte,想了半天到底何意味,然后在Hertz仓库的issues里面搜了一下,发现有个issue里说这玩意压根不支持生成multipart.FileHeader,然后示例文档里那个生成的模型显然是人为改的,什么叫"DO NOT EDIT",何意味。
以上的问题Thrift IDL都没有,甚至人家还支持设置默认值
构式Gen#
Gen的优点是可以根据数据库的表结构来生成对应的数据库模型和查询接口,但是这个查询接口非常垃圾,只能做一些非常简单的查询,稍微复杂点就是各种类型不匹配,例如子查询他支持就是依托构式,然后就只能写一堆LeftJoin去关联表去计数或者把查询分开
总结:不要用Gen生成查询,因为他真的也只能搓点CRUD了,复杂一点还是直接用Gorm吧,不然难受死… [某Issue]
碎碎念#
缓存#
项目结构是改了好几次,一直不知道要不要加repo层整合一下dao和cache,然后查了点资料,发现其实缓存不一定要塞repo里去整合,由service层去处理也可以,考虑到我才几个接口要实现缓存,所以就不写repo了,写个dao够用了,然后cache单独一个包,然后塞给service层去处理
模型转换#
一开始不知道dao转dto模型的方法应该塞哪里pkg? service? dao? 我觉得这层转换应该是交给service的,因为service作为dto和dao的中间层,所以应该由service层去处理这个模型转换
FFmpeg#
项目用FFmpeg去提取视频封面,还有给视频统一转码成H264 fmp4方便存储和视频分发。FFmpeg是一个命令行工具,所以传数据不是很方便,一开始是直接用两个pipe输入输出视频流,然后有一些视频是需要时不时回来Peek一下头的,然后pipe输入就爆了,所以后面直接调用系统的API去创建一个临时文件,然后把视频塞那里再转码,pipe是可以正常输出fmp4的,所以输出就正常pipe可以少些一些代码,而且fmp4也更适合视频网站
事务#
我这里Dao有写方法需要多次查询,多次查询应该用事务来保证原子性,但是我偷懒没写
约束#
我表一个约束和关系也没设计,所以很多时候都要多判断一个存在性,比如点赞的视频是否存在,多一个查询就直接变成N+1了…,也许下次可以赛一个外键约束然后这样创建点赞之类的炸了只要判断一下错误就行了,不需要去额外查询视频是否存在
上传视频#
我上传视频用Minio做对象存储,然后键正好要用视频ID,但是视频要插入之后才有ID。所以我一开始是先创建视频得到视频ID,然后再上传视频,最后更新视频的链接,这有一个问题,就是上传视频到对象存储有可能失败,失败之后那个视频会变成脏数据,需要给他清掉,也就是需要rollback,所以直接用了事务,炸了直接回滚不会有脏数据。不过我现在想了想其实可以直接生成一串UUID来作为视频和封面的键,然后视频上传成功了再在数据库里创建视频,这样就不需要更新视频和封面链接了。不过我好像没处理视频脏数据…,如果发生回滚,那对象存储的视频没删掉
热门视频排行榜缓存#
这个接口要分页,所以一开始想要不把视频+分页+页码作为键,但是这样就会一点抽象,因为页码和分页组合会有很多,最后的办法是缓存固定的数量,比如缓存前100个视频数据,然后缓存命中就在service里找到用户请求的那部分并且返回,没命中再查数据库。不过我设计的貌似是有点问题的,比如引入缓存后如果如果排行榜突然变化很大,那么就会更新不及时,没有机制来清除缓存,但是10分钟缓存,我觉得对热门排行榜来说问题不大
点赞操作缓存#
一开始是想缓存一个视频所有点赞用户的ID集合,然后感觉视频动辄几万点赞,感觉存这个redis要爆内存,所以改成存用户点赞过的视频
Docker编译#
docker镜像构建时的环境变量和宿主机是独立的,所以要单独设GO_PROXY,不然慢死
Docker依赖#
因为镜像是精简过的,所以可能会缺时区数据,要用装一个tzdata。而且要用到ffmpeg,所以也要用提前装好,还有ca-certificates,虽然目前没遇到炸CA证书的,但是保险起见装一下
不装tzdata加时区环境,变量数据库初始化直接炸panic,大坑
Context#
context按理说应该注入到service然后逐层传递来做超时控制,我偷懒全塞Background()了,超级技术债Be Like
响应打包#
发现很多API的响应是一样的,也许可以做几个公共方法,来减少重复代码
视频访问量统计#
我没看懂Apifox里那个视频要如何增加访问量,我不觉得直接从下载视频的URL里面计数是好主意,因为这些视频最后肯定要给CDN缓存的,要计数只能no-cache,这绝对要给服务器打炸,所以我单独设计了个visit接口来增加访问量
刷新Token接口#
Apifox的示例里好像每次都会携带Access和Refresh两个Token,我觉得是不太好的,两个一起带安全性也不够,所以我请求只带了AccessToken,然后加了个接口来用RefreshToken刷新
Slices.Contains#
才发现Go的切片原来有Contains…
这里还没有任何文章可以列出。