From 5dcd98ab3a5c12d6445b34ca33cb1bdfe4af58d3 Mon Sep 17 00:00:00 2001 From: junleea <354425203@qq.com> Date: Fri, 3 Jan 2025 15:28:36 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=94=A8=E6=88=B7=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E5=8F=8A=E6=9B=B4=E6=96=B0=EF=BC=8C=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E5=A2=9E=E5=88=A0=E6=94=B9?= =?UTF-8?q?=E6=9F=A5=E6=8E=A5=E5=8F=A3=EF=BC=8C=E6=B7=BB=E5=8A=A0=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dao/db.go | 12 +++ dao/file.go | 81 +++++++++++++++++ handler/file.go | 194 +++++++++++++++++++++++++++++++++++++++++ proto/conf.go | 2 +- proto/file_req.go | 28 ++++++ proto/status.go | 5 ++ service/fileService.go | 150 +++++++++++++++++++++++++++++++ service/userService.go | 50 +++++++++++ 8 files changed, 521 insertions(+), 1 deletion(-) create mode 100644 handler/file.go create mode 100644 proto/file_req.go diff --git a/dao/db.go b/dao/db.go index 24477bb..870e76d 100644 --- a/dao/db.go +++ b/dao/db.go @@ -82,6 +82,18 @@ func Init() error { 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 + } + err = db.AutoMigrate(&Friend{}) if err != nil { fmt.Println("friend table:", err) diff --git a/dao/file.go b/dao/file.go index 8133f31..9a59a2d 100644 --- a/dao/file.go +++ b/dao/file.go @@ -18,6 +18,23 @@ type File struct { 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) @@ -96,3 +113,67 @@ func FindFileByMd5(md5 string) 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/handler/file.go b/handler/file.go new file mode 100644 index 0000000..74b82d0 --- /dev/null +++ b/handler/file.go @@ -0,0 +1,194 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + "net/http" + "videoplayer/dao" + "videoplayer/proto" + "videoplayer/service" +) + +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("/:filename", GetFile) + +} + +func AddConfigFile(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.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/proto/conf.go b/proto/conf.go index 9a7ff3f..e6ed1c3 100644 --- a/proto/conf.go +++ b/proto/conf.go @@ -11,7 +11,7 @@ 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" diff --git a/proto/file_req.go b/proto/file_req.go new file mode 100644 index 0000000..50e40c3 --- /dev/null +++ b/proto/file_req.go @@ -0,0 +1,28 @@ +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" binding:"required"` + Type string `json:"type" form:"type" binding:"required"` //查询类型,one,all + DelFile bool `json:"del_file" form:"del_file"` //删除文件 + FileName string `json:"file_name" form:"file_name" binding:"required"` + FilePath string `json:"file_path" form:"file_path" binding:"required"` + Content string `json:"content" form:"content" binding:"required"` +} + +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 index 9c7fce5..3944848 100644 --- a/proto/status.go +++ b/proto/status.go @@ -63,4 +63,9 @@ const ( DeleteFileInfoFailed = 78 // 删除文件信息失败 DataFormatError = 80 // 数据格式错误 + + AddConfigFileFailed = 90 // 添加配置文件失败 + UpdateConfigFailed = 91 // 更新配置失败 + DeleteConfigFailed = 92 // 删除配置失败 + SearchConfigFileFailed = 93 // 获取配置失败 ) diff --git a/service/fileService.go b/service/fileService.go index ee75bd6..c604cec 100644 --- a/service/fileService.go +++ b/service/fileService.go @@ -10,6 +10,7 @@ import ( "os" "path" "time" + "videoplayer/dao" "videoplayer/proto" "videoplayer/worker" ) @@ -55,3 +56,152 @@ func CalculateFileMd5(file io.Reader) string { } 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.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 + } + //查看系统中是否存在文件,不存在则创建 + file := req.FilePath + "/" + req.FileName + _, err = os.Stat(file) + if err != 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, 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/userService.go b/service/userService.go index c04f1e1..a7fc352 100644 --- a/service/userService.go +++ b/service/userService.go @@ -1,6 +1,7 @@ package service import ( + "encoding/json" "errors" "fmt" "regexp" @@ -51,6 +52,38 @@ 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) } @@ -63,6 +96,7 @@ func UpdateUser(user_id int, req proto.UpdateUserInfoReq) (int, error) { //添加修改用户信息到同步列表 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 @@ -74,6 +108,7 @@ func UpdateUser(user_id int, req proto.UpdateUserInfoReq) (int, error) { 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 @@ -85,6 +120,21 @@ func UpdateUser(user_id int, req proto.UpdateUserInfoReq) (int, error) { } } +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 {