commit 7741066aeb31eee13c705f3c37a05ae5275525f8 Author: junleea <354425203@qq.com> Date: Tue Mar 18 13:22:20 2025 +0800 大学生学业作品AI生成工具开发,项目框架 diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/StuAcaWorksAI.iml b/.idea/StuAcaWorksAI.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/StuAcaWorksAI.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..771b519 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/dao/db.go b/dao/db.go new file mode 100644 index 0000000..51c5b26 --- /dev/null +++ b/dao/db.go @@ -0,0 +1,66 @@ +package dao + +import ( + "StuAcaWorksAI/proto" + "fmt" + "gorm.io/driver/mysql" + "gorm.io/driver/postgres" + "gorm.io/gorm" +) + +var DB *gorm.DB + +func Init() error { + var db *gorm.DB + var err error + var dsn string + if proto.Config.DB == 0 { + dsn = proto.Config.MYSQL_DSN + db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) + } else if proto.Config.DB == 1 { + dsn = proto.Config.PG_DSN + db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{}) + } + + if err != nil { + panic("failed to connect database") + return err + } + err = db.AutoMigrate(&User{}) + if err != nil { + fmt.Println("user table:", err) + return err + } // 自动迁移,创建表,如果表已经存在,会自动更新表结构,不会删除表,只会创建不存在的表 + err = db.AutoMigrate(&File{}) + if err != nil { + fmt.Println("file table:", err) + } + + err = db.AutoMigrate(&File{}) + if err != nil { + fmt.Println("file table:", err) + return err + } + + err = db.AutoMigrate(&ConfigFile{}) + if err != nil { + fmt.Println("config file table:", err) + return err + } + + err = db.AutoMigrate(&FileAuth{}) + if err != nil { + fmt.Println("file auth table:", err) + return err + } + DB = db + return err +} + +func Close() { + sqlDB, err := DB.DB() + if err != nil { + panic("failed to connect database") + } + sqlDB.Close() +} diff --git a/dao/file.go b/dao/file.go new file mode 100644 index 0000000..a3a8451 --- /dev/null +++ b/dao/file.go @@ -0,0 +1,179 @@ +package dao + +import ( + "StuAcaWorksAI/proto" + "gorm.io/gorm" +) + +type File struct { + gorm.Model + // 存储文件名 + FileStoreName string `gorm:"column:file_store_name;type:varchar(255);uniqueIndex:idx_file_name"` + NeedAuth bool `gorm:"column:need_auth"` + FileName string `gorm:"column:file_name"` + FileSize int `gorm:"column:file_size"` + FileType string `gorm:"column:file_type"` + FilePath string `gorm:"column:file_path"` + AuthID int `gorm:"column:auth_id"` + Md5 string `gorm:"column:md5;type:varchar(255);uniqueIndex:idx_file_name"` +} + +type FileAuth struct { + gorm.Model + AuthID int `gorm:"column:auth_id"` + FileID int `gorm:"column:file_id"` + UserFileName string `gorm:"column:user_file_name;type:varchar(255);uniqueIndex:idx_file_name"` //对于同一个文件,不同用户有不同的文件名 + UploadType string `gorm:"column:upload_type"` // 上传类型: im,avatar,file,config,config为系统文件 + IsPrivate int `gorm:"column:is_private"` // 是否私有,私有文件只能自己下载或者通过分享链接下载,1为私有,0为公开 + ShareCode string `gorm:"column:share_code"` // 分享码,用于分享时的验证,构建分享链接 +} + +type ConfigFile struct { + gorm.Model + AuthID int `gorm:"column:auth_id"` + FileName string `gorm:"column:file_name"` + FilePath string `gorm:"column:file_path"` +} + +func CreateFile(fileStoreName, fileName, fileType, filePath, md5Str string, fileSize, authID int, NeedAuth bool) File { + file := File{FileStoreName: fileStoreName, FileName: fileName, FileType: fileType, FilePath: filePath, FileSize: fileSize, AuthID: authID, NeedAuth: NeedAuth, Md5: md5Str} + result := DB.Create(&file) + if result.Error != nil { + return File{} + } + return file +} + +func DeleteFileByID(id, user int) bool { + res := DB.Model(&File{}).Where("id = ? and auth_id = ?", id, user).Delete(&File{}) + if res.Error != nil { + return false + } + return true +} + +func FindFileByID(id, auth_id int) File { + var file File + DB.Where("id = ? and auth_id = ?", id, auth_id).First(&file) + return file +} + +func FindFileByNames(fileName string, auth_id int) File { + var file File + DB.Where("file_name = ? and auth_id = ?", fileName, auth_id).First(&file) + return file +} + +func FindFileByName(fileName string) File { + var file File + DB.Where("file_store_name = ?", fileName).First(&file) + return file +} + +func FindFileByAuthID(auth_id int) []File { + var files []File + DB.Where("auth_id = ?", auth_id).Find(&files) + return files +} + +func UpdateFileByID(id, auth_id int, fileStoreName, fileName, fileType, filePath string, fileSize int) bool { + pd := FindFileByID(id, auth_id) + if pd.ID == 0 { + return false + } + result := DB.Model(&File{}).Where("id = ? and auth_id = ?", id, auth_id).Updates(File{FileStoreName: fileStoreName, FileName: fileName, FileType: fileType, FilePath: filePath, FileSize: fileSize}) + if result.Error != nil { + return false + } + return true +} + +func DeleteFileByAuthID(auth_id int) bool { + res := DB.Model(&File{}).Where("auth_id = ?", auth_id).Delete(&File{}) + if res.Error != nil { + return false + } + return true +} + +func DeleteFileById(id int) bool { + res := DB.Model(&File{}).Where("id = ?", id).Delete(&File{}) + if res.Error != nil { + return false + } + return true +} + +func FindFileByMd5(md5 string) File { + var file File + if proto.Config.SERVER_SQL_LOG { + DB.Debug().Where("md5 = ?", md5).First(&file) + } else { + DB.Where("md5 = ?", md5).First(&file) + } + return file +} + +func CreateFileAuth(authID, fileID int, userFileName, uploadType string, isPrivate int, shareCode string) FileAuth { + fileAuth := FileAuth{AuthID: authID, FileID: fileID, UserFileName: userFileName, UploadType: uploadType, IsPrivate: isPrivate, ShareCode: shareCode} + var result *gorm.DB + if proto.Config.SERVER_SQL_LOG { + result = DB.Debug().Create(&fileAuth) + } else { + result = DB.Create(&fileAuth) + } + if result.Error != nil { + return FileAuth{} + } + return fileAuth +} + +func CreateConfigFile(file ConfigFile) (id uint, err error) { + var result *gorm.DB + if proto.Config.SERVER_SQL_LOG { + result = DB.Debug().Create(&file) + } else { + result = DB.Create(&file) + } + return file.ID, result.Error +} + +func FindConfigFileByID(id int, user_id int) ConfigFile { + var file ConfigFile + if proto.Config.SERVER_SQL_LOG { + DB.Debug().Where("id = ? and auth_id = ?", id, user_id).First(&file) + } else { + DB.Where("id = ? and auth_id = ?", id, user_id).First(&file) + } + return file +} + +func DeleteConfigFileByID(id int) error { + var res *gorm.DB + if proto.Config.SERVER_SQL_LOG { + res = DB.Debug().Delete(&ConfigFile{}, id) + } else { + res = DB.Delete(&ConfigFile{}, id) + } + return res.Error +} + +func UpdateConfigFileByID(id int, file ConfigFile) error { + var res *gorm.DB + if proto.Config.SERVER_SQL_LOG { + res = DB.Debug().Model(&ConfigFile{}).Where("id = ?", id).Updates(&file) + } else { + res = DB.Model(&ConfigFile{}).Where("id = ?", id).Updates(&file) + } + return res.Error +} + +func FindConfigFileByAuthID(auth_id int) []ConfigFile { + var files []ConfigFile + if proto.Config.SERVER_SQL_LOG { + DB.Debug().Where("auth_id = ?", auth_id).Find(&files) + } else { + DB.Where("auth_id = ?", auth_id).Find(&files) + } + return files +} diff --git a/dao/logger.go b/dao/logger.go new file mode 100644 index 0000000..b5b6482 --- /dev/null +++ b/dao/logger.go @@ -0,0 +1,38 @@ +package dao + +import ( + "fmt" + "gorm.io/gorm" +) + +type Logger struct { + gorm.Model + Url string `gorm:"column:url"` + IP string `gorm:"column:ip"` + Method string `gorm:"column:method"` + Params string `gorm:"column:params"` +} + +func InsertLogToDB(url, ip, method, params string) uint { + logger := Logger{Url: url, IP: ip, Method: method, Params: params} + DB.Create(&logger) + if logger.ID == 0 { + fmt.Println("InsertLogToDB error") + } + return logger.ID +} + +func deleteByID(id int) bool { + DB.Where("ID = ?", id).Delete(&Logger{}) + return true +} + +// 删除3天前的日志 +func DeleteLog(days int) bool { + res := DB.Exec("delete from loggers where created_at < DATE_SUB(CURDATE(), INTERVAL ? DAY)", days) + if res.Error != nil { + fmt.Println("DeleteLog error", res.Error) + return false + } + return true +} diff --git a/dao/user.go b/dao/user.go new file mode 100644 index 0000000..c28de65 --- /dev/null +++ b/dao/user.go @@ -0,0 +1,167 @@ +package dao + +import ( + "StuAcaWorksAI/proto" + "fmt" + "gorm.io/gorm" +) + +type User struct { + gorm.Model + Name string `gorm:"column:name"` + Age int `gorm:"column:age"` + Email string `gorm:"column:email"` + Password string `gorm:"column:password"` + Gender string `gorm:"column:gender"` + Role string `gorm:"column:role"` + Redis bool `gorm:"column:redis"` + Run bool `gorm:"column:run"` + Upload bool `gorm:"column:upload"` + VideoFunc bool `gorm:"column:video_func"` //视频功能 + DeviceFunc bool `gorm:"column:device_func"` //设备功能 + CIDFunc bool `gorm:"column:cid_func"` //持续集成功能 + Avatar string `gorm:"column:avatar"` + CreateTime string `gorm:"column:create_time"` + UpdateTime string `gorm:"column:update_time"` +} + +func CreateUser(name, password, email, gender string, age int) uint { + user := User{Name: name, Email: email, Password: password, Gender: gender, Age: age} + res := DB.Create(&user) + if res.Error != nil { + return 0 + } + return user.ID +} + +func DeleteUserByID(id int) int { + res := DB.Delete(&User{}, id) + if res.Error != nil { + return 0 + } + return id +} + +func FindUserByID(id int) []proto.User { + var users []proto.User + DB.Where("id = ?", id).First(&users) + return users +} +func FindUserByID2(id int) User { + var user User + DB.Where("id = ?", id).First(&user) + return user +} + +func FindUserByUserID(id int) User { + var user User + DB.Where("id = ?", id).First(&user) + return user +} + +func FindUserByName(name string) User { + var user User + fmt.Println("name:", name) + DB.Where("name = ?", name).First(&user) + return user +} + +// 根据name模糊查询,邮箱也是,不查询密码 +func FindUserByNameLike(name string) []proto.User { + var users []proto.User + DB.Where("name LIKE ? OR email LIKE ?", "%"+name+"%", "%"+name+"%").Find(&users).Limit(32) + return users +} + +func FindUserByEmail(email string) User { + var user User + DB.Where("email = ?", email).First(&user) + return user +} + +func UpdateUserByID(id int, name, password, email string) { + DB.Model(&User{}).Where("id = ?", id).Updates(User{Name: name, Password: password, Email: email}) +} + +// 管理员修改用户信息 +func UpdateUserByID2(id int, req proto.UpdateUserInfoReq) error { + updateData := make(map[string]interface{}) + updateData["Name"] = req.Username + updateData["Age"] = req.Age + updateData["Role"] = req.Role + updateData["Run"] = req.Run + updateData["Redis"] = req.Redis + updateData["Upload"] = req.Upload + updateData["VideoFunc"] = req.VideoFunc + updateData["DeviceFunc"] = req.DeviceFunc + updateData["CIDFunc"] = req.CIDFunc + updateData["Avatar"] = req.Avatar + updateData["Gender"] = req.Gender + res := DB.Model(&User{}).Where("id =?", id).Updates(updateData) + if res.Error != nil { + return res.Error + } + return nil +} + +// 用户修改自己的信息 +func UpdateUserByID3(id int, req proto.UpdateUserInfoReq) error { + res := DB.Model(&User{}).Where("id = ?", id).Updates(User{Name: req.Username, Age: req.Age, Avatar: req.Avatar, Gender: req.Gender}) + return res.Error +} + +// 用户数据同步-添加 +func AddUserSync(req proto.UserAddOrUpdate) uint { + res := DB.Exec("insert into users (id, created_at, updated_at, deleted_at, name, age, email, password,gender,role,redis,run,upload,video_func,device_func,cid_func,avatar,create_time,update_time) values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", req.ID, req.CreatedAt, req.UpdatedAt, req.DeletedAt, req.Name, req.Age, req.Email, req.Password, req.Gender, req.Role, req.Redis, req.Run, req.Upload, req.VideoFunc, req.DeviceFunc, req.CIDFunc, req.Avatar, req.CreateTime, req.UpdateTime) + if res.Error != nil { + return 0 + } + res = DB.Debug().Exec("update users set deleted_at=null where id=?", req.ID) + if res.Error != nil { + return 0 + } + return req.ID +} + +// 用户数据同步-更新 +func UpdateUserSync(req proto.UserAddOrUpdate) uint { + //事务 + res := DB.Exec("update users set created_at=?, updated_at=?, deleted_at=?, name=?, age=?, email=?, password=?,gender=?,role=?,redis=?,run=?,upload=?,video_func=?,device_func=?,cid_func=?,avatar=?,create_time=?,update_time=? where id=?", req.CreatedAt, req.UpdatedAt, req.DeletedAt, req.Name, req.Age, req.Email, req.Password, req.Gender, req.Role, req.Redis, req.Run, req.Upload, req.VideoFunc, req.DeviceFunc, req.CIDFunc, req.Avatar, req.CreateTime, req.UpdateTime, req.ID) + if res.Error != nil { + return 0 + } + res = DB.Debug().Exec("update users set deleted_at=null where id=?", req.ID) + if res.Error != nil { + return 0 + } + return req.ID +} + +// 用户数据同步-删除 +func DeleteUserSync(req proto.UserDelID) uint { + res := DB.Delete(&User{}, req.ID) + if res.Error != nil { + return 0 + } + return req.ID +} + +// 获取所有用户 +func GetAllUser() []User { + var users []User + DB.Find(&users) + return users +} + +// 用户数据同步 +type UserSyncResp struct { + Update []User `json:"update" form:"update"` //更新用户 + Add []User `json:"add" form:"add"` //添加用户 + Delete []proto.UserDelID `json:"delete" form:"delete"` //删除用户 +} + +// 清空用户表 +func ClearAllUsers() error { + res := DB.Exec("TRUNCATE TABLE users") + return res.Error +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..62845e8 --- /dev/null +++ b/go.mod @@ -0,0 +1,57 @@ +module StuAcaWorksAI + +go 1.23 + +toolchain go1.23.7 + +require ( + github.com/gin-gonic/gin v1.10.0 + github.com/go-redis/redis/v8 v8.11.5 + github.com/golang-jwt/jwt v3.2.2+incompatible + github.com/google/uuid v1.6.0 + github.com/robfig/cron/v3 v3.0.1 + gorm.io/driver/mysql v1.5.7 + gorm.io/driver/postgres v1.5.11 + gorm.io/gorm v1.25.12 +) + +require ( + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/go-sql-driver/mysql v1.7.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.5.5 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sync v0.1.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.15.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..fbf8a54 --- /dev/null +++ b/go.sum @@ -0,0 +1,144 @@ +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= +github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314= +gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/handler/cross.go b/handler/cross.go new file mode 100644 index 0000000..0aec704 --- /dev/null +++ b/handler/cross.go @@ -0,0 +1,32 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + //"net/http" +) + +// 跨域访问:cross origin resource share +func CrosHandler() gin.HandlerFunc { + return func(context *gin.Context) { + //method := context.Request.Method + context.Writer.Header().Set("Access-Control-Allow-Origin", "*") + context.Header("Access-Control-Allow-Origin", "*") // 设置允许访问所有域 + context.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE") + context.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma,token,openid,opentoken") + context.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar") + context.Header("Access-Control-Max-Age", "172800") + context.Header("Access-Control-Allow-Credentials", "false") + context.Set("content-type", "application/json") //设置返回格式是json + + // if method == "OPTIONS" { + // context.JSON(http.StatusOK, gin.H{ + // "code":1, + // "message":"error", + // "data":"request error", + // }) + // } + + //处理请求 + context.Next() + } +} diff --git a/handler/file.go b/handler/file.go new file mode 100644 index 0000000..be44adb --- /dev/null +++ b/handler/file.go @@ -0,0 +1,194 @@ +package handler + +import ( + "StuAcaWorksAI/dao" + "StuAcaWorksAI/proto" + "StuAcaWorksAI/service" + "github.com/gin-gonic/gin" + "net/http" +) + +func SetUpFileGroup(router *gin.Engine) { + fileGroup := router.Group("/file") + fileGroup.POST("/config_add", AddConfigFile) + fileGroup.POST("/config_delete", DeleteConfigFile) + fileGroup.POST("/config_update", UpdateConfigFile) + fileGroup.POST("/config_search", SearchConfigFile) + fileGroup.POST("/upload", UploadFileV2) + fileGroup.GET("/general/:filename", GetFile) + +} + +func AddConfigFile(c *gin.Context) { + id, _ := c.Get("id") + user_id := int(id.(float64)) + var req proto.AddConfigFileReq + if err := c.ShouldBind(&req); err == nil { + err2 := service.CreateConfigFile(&req, user_id) + if err2 != nil { + c.JSON(http.StatusOK, gin.H{"error": "add config file failed:" + err2.Error(), "code": proto.AddConfigFileFailed, "message": "failed"}) + return + } else { + c.JSON(http.StatusOK, gin.H{"code": proto.SuccessCode, "message": "success"}) + return + } + + } else { + c.JSON(http.StatusOK, gin.H{"error": "upload form parameter decode error:" + err.Error(), "code": proto.ParameterError, "message": "failed"}) + return + } + +} + +func DeleteConfigFile(c *gin.Context) { + id, _ := c.Get("id") + user_id := int(id.(float64)) + var req proto.ConfigFileReq + if err := c.ShouldBind(&req); err == nil { + err2 := service.DeleteConfigFile(&req, user_id) + if err2 != nil { + c.JSON(http.StatusOK, gin.H{"error": "delete config file failed:" + err2.Error(), "code": proto.DeleteConfigFailed, "message": "failed"}) + return + } else { + c.JSON(http.StatusOK, gin.H{"code": proto.SuccessCode, "message": "success"}) + return + } + + } else { + c.JSON(http.StatusOK, gin.H{"error": "upload form parameter decode error:" + err.Error(), "code": proto.ParameterError, "message": "failed"}) + return + } +} + +func UpdateConfigFile(c *gin.Context) { + id, _ := c.Get("id") + user_id := int(id.(float64)) + var req proto.ConfigFileReq + if err := c.ShouldBind(&req); err == nil { + var configFileService service.ConfigFileService + err2 := configFileService.UpdateConfigFile(&req, user_id) + if err2 != nil { + c.JSON(http.StatusOK, gin.H{"error": "update config file failed:" + err2.Error(), "code": proto.UpdateConfigFailed, "message": "failed"}) + return + } else { + c.JSON(http.StatusOK, gin.H{"code": proto.SuccessCode, "message": "success"}) + return + } + + } else { + c.JSON(http.StatusOK, gin.H{"error": "upload form parameter decode error:" + err.Error(), "code": proto.ParameterError, "message": "failed"}) + return + } +} + +func SearchConfigFile(c *gin.Context) { + id, _ := c.Get("id") + user_id := int(id.(float64)) + var req proto.ConfigFileReq + if err := c.ShouldBind(&req); err == nil { + var configFileService service.ConfigFileService + if req.Type == "one" { + //有文件内容 + configFile, err2 := configFileService.SearchOneConfigFile(&req, user_id) + if err2 == nil { + c.JSON(http.StatusOK, gin.H{"code": proto.SuccessCode, "msg": "success", "data": configFile}) + } else { + c.JSON(http.StatusOK, gin.H{"code": proto.SearchConfigFileFailed, "msg": "info:" + err2.Error(), "data": configFile}) + } + } else if req.Type == "all" { + configFileList, err3 := configFileService.SearchAllConfigFile(user_id) + if err3 == nil { + c.JSON(http.StatusOK, gin.H{"code": proto.SuccessCode, "msg": "success", "data": configFileList}) + } else { + c.JSON(http.StatusOK, gin.H{"code": proto.SearchConfigFileFailed, "msg": "info:" + err3.Error(), "data": configFileList}) + } + } else { + c.JSON(http.StatusOK, gin.H{"error": "search config file type error", "code": proto.ParameterError, "message": "failed"}) + } + } else { + c.JSON(http.StatusOK, gin.H{"error": "upload form parameter decode error:" + err.Error(), "code": proto.ParameterError, "message": "failed"}) + return + } +} + +func UploadFileV2(c *gin.Context) { + //先查看是否有权限 + id, _ := c.Get("id") + id1 := int(id.(float64)) + + var req proto.FileUploadReq + //获取post form参数 + if err := c.ShouldBind(&req); err == nil { + //检查参数 + if err2 := service.CheckUploadRequestParameters(&req); err2 != nil { + c.JSON(http.StatusOK, gin.H{"error": "upload form parameter check error:" + err2.Error(), "code": proto.ParameterError, "message": "failed"}) + return + } + // + + } else { + c.JSON(http.StatusOK, gin.H{"error": "upload form parameter decode error:" + err.Error(), "code": proto.ParameterError, "message": "failed"}) + return + } + + //从请求头获取upload_type + uploadType := c.PostForm("upload_type") + authType := c.PostForm("auth_type") + md5_ := c.PostForm("md5") + if uploadType == "" { + c.JSON(http.StatusOK, gin.H{"error": "upload_type is empty", "code": proto.ParameterError, "message": "failed"}) + return + } + + user := dao.FindUserByUserID(id1) + if user.Upload == false { + c.JSON(http.StatusOK, gin.H{"error": "no upload Permissions", "code": proto.NoUploadPermissions, "message": "failed"}) + return + } + //上传文件 + file, err := c.FormFile("file") + if err != nil { + c.JSON(http.StatusOK, gin.H{"error": "upload file failed", "code": proto.UploadFileFailed, "message": "failed"}) + return + } + //计算文件md5值 + if md5_ == "" { + file_, _ := file.Open() + md5_ = service.CalculateFileMd5(file_) + if md5_ == "" { + c.JSON(http.StatusOK, gin.H{"error": "计算文件MD5值失败", "code": proto.UploadFileFailed, "message": "failed"}) + return + } + } + //查询文件是否已存在 + fileExist := dao.FindFileByMd5(md5_) + if fileExist.ID != 0 { + fileExist.FilePath = "" + c.JSON(http.StatusOK, gin.H{"code": proto.SuccessCode, "message": "success", "data": fileExist}) + return + } + + //保存文件 + filePath, fileStoreName, err := service.SaveFile(c, file, uploadType) + if err != nil { + c.JSON(http.StatusOK, gin.H{"error": "save file failed", "code": proto.SaveFileFailed, "message": "failed"}) + return + } + //保存文件信息 + fileSize := int(file.Size) + fileName := file.Filename + fileType := file.Header.Get("file_type") + var auth_type_ bool + if authType == "public" || authType == "" { + auth_type_ = false + } else if authType == "private" { + auth_type_ = true + } + file_record := dao.CreateFile(fileStoreName, fileName, fileType, filePath, md5_, fileSize, id1, auth_type_) + if file_record.ID == 0 { + c.JSON(http.StatusOK, gin.H{"error": "save file info failed", "code": proto.SaveFileInfoFailed, "message": "failed"}) + return + } + file_record.FilePath = "" + c.JSON(http.StatusOK, gin.H{"code": proto.SuccessCode, "message": "success", "data": file_record}) +} diff --git a/handler/tool.go b/handler/tool.go new file mode 100644 index 0000000..ec58474 --- /dev/null +++ b/handler/tool.go @@ -0,0 +1,405 @@ +package handler + +import ( + "StuAcaWorksAI/dao" + "StuAcaWorksAI/proto" + "StuAcaWorksAI/service" + "StuAcaWorksAI/worker" + "fmt" + "github.com/gin-gonic/gin" + "io" + "net/http" + "os" + "strconv" + "time" +) + +type SetRedisReq struct { + Option string `json:"option" form:"option"` + Key string `json:"key" form:"key"` + Value string `json:"value" form:"value"` + Expire int `json:"expire" form:"expire"` +} + +type SetDeviceStatusReq struct { + ID string `json:"id" form:"id"` //设备编码 + Status string `json:"status" form:"status"` //设备状态 +} + +type GetFileListReq struct { + Type string `json:"type" form:"type"` //请求类型,1:按md5查询,2:按文件名查询;3:查询待删除文件 + Md5 string `json:"md5" form:"md5"` +} + +type SendMailReq struct { + Title string `json:"title" form:"title"` + Content string `json:"content" form:"content"` + To string `json:"to" form:"to"` +} + +func SetUpToolGroup(router *gin.Engine) { + toolGroup := router.Group("/tool") + toolGroup.POST("/set_redis", SetRedis) + toolGroup.POST("/get_redis", GetRedis) + + //文件上传、下载 + toolGroup.POST("/upload", UploadFile) + toolGroup.GET("/download", DownloadFile) + toolGroup.GET("/file/:filename", GetFile) + toolGroup.POST("/file_list", GetFileList) + //文件管理 + toolGroup.POST("/file_del", DelFile) + //服务器、设备状态接口 + toolGroup.POST("/monitor", SetDeviceStatusV2) + //发送邮件 + toolGroup.POST("/send_mail", SendMailTool) +} + +func SetDeviceStatusV2(c *gin.Context) { + // TODO + var req SetDeviceStatusReq + if err := c.ShouldBind(&req); err != nil { + c.JSON(200, gin.H{"code": 400, "message": "参数错误"}) + return + } else { + token := c.Request.Header.Get("token") + if token == "" { + c.JSON(200, gin.H{"code": 401, "message": "token为空"}) + return + } + devices := worker.GetRedisSetMembers(token) + if len(devices) == 0 { + c.JSON(200, gin.H{"code": 402, "message": "设备为空"}) + return + } + for _, v := range devices { + if v == req.ID { + // 继续处理请求 + //是否是暂停之后第一次上线,如果是则发送邮件通知 + device_status := worker.GetRedis("monitor_" + req.ID) + isExist := worker.IsContainKey("monitor_" + req.ID) + if device_status == "2" || !isExist { + //发送邮件通知 + title := "设备上线" + content := "设备上线\n设备:" + req.ID + "\t状态:" + req.Status + "\t时间:" + time.Now().String() + go SendMail(title, content) + } + worker.SetRedisWithExpire("monitor_"+req.ID, "1", time.Second*300) + c.JSON(200, gin.H{"code": 0, "message": "success"}) + return + } + } + c.JSON(200, gin.H{"code": 402, "message": "设备不存在"}) + } + +} + +func GetFileList(c *gin.Context) { + //解析请求参数 + var req GetFileListReq + if err := c.ShouldBind(&req); err == nil { + if req.Type == "1" { + //按md5查询 + if req.Md5 == "" { + c.JSON(http.StatusOK, gin.H{"error": "md5 is empty", "code": proto.ParameterError, "message": "failed"}) + return + } + file := dao.FindFileByMd5(req.Md5) + if file.ID == 0 { + c.JSON(http.StatusOK, gin.H{"error": "file not found", "code": proto.FileNotFound, "message": "failed"}) + return + } + c.JSON(http.StatusOK, gin.H{"code": proto.SuccessCode, "message": "success", "data": file}) + } + } else { + c.JSON(http.StatusOK, gin.H{"error": "parameter error", "code": proto.ParameterError, "message": "failed"}) + return + } +} + +func DelFile(c *gin.Context) { + //先查看是否有权限 + id, _ := c.Get("id") + id1 := int(id.(float64)) + + file_id, _ := strconv.Atoi(c.PostForm("id")) + + file_ := dao.FindFileByID(file_id, id1) + if file_.ID == 0 { + c.JSON(http.StatusOK, gin.H{"error": "file not found", "code": proto.FileNotFound, "message": "failed"}) + return + } + //删除文件 + err := os.Remove(file_.FilePath + "/" + file_.FileStoreName) + if err != nil { + c.JSON(http.StatusOK, gin.H{"error": "delete file failed", "code": proto.DeleteFileFailed, "message": "failed"}) + return + } + //删除文件信息 + if res := dao.DeleteFileById(file_id); !res { + c.JSON(http.StatusOK, gin.H{"error": "delete file info failed", "code": proto.DeleteFileInfoFailed, "message": "failed"}) + return + } + c.JSON(http.StatusOK, gin.H{"code": proto.SuccessCode, "message": "success"}) + +} + +func GetFile(c *gin.Context) { + //先查看是否有权限 + filename := c.Param("filename") + if filename == "" { + c.JSON(http.StatusOK, gin.H{"error": "filename is empty", "code": proto.ParameterError, "message": "failed"}) + return + } + //查询文件信息 + file := dao.FindFileByName(filename) + if file.ID == 0 { + c.JSON(http.StatusOK, gin.H{"error": "file not found", "code": proto.FileNotFound, "message": "failed"}) + return + } + //下载文件 + if file.NeedAuth == false { + c.File(file.FilePath + "/" + file.FileStoreName) + } else { + c.JSON(http.StatusOK, gin.H{"code": proto.SuccessCode, "message": "file must auth", "data": "file must auth"}) + } +} + +func UploadFile(c *gin.Context) { + //先查看是否有权限 + id, _ := c.Get("id") + id1 := int(id.(float64)) + //从请求头获取upload_type + uploadType := c.PostForm("upload_type") + authType := c.PostForm("auth_type") + md5_ := c.PostForm("md5") + if uploadType == "" { + c.JSON(http.StatusOK, gin.H{"error": "upload_type is empty", "code": proto.ParameterError, "message": "failed"}) + return + } + + user := dao.FindUserByUserID(id1) + if user.Upload == false { + c.JSON(http.StatusOK, gin.H{"error": "no upload Permissions", "code": proto.NoUploadPermissions, "message": "failed"}) + return + } + //上传文件 + file, err := c.FormFile("file") + if err != nil { + c.JSON(http.StatusOK, gin.H{"error": "upload file failed", "code": proto.UploadFileFailed, "message": "failed"}) + return + } + //计算文件md5值 + if md5_ == "" { + file_, _ := file.Open() + md5_ = service.CalculateFileMd5(file_) + if md5_ == "" { + c.JSON(http.StatusOK, gin.H{"error": "计算文件MD5值失败", "code": proto.UploadFileFailed, "message": "failed"}) + return + } + } + //查询文件是否已存在 + fileExist := dao.FindFileByMd5(md5_) + if fileExist.ID != 0 { + fileExist.FilePath = "" + c.JSON(http.StatusOK, gin.H{"code": proto.SuccessCode, "message": "success", "data": fileExist}) + return + } + + //保存文件 + filePath, fileStoreName, err := service.SaveFile(c, file, uploadType) + if err != nil { + c.JSON(http.StatusOK, gin.H{"error": "save file failed", "code": proto.SaveFileFailed, "message": "failed"}) + return + } + //保存文件信息 + fileSize := int(file.Size) + fileName := file.Filename + fileType := file.Header.Get("file_type") + var auth_type_ bool + if authType == "public" || authType == "" { + auth_type_ = false + } else if authType == "private" { + auth_type_ = true + } + file_record := dao.CreateFile(fileStoreName, fileName, fileType, filePath, md5_, fileSize, id1, auth_type_) + if file_record.ID == 0 { + c.JSON(http.StatusOK, gin.H{"error": "save file info failed", "code": proto.SaveFileInfoFailed, "message": "failed"}) + return + } + file_record.FilePath = "" + c.JSON(http.StatusOK, gin.H{"code": proto.SuccessCode, "message": "success", "data": file_record}) + +} + +func DownloadFile(c *gin.Context) { + //参数 + //filename := c.Param("filename") + file_id, _ := strconv.Atoi(c.Query("id")) + id, _ := c.Get("id") + //查询文件信息 + //file := dao.FindFileByNames(file_id, int(id.(float64))) + file_ := dao.FindFileByID(file_id, int(id.(float64))) + if file_.ID == 0 { + c.JSON(http.StatusOK, gin.H{"error": "file not found", "code": proto.FileNotFound, "message": "failed"}) + return + } + //下载文件 + // 打开文件 + file, err := os.Open(file_.FilePath + "/" + file_.FileStoreName) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal Server Error", "message": "Failed to open file"}) + return + } + defer file.Close() + + // 设置响应头 + c.Writer.Header().Set("Content-Type", "application/octet-stream") + c.Writer.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", file_.FileName)) + + // 发送文件内容 + _, err = io.Copy(c.Writer, file) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal Server Error", "message": "Failed to send file"}) + return + } + + c.Status(http.StatusOK) +} + +func SetRedis(c *gin.Context) { + //先查看是否有权限 + id, _ := c.Get("id") + id1 := int(id.(float64)) + user := dao.FindUserByUserID(id1) + if user.Redis == false { + c.JSON(http.StatusOK, gin.H{"error": "no redis Permissions", "code": proto.NoRedisPermissions, "message": "failed"}) + return + } + //解析请求参数 + var req SetRedisReq + if err := c.ShouldBind(&req); err == nil { + var code int + var message string + if req.Option == "list" { + code, message = service.SetToolRedisList(req.Key, req.Value, req.Expire) + } else if req.Option == "set" { + code, message = service.SetToolRedisSet(req.Key, req.Value, req.Expire) + } else if req.Option == "kv" { + code, message = service.SetToolRedisKV(req.Key, req.Value, req.Expire) + } + c.JSON(http.StatusOK, gin.H{"code": code, "message": message}) + } else { + c.JSON(http.StatusOK, gin.H{"error": "parameter error", "code": proto.ParameterError, "message": "failed"}) + return + } +} + +func GetRedis(c *gin.Context) { + //先查看是否有权限 + id, _ := c.Get("id") + id1 := int(id.(float64)) + user := dao.FindUserByUserID(id1) + if user.Redis == false { + c.JSON(http.StatusOK, gin.H{"error": "no redis Permissions", "code": proto.NoRedisPermissions, "message": "failed"}) + return + } + //解析请求参数 + var req SetRedisReq + if err := c.ShouldBind(&req); err == nil { + if req.Option == "one" { + code, message := service.GetToolRedis(req.Key) + req.Value = message + c.JSON(http.StatusOK, gin.H{"code": code, "message": message, "data": req}) + } else if req.Option == "all" { + code, message, data := service.GetAllRedis() + c.JSON(http.StatusOK, gin.H{"code": code, "message": message, "data": data}) + } + } else { + c.JSON(http.StatusOK, gin.H{"error": "parameter error", "code": proto.ParameterError, "message": "failed"}) + return + } +} + +// 服务器、设备状态扫描 +func ScanDeviceStatus() { + // TODO + // 检查设备状态 + // 如果设备状态异常, 则发送邮件通知 + devices := worker.GetRedisSetMembers("627gyf3488h") + offline := "" + for _, v := range devices { + c := worker.IsContainKey("monitor_" + v) + if c == false { + worker.SetRedisWithExpire("monitor_"+v, "2", time.Hour*24) + offline += v + "," + } + } + + if offline != "" { + title := "设备状态异常" + content := "设备状态异常\n设备: " + offline + "\t时间:" + time.Now().String() + go SendMail(title, content) + } + +} + +func SendMail(title, content string) { + //捕获异常 + defer func() { + if err := recover(); err != nil { + fmt.Errorf("tool send mail error: %s", err) + } + }() + // TODO + // 发送邮件 + // 邮件内容 + // 邮件标题 + // 收件人 + // 发送邮件 + // 发送邮件通知 + // 发送邮件通知 + var em worker.MyEmail + em.SmtpPassword = "nihzazdkmucnbhid" + em.SmtpHost = "pop.qq.com:587" + em.SmtpUserName = "354425203@qq.com" + em.SmtpPort = 587 + em.ImapPort = 993 + err := em.Send(title, content, []string{"3236990479@qq.com", "lijun@ljsea.top"}) + if err != nil { + fmt.Println(err) + } +} + +func SendMailTool(c *gin.Context) { + id, _ := c.Get("id") + id1 := int(id.(float64)) + + var req SendMailReq + if err := c.ShouldBind(&req); err == nil { + user := dao.FindUserByUserID(id1) + if user.ID == 0 { + c.JSON(http.StatusOK, gin.H{"error": "user not found", "code": proto.ParameterError, "message": "failed"}) + return + } + //目标邮箱地址是否合法 + if !service.CheckEmail(req.To) { + c.JSON(http.StatusOK, gin.H{"error": "email address is invalid", "code": proto.ParameterError, "message": "failed"}) + return + } + if req.Title == "" || req.Content == "" { + c.JSON(http.StatusOK, gin.H{"error": "title or content is empty", "code": proto.ParameterError, "message": "failed"}) + return + } + //发送邮件 + if user.Role == "admin" { + go service.SendEmail(req.To, req.Title, req.Content) + c.JSON(http.StatusOK, gin.H{"code": proto.SuccessCode, "message": "success", "data": "mail will be sent"}) + } else { + c.JSON(http.StatusOK, gin.H{"error": "no send mail permission", "code": proto.PermissionDenied, "message": "failed"}) + } + } else { + c.JSON(http.StatusOK, gin.H{"error": err.Error(), "code": proto.ParameterError, "message": "failed"}) + } + +} diff --git a/handler/user.go b/handler/user.go new file mode 100644 index 0000000..d1bb0cc --- /dev/null +++ b/handler/user.go @@ -0,0 +1,496 @@ +package handler + +import ( + "StuAcaWorksAI/dao" + "StuAcaWorksAI/proto" + "StuAcaWorksAI/service" + "StuAcaWorksAI/worker" + "crypto/md5" + "encoding/hex" + "encoding/json" + "fmt" + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt" + "github.com/google/uuid" + "time" +) + +func SetUpUserGroup(router *gin.Engine) { + userGroup := router.Group("/user") + userGroup.POST("/register", registerHandler) + userGroup.POST("/login", loginHandler) + userGroup.POST("/uuid", GetScanUUID) + userGroup.POST("/gqr", GetQRStatus) + userGroup.POST("/sqr", SetQRStatus) + userGroup.POST("/confirm", ConfirmQRLogin) + userGroup.POST("/search", SearchHandler) + userGroup.POST("/info", GetUserInfo) + userGroup.POST("/update", UpdateUserInfo) + userGroup.POST("/sync", GetSyncUserInfo) + userGroup.POST("/delete", DeleteUser) + userGroup.POST("/reset", ResetPassword) +} + +type RLReq struct { + User string `json:"username" form:"username"` + Email string `json:"email" form:"email"` + Password string `json:"password" form:"password"` + Age int `json:"age" form:"age"` + Gender string `json:"gender" form:"gender"` +} + +type QRReq struct { + UUID string `json:"uuid" form:"uuid"` + Address string `json:"address" form:"address"` + IP string `json:"ip" form:"ip"` +} + +type SearchReq struct { + Keyword string `json:"keyword" form:"keyword"` + ID int `json:"id" form:"id"` +} +type GetUserInfoReq struct { + ID int `json:"id" form:"id"` +} + +type ResetPasswordReq struct { + Email string `json:"email" form:"email"` + OldPassword string `json:"old_password" form:"old_password"` + NewPassword string `json:"new_password" form:"new_password"` + Type int `json:"type" form:"type"` //0获取验证码,2为邮箱重置密码,1为旧密码重置密码 + Code string `json:"code" form:"code"` //验证码 +} + +func ResetPassword(c *gin.Context) { + var req_data ResetPasswordReq + if err := c.ShouldBind(&req_data); err == nil { + if req_data.Type == 0 { + //获取验证码 + //查看是否存在该邮箱 + user := dao.FindUserByEmail(req_data.Email) + if user.ID == 0 { + c.JSON(200, gin.H{"code": proto.OperationFailed, "message": "邮箱不存在", "data": "2"}) + return + } + //随机字符串验证码大写 + code := worker.GetRandomString(6) + worker.SetRedisWithExpire("reset_password_"+req_data.Email, code, time.Minute*5) //设置5分钟过期` + //发送邮件 + service.SendEmail(req_data.Email, "大学生学业作品AI生成工具开发重置密码", "验证码:"+code+" ,请在5分钟内使用!") + } else if req_data.Type == 1 { + //旧密码重置密码 + if len(req_data.OldPassword) != 32 { + hasher := md5.New() + hasher.Write([]byte(req_data.OldPassword)) // 生成密码的 MD5 散列值 + req_data.OldPassword = hex.EncodeToString(hasher.Sum(nil)) // 生成密码的 MD5 散列值 + } + if len(req_data.NewPassword) != 32 { + hasher := md5.New() + hasher.Write([]byte(req_data.NewPassword)) // 生成密码的 MD5 散列值 + req_data.NewPassword = hex.EncodeToString(hasher.Sum(nil)) // 生成密码的 MD5 散列值 + } + user := dao.FindUserByEmail(req_data.Email) + if user.ID == 0 { + c.JSON(200, gin.H{"code": proto.OperationFailed, "message": "邮箱不存在", "data": "2"}) + return + } + if user.Password != req_data.OldPassword { + c.JSON(200, gin.H{"code": proto.OperationFailed, "message": "旧密码错误", "data": "2"}) + return + } + if user.Password == req_data.NewPassword { + c.JSON(200, gin.H{"code": proto.OperationFailed, "message": "新旧密码相同", "data": "2"}) + return + } + dao.UpdateUserByID(int(user.ID), user.Name, req_data.NewPassword, user.Email) + } else if req_data.Type == 2 { + //邮箱重置密码 + if len(req_data.NewPassword) != 32 { + hasher := md5.New() + hasher.Write([]byte(req_data.NewPassword)) // 生成密码的 MD5 散列值 + req_data.NewPassword = hex.EncodeToString(hasher.Sum(nil)) // 生成密码的 MD5 散列值 + } + user := dao.FindUserByEmail(req_data.Email) + if user.ID == 0 { + c.JSON(200, gin.H{"code": proto.OperationFailed, "message": "邮箱不存在", "data": "2"}) + return + } + code := worker.GetRedis("reset_password_" + req_data.Email) + if code != req_data.Code { + c.JSON(200, gin.H{"code": proto.OperationFailed, "message": "验证码错误", "data": "2"}) + return + } + dao.UpdateUserByID(int(user.ID), user.Name, req_data.NewPassword, user.Email) + } else { + c.JSON(200, gin.H{"code": proto.OperationFailed, "message": "type error", "data": "2"}) + return + } + + } else { + c.JSON(200, gin.H{"code": proto.ParameterError, "message": err, "data": "2"}) + return + } + c.JSON(200, gin.H{"code": proto.SuccessCode, "message": "success", "data": "2"}) +} + +func GetUserInfo(c *gin.Context) { + var req_data GetUserInfoReq + id, _ := c.Get("id") + user_id := int(id.(float64)) + if err := c.ShouldBind(&req_data); err == nil { + var user dao.User + if req_data.ID == user_id { + user = dao.FindUserByID2(user_id) + user.Password = "" //不返回密码 + } else { + //判断当前用户是否有权限查看 + cur_user := dao.FindUserByID2(user_id) + if cur_user.Role == "admin" { + user = dao.FindUserByID2(req_data.ID) + user.Password = "" //不返回密码 + } else { + c.JSON(200, gin.H{"code": proto.PermissionDenied, "message": "无权查看", "data": "2"}) + return + } + } + c.JSON(200, gin.H{"code": proto.SuccessCode, "message": "success", "data": user}) + } else { + c.JSON(200, gin.H{"code": proto.ParameterError, "message": err, "data": "2"}) + return + } +} + +func DeleteUser(c *gin.Context) { + var req GetUserInfoReq + id, _ := c.Get("id") + user_id := int(id.(float64)) + if err := c.ShouldBind(&req); err == nil { + res := service.DeleteUserService(req.ID, user_id) + if res != 0 { + c.JSON(200, gin.H{"code": proto.SuccessCode, "message": "success", "data": res}) + } else { + c.JSON(200, gin.H{"code": proto.OperationFailed, "message": "failed", "data": res}) + } + } else { + c.JSON(200, gin.H{"code": proto.ParameterError, "message": err, "data": "2"}) + return + } +} + +func UpdateUserInfo(c *gin.Context) { + var req_data proto.UpdateUserInfoReq + id, _ := c.Get("id") + user_id := int(id.(float64)) + if err := c.ShouldBind(&req_data); err == nil { + rid, err2 := service.UpdateUser(user_id, req_data) + if err2 != nil { + c.JSON(200, gin.H{"code": proto.OperationFailed, "message": "failed", "data": "2"}) + return + } + c.JSON(200, gin.H{"code": proto.SuccessCode, "message": "success", "data": rid}) + } else { + c.JSON(200, gin.H{"code": proto.ParameterError, "message": err, "data": "2"}) + return + } + +} + +func GetScanUUID(c *gin.Context) { + var ReqData QRReq + if err := c.ShouldBind(&ReqData); err != nil { + c.JSON(200, gin.H{"code": proto.DeviceRestartFailed, "message": err, "data": "2"}) + return + } + data := map[string]interface{}{"status": "0", "address": ReqData.Address, "ip": c.ClientIP()} + jsonData, err := json.Marshal(data) + if err != nil { + c.JSON(200, gin.H{"code": proto.DeviceRestartFailed, "message": err, "data": "2"}) + return + } + id := uuid.New() + res := worker.SetRedisWithExpire(id.String(), string(jsonData), time.Minute*30) + if res { + var retrievedData map[string]interface{} + if err2 := json.Unmarshal([]byte(worker.GetRedis(id.String())), &retrievedData); err2 != nil { + c.JSON(200, gin.H{"code": proto.DeviceRestartFailed, "message": err2, "data": "2"}) + return + } + c.JSON(200, gin.H{"code": proto.SuccessCode, "message": retrievedData, "data": id.String()}) + } else { + c.JSON(200, gin.H{"code": proto.RedisSetError, "message": "qr code invalid", "data": "1"}) + } +} + +func SetQRStatus(c *gin.Context) { + var qrsetReq QRReq + if err := c.ShouldBind(&qrsetReq); err == nil && qrsetReq.UUID != "" { + if worker.IsContainKey(qrsetReq.UUID) == false { + c.JSON(200, gin.H{"code": proto.UUIDNotFound, "message": "uuid not found in server", "data": "0"}) + return + } + var retrievedData map[string]interface{} + if err2 := json.Unmarshal([]byte(worker.GetRedis(qrsetReq.UUID)), &retrievedData); err2 != nil { + c.JSON(200, gin.H{"code": proto.DeviceRestartFailed, "message": err2, "data": "2"}) + return + } + retrievedData["status"] = "1" + jsonData, err2 := json.Marshal(retrievedData) + if err2 != nil { + c.JSON(200, gin.H{"code": proto.DeviceRestartFailed, "message": err2, "data": "2"}) + return + } + res := worker.SetRedisWithExpire(qrsetReq.UUID, string(jsonData), time.Minute*30) + if res { + c.JSON(200, gin.H{"code": proto.SuccessCode, "message": "success", "data": retrievedData}) + } else { + c.JSON(200, gin.H{"code": proto.RedisSetError, "message": "qr code invalid", "data": "1"}) + } + } else { + c.JSON(200, gin.H{"code": proto.DeviceRestartFailed, "message": err, "data": "2"}) + } +} + +// 确认返回token数据 +func ConfirmQRLogin(c *gin.Context) { + var qrsetReq QRReq + if err := c.ShouldBind(&qrsetReq); err == nil && qrsetReq.UUID != "" { + //user_id, _ := c.Get("id") + user_name, _ := c.Get("username") + if user_name != "" { + key := "user_" + user_name.(string) + token := worker.GetRedis(key) + if token == "" { + c.JSON(200, gin.H{"code": proto.RedisGetError, "message": "Token不存在", "data": "20"}) + } + if worker.IsContainKey(qrsetReq.UUID) == false { + c.JSON(200, gin.H{"code": proto.UUIDNotFound, "message": "uuid not found in server", "data": "0"}) + return + } + var retrievedData map[string]interface{} + if err2 := json.Unmarshal([]byte(worker.GetRedis(qrsetReq.UUID)), &retrievedData); err2 != nil { + c.JSON(200, gin.H{"code": proto.DeviceRestartFailed, "message": err2, "data": "2"}) + return + } + retrievedData["status"] = token + jsonData, err2 := json.Marshal(retrievedData) + if err2 != nil { + c.JSON(200, gin.H{"code": proto.DeviceRestartFailed, "message": err2, "data": "2"}) + return + } + if worker.SetRedisWithExpire(qrsetReq.UUID, string(jsonData), time.Minute*10) { + c.JSON(200, gin.H{"code": 0, "message": "success", "data": "0"}) + } else { + c.JSON(200, gin.H{"code": proto.RedisSetError, "message": "设置Token失败", "data": "8"}) + } + } else { + c.JSON(200, gin.H{"code": proto.RedisGetError, "message": "failed", "data": "20"}) + } + } else { + c.JSON(200, gin.H{"code": proto.DeviceRestartFailed, "message": err, "data": "3"}) + } +} + +func GetQRStatus(c *gin.Context) { + var qrReq QRReq + if err := c.ShouldBind(&qrReq); err == nil { + var retrievedData map[string]interface{} + if err2 := json.Unmarshal([]byte(worker.GetRedis(qrReq.UUID)), &retrievedData); err2 != nil { + c.JSON(200, gin.H{"code": proto.DeviceRestartFailed, "message": err2, "data": "2"}) + return + } + str := retrievedData["status"].(string) + switch str { + case "": + c.JSON(200, gin.H{"code": proto.UUIDNotFound, "message": "uuid not found", "data": "0"}) //空值 + case "0": + c.JSON(200, gin.H{"code": proto.SuccessCode, "message": "success", "data": "0"}) //空值 + case "1": + c.JSON(200, gin.H{"code": proto.SuccessCode, "message": "success", "data": "1"}) //已扫描待确认 + default: + // 解析 JWT 令牌 + token, err := jwt.Parse(str, func(token *jwt.Token) (interface{}, error) { + return proto.SigningKey, nil + }) + if err != nil { + c.JSON(200, gin.H{"error": err.Error(), "code": proto.TokenParseError, "message": "error"}) + return + } + // 返回令牌 + data := make(map[string]interface{}) + data["id"] = token.Claims.(jwt.MapClaims)["id"] + data["username"] = token.Claims.(jwt.MapClaims)["username"] + data["email"] = token.Claims.(jwt.MapClaims)["email"] + data["token"] = str + c.JSON(200, gin.H{"code": proto.SuccessCode, "message": "success", "data": data}) //确认返回token数据 + } + } else { + c.JSON(200, gin.H{"error": err.Error(), "code": proto.DeviceRestartFailed, "message": "error"}) + } +} + +func SearchHandler(c *gin.Context) { + var req_data SearchReq + if err := c.ShouldBind(&req_data); err == nil { + if req_data.ID != -1 { + user := service.GetUserByID(req_data.ID) + c.JSON(200, gin.H{"code": proto.SuccessCode, "message": "success", "data": user}) + return + } else if req_data.Keyword != "" { + users := service.GetUserByNameLike(req_data.Keyword) + c.JSON(200, gin.H{"code": proto.SuccessCode, "message": "success", "data": users}) + return + } else { + c.JSON(200, gin.H{"code": proto.ParameterError, "message": "error", "data": "无ID 与 关键字"}) + } + } else { + c.JSON(200, gin.H{"error": err.Error(), "code": proto.ParameterError, "message": "error"}) + } +} + +func loginHandler(c *gin.Context) { + var req_data RLReq + tokenString := "" + if err := c.ShouldBind(&req_data); err == nil { + if len(req_data.Password) != 32 { + hasher := md5.New() + hasher.Write([]byte(req_data.Password)) // 生成密码的 MD5 散列值 + req_data.Password = hex.EncodeToString(hasher.Sum(nil)) // 生成密码的 MD5 散列值 + } + user := service.GetUser(req_data.User, req_data.Password, req_data.Password) + if user.ID != 0 { + key := "user_" + user.Name + redis_token := worker.GetRedis(string(key)) + if redis_token == "" { + // 生成 JWT 令牌 + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "username": user.Name, + "id": user.ID, + "exp": time.Now().Add(time.Hour * 10).Unix(), // 令牌过期时间, 10小时后过期 + }) + tokenString, err = token.SignedString(proto.SigningKey) + if err != nil { + c.JSON(200, gin.H{"error": err.Error(), "code": proto.TokenGenerationError, "message": "error"}) + return + } + + worker.SetRedisWithExpire("user_"+user.Name, tokenString, time.Hour*10) // 将用户信息存入 + worker.SetRedisWithExpire(tokenString, tokenString, time.Hour*10) // 设置过期时间为10分钟 + data := make(map[string]interface{}) + data["id"] = user.ID + data["username"] = user.Name + data["email"] = user.Email + worker.SetHash(tokenString, data) // 将用户信息存入 + } else { + tokenString = redis_token + } + // 返回令牌 + data := make(map[string]interface{}) + data["id"] = user.ID + data["username"] = user.Name + data["email"] = user.Email + data["token"] = tokenString + c.JSON(200, gin.H{"code": proto.SuccessCode, "message": "success", "data": data}) + } else { + //用户名或密码错误 + c.JSON(200, gin.H{"error": "用户名或密码错误", "code": proto.UsernameOrPasswordError, "message": "error"}) + } + } else { + c.JSON(200, gin.H{"error": err.Error(), "code": proto.DeviceRestartFailed, "message": "error"}) + } +} + +func registerHandler(c *gin.Context) { + var req_data RLReq + tokenString := "" + var id uint + if err := c.ShouldBind(&req_data); err == nil { + if len(req_data.Password) != 32 { + hasher := md5.New() + hasher.Write([]byte(req_data.Password)) // 生成密码的 MD5 散列值 + req_data.Password = hex.EncodeToString(hasher.Sum(nil)) // 生成密码的 MD5 散列值 + } + if service.ContainsUser(req_data.User, req_data.Email) == true { + c.JSON(200, gin.H{"error": "user already exists", "code": proto.UsernameExists, "message": "error"}) + return + } + id = service.CreateUser(req_data.User, req_data.Password, req_data.Email, req_data.Gender, req_data.Age) + if id == 0 { + c.JSON(200, gin.H{"error": "create user error", "code": proto.OperationFailed, "message": "error"}) + return + } + // 生成 JWT 令牌 + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "username": req_data.User, + "id": id, + "exp": time.Now().Add(time.Hour * 10).Unix(), // 令牌过期时间, 1分钟后过期 + }) + tokenString, err = token.SignedString(proto.SigningKey) + if err != nil { + c.JSON(200, gin.H{"error": err.Error(), "code": proto.TokenGenerationError, "message": "error"}) + return + } + } else { + c.JSON(200, gin.H{"error": err.Error(), "code": proto.ParameterError, "message": "error"}) + return + } + fmt.Println(req_data) + res := worker.SetRedisWithExpire(tokenString, tokenString, time.Hour*10) // 设置过期时间为10分钟 + if !res { + c.JSON(200, gin.H{"error": "set token error", "code": proto.RedisSetError, "message": "error"}) + return + } + // 返回令牌 + data := make(map[string]interface{}) + data["id"] = id + data["username"] = req_data.User + data["email"] = req_data.Email + data["token"] = tokenString + c.JSON(200, gin.H{"code": proto.SuccessCode, "message": "success", "data": data}) + return +} + +func GetSyncUserInfo(c *gin.Context) { + var req_data proto.SyncUserReq + if err := c.ShouldBind(&req_data); err == nil { + if req_data.Token == "" { + c.JSON(200, gin.H{"code": proto.ParameterError, "message": "error", "data": "token is empty"}) + return + } else { + if worker.IsContainSet("super_permission_tokens", req_data.Token) { + if proto.Config.SERVER_USER_TYPE == "master" { + if req_data.Types == 1 { //1为全量同步 + add_users := dao.GetAllUser() + resp := dao.UserSyncResp{} + resp.Add = add_users + c.JSON(200, gin.H{"code": proto.SuccessCode, "message": "success", "data": resp}) + } else if req_data.Types == 2 { //2为增量同步 + if req_data.Device == "" || worker.IsContainSet("sync_devices_ids", req_data.Device) == false { + c.JSON(200, gin.H{"code": proto.ParameterError, "message": "error", "data": "device is empty or not exist"}) + return + } + res := service.GetUserSyncData(req_data.Device) + c.JSON(200, gin.H{"code": proto.SuccessCode, "message": "success", "data": res}) + } else if req_data.Types == 3 { //3为确认同步数据 + res := service.ConfirmSyncUserData(req_data.Device, req_data.Confirm) + if res == nil { + c.JSON(200, gin.H{"code": proto.SuccessCode, "message": "success", "data": "success"}) + } else { + c.JSON(200, gin.H{"code": proto.OperationFailed, "message": "failed:" + res.Error(), "data": "failed"}) + } + } else { + c.JSON(200, gin.H{"code": proto.ParameterError, "message": "type is error", "data": dao.UserSyncResp{}}) + } + } else { + c.JSON(200, gin.H{"code": proto.NoPermission, "message": "no permission,server is not master", "data": proto.UserSync{}}) + } + } else { + c.JSON(200, gin.H{"code": proto.NoPermission, "message": "error", "data": "no permission"}) + return + } + } + + } else { + c.JSON(200, gin.H{"error": err.Error(), "code": proto.ParameterError, "message": "error"}) + return + } +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..c21917e --- /dev/null +++ b/main.go @@ -0,0 +1,363 @@ +package main + +import ( + "StuAcaWorksAI/dao" + "StuAcaWorksAI/handler" + "StuAcaWorksAI/proto" + "StuAcaWorksAI/service" + "StuAcaWorksAI/worker" + "encoding/json" + "fmt" + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt" + "github.com/robfig/cron/v3" + "io" + "log" + "net/http" + "os" + "strconv" + "strings" +) + +func main() { + gin.SetMode(gin.ReleaseMode) + r := gin.Default() + err := dao.Init() + if err != nil { + panic("failed to connect database:" + err.Error()) + } + err = worker.InitRedis() + if err != nil { + panic("failed to connect redis:" + err.Error()) + } + r.Use(handler.CrosHandler()) + r.Use(JWTAuthMiddleware()) // 使用 JWT 认证中间件 + handler.SetUpUserGroup(r) // User + handler.SetUpToolGroup(r) // Tool + handler.SetUpFileGroup(r) // File + defer dao.Close() + defer worker.CloseRedis() + //定时任务 + c := cron.New(cron.WithSeconds()) + // 添加每 10 秒执行一次的任务 + _, err = c.AddFunc("@every 10s", myTask) + if err != nil { + log.Fatal("添加定时任务失败: ", err) + } + c.Start() + //读取配置文件,设置系统 + ReadConfigToSetSystem() + r.Run(":" + proto.Config.SERVER_PORT) // listen and serve on 0.0.0.0:8083 +} +func init() { + // 创建cid的目录 + os.MkdirAll(proto.CID_BASE_DIR, os.ModePerm) + os.MkdirAll(proto.CID_BASE_DIR+"script", os.ModePerm) + os.MkdirAll(proto.CID_BASE_DIR+"workspace", os.ModePerm) + //读取配置文件 + //文件地址/home/saw-ai/saw-ai.conf + configPath := "/home/saw-ai/saw-ai.conf" + //读取配置文件 + err := proto.ReadConfig(configPath) + if err != nil { + panic("failed to read config file:" + err.Error()) + } +} + +func writeLogger(c *gin.Context) { + ip := c.ClientIP() + method := c.Request.Method + path := c.Request.URL.Path + params := "" + + if method == "GET" { + params = c.Request.URL.RawQuery + } + if method == "POST" && !strings.Contains(c.Request.URL.Path, "/upload") { + params = c.Request.PostForm.Encode() + if params == "" { + // 请求体 + bodyBytes, _ := io.ReadAll(c.Request.Body) + c.Request.Body = io.NopCloser(strings.NewReader(string(bodyBytes))) // Write body back, so other handler can read it too + params = string(bodyBytes) + } + } + if strings.Contains(c.Request.URL.Path, "/upload") { + params = "upload file" + } + go dao.InsertLogToDB(path, ip, method, params) +} + +func JWTAuthMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + if proto.Config.LOG_SAVE_DAYS > 0 { + writeLogger(c) + } + // 从请求头中获取 JWT 令牌 + tokenString := c.Request.Header.Get("token") + + //请求方式为get时,从url中获取token + if tokenString == "" { + tokenString = c.Query("token") + } + //如果请求为login或register,则不需要验证token + for k, _ := range proto.Url_map { + if strings.Contains(c.Request.URL.Path, k) { + c.Next() + return + } + } + if tokenString == "" { + //c.AbortWithStatus(200) + c.JSON(200, gin.H{ + "message": "Unauthorized", + "error": "token is empty", + "code": proto.TokenIsNull, + }) + return + } + if proto.Config.TOKEN_USE_REDIS { + redisToken := worker.GetRedis(tokenString) + if redisToken == "" { + c.AbortWithStatus(200) + c.JSON(200, gin.H{ + "message": "NOT_LOGIN", + "error": "server token is empty", + "code": proto.TokenIsNull, + }) + return + } + } + + //查看token是否在超级token中 + if worker.IsContainSet("super_permission_tokens", tokenString) { + sId := c.Request.Header.Get("super_id") + if sId == "" { + sId = c.Query("super_id") + } + if sId == "" { + c.AbortWithStatus(200) + c.JSON(200, gin.H{ + "message": "NOT_LOGIN", + "error": "super_id is empty", + "code": proto.TokenIsNull, + }) + return + } + id, _ := strconv.Atoi(sId) + idFloat64 := float64(id) + //查看s_id类型 + c.Set("id", idFloat64) + c.Next() + return + } + + // 使用加密secret 解析 JWT 令牌 + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + return proto.SigningKey, nil + }) + + // 验证令牌 + if err != nil || !token.Valid { + c.AbortWithStatus(200) + c.JSON(200, gin.H{ + "message": "NOT_LOGIN", + "error": "Invalid token", + "code": proto.TokenExpired, + }) + return + } + + // 将用户信息添加到上下文中 + c.Set("id", token.Claims.(jwt.MapClaims)["id"]) + c.Set("username", token.Claims.(jwt.MapClaims)["username"]) + + if UserFuncIntercept(int(token.Claims.(jwt.MapClaims)["id"].(float64)), c.Request.URL.Path) { + c.AbortWithStatus(200) + c.JSON(http.StatusOK, gin.H{ + "message": "no function permission", + "error": "no permission", + "code": proto.NoPermission, + }) + return + } + + // 继续处理请求 + c.Next() + } +} + +func myTask() { + // 定时任务 + //redis中取出数据 + //handler.RunCron() + if proto.Config.MONITOR { + handler.ScanDeviceStatus() + } + //其它定时任务-通用 + RunGeneralCron() +} + +func ReadConfigToSetSystem() { + //将当前配置文件的信息写入redis,用于程序运行时排查 + config_json, c_err := json.Marshal(proto.Config) + if c_err != nil { + fmt.Println("ReadConfigToSetSystem Error encoding config,err :", c_err) + } else { + worker.SetRedis("system_config_info", string(config_json)) + } + + //redis添加通用定时任务 + key := "cron_info" + //日志清理 + res := worker.GetRedis(key) + var cron_infos []proto.CronInfo + if res != "" { + err := json.Unmarshal([]byte(res), &cron_infos) + if err != nil { + fmt.Println("ReadConfigToSetSystem Error decoding config,key value is :", res) + } + + //查看清除日志任务是否存在 + if proto.Config.LOG_SAVE_DAYS > 0 { + var is_exist bool + for _, v := range cron_infos { + if v.Type == 1 { + is_exist = true + break + } + } + if !is_exist { + var logClean proto.CronInfo + logClean.Type = 1 + logClean.Info = "日志清理" + logClean.Curr = 86400 + logClean.Every = 86400 + cron_infos = append(cron_infos, logClean) + } + } + + is_exist := false + user_sync_id := -1 //用户同步任务索引 + for i, v := range cron_infos { + if v.Type == 2 { + is_exist = true + if proto.Config.USER_SYNC_TIME != v.Every { + v.Every = proto.Config.USER_SYNC_TIME + v.Curr = proto.Config.USER_SYNC_TIME + } + user_sync_id = i + cron_infos[i] = v + break + } + } + if proto.Config.SERVER_USER_TYPE == "slave" { + if proto.Config.USER_SYNC_TIME > 0 && !is_exist { + var userSync proto.CronInfo + userSync.Type = 2 + userSync.Info = "user" + userSync.Curr = proto.Config.USER_SYNC_TIME + userSync.Every = proto.Config.USER_SYNC_TIME + cron_infos = append(cron_infos, userSync) + } else if user_sync_id != -1 { + cron_infos = append(cron_infos[:user_sync_id], cron_infos[user_sync_id+1:]...) //删除 + } + } + + } else { + if proto.Config.LOG_SAVE_DAYS > 0 { + var logClean proto.CronInfo + logClean.Type = 1 + logClean.Info = "日志清理" + logClean.Curr = 86400 + logClean.Every = 86400 + cron_infos = append(cron_infos, logClean) + } + if proto.Config.SERVER_USER_TYPE == "slave" && proto.Config.USER_SYNC_TIME > 0 { + var userSync proto.CronInfo + userSync.Type = 2 + userSync.Info = "user" + userSync.Curr = proto.Config.USER_SYNC_TIME + userSync.Every = proto.Config.USER_SYNC_TIME + cron_infos = append(cron_infos, userSync) + } + } + //存入redis + json_data, err := json.Marshal(cron_infos) + if err != nil { + fmt.Println("ReadConfigToSetSystem Error encoding config,value is :", cron_infos) + } else { + worker.SetRedis(key, string(json_data)) + } +} + +func RunGeneralCron() { + //redis添加通用定时任务 + key := "cron_info" + //日志清理 + res := worker.GetRedis(key) + var cron_infos []proto.CronInfo + if res != "" { + err := json.Unmarshal([]byte(res), &cron_infos) + if err != nil { + fmt.Println("RunGeneralCron Error decoding config,key value is :", res) + } + for i, v := range cron_infos { + //1:日志清理,其他待定 + if v.Type == 1 { + //日志清理 + if v.Curr <= 0 { + //执行日志清理 + go dao.DeleteLog(proto.Config.LOG_SAVE_DAYS) + v.Curr = v.Every + } else { + v.Curr -= 10 + } + cron_infos[i] = v + continue + } + //2 从服务器同步数据 + if v.Type == 2 { + if v.Curr <= 0 { + //执行从服务器同步数据 + if proto.Config.SERVER_USER_TYPE == "slave" && v.Info == "user" { + go service.UserSyncDataFromMaster() + } + v.Curr = v.Every + } else { + v.Curr -= 10 + } + cron_infos[i] = v + continue + } + } + //存入redis + json_data, err := json.Marshal(cron_infos) + if err != nil { + fmt.Println("RunGeneralCron Error encoding config,value is :", cron_infos) + } else { + worker.SetRedis(key, string(json_data)) + } + } +} + +// 用户功能拦截,返回true表示拦截,false表示不拦截 +func UserFuncIntercept(id int, url string) bool { + //先查看是否有权限 + user := dao.FindUserByUserID(id) + //如果用户有权限,则不拦截 + for k, v := range proto.Per_menu_map { + if strings.Contains(url, k) { + if v == 1 && user.VideoFunc == false { + return true + } + if v == 2 && user.DeviceFunc == false { + return true + } + if v == 3 && user.CIDFunc == false { + return true + } + } + } + return false +} diff --git a/proto/conf.go b/proto/conf.go new file mode 100644 index 0000000..e6ed1c3 --- /dev/null +++ b/proto/conf.go @@ -0,0 +1,151 @@ +package proto + +import ( + "encoding/json" + "fmt" + "gorm.io/gorm" + "os" +) + +var Config ConfigStruct +var SigningKey = []byte{} +var Url_map = map[string]bool{"/login": true, "/register": true, "/uuid": true, "/gqr": true, "/cid/callback": true, "/tool/monitor": true, "/user/sync": true, "/tool/file/": true} // 不需要token验证的url +var Per_menu_map = map[string]int{"/video/": 1, "/device/": 2, "/cid/": 3} +var File_Type = map[string]int{"im": 1, "avatar": 2, "file": 3, "config": 4} // 文件类型 +const ( + MYSQL_USER = "video_t2" + MYSQL_DB = "video_t2" + MYSQL_PASSWORD = "2t2SKHmWEYj2xFKF" + MYSQL_PORT = "3306" + MYSQL_HOST = "127.0.0.1" + MYSQL_DSN = MYSQL_USER + ":" + MYSQL_PASSWORD + "@tcp(" + MYSQL_HOST + ":" + MYSQL_PORT + ")/" + MYSQL_DB + "?charset=utf8mb4&parseTime=True&loc=Local" + + REDIS_ADDR = "127.0.0.1:6379" + REDIS_PASSWORD = "lj502138" + REIDS_DB = 2 + + TOKEN_SECRET = "mfjurnc_32ndj9dfhj" + + // 以下是持续集成、部署的配置 + CID_BASE_DIR = "/home/lijun/cid/" + + // 以下是文件上传的配置 + FILE_BASE_DIR = "/home/lijun/file/" +) + +const ( + // 以下是消息类型 + MSG_TYPE_SIMPLE = 1 // 单聊 + MSG_TYPE_GROUP = 2 // 群聊 + MSG_TYPE_SYSTEM = 3 // 系统消息 + MSG_TYPE_FRIEND = 4 // 好友请求 + MSG_TYPE_GROUP_ADD = 5 // 加入群聊请求 + MSG_TYPE_GROUP_INVI = 6 // 邀请加入群聊 + + // 以下是消息状态 + MSG_STATUS_READ = 1 // 已读 + MSG_STATUS_UNREAD = 0 // 未读 +) + +const ( + //文件上传类型 + File_TYPE = 1 // 通用文件 + //用于视频解析 + Video_TYPE = 2 // 视频文件 +) + +type User struct { + gorm.Model + Name string `gorm:"column:name"` + Age int `gorm:"column:age"` + Email string `gorm:"column:email"` + Gender string `gorm:"column:gender"` +} + +type ConfigStruct struct { + DB int `json:"db"` // 0: mysql, 1: pg + MYSQL_DSN string `json:"mysql_dsn"` + PG_DSN string `json:"pg_dsn"` + REDIS_ADDR string `json:"redis_addr"` + TOKEN_USE_REDIS bool `json:"token_use_redis"` + REDIS_User_PW bool `json:"redis_user_pw"` // 是否使用密码 + REDIS_PASSWORD string `json:"redis_password"` + REDIS_DB int `json:"redis_db"` + TOKEN_SECRET string `json:"token_secret"` + CID_BASE_DIR string `json:"cid_base_dir"` + FILE_BASE_DIR string `json:"file_base_dir"` + MONITOR bool `json:"monitor"` // 状态监控及邮件通知 + SERVER_SQL_LOG bool `json:"server_sql_log"` // 服务器sql日志 + SERVER_PORT string `json:"server_port"` // 服务端口 + LOG_SAVE_DAYS int `json:"log_save_days"` // 日志保存天数,-1表示不保存,0表示永久保存 + SERVER_USER_TYPE string `json:"user_type"` // 服务器用户类型,master: 主服务器,slave: 从服务器,从服务器会定时同步数据 + MASTER_SERVER_DOMAIN string `json:"master_server_domain"` // 主服务器域名 + USER_SYNC_TIME int `json:"user_sync_time"` // 用户数据同步时间,单位秒 + SERVER_NAME string `json:"server_name"` // 服务器名称,用于区分不同服务器 +} + +// 读取配置文件 +func ReadConfig(path string) error { + //查看配置文件是否存在,不存在则创建 + _, err := os.Stat(path) + if err != nil { + fmt.Println("Config file not found!") + //创建默认配置 + DefaultConfig() + //写入json文件 + file, err := os.Create(path) + if err != nil { + fmt.Println("Error creating config file") + return err + } + defer file.Close() + encoder := json.NewEncoder(file) + err = encoder.Encode(&Config) + if err != nil { + fmt.Println("Error encoding config") + } + return err + } + + //读json文件 + file, err := os.Open(path) + if err != nil { + fmt.Println("Error opening config file") + return err + } + defer file.Close() + decoder := json.NewDecoder(file) + err = decoder.Decode(&Config) + if err != nil { + fmt.Println("Error decoding config") + } else { + if Config.SERVER_PORT == "" { + Config.SERVER_PORT = "8083" // 默认端口 + } + } + SigningKey = []byte(Config.TOKEN_SECRET) + return err +} + +// 默认配置 +func DefaultConfig() { + Config.DB = 2 + Config.MYSQL_DSN = MYSQL_DSN + Config.PG_DSN = "" + Config.REDIS_ADDR = REDIS_ADDR + Config.TOKEN_USE_REDIS = false + Config.REDIS_User_PW = false + Config.REDIS_PASSWORD = REDIS_PASSWORD + Config.REDIS_DB = REIDS_DB + Config.TOKEN_SECRET = TOKEN_SECRET + Config.CID_BASE_DIR = CID_BASE_DIR + Config.FILE_BASE_DIR = FILE_BASE_DIR + Config.MONITOR = false + Config.SERVER_SQL_LOG = false + Config.SERVER_PORT = "8083" + Config.LOG_SAVE_DAYS = 7 + Config.SERVER_USER_TYPE = "master" + Config.MASTER_SERVER_DOMAIN = "" + Config.USER_SYNC_TIME = 86400 + Config.SERVER_NAME = "default" +} diff --git a/proto/file_req.go b/proto/file_req.go new file mode 100644 index 0000000..a0bbae3 --- /dev/null +++ b/proto/file_req.go @@ -0,0 +1,32 @@ +package proto + +import "time" + +type FileUploadReq struct { + UploadType string `json:"upload_type" form:"upload_type" binding:"required"` + AuthType string `json:"auth_type" form:"auth_type"` + Md5 string `json:"md5" form:"md5"` //文件md5值 + Type string `json:"type" form:"type"` //类型,im,avatar,file,config,config为系统文件 +} + +type ConfigFileReq struct { + ID int `json:"id" form:"id"` + Type string `json:"type" form:"type"` //查询类型,one,all + DelFile bool `json:"del_file"` //删除文件 + FileName string `json:"file_name" form:"file_name"` + FilePath string `json:"file_path" form:"file_path"` + Content string `json:"content" form:"content"` +} + +type AddConfigFileReq struct { + FileName string `json:"file_name" form:"file_name" required:"true"` + FilePath string `json:"file_path" form:"file_path" required:"true"` +} +type SearchOneConfigFileResp struct { + ID uint `json:"id" form:"id"` + CreatedAt time.Time `json:"created_at" form:"created_at"` + UpdatedAt time.Time `json:"updated_at" form:"updated_at"` + FileName string `json:"file_name" form:"file_name"` + FilePath string `json:"file_path" form:"file_path"` + Content string `json:"content" form:"content"` +} diff --git a/proto/status.go b/proto/status.go new file mode 100644 index 0000000..a4e005a --- /dev/null +++ b/proto/status.go @@ -0,0 +1,76 @@ +package proto + +const ( + SuccessCode = 0 // 成功 + + // 通用错误码 + ErrorCode = 1 // 未知错误或服务器内部错误 + ParameterError = 9 // 请求参数解析错误 + OperationFailed = 17 // 数据库数据操作失败 + DataNotFound = 14 // 查询数据失败 + InternalServerError = 10 // 服务器内部错误 + + // Token相关错误码 + TokenInvalid = 2 // Token失效,未登录 + TokenIsNull = 3 // Token为空 + TokenExpired = 4 // Token已过期 + TokenGenerationError = 5 // Token生成错误 + TokenParseError = 19 // Token解析错误 + + // 用户名密码相关错误码 + UsernameOrPasswordError = 6 // 用户名或密码错误 + UsernameExists = 7 // 用户名已存在 + PermissionDenied = 21 // 权限不足 + + // Redis相关错误码 + RedisSetError = 8 // 设置redis错误 + RedisGetError = 20 // 获取redis错误 + + // 视频操作相关错误码 + VideoDelayOperationFailed = 11 // 视频延迟操作失败 + VideoDeleteFailed = 12 // 视频删除失败 + + // 设备操作相关错误码 + DeviceRestartFailed = 13 // 设备重启失败 + DeviceAddFailed = 15 // 设备添加失败 + DeviceUpdateFailed = 16 // 设备修改失败 + + // 撤销操作相关错误码 + RevokeOperation = 30 // 撤销 + RevokeDelayOperationFailed = 31 // 撤销延迟操作失败 + RevokeOperationFailed = 32 // 撤销操作失败 + + // UUID相关错误码 + UUIDNotFound = 18 // uuid不存在 + + //Tool + NoRedisPermissions = 51 + NoRunPermissions = 52 + NoDevicePermissions = 53 + NoPermission = 54 + + //消息错误码 + MsgSendFailed = 61 // 消息发送失败 + + //文件错误码 + FileNotFound = 71 // 文件不存在 + FileUploadFailed = 72 // 文件上传失败 + SaveFileInfoFailed = 73 // 保存文件信息失败 + SaveFileFailed = 74 // 保存文件失败 + UploadFileFailed = 75 // 上传文件失败 + NoUploadPermissions = 76 // 无上传权限 + DeleteFileFailed = 77 // 删除文件失败 + DeleteFileInfoFailed = 78 // 删除文件信息失败 + + DataFormatError = 80 // 数据格式错误 + + AddConfigFileFailed = 90 // 添加配置文件失败 + UpdateConfigFailed = 91 // 更新配置失败 + DeleteConfigFailed = 92 // 删除配置失败 + SearchConfigFileFailed = 93 // 获取配置失败 + + ShellCreateFailed = 100 // 创建shell失败 + ShellUpdateFailed = 101 // 更新shell失败 + ShellDeleteFailed = 102 // 删除shell失败 + ShellSearchFailed = 103 // 获取shell失败 +) diff --git a/proto/user_req.go b/proto/user_req.go new file mode 100644 index 0000000..61875d6 --- /dev/null +++ b/proto/user_req.go @@ -0,0 +1,112 @@ +package proto + +import ( + "time" +) + +type UpdateUserInfoReq struct { + ID int `json:"id" form:"id"` //用户id + Username string `json:"name" form:"name"` //用户名 + Age int `json:"age" form:"age"` //年龄 + Role string `json:"role" form:"role"` //角色 + Gender string `json:"gender" form:"gender"` //性别 + Redis bool `json:"redis" form:"redis"` //是否刷新redis + Upload bool `json:"upload" form:"upload"` //是否上传头像 + VideoFunc bool `json:"video_func" form:"video_func"` //视频功能 + DeviceFunc bool `json:"device_func" form:"device_func"` //设备功能 + CIDFunc bool `json:"cid_func" form:"cid_func"` //持续集成功能 + Run bool `json:"run" form:"run"` //是否运行 + Avatar string `json:"avatar" form:"avatar"` //头像 +} + +type CIDRUN struct { + CID uint `json:"cid" form:"cid"` //持续集成ID,查找持续集成任务 + Curr int `json:"curr" form:"curr"` //当前剩余时间,每次执行减10s小于等于0则执行 + Every int `json:"every" form:"every"` //每隔多少秒执行一次,小于等于0表示不执行,时间粒度为10s +} + +// 用于执行函数,方法 +type CronInfo struct { + Type int `json:"type" form:"type"` //类型编码,1日志清理(且只会有一个),其他待定,2从服务器同步数据 + Info string `json:"info" form:"info"` //信息 + Curr int `json:"curr" form:"curr"` //当前剩余时间,每次执行减10s小于等于0则执行 + Every int `json:"every" form:"every"` //每隔多少秒执行一次,小于等于0表示不执行,时间粒度为10s +} + +// 用户数据同步 +type UserSync struct { + Update []UserAddOrUpdate `json:"update" form:"update"` //更新用户 + Add []UserAddOrUpdate `json:"add" form:"add"` //添加用户 + Delete []UserDelID `json:"delete" form:"delete"` //删除用户 +} + +// 用户数据同步确认 +type UserSyncConfirm struct { + Add []UserConfirmID `json:"add" form:"add"` //添加用户 + Update []UserConfirmID `json:"update" form:"update"` //更新用户 + Delete []UserConfirmID `json:"delete" form:"delete"` //删除用户 +} + +type UserConfirmID struct { + ID uint `json:"id" form:"id"` //用户id +} + +type UserDelID struct { + ID uint `json:"ID" form:"ID"` //用户id +} + +type UserAddOrUpdate struct { + ID uint `json:"ID" form:"ID"` //用户id + CreatedAt time.Time `json:"CreatedAt" form:"CreatedAt"` //创建时间 + UpdatedAt time.Time `json:"UpdatedAt" form:"UpdatedAt"` //更新时间 + DeletedAt time.Time `json:"DeletedAt" form:"DeletedAt"` //删除时间 + Name string `json:"Name" form:"Name"` //用户名 + Age int `json:"Age" form:"Age"` //年龄 + Email string `json:"Email" form:"Email"` //邮箱 + Password string `json:"Password" form:"Password"` //密码 + Gender string `json:"Gender" form:"Gender"` //性别 + Role string `json:"Role" form:"Role"` //角色 + Redis bool `json:"Redis" form:"Redis"` //是否刷新redis + Run bool `json:"Run" form:"Run"` //是否运行 + Upload bool `json:"Upload" form:"Upload"` //是否上传头像 + VideoFunc bool `json:"VideoFunc" form:"VideoFunc"` //视频功能 + DeviceFunc bool `json:"DeviceFunc" form:"DeviceFunc"` + CIDFunc bool `json:"CIDFunc" form:"CIDFunc"` + Avatar string `json:"Avatar" form:"Avatar"` //头像 + CreateTime string `json:"CreateTime" form:"CreateTime"` + UpdateTime string `json:"UpdateTime" form:"UpdateTime"` +} + +// 数据同步请求 +type SyncUserReq struct { + Token string `json:"token" form:"token"` + Types int `json:"type" form:"type"` // 1为全量同步 2为增量同步 + Device string `json:"device" form:"device"` + Confirm UserSyncConfirm `json:"confirm" form:"confirm"` +} + +// shell待执行 +type SyncUserShellReq struct { + Token string `json:"token" form:"token"` + Server string `json:"server" form:"server"` +} + +type UpdateShellReq struct { + ID uint `json:"id"` + ShellName string `json:"shell_name"` + ShellContent string `json:"shell_content"` + Server string `json:"server"` + Status int `json:"status"` + ShellResult string `json:"shell_result"` +} + +// shell 执行结果返回 +type SyncUserShellResp struct { + Token string `json:"token" form:"token"` + Shells []UpdateShellReq `json:"shells" form:"shells"` +} + +type UpdateShellRespV2 struct { + ID uint `json:"id" form:"id"` + Status int `json:"status" form:"status"` +} diff --git a/saw-ai.conf b/saw-ai.conf new file mode 100644 index 0000000..22589d3 --- /dev/null +++ b/saw-ai.conf @@ -0,0 +1,14 @@ +{ + "db":0, + "mysql_dsn":"video_t2:2t2SKHmWEYj2xFKF@tcp(127.0.0.1:3306)/video_t2?charset=utf8mb4&parseTime=True&loc=Local", + "pg_dsn":"host=localhost user=video_t2 dbname=video_t2 password=2t2SKHmWEYj2xFKF port=5432 TimeZone=Asia/Shanghai", + "redis_addr":"127.0.0.1:6379", + "redis_db":2, + "redis_user_pw":true, + "token_use_redis":true, + "redis_password":"lj502138", + "token_secret":"mfjurnc_32ndj9dfhj", + "file_base_dir":"/home/lijun/file/", + "monitor":false, + "server_port":"8083" +} \ No newline at end of file diff --git a/service/fileService.go b/service/fileService.go new file mode 100644 index 0000000..31de438 --- /dev/null +++ b/service/fileService.go @@ -0,0 +1,219 @@ +package service + +import ( + "StuAcaWorksAI/dao" + "StuAcaWorksAI/proto" + "StuAcaWorksAI/worker" + "crypto/md5" + "fmt" + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "io" + "mime/multipart" + "os" + "path" + "regexp" + "time" +) + +// 检查path是否存在当前日期文件夹如(2024-08-09),不存在则path下当前日期文件夹创建,存在则返回 +func getFilePath(path string) string { + //当前日期,格式为2024-08-09 + date := time.Now().Format("2006-01-02") + //拼接文件路径 + filePath := path + "/" + date + //判断文件夹是否存在 + _, err := os.Stat(filePath) + if err != nil { + //不存在则创建 + os.MkdirAll(filePath, os.ModePerm) + } + return filePath +} + +func SaveFile(c *gin.Context, file *multipart.FileHeader, uploadType string) (string, string, error) { + //获取文件后缀 + fileSuffix := path.Ext(file.Filename) + //生成文件名 + fileStoreName := uuid.NewString() + fileSuffix + //生成文件路径 + path_ := getFilePath(proto.FILE_BASE_DIR) + filePath := path_ + "/" + fileStoreName + //保存文件 + if err := c.SaveUploadedFile(file, filePath); err != nil { + return "", "", err + } + if uploadType == "2" { + worker.PushRedisList("video_need_handle", filePath) + } + + return path_, fileStoreName, nil +} + +func CalculateFileMd5(file io.Reader) string { + hash := md5.New() + if _, err := io.Copy(hash, file); err != nil { + return "" + } + return fmt.Sprintf("%x", hash.Sum(nil)) +} + +func CheckUploadRequestParameters(req *proto.FileUploadReq) error { + var err error + if req.AuthType == "" { + err = fmt.Errorf("auth_type is empty") + } + if req.UploadType == "" { + req.UploadType = "1" + } + if req.UploadType != "1" { + req.UploadType = "2" + } + if proto.File_Type[req.Type] == 0 { + err = fmt.Errorf("file type is invalid") + } + return err +} + +func CreateConfigFile(req *proto.AddConfigFileReq, userId int) error { + var err error + user := GetUserByIDWithCache(userId) + if user.ID == 0 || user.Role != "admin" { + err = fmt.Errorf("user not found or no permission") + return err + } + if req.FileName == "" || req.FilePath == "" { + err = fmt.Errorf("file name or file path is empty") + return err + } + //查看系统中是否存在文件,不存在则创建 + file := req.FilePath + "/" + req.FileName + //正则判断文件名是否合法 + pattern := `^/([^/:\*?]+/)*([^/:\*?]+)?$` + reg := regexp.MustCompile(pattern) + if reg.MatchString(file) == false { + err = fmt.Errorf("file path is invalid") + return err + } + _, fileErr := os.Stat(file) + if fileErr != nil { + //创建文件 + f, err2 := os.Create(file) + if err2 != nil { + err = err2 + return err + } + defer func(f *os.File) { + err := f.Close() + if err != nil { + fmt.Println("Error closing file") + } + }(f) + } + //创建 + configFile := dao.ConfigFile{FilePath: req.FilePath, FileName: req.FileName, AuthID: userId} + _, err3 := dao.CreateConfigFile(configFile) + if err3 != nil { + err = err3 + return err + } + return err +} + +func DeleteConfigFile(req *proto.ConfigFileReq, userId int) error { + var err error + user := GetUserByIDWithCache(userId) + if user.ID == 0 || user.Role != "admin" { + err = fmt.Errorf("user not found or no permission") + return err + } + //删除文件 + config_file := dao.FindConfigFileByID(req.ID, userId) + if config_file.ID == 0 { + err = fmt.Errorf("config file not found") + return err + } + err = dao.DeleteConfigFileByID(req.ID) + if req.DelFile { + file := config_file.FilePath + "/" + config_file.FileName + err = os.Remove(file) + if err != nil { + return err + } + } + //删除数据库记录 + return err +} + +type ConfigFileService struct { +} + +func (c *ConfigFileService) UpdateConfigFile(req *proto.ConfigFileReq, userId int) error { + var err error + user := GetUserByIDWithCache(userId) + if user.ID == 0 || user.Role != "admin" { + err = fmt.Errorf("user not found or no permission") + return err + } + config_file := dao.FindConfigFileByID(req.ID, userId) + if config_file.ID == 0 { + err = fmt.Errorf("config file not found") + return err + } + //修改文件名 + if req.FileName != "" { + file := config_file.FilePath + "/" + config_file.FileName + new_file := config_file.FilePath + "/" + req.FileName + err = os.Rename(file, new_file) + if err != nil { + return err + } + err = dao.UpdateConfigFileByID(req.ID, dao.ConfigFile{FileName: req.FileName}) + } + if req.Content != "" { + file := config_file.FilePath + "/" + config_file.FileName + f, err2 := os.OpenFile(file, os.O_WRONLY|os.O_TRUNC, 0644) //打开文件,清空文件内容,写入新内容,不存在则创建 + if err2 != nil { + err = err2 + return err + } + defer func(f *os.File) { + err3 := f.Close() + if err3 != nil { + fmt.Println("Error closing file") + } + }(f) + _, err = f.WriteString(req.Content) + if err != nil { + return err + } + } + return err +} + +func (c *ConfigFileService) SearchOneConfigFile(req *proto.ConfigFileReq, userId int) ([]proto.SearchOneConfigFileResp, error) { + user := GetUserByIDWithCache(userId) + if user.ID == 0 || user.Role != "admin" { + return []proto.SearchOneConfigFileResp{}, fmt.Errorf("user not found or no permission") + } + config_file := dao.FindConfigFileByID(req.ID, userId) + if config_file.ID == 0 { + return []proto.SearchOneConfigFileResp{}, fmt.Errorf("config file not found") + } + file := config_file.FilePath + "/" + config_file.FileName + content, err2 := os.ReadFile(file) + if err2 != nil { + return []proto.SearchOneConfigFileResp{}, err2 + } + resp := []proto.SearchOneConfigFileResp{{ID: config_file.ID, FilePath: config_file.FilePath, FileName: config_file.FileName, Content: string(content), CreatedAt: config_file.CreatedAt, UpdatedAt: config_file.UpdatedAt}} + return resp, nil +} + +func (c *ConfigFileService) SearchAllConfigFile(userId int) ([]dao.ConfigFile, error) { + user := GetUserByIDWithCache(userId) + if user.ID == 0 || user.Role != "admin" { + return []dao.ConfigFile{}, fmt.Errorf("user not found or no permission") + } + config_files := dao.FindConfigFileByAuthID(userId) + return config_files, nil +} diff --git a/service/loggerService.go b/service/loggerService.go new file mode 100644 index 0000000..4a39db9 --- /dev/null +++ b/service/loggerService.go @@ -0,0 +1,7 @@ +package service + +import "StuAcaWorksAI/dao" + +func insertLog(url, ip, method, params string) uint { + return dao.InsertLogToDB(url, ip, method, params) +} diff --git a/service/toolService.go b/service/toolService.go new file mode 100644 index 0000000..c3979d0 --- /dev/null +++ b/service/toolService.go @@ -0,0 +1,115 @@ +package service + +import ( + "StuAcaWorksAI/proto" + "StuAcaWorksAI/worker" + "fmt" + "regexp" + "time" +) + +func SetToolRedisList(key string, value string, expire int) (code int, message string) { + if expire == 0 { + if worker.PushRedisList(key, value) { + return proto.SuccessCode, "success" + } else { + return proto.OperationFailed, "push redis list failed" + } + } else if expire > 0 { + if worker.PushRedisListWithExpire(key, value, time.Duration(expire)) { + return proto.SuccessCode, "success" + } else { + return proto.OperationFailed, "push redis list with expire failed" + } + } else { + return proto.ParameterError, "expire time can not be negative" + } +} + +func SetToolRedisSet(key string, value string, expire int) (code int, message string) { + if expire == 0 { + if worker.SetRedis(key, value) { + return proto.SuccessCode, "success" + } else { + return proto.OperationFailed, "set redis failed" + } + } else if expire > 0 { + if worker.SetRedisWithExpire(key, value, time.Duration(expire)) { + return proto.SuccessCode, "success" + } else { + return proto.OperationFailed, "set redis with expire failed" + } + } else { + return proto.ParameterError, "expire time can not be negative" + } +} + +func SetToolRedisKV(key string, value string, expire int) (code int, message string) { + if expire == 0 { + if worker.SetRedis(key, value) { + return proto.SuccessCode, "success" + } else { + return proto.OperationFailed, "set redis failed" + } + } else if expire > 0 { + if worker.SetRedisWithExpire(key, value, time.Duration(expire)) { + return proto.SuccessCode, "success" + } else { + return proto.OperationFailed, "set redis with expire failed" + } + } else { + return proto.ParameterError, "expire time can not be negative" + } +} + +func GetToolRedis(key string) (code int, message string) { + val := worker.GetRedis(key) + if val == "" { + return proto.OperationFailed, "get redis failed" + } else { + return proto.SuccessCode, val + } +} + +func GetAllRedis() (code int, msg string, data []worker.RedisInfo) { + data, err := worker.GetAllRedisInfo() + if err != nil { + return proto.OperationFailed, err.Error(), nil + } + return proto.SuccessCode, "success", data +} + +func SendEmail(email, subject, body string) { + //捕获异常 + defer func() { + if err := recover(); err != nil { + fmt.Errorf("tool send mail error: %s", err) + } + }() + // TODO + // 发送邮件 + // 邮件内容 + // 邮件标题 + // 收件人 + // 发送邮件 + // 发送邮件通知 + // 发送邮件通知 + var em worker.MyEmail + em.SmtpPassword = "nihzazdkmucnbhid" + em.SmtpHost = "pop.qq.com:587" + em.SmtpUserName = "354425203@qq.com" + em.SmtpPort = 587 + em.ImapPort = 993 + err := em.Send(subject, body, []string{email}) + if err != nil { + fmt.Println("send mail error:", err) + } +} + +// 地址校验 +func CheckEmail(email string) bool { + //正则表达式判断是否是邮箱 + pattern := `^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-])+$` + reg := regexp.MustCompile(pattern) + return reg.MatchString(email) +} diff --git a/service/userService.go b/service/userService.go new file mode 100644 index 0000000..82b737b --- /dev/null +++ b/service/userService.go @@ -0,0 +1,369 @@ +package service + +import ( + "StuAcaWorksAI/dao" + "StuAcaWorksAI/proto" + "StuAcaWorksAI/worker" + "encoding/json" + "errors" + "fmt" + "regexp" + "strconv" + "time" +) + +func CreateUser(name, password, email, gender string, age int) uint { + id := dao.CreateUser(name, password, email, gender, age) + if id != 0 { + //添加用户信息到同步列表 + err := setSyncUserDataSet("add", int(id)) + if err != nil { + return id + } + } + return id +} + +func GetUser(name, email, password string) dao.User { + emailRegex := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$` + re := regexp.MustCompile(emailRegex) + var user dao.User + if re.MatchString(name) { + user = dao.FindUserByEmail(name) + } else { + user = dao.FindUserByName(name) + } + if user.ID != 0 && user.Password == password { + return user + } + return dao.User{} +} + +func ContainsUser(name, email string) bool { + user := dao.FindUserByName(name) + user2 := dao.FindUserByEmail(email) + if user.ID != 0 || user2.ID != 0 { + return true + } + return false +} + +func GetUserByID(id int) []proto.User { + return dao.FindUserByID(id) +} + +// 获取用户信息,有redis缓存 +func GetUserByIDWithCache(id int) dao.User { + if id <= 0 { + return dao.User{} + } + var user dao.User + //先从redis获取 + key := "user_info_" + strconv.Itoa(id) + user_str := worker.GetRedis(key) + if user_str != "" { + err := json.Unmarshal([]byte(user_str), &user) + if err != nil { + fmt.Println("get user info , json unmarshal error:", err, "\tuser_str:", user_str) + return dao.User{} + } + } else { + user = dao.FindUserByID2(id) + if user.ID != 0 { + userJson, err := json.Marshal(user) + if err != nil { + fmt.Println("get user info , json marshal error:", err) + return dao.User{} + } + success := worker.SetRedis(key, string(userJson)) + if !success { + fmt.Println("set redis error,user json:", string(userJson)) + } + } + } + return user +} + +func GetUserByNameLike(name string) []proto.User { + return dao.FindUserByNameLike(name) +} + +func UpdateUser(user_id int, req proto.UpdateUserInfoReq) (int, error) { + cur_user := dao.FindUserByID2(user_id) + //fmt.Println("cur_user:", cur_user, "req:", req) + if user_id == req.ID && cur_user.Role != "admin" { + err := dao.UpdateUserByID3(user_id, req) //用户修改自己的信息,不能修改权限信息 + //添加修改用户信息到同步列表 + if err == nil { + err2 := setSyncUserDataSet("update", user_id) + UpdateUserCache(user_id) + if err2 != nil { + fmt.Println("set sync user data set error:", err2) + return user_id, nil + } + } + return user_id, err + } else if cur_user.Role == "admin" { + err := dao.UpdateUserByID2(req.ID, req) + if err == nil { + //添加修改用户信息到同步列表 + err2 := setSyncUserDataSet("update", req.ID) + UpdateUserCache(req.ID) + if err2 != nil { + fmt.Println("set sync user data set error:", err2) + return req.ID, nil + } + } + return req.ID, nil + } else { + return 0, nil + } +} + +func UpdateUserCache(id int) { + key := "user_info_" + strconv.Itoa(id) + if worker.IsContainKey(key) { + user := GetUserByID(id) + userJson, err := json.Marshal(user) + if err != nil { + fmt.Println("get user info , json marshal error:", err) + } + success := worker.SetRedis(key, string(userJson)) + if !success { + fmt.Println("set redis error,user json:", string(userJson)) + } + } +} + +func DeleteUserService(id, user_id int) int { + res := 0 + if user_id == id { + res = dao.DeleteUserByID(id) + } else { + user := dao.FindUserByID2(user_id) + if user.Role == "admin" { + res = dao.DeleteUserByID(id) + } + } + if res != 0 { + //添加删除用户信息到同步列表 + err := setSyncUserDataSet("delete", id) + if err != nil { + return res + } + } + return res +} +func UserSyncDataFromMaster() { + //从接口获取数据 + url := "https://" + proto.Config.MASTER_SERVER_DOMAIN + "/user/sync" + tokens := worker.GetRedisSetMembers("super_permission_tokens") + var req proto.SyncUserReq + req.Token = tokens[0] + req.Device = proto.Config.SERVER_NAME + all := worker.GetRedis("user_sync_all") + is_all := false //是否全量同步 + if all == "" || all == "1" { + is_all = true + //清空数据表 + err := dao.ClearAllUsers() + if err != nil { + fmt.Println("All ClearAllUsers error:", err) + return + } + worker.SetRedisForever("user_sync_all", "1") + req.Types = 1 + } else { + worker.SetRedisForever("user_sync_all", "2") + req.Types = 2 + } + + user_sync_data, err := worker.SyncDataFromMasterReq2(url, req) + if err != nil { + fmt.Println("UserSyncDataFromMaster error:", err) + return + } + add_users := user_sync_data.Add + update_users := user_sync_data.Update + delete_users := user_sync_data.Delete + //未成功操作的id + var fail_ids []uint + + //添加用户 + var add_confirm []proto.UserConfirmID + for _, v := range add_users { + res := dao.AddUserSync(v) + if res == 0 { + fail_ids = append(fail_ids, v.ID) + } else { + add_confirm = append(add_confirm, proto.UserConfirmID{ID: v.ID}) + } + } + //更新用户 + var update_confirm []proto.UserConfirmID + for _, v := range update_users { + res := dao.UpdateUserSync(v) + if res == 0 { + fail_ids = append(fail_ids, v.ID) + } else { + update_confirm = append(update_confirm, proto.UserConfirmID{ID: v.ID}) + } + } + //删除用户 + var delete_confirm []proto.UserConfirmID + for _, v := range delete_users { + res := dao.DeleteUserSync(v) + if res == 0 { + fail_ids = append(fail_ids, v.ID) + } else { + delete_confirm = append(delete_confirm, proto.UserConfirmID{ID: v.ID}) + } + } + + //确认同步数据 + if is_all == false { + var data proto.UserSyncConfirm + data.Add = add_confirm + data.Update = update_confirm + data.Delete = delete_confirm + //确认同步数据请求 + var confirm_req proto.SyncUserReq + confirm_req.Token = tokens[0] + confirm_req.Device = proto.Config.SERVER_NAME + confirm_req.Types = 3 + confirm_req.Confirm = data + worker.SyncDataFromMasterReq2(url, confirm_req) + } else { + worker.SetRedisForever("user_sync_all", "2") + } +} + +// 同步数据到主服务器-增删改数据 +func GetUserSyncData(device string) dao.UserSyncResp { + key := device + "_sync_user_ids" + add_temp_key := device + "_sync_user_ids_add_confirm_temp" + update_temp_key := device + "_sync_user_ids_update_confirm_temp" + delete_temp_key := device + "_sync_user_ids_delete_confirm_temp" + //需要获取暂存集合的并集,清空暂存集合,存入待确认集合 + add_user_ids := worker.GetRedisSetUnion(key+"_add", add_temp_key) + update_user_ids := worker.GetRedisSetUnion(key+"_update", update_temp_key) + delete_user_ids := worker.GetRedisSetUnion(key+"_delete", delete_temp_key) + add_users := []dao.User{} + update_users := []dao.User{} + delete_users := []proto.UserDelID{} + for _, v := range add_user_ids { + id, _ := strconv.Atoi(v) + user := dao.FindUserByUserID(id) + add_users = append(add_users, user) + } + + for _, v := range update_user_ids { + id, _ := strconv.Atoi(v) + user := dao.FindUserByUserID(id) + update_users = append(update_users, user) + } + + for _, v := range delete_user_ids { + id, _ := strconv.Atoi(v) + delete_users = append(delete_users, proto.UserDelID{ID: uint(id)}) + } + //将id存入暂存集合,清空原集合,存入待确认集合主要保证在确认时,有新的数据加入不会在确认时漏掉 + worker.SetRedisSetUnionAndStore(add_temp_key, key+"_add") + worker.ClearRedisSet(key + "_add") + worker.SetRedisSetUnionAndStore(update_temp_key, key+"_update") + worker.ClearRedisSet(key + "_update") + worker.SetRedisSetUnionAndStore(delete_temp_key, key+"_delete") + worker.ClearRedisSet(key + "_delete") + return dao.UserSyncResp{Add: add_users, Update: update_users, Delete: delete_users} +} + +func setSyncUserDataSet(t string, id int) error { + devices := worker.GetRedisSetMembers("sync_devices_ids") //主服务器查看从服务器的设备列表 + fmt.Println("set sync user data set devices:", devices, "t:", t, "id:", id) + var err error + for _, device := range devices { + key := device + "_sync_user_ids" + if t == "add" { + key_ := key + "_add" + worker.SetRedisSetAdd(key_, strconv.Itoa(id)) + } else if t == "update" { + key_ := key + "_update" + worker.SetRedisSetAdd(key_, strconv.Itoa(id)) + } else if t == "delete" { + key_ := key + "_delete" + worker.SetRedisSetAdd(key_, strconv.Itoa(id)) + } else { + err = errors.New("error") + } + } + return err +} + +// 确认同步数据 +func ConfirmSyncUserData(device string, data proto.UserSyncConfirm) error { + var err error + if len(data.Add) > 0 { + var ids_add []string + + for _, v := range data.Add { + ids_add = append(ids_add, strconv.Itoa(int(v.ID))) + } + add_key := device + "_sync_user_ids_add_confirm" + isSuccess := worker.SetRedisSetAddBatchWithExpire(add_key, ids_add, time.Second*30) + if !isSuccess { + err = errors.New("set add confirm error") + return err + } + ids_add_confirm_temp := device + "_sync_user_ids_add_confirm_temp" + //取差集 + add_diff := worker.SetRedisSetDiffAndStore(ids_add_confirm_temp, add_key) + if add_diff == false { + err = errors.New("add diff error") + return err + } + } + + if len(data.Update) > 0 { + + var ids_update []string + for _, v := range data.Update { + ids_update = append(ids_update, strconv.Itoa(int(v.ID))) + } + update_key := device + "_sync_user_ids_update_confirm" + isSuccess := worker.SetRedisSetAddBatchWithExpire(update_key, ids_update, time.Second*30) + if !isSuccess { + err = errors.New("set update confirm error") + return err + } + ids_update_confirm_temp := device + "_sync_user_ids_update_confirm_temp" + + update_diff := worker.SetRedisSetDiffAndStore(ids_update_confirm_temp, update_key) + if update_diff == false { + err = errors.New("update diff error") + return err + } + + } + + if len(data.Delete) > 0 { + var ids_delete []string + for _, v := range data.Delete { + ids_delete = append(ids_delete, strconv.Itoa(int(v.ID))) + } + del_key := device + "_sync_user_ids_delete_confirm" + isSuccess := worker.SetRedisSetAddBatchWithExpire(del_key, ids_delete, time.Second*30) + if !isSuccess { + err = errors.New("set del confirm error") + return err + } + + //待确认集合暂存 + ids_delete_confirm_temp := device + "_sync_user_ids_delete_confirm_temp" + delete_diff := worker.SetRedisSetDiffAndStore(ids_delete_confirm_temp, del_key) + if delete_diff == false { + err = errors.New("delete diff error") + return err + } + } + return err +} diff --git a/worker/myMail.go b/worker/myMail.go new file mode 100644 index 0000000..b531acd --- /dev/null +++ b/worker/myMail.go @@ -0,0 +1,45 @@ +package worker + +import ( + "fmt" + "net/smtp" +) + +type MyEmail struct { + SmtpPort int + ImapPort int + SmtpHost string + SmtpUserName string + SmtpPassword string +} + +func (e *MyEmail) Send(title, content string, toEmail []string) error { + //捕获异常 + defer func() { + if err := recover(); err != nil { + fmt.Errorf("MyEmail send mail error: %s", err) + } + }() + + // 设置邮件头部 + header := make(map[string]string) + header["From"] = e.SmtpUserName + header["To"] = toEmail[0] + + header["Subject"] = title + + // 组装邮件消息 + message := "" + for k, v := range header { + message += fmt.Sprintf("%s: %s\r\n", k, v) + } + message += "\r\n" + content + // 发送邮件 + err := smtp.SendMail(e.SmtpHost, smtp.PlainAuth("", e.SmtpUserName, e.SmtpPassword, "pop.qq.com"), e.SmtpUserName, toEmail, []byte(message)) + if err != nil { + //log.Fatalf("smtp error: %s", err) + fmt.Errorf("send mail error: %s", err) + } + + return nil +} diff --git a/worker/redis.go b/worker/redis.go new file mode 100644 index 0000000..5b0ac7c --- /dev/null +++ b/worker/redis.go @@ -0,0 +1,544 @@ +package worker + +import ( + "StuAcaWorksAI/proto" + "context" + "encoding/json" + "fmt" + "github.com/go-redis/redis/v8" + "strconv" + "time" +) + +var RedisClient *redis.Client // Redis 客户端, 用于连接 Redis 服务器 +func InitRedis() error { + ctx := context.Background() + + if proto.Config.REDIS_User_PW == false { + // 连接redis + RedisClient = redis.NewClient(&redis.Options{ + Addr: proto.Config.REDIS_ADDR, // Redis 服务器地址 + DB: proto.Config.REDIS_DB, // 使用的数据库编号 + }) + } else { + // 连接redis + RedisClient = redis.NewClient(&redis.Options{ + Addr: proto.Config.REDIS_ADDR, // Redis 服务器地址 + Password: proto.Config.REDIS_PASSWORD, // 如果 Redis 设置了密码 + DB: proto.Config.REDIS_DB, // 使用的数据库编号 + }) + } + + // 验证 Redis 客户端是否可以正常工作 + _, err := RedisClient.Ping(ctx).Result() + if err != nil { + fmt.Println("Error connecting to Redis: %v", err) + } + return err +} + +func CloseRedis() { + // 关闭 Redis 客户端 + if err := RedisClient.Close(); err != nil { + fmt.Println("Error closing Redis client: %v", err) + } +} + +func IsContainKey(key string) bool { + ctx := context.Background() + val, err := RedisClient.Exists(ctx, key).Result() // 检查键是否存在, 如果存在则返回 1, 否则返回 0 + if err != nil { + fmt.Println("Error getting key: %v", err) + return false + } + if val == 0 { + return false + } + return true +} + +// 设置redis +func SetRedis(key string, value string) bool { + ctx := context.Background() + // 设置键值对, 0 表示不设置过期时间, 如果需要设置过期时间, 可以设置为 time.Second * 10 等 + err := RedisClient.Set(ctx, key, value, time.Minute*30).Err() + if err != nil { + fmt.Println("Error setting key: %v", err) + return false + } + return true +} + +// 设置redis,永久 +func SetRedisForever(key string, value string) bool { + ctx := context.Background() + err := RedisClient.Set(ctx, key, value, 0).Err() + if err != nil { + fmt.Println("Error setting key: %v", err) + return false + } + return true +} + +// 设置hash +func SetHashWithTime(key string, id int, name, email string, duration time.Duration) bool { + //捕获错误,如果错误返回 + + ctx := context.Background() // 创建一个上下文 + fields := map[string]interface{}{ + "id": strconv.Itoa(id), + "name": name, + "email": email, + } + + // 设置哈希表的字段值, 0 表示不设置过期时间, 如果需要设置过期时间, 可以设置为 time.Second * 10 等 + err := RedisClient.HSet(ctx, key, fields).Err() + if err != nil { + fmt.Println("Error setting key: %v", err) + return false + } + err = RedisClient.Expire(ctx, key, time.Hour*10).Err() + if err != nil { + fmt.Println("Error setting key: %v", err) + return false + } + return true +} + +// 设置redis hash,设置过期时间 +func SetHash(key string, data map[string]interface{}) bool { + ctx := context.Background() + err := RedisClient.HSet(ctx, key, data).Err() + if err != nil { + fmt.Println("%v :Error setting hash: %v", key, err) + return false + } + err = RedisClient.Expire(ctx, key, time.Minute*30).Err() + if err != nil { + fmt.Println("%v :Error setting expire: %v", key, err) + return false + } + return true +} + +func SetHashWithField(key string, field string, value string) bool { + ctx := context.Background() + err := RedisClient.HSet(ctx, key, field, value).Err() + if err != nil { + fmt.Println("Error setting key: %v", err) + return false + } + return true +} + +func GetHash(key string, field string) string { + ctx := context.Background() + val, err := RedisClient.HGet(ctx, key, field).Result() + if err != nil { + fmt.Println("Error getting hash: %v", err) + return "" + } + return val +} + +func GetHashAll(key string) map[string]string { + ctx := context.Background() + val, err := RedisClient.HGetAll(ctx, key).Result() + if err != nil { + fmt.Println("Error getting hash: %v", err) + return nil + } + return val +} + +// 设置redis +func SetRedisWithExpire(key string, value string, expire time.Duration) bool { // 设置键值对, 0 表示不设置过期时间, 如果需要设置过期时间, 可以设置为 time.Second * 10 等 + ctx := context.Background() + // 设置键值对, 0 表示不设置过期时间, 如果需要设置过期时间, 可以设置为 time.Second * 10 等 + err := RedisClient.Set(ctx, key, value, expire).Err() + if err != nil { + fmt.Println("Error setting key: %v", err) + return false + } + return true +} + +// 获取redis +func GetRedis(key string) string { + ctx := context.Background() + val, err := RedisClient.Get(ctx, key).Result() // 从 Redis 读取键值, 如果键不存在则返回空字符串, 如果出现错误则返回错误 + if err != nil { + fmt.Println(key, " Error getting key: %v", err) + return "" + } + return val +} + +// pop redis list from right,as stack +func PopRedisList(key string) string { + ctx := context.Background() + val, err := RedisClient.RPop(ctx, key).Result() // 从 Redis 读取键值, 如果键不存在则返回空字符串, 如果出现错误则返回错误 + if err != nil { + fmt.Println(key, " Error reading from Redis: %v", err) + return "" + } + return val +} + +// pop redis list from left,as queue +func PopRedisListLeft(key string) string { + ctx := context.Background() + val, err := RedisClient.LPop(ctx, key).Result() // 从 Redis 读取键值, 如果键不存在则返回空字符串, 如果出现错误则返回错误 + if err != nil { + return "" + } + return val +} + +func DelRedis(key string) { + ctx := context.Background() + err := RedisClient.Del(ctx, key).Err() + if err != nil { + fmt.Println("Error deleting key: %v", err) + } +} + +// push redis list from right +func PushRedisList(key string, value string) bool { + ctx := context.Background() + err := RedisClient.RPush(ctx, key, value).Err() + if err != nil { + fmt.Println("Error setting key: %v", err) + return false + } + return true +} + +func GetRedisListLen(key string) int64 { + ctx := context.Background() + val, err := RedisClient.LLen(ctx, key).Result() + if err != nil { + fmt.Println("Error getting key: %v", err) + return 0 + } + return val +} + +func PushRedisListWithExpire(key string, value string, expire time.Duration) bool { + ctx := context.Background() + err := RedisClient.RPush(ctx, key, value).Err() + if err != nil { + fmt.Println("Error setting key: %v", err) + return false + } + err = RedisClient.Expire(ctx, key, expire).Err() + if err != nil { + fmt.Println("Error setting key: %v", err) + return false + } + return true +} + +// delete redis key +func delRedis(key string) { + ctx := context.Background() + err := RedisClient.Del(ctx, key).Err() + if err != nil { + fmt.Println("Error setting key: %v", err) + } +} + +// User 用户,用于存入 Redis hash +type RUser struct { + ID int `json:"id"` + Name string `json:"name"` + Age int `json:"age"` + Email string `json:"email"` +} + +func (u *RUser) toJSONString() string { + // 将User对象编码为JSON字符串 + userJSON, err := json.Marshal(u) + if err != nil { + fmt.Println("Failed to marshal user: %v", err) + } + return string(userJSON) +} + +// put hash to redis +func hSetRedis(key string, field string, value string) { + ctx := context.Background() + err := RedisClient.HSet(ctx, key, field, value).Err() + if err != nil { + fmt.Println("Error setting key: %v", err) + } +} + +// get hash from redis +func hGetRedis(key string, field string) string { + ctx := context.Background() + val, err := RedisClient.HGet(ctx, key, field).Result() + if err != nil { + fmt.Println("Error getting key: %v", err) + } + return val +} + +// 设置set,有过期时间 +func SetRedisSet(key string, values []string, expire time.Duration) bool { + ctx := context.Background() + err := RedisClient.SAdd(ctx, key, values).Err() + if err != nil { + fmt.Println("Error setting key: %v", err) + return false + } + err = RedisClient.Expire(ctx, key, expire).Err() + if err != nil { + fmt.Println("Error setting key: %v", err) + return false + } + return true +} + +// 设置set,添加元素 +func SetRedisSetAdd(key string, value string) bool { + ctx := context.Background() + err := RedisClient.SAdd(ctx, key, value).Err() + if err != nil { + fmt.Println("Error setting key: %v", err) + return false + } + return true +} + +// 批量添加元素 +func SetRedisSetAddBatchWithExpire(key string, values []string, expire time.Duration) bool { + ctx := context.Background() + err := RedisClient.SAdd(ctx, key, values).Err() + if err != nil { + fmt.Println("SetRedisSetAddBatchWithExpire Error setting key: %v", err) + return false + } + err = RedisClient.Expire(ctx, key, expire).Err() + if err != nil { + fmt.Println("SetRedisSetAddBatchWithExpire Error setting key: %v", err) + return false + } + return true + +} + +// 设置set,添加元素 +func SetRedisSetAddWithExpire(key string, value string, expire time.Duration) bool { + ctx := context.Background() + err := RedisClient.SAdd(ctx, key, value).Err() + if err != nil { + fmt.Println("SetRedisSetAddWithExpire Error setting key: %v", err) + return false + } + err = RedisClient.Expire(ctx, key, expire).Err() + if err != nil { + fmt.Println("SetRedisSetAddWithExpire Error setting key: %v", err) + return false + } + return true +} + +// 设置set,删除元素 +func SetRedisSetRemove(key string, value string) bool { + ctx := context.Background() + err := RedisClient.SRem(ctx, key, value).Err() + if err != nil { + fmt.Println("Error setting key: %v", err) + return false + } + return true +} + +// 获取两个set的交集 +func GetRedisSetIntersect(key1 string, key2 string) []string { + ctx := context.Background() + val, err := RedisClient.SInter(ctx, key1, key2).Result() + if err != nil { + fmt.Println("Error getting key: %v", err) + return nil + } + return val +} + +// 查看set是否包含元素 +func IsContainSet(key string, value string) bool { + ctx := context.Background() + val, err := RedisClient.SIsMember(ctx, key, value).Result() + if err != nil { + fmt.Println("Error getting key: %v", err) + return false + } + return val +} + +// 查看set的所有元素 +func GetRedisSetMembers(key string) []string { + ctx := context.Background() + val, err := RedisClient.SMembers(ctx, key).Result() + if err != nil { + fmt.Println("Error getting key: %v", err) + return nil + } + return val +} + +// BITMAP +func SetRedisBitmap(key string, offset int64, value int) bool { + ctx := context.Background() + err := RedisClient.SetBit(ctx, key, offset, value).Err() + if err != nil { + fmt.Println("Error setting key: %v", err) + return false + } + return true +} + +// BITMAP获取 +func GetRedisBitmap(key string, offset int64) int { + ctx := context.Background() + val, err := RedisClient.GetBit(ctx, key, offset).Result() + if err != nil { + fmt.Println("Error getting key: %v", err) + return 0 + } + return int(val) +} + +// 发布订阅者模式-发布消息 +func Publish(channel string, message string, expire time.Duration) { + ctx := context.Background() + err := RedisClient.Publish(ctx, channel, message).Err() + if err != nil { + fmt.Println("Error publishing message: %v", err) + } + err = RedisClient.Expire(ctx, channel, expire).Err() + if err != nil { + fmt.Println("Error setting key: %v", err) + } +} + +// 发布订阅者模式-订阅消息 +func Subscribe(channel string) []string { + ctx := context.Background() + pubsub := RedisClient.Subscribe(ctx, channel) + ch := pubsub.Channel() + defer pubsub.Close() + var messages []string + for msg := range ch { + messages = append(messages, msg.Payload) + } + return messages +} + +// redis两个set求差集存入第一个set +func SetRedisSetDiffAndStore(key1 string, key2 string) bool { + ctx := context.Background() + err := RedisClient.SDiffStore(ctx, key1, key1, key2).Err() //将key1和key2的差集存入key1 + if err != nil { + fmt.Println("SetRedisSetDiffAndStore Error setting key: %v", err) + return false + } + return true +} + +// redis将第二个set存入第一个set +func SetRedisSetUnionAndStore(key1 string, key2 string) bool { + ctx := context.Background() + err := RedisClient.SUnionStore(ctx, key1, key1, key2).Err() //将key1和key2的并集存入key1 + if err != nil { + fmt.Println("SetRedisSetUnionAndStore Error setting key: %v", err) + return false + } + return true +} + +// redis 清空set +func ClearRedisSet(key string) bool { + ctx := context.Background() + err := RedisClient.Del(ctx, key).Err() + if err != nil { + fmt.Println("Error setting key: %v", err) + return false + } + return true +} + +// 获取两个集合的并集 +func GetRedisSetUnion(key1 string, key2 string) []string { + ctx := context.Background() + val, err := RedisClient.SUnion(ctx, key1, key2).Result() + if err != nil { + fmt.Println("Error getting key: %v", err) + return nil + } + return val +} + +type RedisInfo struct { + Key string + Value string + Type string + Expire int // 过期时间, 单位: 秒 +} + +// 获取所有的key和value,及其对应的过期时间 +func GetAllRedisInfo() ([]RedisInfo, error) { + ctx := context.Background() + keys, err := RedisClient.Keys(ctx, "*").Result() + if err != nil { + fmt.Println("Error getting key: %v", err) + return nil, err + } + var redisInfos []RedisInfo + for _, key := range keys { + //先查看key类型,再根据类型获取value + key_type, val, err := getKeyTypeAndData(key) + if err != nil { + fmt.Println("Error getting key: %v", err) + return nil, err + } + expire, err := RedisClient.TTL(ctx, key).Result() + if err != nil { + fmt.Println("Error getting key: %v", err) + return nil, err + } + redisInfo := RedisInfo{ + Key: key, + Value: val, + Type: key_type, + Expire: int(expire.Seconds()), + } + redisInfos = append(redisInfos, redisInfo) + } + return redisInfos, nil +} + +func getKeyTypeAndData(key string) (string, string, error) { + ctx := context.Background() + key_type := RedisClient.Type(ctx, key).Val() + var val interface{} + var err error + switch key_type { + case "string": + val, err = RedisClient.Get(ctx, key).Result() + case "hash": + val, err = RedisClient.HGetAll(ctx, key).Result() + case "list": + val, err = RedisClient.LRange(ctx, key, 0, -1).Result() + case "set": + val, err = RedisClient.SMembers(ctx, key).Result() + case "zset": + val, err = RedisClient.ZRange(ctx, key, 0, -1).Result() + case "bitmap": + val, err = RedisClient.GetBit(ctx, key, 0).Result() + default: + val = "unknown type" + } + return key_type, fmt.Sprintf("%v", val), err +} diff --git a/worker/req.go b/worker/req.go new file mode 100644 index 0000000..c2bd4d5 --- /dev/null +++ b/worker/req.go @@ -0,0 +1,180 @@ +package worker + +import ( + "StuAcaWorksAI/proto" + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "strings" +) + +var client *http.Client + +// 初始化 +func InitReq() { + client = &http.Client{} +} + +// 发起post请求 +func Post(url string, bodyType string, body string) (*http.Response, error) { + req, err := http.NewRequest("POST", url, nil) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", bodyType) + req.Body = io.NopCloser(strings.NewReader(body)) + return client.Do(req) +} + +// 发送到机器人 +func SendToRobot(url string, body string) (map[string]interface{}, error) { + resp, err := Post(url, "application/json", body) + if err != nil { + return nil, err + } + defer resp.Body.Close() + m := make(map[string]interface{}) + err = json.NewDecoder(resp.Body).Decode(&m) + if err != nil { + return nil, err + } + return m, nil +} + +// 生成补全的函数 +func GenerateCompletion(url, prompt string, model string) (map[string]interface{}, error) { + data := map[string]interface{}{ + "model": model, + "prompt": prompt, + "stream": false, + } + jsonData, err := json.Marshal(data) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json") + + client_ := &http.Client{} + resp, err := client_.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var result map[string]interface{} + err = json.Unmarshal(body, &result) + if err != nil { + return nil, err + } + + return result, nil +} + +// 获取同步数据通用方法 +func SyncDataFromMasterReq(url string, token string) proto.UserSync { + //从接口获取数据 + req, err := http.NewRequest("POST", url, nil) + if err != nil { + return proto.UserSync{} + } + req.Header.Set("token", token) + //json负载 + req.Header.Set("Content-Type", "application/json") + //传输数据 + m := make(map[string]interface{}) + m["token"] = token + m["device"] = "" + + if client == nil { + client = &http.Client{} + } + client = &http.Client{} + //获取数据 + resp, err := client.Do(req) + if err != nil { + return proto.UserSync{} + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return proto.UserSync{} + } + var result map[string]interface{} + err = json.Unmarshal(body, &result) + if err != nil { + return proto.UserSync{} + } + fmt.Println("SyncDataFromMasterReq result:", result) + if result["code"].(float64) != 0 { + return proto.UserSync{} + } + var userSync proto.UserSync + err = json.Unmarshal([]byte(result["data"].(string)), &userSync) + if err != nil { + return proto.UserSync{} + } + return userSync +} + +type Response struct { + Code int `json:"code"` + Message string `json:"message"` + Data proto.UserSync `json:"data"` +} + +// 获取数据,全量及增量 +func SyncDataFromMasterReq2(url string, data proto.SyncUserReq) (proto.UserSync, error) { + defer func() { + if r := recover(); r != nil { + fmt.Println("SyncDataFromMasterReq2 error:", r) + } + }() + + var res proto.UserSync + //从接口获取数据 + json_data, err := json.Marshal(data) + if err != nil { + return res, err + } + req, err := http.NewRequest("POST", url, bytes.NewBuffer(json_data)) + if err != nil { + return res, err + } + req.Header.Set("Content-Type", "application/json") + //传输数据 + if client == nil { + client = &http.Client{} + } + //获取数据 + resp, err := client.Do(req) + if err != nil { + return res, err + } + defer resp.Body.Close() + //解析数据 + responseBod, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + var response Response + err = json.Unmarshal(responseBod, &response) + if err != nil { + return res, err + } + res = response.Data + fmt.Println("SyncDataFromMasterReq2 result add data:", len(res.Add), "update data:", len(res.Update), "delete data:", len(res.Delete)) + return res, nil +} diff --git a/worker/tool.go b/worker/tool.go new file mode 100644 index 0000000..e9a0b55 --- /dev/null +++ b/worker/tool.go @@ -0,0 +1,13 @@ +package worker + +import "math/rand" + +func GetRandomString(l int) string { + str := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + bytes := []byte(str) + var result []byte + for i := 0; i < l; i++ { + result = append(result, bytes[rand.Intn(len(bytes))]) + } + return string(result) +}