hugo/content/posts/golang_backend.md
2024-11-19 23:49:38 +08:00

208 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: golang后端开发笔记
author: Logic
date: 2022-11-08
categories: ["开发"]
draft: false
tags: []
---
1. [golang语言问题](#golang语言问题)
- [slice传参](#slice传参)
- [修改slice中struct的问题](#修改slice中struct的问题)
# golang后端开发笔记
之前的小程序使用的是原生的云开发方便固然方便但实际上并没有做太多的工作也不需要考虑过多的问题这次用golang重写后端算是对自身的一个挑战从头梳理后端开发的基本内容并且深入理解golang语言和网络相关的知识。
主要使用gin+gorm+Mysql+Redis
## golang语言问题
### slice传参
### 修改slice中struct的问题
在修改slice中的数据时自然会想到用for range循环。但在实际使用的过程中需要注意golang语言中range循环的一些特性。下面给出一个例子
```go
func main() {
slice := []int{10, 20, 30, 40}
for index, value := range slice {
fmt.Printf("value = %d , value-addr = %x , slice-addr = %x\n", value, &value, &slice[index])
}
}
```
这里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中给出了相关的说明官方给出的例子如下
```go
func main() {
var out []*int
for i := 0; i < 3; i++ {
out = append(out, &i)
}
fmt.Println("Values:", *out[0], *out[1], *out[2])
fmt.Println("Addresses:", out[0], out[1], out[2])
}
```
官方给出的例子也非常有趣,这个例子实际上的输出结果为
> Values: 3 3 3
>
> Addresses: 0x40e020 0x40e020 0x40e020
在Go中循环过程中的循环迭代器如第二个例子中的i以及第一个例子中的value都是一个单独的变量在循环进行过程中会将不同的值拷贝给这个变量这样的好处在于只需要为这个变量申请一次内存空间之后改变它的值就可以了。但是因为只是将不同的值赋给这一个变量该变量的地址一直没有变化这时我们将该变量的地址放入out切片中则最后通过解引用取得的值是该变量最后一次被赋予的值。即循环过程可以理解为
```go
func main(){
var new_variable int
var out []*int
// 模拟循环的过程
new_variable = 1
append(out, &new_variable)
new_variable = 2
append(out, &new_variable)
new_variable = 3
append(out, &new_variable)
}
```
这样一来这个循环最后的结果就很容易理解了。这个问题看似是一个小问题但在实际编写程序的过程中常常会因疏忽这个问题而出错比如在遍历一个结构体切片时赋值给迭代器的成员变量循环结束后会发现结构体切片中的值并没有改变实际上应该使用诸如out[i]这种通过下标访问的形式修改切片中的值,而不是使用迭代器。这点要时刻注意
### 使用的三方库更新问题
使用gorm时使用它的<a href="https://gorm.io/zh_CN/docs/update.html#%E4%BD%BF%E7%94%A8-SQL-%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%9B%B4%E6%96%B0">sql表达式更新</a>功能时发现会出现莫名其妙的bug, 更新表达式是减但是得到的sql语句是直接置零. 让我非常不解, 在研究文档的过程中, 我偶然发现当前的gorm文档是gorm 2.0,而我之前导入的包是gorm 1.0的包. 因此之前的1.0中可能并没有实现这个功能, 非常的尴尬. 将导入的包切换为gorm 2.0后一切功能正常.
这提醒我们在使用三方库时, 可能三方库会因为更新等原因有一些api上的重大变化, 要注意使用的包的版本.
### 结构体成员可访问的作用域问题
上例子
```go
usrid, _ := c.Get("user")
var reqData struct {
prompt string `json:"prompt"`
}
if err := c.BindJSON(&reqData); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request data"})
return
}
```
其中c是gin框架中的 `*gin.Context`, 这段代码乍一看好像没什么问题, 但实际运行就会发现reqData中的prompt无法通过bindjson绑定到post请求中的prompt字段的数据上. 在go语言中:
> 当标识符包括常量、变量、类型、函数名、结构字段等等以一个大写字母开头Group1那么使用这种形式的标识符的对象就可以被外部包的代码所使用客户端程序需要先导入这个包这被称为导出像面向对象语言中的 public标识符如果以小写字母开头则对包外是不可见的但是他们在整个包的内部是可见并且可用的像面向对象语言中的 private )。
因此在绑定json数据到reqData结构体时, 实际上实在其他包中访问了reqData结构体中的prompt属性, 但由于这个属性不是导出属性, 因此在其他包(这里是gin框架中json处理相关的包)中无法访问到这个属性, 自然也就不能修改它的值. 因此后面访问这个结构体reqData中的prompt属性时就会永远为空字符串.
正确代码应该如下
```go
usrid, _ := c.Get("user")
var reqData struct {
prompt string `json:"prompt"`
}
if err := c.BindJSON(&reqData); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request data"})
return
}
```
### 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同步源的配置要求
```conf
[mariadb]
log-bin = mysql-bin
server-id = 223322
max-binlog-size=400M
expire_logs_days = 7
binlog_format=row
```
根据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 用户,而不是所有用户,可以这样做:
```sh
sudo setfacl -m u:libvirt-qemu:x {目录名}
```
这个命令使用 ACL访问控制列表来专门给 libvirt-qemu 用户 对应 目录的执行权限。
确保 ISO 文件和 qcow2 文件有正确的读取权限:
```sh
sudo chmod 644 /kvm/ubuntu-20.04.6-desktop-amd64.iso
sudo chmod 644 /kvm/ubuntu_disk.qcow2
```
也给这些文件添加 libvirt-qemu 用户的读取权限:
```sh
sudo setfacl -m u:libvirt-qemu:r /kvm/ubuntu-20.04.6-desktop-amd64.iso
sudo setfacl -m u:libvirt-qemu:r /kvm/ubuntu_disk.qcow2
```
完成这些步骤后,再次尝试运行 virt-install 命令。
## casdoor接口开发问题
![11192ti5ijgPw2AI](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/11192ti5ijgPw2AI.png)
此处的id后面括号的意思是这个id是由 owner/name这样的组合而成的即owner加一个斜线再加一个name就构成了id。这个看源码可以发现如果用错了id会类似下面的错误
> GetOwnerAndNameFromId() error, wrong token count for ID:
同时对于接口`/api/get-user`restful api文档中声明的返回的数据
![1119dWSo2uhopXNf](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/1119dWSo2uhopXNf.png)
并不是只返回这个数据对象而是这个数据对象是在data中的数据除此之外还默认会返回status, msg,sub这三个字段就像post请求中一般的返回数据一样除了这三个字段外还有data字段这个字段会放Get请求中我们请求的目标数据。