package controllers import ( "crypto/md5" "encoding/hex" "encoding/json" "fmt" "io" "os" "strconv" "strings" "time" "server/models" "server/pkg/jwtutil" "server/services" "github.com/beego/beego/v2/client/orm" beego "github.com/beego/beego/v2/server/web" ) // BackendFileController 平台端文件管理(yz_system_files / yz_system_files_category) type BackendFileController struct { beego.Controller } const fileUploadMaxMB = 2048 // 2GB,适用于大型软件安装包 const fileUploadMaxBytes = fileUploadMaxMB * 1024 * 1024 var fileTypeByCategory = map[string]uint8{ "image": 1, "document": 2, "video": 3, "audio": 4, "appsupgrade": 2, } var allowedExtByCategory = map[string][]string{ "image": {"jpg", "jpeg", "png", "gif", "bmp", "webp"}, "document": {"pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt"}, "video": {"mp4", "webm", "mov"}, "audio": {"mp3", "wav", "ogg"}, // 安装包 / 软件升级(上传时 cate 选 appsupgrade 分类即可,扩展名在此放行) "appsupgrade": {"zip", "exe", "dmg", "msi", "msix", "apk", "deb", "rpm", "7z", "tar", "gz", "pkg"}, } func (c *BackendFileController) backendClaims() (*jwtutil.Claims, error) { auth := c.Ctx.Request.Header.Get("Authorization") if auth == "" { return nil, fmt.Errorf("未登录") } parts := strings.SplitN(auth, " ", 2) if len(parts) != 2 || parts[0] != "Bearer" { return nil, fmt.Errorf("认证信息格式错误") } claims, err := jwtutil.ParseToken(parts[1]) if err != nil { return nil, fmt.Errorf("无效的token") } if claims.UserType != "backend" { return nil, fmt.Errorf("无权访问") } return claims, nil } func (c *BackendFileController) effectiveTid(claims *jwtutil.Claims) uint64 { _ = c.ParseForm(1 << 20) if tid, err := c.GetUint64("tid"); err == nil && tid > 0 { return tid } if h := strings.TrimSpace(c.Ctx.Request.Header.Get("X-Tenant-Id")); h != "" { if v, e := strconv.ParseUint(h, 10, 64); e == nil { return v } } if claims != nil && claims.TenantId > 0 { return uint64(claims.TenantId) } return 0 } // resolveTuid 解析归属用户 ID,返回 0 表示租户共享文件。 // // 取值顺序: // 1. 表单/请求头的 tuid(代他人上传场景,如 HR 代员工上传票据) // 2. scope=user 时取当前登录的租户用户 UID(backend 端 JWT 的 UserID 即 yz_system_tenant_user.uid) // —— 强制取自身 UID,避免前端伪造他人 uid 把文件塞进别人目录 // 3. 其余情况返回 0(租户共享) func (c *BackendFileController) resolveTuid(claims *jwtutil.Claims) uint64 { if v, err := c.GetUint64("tuid"); err == nil && v > 0 { return v } if h := strings.TrimSpace(c.Ctx.Request.Header.Get("X-Tenant-User-Id")); h != "" { if v, e := strconv.ParseUint(h, 10, 64); e == nil && v > 0 { return v } } if strings.TrimSpace(c.GetString("scope")) == services.ScopeUser { if claims != nil && claims.UserID > 0 { return uint64(claims.UserID) } } return 0 } // currentTuid 当前登录者对应的租户用户 UID(backend 端 JWT 的 UserID 即 yz_system_tenant_user.uid) func (c *BackendFileController) currentTuid(claims *jwtutil.Claims) uint64 { if claims != nil && claims.UserID > 0 { return uint64(claims.UserID) } return 0 } // scopeFilter 按归属范围过滤: // - scope=user → 只看当前登录用户的个人文件 // - 其它(默认)→ 只看租户共享文件 func scopeFilter(qs orm.QuerySeter, scope string, tuid uint64) orm.QuerySeter { if scope == services.ScopeUser { return qs.Filter("tuid", tuid) } return qs.Filter("tuid__isnull", true) } func (c *BackendFileController) jsonErr(httpStatus, bizCode int, msg string) { c.Ctx.Output.SetStatus(httpStatus) c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} _ = c.ServeJSON() } func (c *BackendFileController) jsonOK(data interface{}) { c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data} _ = c.ServeJSON() } func detectFileType(ext string) uint8 { ext = strings.ToLower(strings.TrimPrefix(ext, ".")) for cat, exts := range allowedExtByCategory { for _, e := range exts { if e == ext { if t, ok := fileTypeByCategory[cat]; ok { return t } return 2 } } } return 2 } func fileExt(name string) string { name = strings.TrimSpace(name) if i := strings.LastIndex(name, "."); i >= 0 && i < len(name)-1 { return strings.ToLower(name[i+1:]) } return "" } func fileToMap(f *models.SystemFile) map[string]interface{} { ct := f.CreateTime.Format("2006-01-02 15:04:05") m := map[string]interface{}{ "id": f.ID, "tid": f.Tid, "name": f.Name, "type": f.Type, "cate": f.Cate, "size": f.Size, "src": f.Src, "uploader": f.Uploader, "md5": f.Md5, "create_time": ct, "createTime": ct, "groupId": f.Cate, "url": f.Src, } if f.Uid != nil { m["uid"] = *f.Uid } if f.Tuid != nil { m["tuid"] = *f.Tuid } return m } func removePhysicalBySrc(webSrc string) { webSrc = strings.TrimSpace(webSrc) if webSrc == "" { return } webSrc = strings.TrimPrefix(webSrc, "/") _ = os.Remove(webSrc) } // GetAllFiles GET /backend/allfiles func (c *BackendFileController) GetAllFiles() { claims, err := c.backendClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) page, _ := c.GetInt("page", 1) pageSize, _ := c.GetInt("pageSize", 10) if page < 1 { page = 1 } if pageSize < 1 { pageSize = 10 } cate, _ := c.GetUint64("cate") keyword := strings.TrimSpace(c.GetString("keyword")) scope := strings.TrimSpace(c.GetString("scope")) qs := models.Orm.QueryTable(new(models.SystemFile)). Filter("source", services.SourceBackend). Filter("tid", tid). Filter("delete_time__isnull", true) qs = scopeFilter(qs, scope, c.currentTuid(claims)) if cate > 0 { qs = qs.Filter("cate", cate) } if keyword != "" { qs = qs.Filter("name__icontains", keyword) } total, err := qs.Count() if err != nil { c.jsonErr(500, 500, "获取文件列表失败: "+err.Error()) return } var rows []models.SystemFile _, err = qs.OrderBy("-create_time").Limit(pageSize, (page-1)*pageSize).All(&rows) if err != nil { c.jsonErr(500, 500, "获取文件列表失败: "+err.Error()) return } list := make([]map[string]interface{}, 0, len(rows)) for i := range rows { list = append(list, fileToMap(&rows[i])) } c.jsonOK(map[string]interface{}{ "list": list, "total": total, "page": page, "pageSize": pageSize, }) } // GetUserCate GET /backend/usercate func (c *BackendFileController) GetUserCate() { claims, err := c.backendClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) var cates []models.SystemFilesCategory _, err = models.Orm.QueryTable(new(models.SystemFilesCategory)). Filter("tid", tid). Filter("delete_time__isnull", true). OrderBy("id"). All(&cates) if err != nil { c.jsonErr(500, 500, "获取用户分类失败: "+err.Error()) return } out := make([]map[string]interface{}, 0, len(cates)) for i := range cates { cnt, _ := models.Orm.QueryTable(new(models.SystemFile)). Filter("tid", tid). Filter("cate", cates[i].ID). Filter("delete_time__isnull", true). Count() out = append(out, map[string]interface{}{ "id": cates[i].ID, "name": cates[i].Name, "total": cnt, }) } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} _ = c.ServeJSON() } type createCateBody struct { Name string `json:"name"` Tuid *uint64 `json:"tuid"` } // CreateFileCate POST /backend/createfilecate func (c *BackendFileController) CreateFileCate() { claims, err := c.backendClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.jsonErr(400, 400, "参数错误") return } var body createCateBody if err := json.Unmarshal(raw, &body); err != nil { c.jsonErr(400, 400, "参数错误") return } name := strings.TrimSpace(body.Name) if name == "" { c.jsonErr(400, 400, "分组名称不能为空") return } uid := uint64(claims.UserID) row := &models.SystemFilesCategory{ Tid: tid, Name: name, Uid: &uid, Tuid: body.Tuid, } id, err := models.Orm.Insert(row) if err != nil { c.jsonErr(500, 500, "新建文件分组失败: "+err.Error()) return } c.Data["json"] = map[string]interface{}{ "code": 200, "msg": "新建文件分组成功", "data": map[string]interface{}{"id": uint64(id)}, } _ = c.ServeJSON() } type renameCateBody struct { Name string `json:"name"` } // RenameFileCate POST /backend/renamefilecate/:id func (c *BackendFileController) RenameFileCate() { claims, err := c.backendClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) idStr := c.Ctx.Input.Param(":id") id, err := strconv.ParseUint(idStr, 10, 64) if err != nil || id == 0 { c.jsonErr(400, 400, "无效的分组ID") return } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.jsonErr(400, 400, "参数错误") return } var body renameCateBody if err := json.Unmarshal(raw, &body); err != nil { c.jsonErr(400, 400, "参数错误") return } name := strings.TrimSpace(body.Name) if name == "" { c.jsonErr(400, 400, "分组名称不能为空") return } n, err := models.Orm.QueryTable(new(models.SystemFilesCategory)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). Update(map[string]interface{}{"name": name}) if err != nil { c.jsonErr(500, 500, "重命名文件分组失败: "+err.Error()) return } if n == 0 { c.jsonErr(404, 404, "分组不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "重命名文件分组成功"} _ = c.ServeJSON() } // DeleteFileCate DELETE /backend/deletefilecate/:id func (c *BackendFileController) DeleteFileCate() { claims, err := c.backendClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) idStr := c.Ctx.Input.Param(":id") id, err := strconv.ParseUint(idStr, 10, 64) if err != nil || id == 0 { c.jsonErr(400, 400, "无效的分组ID") return } cnt, err := models.Orm.QueryTable(new(models.SystemFile)). Filter("cate", id). Filter("tid", tid). Filter("delete_time__isnull", true). Count() if err != nil { c.jsonErr(500, 500, "删除文件分组失败: "+err.Error()) return } if cnt > 0 { c.jsonErr(400, 400, fmt.Sprintf("该分组下还有 %d 个文件,请先删除分组内文件!", cnt)) return } now := time.Now() n, err := models.Orm.QueryTable(new(models.SystemFilesCategory)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). Update(map[string]interface{}{"delete_time": now}) if err != nil { c.jsonErr(500, 500, "删除文件分组失败: "+err.Error()) return } if n == 0 { c.jsonErr(404, 404, "分组不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除文件分组成功"} _ = c.ServeJSON() } // GetCateFiles GET /backend/catefiles/:id func (c *BackendFileController) GetCateFiles() { claims, err := c.backendClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) idStr := c.Ctx.Input.Param(":id") cateID, err := strconv.ParseUint(idStr, 10, 64) if err != nil { c.jsonErr(400, 400, "无效的分类ID") return } page, _ := c.GetInt("page", 1) pageSize, _ := c.GetInt("pageSize", 24) if page < 1 { page = 1 } if pageSize < 1 { pageSize = 24 } keyword := strings.TrimSpace(c.GetString("keyword")) scope := strings.TrimSpace(c.GetString("scope")) qs := models.Orm.QueryTable(new(models.SystemFile)). Filter("source", services.SourceBackend). Filter("tid", tid). Filter("cate", cateID). Filter("delete_time__isnull", true) qs = scopeFilter(qs, scope, c.currentTuid(claims)) if keyword != "" { qs = qs.Filter("name__icontains", keyword) } total, err := qs.Count() if err != nil { c.jsonErr(500, 500, "获取分类文件失败: "+err.Error()) return } var rows []models.SystemFile _, err = qs.OrderBy("-create_time").Limit(pageSize, (page-1)*pageSize).All(&rows) if err != nil { c.jsonErr(500, 500, "获取分类文件失败: "+err.Error()) return } list := make([]map[string]interface{}, 0, len(rows)) for i := range rows { list = append(list, fileToMap(&rows[i])) } c.jsonOK(map[string]interface{}{ "list": list, "total": total, "page": page, "pageSize": pageSize, "categoryId": cateID, }) } // GetFileByID GET /backend/file/:id func (c *BackendFileController) GetFileByID() { claims, err := c.backendClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) idStr := c.Ctx.Input.Param(":id") id, err := strconv.ParseUint(idStr, 10, 64) if err != nil || id == 0 { c.jsonErr(400, 400, "无效的文件ID") return } var f models.SystemFile err = models.Orm.QueryTable(new(models.SystemFile)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). One(&f) if err != nil { c.jsonErr(404, 404, "文件不存在") return } c.jsonOK(fileToMap(&f)) } // UploadFile POST /backend/uploadfile func (c *BackendFileController) UploadFile() { claims, err := c.backendClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } if err := c.Ctx.Request.ParseMultipartForm(fileUploadMaxBytes); err != nil { c.jsonErr(400, 400, "解析上传失败: "+err.Error()) return } tid := c.effectiveTid(claims) // 归属用户:传了 tuid 或 scope=user 即用户个人文件,否则为租户共享文件 tuid := c.resolveTuid(claims) fh, header, err := c.GetFile("file") if err != nil || fh == nil { c.jsonErr(400, 400, "请选择要上传的文件") return } defer fh.Close() if header != nil && header.Size > fileUploadMaxBytes { c.jsonErr(400, 400, fmt.Sprintf("文件大小不能超过%dMB", fileUploadMaxMB)) return } ext := services.FileExt(header.Filename) if ext == "" { c.jsonErr(400, 400, "无法识别文件扩展名") return } // 获取存储服务 storageService, err := services.GetStorageService() if err != nil { c.jsonErr(500, 500, "获取存储服务失败: "+err.Error()) return } // 1. 先写入临时文件并计算 MD5(此时文件尚未进入正式目录) staged, err := storageService.Stage(fh, header) if err != nil { c.jsonErr(500, 500, "读取上传文件失败: "+err.Error()) return } committed := false defer func() { // 去重命中或出错时清理临时文件,避免产生垃圾 if !committed && staged != nil { _ = storageService.Discard(staged) } }() ctx := services.UploadContext{Source: services.SourceBackend, Tid: tid, Tuid: tuid} scope := ctx.Scope() // 2. 精确查重:同端 + 同归属范围 + 同租户 + 同归属用户 + 同 MD5 才算重复。 // 因此租户已有 a 文件、用户 c 上传同样文件不会冲突。 exist, err := services.FindDuplicate(services.SourceBackend, scope, tid, tuid, staged.MD5) if err != nil { c.jsonErr(500, 500, "查重失败: "+err.Error()) return } if exist != nil { c.Data["json"] = map[string]interface{}{ "code": 201, "msg": "文件已存在", "data": map[string]interface{}{ "url": exist.Src, "id": exist.ID, "name": exist.Name, }, } _ = c.ServeJSON() return } // 3. 提交到正式目录:backend/{tid}[/{tuid}]/yyyy/MM/dd/ result, err := storageService.Commit(staged, ctx) if err != nil { c.jsonErr(500, 500, "上传文件失败: "+err.Error()) return } committed = true // 获取分类 cateStr := c.GetString("cate") var cate uint64 if cateStr != "" { cate, _ = strconv.ParseUint(cateStr, 10, 64) } // 4. 保存文件记录到数据库 id, err := services.CreateFileRecord(services.FileUploadParams{ Source: services.SourceBackend, Scope: scope, Tid: tid, Tuid: tuid, Uid: uint64(claims.UserID), Name: header.Filename, Ext: ext, Cate: cate, Size: uint64(result.Size), Src: result.URL, ObjectKey: result.Key, Storage: storageService.Type(), MD5: result.MD5, }) if err != nil { // 数据库插入失败,删除已提交的文件 _ = storageService.Delete(result.Key) c.jsonErr(500, 500, "上传失败: "+err.Error()) return } c.Data["json"] = map[string]interface{}{ "code": 200, "msg": "上传成功", "data": map[string]interface{}{ "url": result.URL, "id": id, "name": header.Filename, }, } _ = c.ServeJSON() } func md5HashFile(path string) (string, error) { f, err := os.Open(path) if err != nil { return "", err } defer f.Close() h := md5.New() if _, err := io.Copy(h, f); err != nil { return "", err } return hex.EncodeToString(h.Sum(nil)), nil } type updateFileBody struct { Name *string `json:"name"` Cate *uint64 `json:"cate"` } // UpdateFile POST /backend/updatefile/:id func (c *BackendFileController) UpdateFile() { claims, err := c.backendClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) idStr := c.Ctx.Input.Param(":id") id, err := strconv.ParseUint(idStr, 10, 64) if err != nil || id == 0 { c.jsonErr(400, 400, "无效的文件ID") return } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.jsonErr(400, 400, "参数错误") return } var body updateFileBody if err := json.Unmarshal(raw, &body); err != nil { c.jsonErr(400, 400, "参数错误") return } up := map[string]interface{}{} if body.Name != nil { up["name"] = strings.TrimSpace(*body.Name) } if body.Cate != nil { up["cate"] = *body.Cate } if len(up) == 0 { c.jsonErr(400, 400, "无更新数据") return } now := time.Now() up["update_time"] = now n, err := models.Orm.QueryTable(new(models.SystemFile)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). Update(up) if err != nil { c.jsonErr(500, 500, "更新失败: "+err.Error()) return } if n == 0 { c.jsonErr(404, 404, "文件不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} _ = c.ServeJSON() } // DeleteFile DELETE /backend/deletefile/:id func (c *BackendFileController) DeleteFile() { claims, err := c.backendClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) idStr := c.Ctx.Input.Param(":id") id, err := strconv.ParseUint(idStr, 10, 64) if err != nil || id == 0 { c.jsonErr(400, 400, "无效的文件ID") return } now := time.Now() n, err := models.Orm.QueryTable(new(models.SystemFile)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). Update(map[string]interface{}{"delete_time": now}) if err != nil { c.jsonErr(500, 500, "删除失败: "+err.Error()) return } if n == 0 { c.jsonErr(404, 404, "文件不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} _ = c.ServeJSON() } // DeleteFilePermanently DELETE /backend/deletefilepermanently/:id func (c *BackendFileController) DeleteFilePermanently() { claims, err := c.backendClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) idStr := c.Ctx.Input.Param(":id") id, err := strconv.ParseUint(idStr, 10, 64) if err != nil || id == 0 { c.jsonErr(400, 400, "无效的文件ID") return } var f models.SystemFile err = models.Orm.QueryTable(new(models.SystemFile)). Filter("id", id). Filter("tid", tid). One(&f) if err != nil { c.jsonErr(404, 404, "文件不存在") return } if svc, e := services.GetStorageService(); e == nil { _ = services.RemovePhysical(svc, f.ObjectKey, f.Src) } _, err = models.Orm.QueryTable(new(models.SystemFile)). Filter("id", id). Filter("tid", tid). Delete() if err != nil { c.jsonErr(500, 500, "永久删除失败: "+err.Error()) return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "永久删除成功"} _ = c.ServeJSON() } // MoveFile GET /backend/movefile/:id func (c *BackendFileController) MoveFile() { claims, err := c.backendClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) idStr := c.Ctx.Input.Param(":id") id, err := strconv.ParseUint(idStr, 10, 64) if err != nil || id == 0 { c.jsonErr(400, 400, "无效的文件ID") return } cate, _ := c.GetUint64("cate") now := time.Now() n, err := models.Orm.QueryTable(new(models.SystemFile)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). Update(map[string]interface{}{"cate": cate, "update_time": now}) if err != nil { c.jsonErr(500, 500, "移动失败: "+err.Error()) return } if n == 0 { c.jsonErr(404, 404, "文件不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "移动成功"} _ = c.ServeJSON() } type idsBody struct { IDs []uint64 `json:"ids"` Cate *uint64 `json:"cate"` } // BatchDeleteFiles POST /backend/batchdeletefiles func (c *BackendFileController) BatchDeleteFiles() { claims, err := c.backendClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.jsonErr(400, 400, "参数错误") return } var body idsBody if err := json.Unmarshal(raw, &body); err != nil { c.jsonErr(400, 400, "参数错误") return } if len(body.IDs) == 0 { c.jsonErr(400, 400, "请选择要删除的文件") return } // 软删除:只标记 delete_time,不删除物理文件(彻底删除接口才会删) now := time.Now() n, err := models.Orm.QueryTable(new(models.SystemFile)). Filter("id__in", body.IDs). Filter("tid", tid). Update(map[string]interface{}{"delete_time": now}) if err != nil { c.jsonErr(500, 500, "批量删除失败: "+err.Error()) return } if n == 0 { c.jsonErr(404, 404, "文件不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量删除成功"} _ = c.ServeJSON() } // BatchDeleteFilesPermanently POST /backend/batchDeleteFilesPermanently func (c *BackendFileController) BatchDeleteFilesPermanently() { claims, err := c.backendClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.jsonErr(400, 400, "参数错误") return } var body idsBody if err := json.Unmarshal(raw, &body); err != nil { c.jsonErr(400, 400, "参数错误") return } if len(body.IDs) == 0 { c.jsonErr(400, 400, "请选择要彻底删除的文件") return } var rows []models.SystemFile _, err = models.Orm.QueryTable(new(models.SystemFile)). Filter("id__in", body.IDs). Filter("tid", tid). All(&rows) if err != nil { c.jsonErr(500, 500, "批量彻底删除失败: "+err.Error()) return } if svc, e := services.GetStorageService(); e == nil { for i := range rows { _ = services.RemovePhysical(svc, rows[i].ObjectKey, rows[i].Src) } } n, err := models.Orm.QueryTable(new(models.SystemFile)). Filter("id__in", body.IDs). Filter("tid", tid). Delete() if err != nil { c.jsonErr(500, 500, "批量彻底删除失败: "+err.Error()) return } if n == 0 { c.jsonErr(404, 404, "文件不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量彻底删除成功"} _ = c.ServeJSON() } // UploadAvatar POST /backend/uploadavatar(占位) func (c *BackendFileController) UploadAvatar() { c.Data["json"] = map[string]interface{}{"code": 501, "msg": "上传头像暂未实现"} _ = c.ServeJSON() } // UpdateAvatar POST /backend/uploadavatar/:id(占位) func (c *BackendFileController) UpdateAvatar() { c.Data["json"] = map[string]interface{}{"code": 501, "msg": "更新头像暂未实现"} _ = c.ServeJSON() } // BatchMoveFiles POST /backend/batchMoveFiles func (c *BackendFileController) BatchMoveFiles() { claims, err := c.backendClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } tid := c.effectiveTid(claims) raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.jsonErr(400, 400, "参数错误") return } var body idsBody if err := json.Unmarshal(raw, &body); err != nil { c.jsonErr(400, 400, "参数错误") return } if len(body.IDs) == 0 { c.jsonErr(400, 400, "请选择要移动的文件") return } if body.Cate == nil { c.jsonErr(400, 400, "缺少目标分类") return } now := time.Now() n, err := models.Orm.QueryTable(new(models.SystemFile)). Filter("id__in", body.IDs). Filter("tid", tid). Filter("delete_time__isnull", true). Update(map[string]interface{}{"cate": *body.Cate, "update_time": now}) if err != nil { c.jsonErr(500, 500, "批量移动失败: "+err.Error()) return } if n == 0 { c.jsonErr(404, 404, "文件不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量移动成功"} _ = c.ServeJSON() }