Last updated on 4 months ago
仓库 觉得写的好就给个star吧https://github.com/taosu0216/go_stu/tree/main/IM_project
IM即时消息系统 照着马士兵的做的https://www.bilibili.com/video/BV1rK4y1w7JB/?p=5&spm_id_from=pageDriver&vd_source=593e95872463bd08c88726bf6aade29c 记一下中间的笔记
Day1 下载gorm go get gorm.io/gorm 前面下错成 jinzhu/gorm 了,但是说那个用的少 测试单独新建test目录 gorm文档https://gorm.io/zh_CN/docs/index.html
msb的官方代码库https://git.mashibing.com/msb_47094/GinChat
go官方文档(用来搜第三方包)https://pkg.go.dev/
gorm官方文档的入门例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package mainimport ( "gorm.io/gorm" "gorm.io/driver/sqlite" )type Product struct { gorm.Model Code string Price uint }func main () { db, err := gorm.Open(sqlite.Open("test.db" ), &gorm.Config{}) if err != nil { panic ("failed to connect database" ) } db.AutoMigrate(&Product{}) db.Create(&Product{Code: "D42" , Price: 100 }) var product Product db.First(&product, 1 ) db.First(&product, "code = ?" , "D42" ) db.Model(&product).Update("Price" , 200 ) db.Model(&product).Updates(Product{Price: 200 , Code: "F42" }) db.Model(&product).Updates(map [string ]interface {}{"Price" : 200 , "Code" : "F42" }) db.Delete(&product, 1 ) }
视频里把sqlite改成了mysql,用phpstudy应该也可以,但为了统一还是搞一下mysql吧https://dev.mysql.com/downloads/installer
选择内存大的那个下载,然后一路下一步就行(甲骨云真他妈麻烦)
验证是否安装成功C:\Program Files\MySQL\MySQL Server 8.0\bin
打开cmd(powershell不行!!!),运行mysql -h localhost -u root -p
,然后输入密码,显示mysql>即安装成功 也可以打开MySQL 8.0 Command Line Client
快捷进入
才发现原来博客的笔记一整个完整的数据库操作都没有,麻了
1 2 3 4 5 6 7 8 9 10 11 12 //创立新的数据库,打分号!!! create database test_2023_10_12; //选择数据库 use test_2023_10_12; //建立表 CREATE TABLE test ( time float NOT NULL )ENGINE=InnoDB DEFAULT CHARSET=utf8; //删除表 drop table test; //显示已有的表,打分号!!! show tables;
剩下的明天再搞
Day2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 //查看一个表的结构 desc test(table name); //查看一个表的数据 select * from test; //重来一遍 create database IM; use IM; create table test( id float ); //添加字段 //是alter!!! alter table test add email int; alter table test add username char(255); alter table test add passwd char(255); //修改字段数据类型 alter table test modify email char(255); //向字段添加数据 insert into test (username,passwd,email,id) values ('taosu','123456','[email protected] ',1); //多创建几个用户用于测试 insert into test (username,passwd,email,id) values ('yblue','qwerty','[email protected] ',2); insert into test (username,passwd,email,id) values ('qlu_coder','my_password','[email protected] ',3); /* mysql> select * from test; +----+------------------+-----------+-------------+ | id | email | username | passwd | +----+------------------+-----------+-------------+ | 1 | [email protected] | taosu | 123456 | | 2 | [email protected] | yblue | qwerty | | 3 | [email protected] | qlu_coder | my_password | +----+------------------+-----------+-------------+ 3 rows in set (0.00 sec) */
暂时跟mysql告一段落了应该,感觉还是直接用phpstudy就行了,没必要安mysql
testGorm.go的操作 1 2 3 4 db, err := gorm.Open(mysql.Open("root:root@tcp(127.0.0.1:3306)/IM?charset=utf8&parseTime=True&loc=Local" ), &gorm.Config{})
完整的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package mainimport ( "IM_project/models" "gorm.io/driver/mysql" "gorm.io/gorm" )type Product struct { gorm.Model Code string Price uint }func main () { db, err := gorm.Open(mysql.Open("root:root@tcp(127.0.0.1:3306)/IM?charset=utf8&parseTime=True&loc=Local" ), &gorm.Config{}) if err != nil { panic ("failed to connect database" ) } db.AutoMigrate(&models.UserBasic{}) user:=&models.UserBasic{} user.Name="taosu" db.Create(user) fmt.Println("db.First(user, 1) : " ,db.First(user, 1 )) db.Model(user).Update("PassWord" , "1234" ) }
迁移 在数据库领域,”迁移”(Migration)是指通过代码和脚本来管理数据库模式(结构)的变化。迁移通常用于以下情况:
创建新表格:当你需要引入新数据表时,迁移会创建该表格的结构。
修改表格结构:如果你需要添加、删除、修改或重命名表格的列,迁移会记录这些更改,并在数据库中应用它们。
填充默认数据:有时需要在表格中填充默认数据,迁移可以包含填充数据的脚本。
删除表格:如果某个表格不再需要,迁移可以包含删除该表格的操作。
数据迁移:当你需要将数据从一个表格或数据库迁移到另一个表格或数据库时,迁移也是有用的。
目录创建 提前创建用于存放各种东西的目录
common(感觉应该是public)
config 配置
router 路由
service 服务
sql
router创建app.go,将原来的UserBasic.go改名成user_basic.go(这里不加_
会报错,很怪)
逻辑 main函数 1 2 3 4 5 6 7 8 9 10 package mainimport ( "IM_project/router" )func main () { r := router.Router() r.Run("127.0.0.1:8099" ) }
在main函数中调用router函数,返回值r是 *gin.Engine
,然后r运行在本地的8099端口
IM_project/router中的router函数(app.go) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package routerimport ( "IM_project/service" "github.com/gin-gonic/gin" )func Router () *gin.Engine { r := gin.Default() r.GET("/index" , service.GetIndex) return r }
首先使用gin框架创建基本路由,这里的r也是 *gin.Engine
类型,然后在有用户使用get请求并且请求路径为uri/index时,调用service包的GetIndex函数,注意这里是调用service.GetIndex的函数,而不是直接使用,是告诉程序有请求时,将请求发送到service.GetIndex函数,所以这里只写函数名,而不是service.GetIndex()
报错修复 在下一个视频中,吧LoginTime,HeartbeatTime和LoginOutTime的类型从uint64修改成了time.Time,但是运行时会报错
1 2 3 4 5 6 7 8 9 10 11 PS IM_project> go run "IM_project\test\testGorm.go" 2023/10/13 13:58:18 IM_project/test/testGorm.go:31 Error 1292 (22007): Incorrect datetime value: '0000-00-00' for column 'login_time' at row 1 [17.203ms] [rows:0] INSERT INTO `user_basic` (`created_at`,`updated_at`,`deleted_at`,`name`,`pass_word`,`identity`,`phone`,`email`,`client_ip`,`client_port`,`login_time`,`heartbeat_time`,`login_out_time`,`is_logout`,`device_info`) VALUES ('2023-10-13 13:58:18.207','2023-10-13 13:58:18.207',NULL,'taosu','','','','','','','0000-00-00 00:00:00','0000-00-00 00:00:00','0000-00-00 00:00:00',false,'') 2023/10/13 13:58:18 IM_project/test/testGorm.go:34 record not found [1.232ms] [rows:0] SELECT * FROM `user_basic` WHERE `user_basic`.`id` = 1 AND `user_basic`.`deleted_at` IS NULL ORDER BY `user_basic`.`id` LIMIT 1 db.First(user, 1) : &{0xc00012e480 record not found 0 0xc00028d880 0} 2023/10/13 13:58:18 IM_project/test/testGorm.go:37 WHERE conditions required [0.475ms] [rows:0] UPDATE `user_basic` SET `pass_word`='1234',`updated_at`='2023-10-13 13:58:18.227' WHERE `user_basic`.`deleted_at` IS NULL
这里很迷,明明grom.Model里的两个时间都是time.Time类型的,但是能用,我这里把自定义的改成time.Time类型就不可以了
问了问gpt,原因是mysql库现在是严格模式,对数据的格式很严,然后time.Time的零值好像是格式有点问题,是存不进去的,然后可以修改成兼容模式 打开mysql终端
1 2 3 4 //查看当前模式 SELECT @@sql_mode; //修改成兼容模式 SET GLOBAL sql_mode = 'NO_ENGINE_SUBSTITUTION';
不报错了
第二天有报错了,这玩意好像每重启电脑就得设置一遍
继续 创建utils目录,建立system_init.go,在config目录下创建app.yml
“utils” 是一个缩写,通常用于描述一组实用工具或函数,这些工具和函数可以帮助程序员更容易地完成一些常见的任务,例如处理日期和时间、处理字符串、读写文件、执行数学运算等。这些工具旨在节省开发时间和减少代码的冗余,因此开发人员可以更轻松地构建应用程序。所以,当你在代码中看到 “utils”,它通常指的是一组实用的程序代码,用于处理各种常见编程任务。
Day3 viper 下载go get github.com/spf13/viper
导入需要
1 import "github.com/spf13/viper"
Viper能够为你执行下列操作:
查找、加载和反序列化JSON
、TOML
、YAML
、HCL
、INI
、envfile
和Java properties
格式的配置文件。
提供一种机制为你的不同配置选项设置默认值。
提供一种机制来通过命令行参数覆盖指定选项的值。
提供别名系统,以便在不破坏现有代码的情况下轻松重命名参数。
当用户提供了与默认值相同的命令行或配置文件时,可以很容易地分辨出它们之间的区别。
Viper会按照下面的优先级。每个项目的优先级都高于它下面的项目:
显示调用Set
设置值
命令行参数(flag)
环境变量
配置文件
key/value存储
默认值
!!! 说人话就是帮忙管理yml,json等这种配置文件的工具 !!!
现在有种感觉就是写之前那个test,就是为了把请求的过程一点点分成很多份,然后分给不同的函数,比如本来test的请求就是直接连接数据库,然后这里是把数据库的信息存放在yml文件中,然后通过viper管理并获取,然后拼接到初始化mysql连接的函数中,然后需要时再调用初始化函数,可能是为了安全和方便修改?
InitMySQL调整 本来是直接mysql.dns获取,改成分批获取配置,增加可读性,更好维护
app.yml
1 2 3 4 5 6 7 8 9 10 11 mysql: dns: root:root@tcp(127.0 .0 .1 :3306 )/IM?charset=utf8mb4&parseTime=True&loc=Local username: root passwd: root host: 127.0 .0 .1 port: 3306 db: IM options: charset: utf8mb4 parseTime: true loc: Local
InitMySQL
1 2 3 4 5 6 7 8 9 10 11 12 13 func InitMySQL () { username := viper.GetString("mysql.username" ) passwd := viper.GetString("mysql.passwd" ) host := viper.GetString("mysql.host" ) port := viper.GetString("mysql.port" ) db := viper.GetString("mysql.db" ) options := viper.Sub("mysql.options" ) charset := options.GetString("charset" ) parseTime := options.GetBool("parseTime" ) loc := options.GetString("loc" ) databaseURL := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=%t&loc=%s" , username, passwd, host, port, db, charset, parseTime, loc) DB, _ = gorm.Open(mysql.Open(databaseURL), &gorm.Config{}) }
小问题 username := viper.GetString(“mysql.username”) 这里是直接获取mysql.username,万一后面不止获取app.yml,又获取了app2.yml,并且app2.yml也有mysql.username这个配置项,这个语句会获取哪个信息
可能是把所有配置都放在app.yml文件里了所以没有这个问题?
swag 1 2 3 4 5 go install github.com/swaggo/swag/cmd/swag@latest
Day4 昨晚装了一晚上没弄好,回宿舍又找了n久,说可能得调GOROOT的路径,然后今天来了一试,GOPATH它又出来了,难道重启电脑真是解决问题的最好答案吗
swag 1 2 3 4 5 6 7 8 swag init swag init -g /service/index.htmlgo run main.go
配下goland Day5 现在看看swag的功能确实挺方便的,但不知道为什么vsc里打开的终端就是不能用
捣鼓了昨天一天,对这玩意大概了解了. 就是一个类似apifox自动化测试的东西,在后端写出了业务逻辑需要测试,要么就手动curl发请求,要么就是apifox写好请求发过去,然后这个swag是在程序界面,用特定的方式(就是// @)写好注释,在init之后,就会在url/swagger/index.html生成对应的请求方法,就是比较方便(好像还有自动写文档的功能?没用到还)
1 2 3 4 5 6 7 8 9 10 go install github.com/swaggo/swag/cmd/swag@latest //然后使用时会自动导包 //app.go //前面的是别名 "IM_project/docs" "IM_project/service" "github.com/gin-gonic/gin" swaggerfiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger"
剩下的现用现查吧,主要东西都在注释里了
new 增删改查(CRUD)正式收工 Create Read Update Delete
对U的信息校验 比如说在手机号那里输字母,进行这种校验
1 go get github.com/asaskevich/govalidator
新加修改手机号和邮箱 防止已注册的手机号/名字/邮箱重复注册 魔改了不少,终于有点自己的想法了
Day6 加盐加密 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 func Md5Encode (data string ) string { h := md5.New() h.Write([]byte (data)) tempStr := h.Sum(nil ) return hex.EncodeToString(tempStr) }func MD5Encode (data string ) string { return strings.ToUpper(Md5Encode(data)) }func MakePassword (plainpwd, salt string ) string { return Md5Encode(plainpwd + salt) }func ValidPassword (plainpwd, salt string , passwd string ) bool { return Md5Encode(plainpwd+salt) == passwd }
在密码学和计算机安全领域,散列(Hash)是一种将任意长度的数据映射为固定长度数据的过程。它的核心目标是将输入数据(称为消息)通过一种数学算法,转换为固定长度的字符串,通常是一串数字和字母组成的十六进制值。这个输出字符串通常称为“散列值”或“摘要”。
散列函数有以下特点:
固定输出长度:无论输入数据的大小,散列函数都生成相同长度的输出,这个输出的长度是固定的。
雪崩效应:即使输入数据的微小变化,散列值应该有显著的差异,这称为雪崩效应。这是散列函数的一个关键特性,因为它保证了数据的微小改变会导致完全不同的散列值。
不可逆性:理论上,从散列值不能还原出原始输入数据。这是散列函数的一个重要特性,通常用于存储密码的散列值,因为不应该直接从密码的散列值反向计算出密码本身。
快速计算:散列函数需要在合理的时间内计算出结果,以便广泛的应用。
散列函数在信息安全中有多种应用,包括密码存储、数据完整性验证、数字签名、数字证书等领域。常见的散列算法包括 MD5、SHA-1、SHA-256 等,其中 SHA 系列较为安全,因此在许多安全应用中被广泛使用。但请注意,随着计算能力的增强,一些散列算法可能不再足够安全,需要采用更强大的算法。
用户登录 post请求,更安全点
修改用户信息 盐重新生成并修改数据库中的盐 加了id校验,视频里面是没有这个的(也可能在后面我还没看到) 但感觉好像没必要,因为真要修改信息的话,肯定是先确定账密(也可能再调用一遍login?),然后修改,不会让用户输入id进行判断是否存在,这个功能后面应该是得删掉的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 func UpdateUser (c *gin.Context) { user := models.UserBasic{} id, err := strconv.Atoi(c.PostForm("id" )) if err != nil { log.Fatalln(err) } user.ID = uint (id) is_Exit, _ := models.FindUserById(user.ID) if !is_Exit { c.JSON(400 , gin.H{ "message" : "用户不存在!" , }) return } user.Name = c.PostForm("name" ) passwd := c.PostForm("password" ) salt := fmt.Sprintf("%06d" , rand.Int31()) user.PassWord = utils.MakePassword(passwd, salt) user.Phone = c.PostForm("phone" ) user.Email = c.PostForm("email" ) user.Salt = salt _, err = govalidator.ValidateStruct(user) if err != nil { fmt.Println(err) c.JSON(400 , gin.H{ "message" : "修改用户信息格式错误!" , }) return } else { err = models.UpdateUser(user) if err != nil { log.Fatalln(err) } fmt.Println("update :" , user) c.JSON(200 , gin.H{ "message" : "修改用户信息成功" , }) } } unc FindUserById(id uint ) (bool , UserBasic) { user := UserBasic{} utils.DB.Where("id = ?" , id).First(&user) if user.ID != 0 { return true , user } return false , user }
新学的东西 思路 对请求分批处理,分成很多小份,交给不同的程序处理
函数 gin相关 1 2 3 4 r:=gin.Default() r.Get("index" ,service.GetIndex)
viper相关 1 2 3 4 5 6 7 8 9 10 11 viper.AddConfigPath("config" ) viper.SetConfigName("app" ) err := viper.ReadInConfig() username := viper.GetString("mysql.username" ) options := viper.Sub("mysql.options" ) charset := options.GetString("charset" )
gorm相关 1 2 3 4 5 6 7 8 mysql.Open("root:root@tcp(127.0.0.1:3306)/IM) func gorm.Open(dialector gorm.Dialector, opts ...gorm.Option) (db *gorm.DB, err error) //db:这是一个 *gorm.DB 类型的指针,表示成功打开的数据库连接。*gorm.DB 是 GORM 库的主要接口,用于执行数据库操作。 //err:这是一个 error 类型的返回值,用于表示在打开数据库连接时是否发生了错误。如果打开成功,err 为 nil;如果发生错误,将包含错误信息。 db, err := gorm.Open(mysql.Open(" root:root@tcp(127.0 .0 .1 :3306 )/IM?charset=utf8mb4&parseTime=True&loc=Local"), &gorm.Config{})
不知道放在哪的 1 2 3 4 5 6 Q: 那用户能在页面看到message" 字段的值 " welecome!!",是通过c.JSON()还是gin.H{},就是是哪个函数把message" 字段的值 "welecome!!" 打印在页面上传给用户的 A: 在用户的浏览器页面上看到 "message" 字段的值 "welecome!!" 实际上是通过 c.JSON() 这个函数实现的。 c.JSON(200, ...) 函数用于生成 JSON 格式的响应,其中的参数是 HTTP 状态码和包含要返回的 JSON 数据的 Go 数据结构。在这里,gin.H{ "message" : "welecome!!" } 构建了 JSON 数据,而 c.JSON(200, ...) 把这个 JSON 数据包装成 HTTP 响应并发送给客户端。 这意味着用户在浏览器上看到的 "message" 字段的值 "welecome!!" 是由 c.JSON(200, ...) 函数生成的响应传输到客户端的结果。所以,c.JSON() 函数实际上是将 JSON 数据发送给用户的一部分,而 gin.H{} 只是用于构建 JSON 数据的辅助工具。
数据结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 gin.Default()返回值 *gin.Engine gorm.Modeltype Model struct { ID uint `gorm:"primarykey"` CreatedAt time.Time UpdatedAt time.Time DeletedAt DeletedAt `gorm:"index"` } *gorm.DB *gin.Context