golang后端开发笔记
golang后端开发笔记
之前的小程序使用的是原生的云开发,方便固然方便但实际上并没有做太多的工作,也不需要考虑过多的问题,这次用golang重写后端算是对自身的一个挑战,从头梳理后端开发的基本内容并且深入理解golang语言和网络相关的知识。 主要使用gin+gorm+Mysql+Redis
golang语言问题
slice传参
修改slice中struct的问题
在修改slice中的数据时,自然会想到用for range循环。但在实际使用的过程中需要注意golang语言中range循环的一些特性。下面给出一个例子
|
|
这里value如果代表的是slice切片中的各个数据,那么value的地址理应随着循环发生变化,但是实际上输出的结果为
value = 10 , value-addr = c4200aedf8 , slice-addr = c4200b0320
value = 20 , value-addr = c4200aedf8 , slice-addr = c4200b0328
value = 30 , value-addr = c4200aedf8 , slice-addr = c4200b0330
value = 40 , value-addr = c4200aedf8 , slice-addr = c4200b0338
显然value的地址并没有发生变化,这说明value本身是一个结构,在循环过程中只是将对应的值拷贝到了value当中,value并不是指向切片中对应值的指针。 关于这点,在golang的官方wiki中的CommonMistakes中给出了相关的说明,官方给出的例子如下
|
|
官方给出的例子也非常有趣,这个例子实际上的输出结果为
Values: 3 3 3
Addresses: 0x40e020 0x40e020 0x40e020
在Go中,循环过程中的循环迭代器如第二个例子中的i以及第一个例子中的value都是一个单独的变量,在循环进行过程中会将不同的值拷贝给这个变量,这样的好处在于只需要为这个变量申请一次内存空间,之后改变它的值就可以了。但是因为只是将不同的值赋给这一个变量,该变量的地址一直没有变化,这时我们将该变量的地址放入out切片中,则最后通过解引用取得的值是该变量最后一次被赋予的值。即循环过程可以理解为
|
|
这样一来这个循环最后的结果就很容易理解了。这个问题看似是一个小问题,但在实际编写程序的过程中常常会因疏忽这个问题而出错,比如在遍历一个结构体切片时,赋值给迭代器的成员变量,循环结束后会发现结构体切片中的值并没有改变,实际上应该使用诸如out[i]这种通过下标访问的形式修改切片中的值,而不是使用迭代器。这点要时刻注意
使用的三方库更新问题
使用gorm时使用它的sql表达式更新功能时发现会出现莫名其妙的bug, 更新表达式是减但是得到的sql语句是直接置零. 让我非常不解, 在研究文档的过程中, 我偶然发现当前的gorm文档是gorm 2.0,而我之前导入的包是gorm 1.0的包. 因此之前的1.0中可能并没有实现这个功能, 非常的尴尬. 将导入的包切换为gorm 2.0后一切功能正常.
这提醒我们在使用三方库时, 可能三方库会因为更新等原因有一些api上的重大变化, 要注意使用的包的版本.
结构体成员可访问的作用域问题
上例子
|
|
其中c是gin框架中的 *gin.Context
, 这段代码乍一看好像没什么问题, 但实际运行就会发现reqData中的prompt无法通过bindjson绑定到post请求中的prompt字段的数据上. 在go语言中:
当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 private )。
因此在绑定json数据到reqData结构体时, 实际上实在其他包中访问了reqData结构体中的prompt属性, 但由于这个属性不是导出属性, 因此在其他包(这里是gin框架中json处理相关的包)中无法访问到这个属性, 自然也就不能修改它的值. 因此后面访问这个结构体reqData中的prompt属性时就会永远为空字符串.
正确代码应该如下
|
|
gorm中由于数据类型不同对空值的不同处理
在gorm中,如果某个字段是string类型,则当该字段不存在时默认授予这个字段空字符串,但sql中标准的空值类型应该为NULL。如果postgresql中某个字段不是字符串相关的类型,则想将这个字段默认设为NULL,应该在gorm的数据映射模型中将这个字段的类型映射为可能类型的指针。
架构设计思路
如何实现对有很长处理过程的图片生成任务的查询
对于midjourney, 自身提供了对任务进度的查询功能, 则直接将其自带的接口封装,由前端进行轮询, 当任务完成时将生成图片上传到自己的图床, 并更新数据库存入完成的图片url数据.
对于dalle, 由于自身并未提供对任务进度的查询功能, 则为了接口的统一性, 自行封装一层, 只要绘图未完成进度就始终为0. 将未完成的任务id和用户id保存在redis中, 当完成绘图时同样上传到自己图床, 并更新redis和数据库中的数据, 前端调用接口查询到成功生成的图片数据后, 删除redis中的已完成数删除redis中的已完成数据.
由此, 数据库中保存用户id, 调用的生图模型, 生图的提示词和成功生成的图片url即可. 生成的图片保存时间有限, 因此要将图片上传到图床以长时间保存. 而对于用户,先返回生成图片的url即可, 后续再次访问时从数据库中取数据则是图床的url.
使用airbyte进行mysql和mongodb的数据库同步
设置mariadb的log-bin和server-id
mariadb的同步设置在/etc/my.cnf或者/etc/my.cnf.d目录下, manjaro中是在/etc/my.cnf.d目录下, 打开这个目录下的server.conf文件, 在[mariadb]下面添加如下内容, 根据airbyte的mysql同步源的配置要求
|
|
根据airbyte的官方教程, 使用官方一键安装脚本启动一个docker, 安装所有容器并启动即可, 官方的脚本是下载一个docker-compose.yml文件, 再使用docker命令启动这些docker.
我在manjaro上启动时, worker会出现一个bug, 导致启动失败, 在ubuntu上就没有出现错误, 正常启动.
启动后, 进入网页, 输入默认用户名密码 airbyte/password, 设置同步的mysql源和mysql目标, 创建一个connection进行同步即可.
使用seatunnel进行mysql的同步
airbyte在同步decimal类型的数据时会默认转换为float, 从而丢失精度, 我的数据仅仅只有六位小数在同步的过程中最后两位的精度还会丢失. 因此尝试使用seatunnel进行mysql的同步.
kvm 启动问题
WARNING /kvm/ubuntu-20.04.6-desktop-amd64.iso may not be accessible by the hypervisor. You will need to grant the ’libvirt-qemu’ user search permissions for the following directories: [’’] WARNING /kvm/ubuntu_disk.qcow2 may not be accessible by the hypervisor. You will need to grant the ’libvirt-qemu’ user search permissions for the following directories: [’’] WARNING /kvm/ubuntu-20.04.6-desktop-amd64.iso may not be accessible by the hypervisor. You will need to grant the ’libvirt-qemu’ user search permissions for the following directories: [’']
这些警告信息表明 libvirt-qemu 用户没有足够的权限来访问 警告对应的 目录及其子目录中的文件。
如果希望限制权限只给 libvirt-qemu 用户,而不是所有用户,可以这样做:
|
|
这个命令使用 ACL(访问控制列表)来专门给 libvirt-qemu 用户 对应 目录的执行权限。
确保 ISO 文件和 qcow2 文件有正确的读取权限:
|
|
也给这些文件添加 libvirt-qemu 用户的读取权限:
|
|
完成这些步骤后,再次尝试运行 virt-install 命令。
casdoor接口开发问题
此处的id后面括号的意思是这个id是由 owner/name这样的组合而成的,即owner加一个斜线再加一个name就构成了id。这个看源码可以发现,如果用错了id会类似下面的错误
GetOwnerAndNameFromId() error, wrong token count for ID:
同时对于接口/api/get-user
restful api文档中声明的返回的数据
并不是只返回这个数据对象,而是这个数据对象是在data中的数据,除此之外还默认会返回status, msg,sub这三个字段,就像post请求中一般的返回数据一样,除了这三个字段外还有data字段,这个字段会放Get请求中我们请求的目标数据。