按用户指示做**一包提交**,不按工作流拆分。本提交刻意混合了多条并行线:
· 本地 ASR 接管:audio 成为与 chat/embed/image/video 同等的路由类别
(IsLocalRoute 单一判据、audio 健康探测、default_audio_route、
auto 占位、GET /api/ai/routes/audio、回退云端时界面明示「音频已出网」)
· LLM 调用层:ctx 贯穿、ToolCall/ToolSchema、EmptyCompletionError /
TransientUpstreamError(按错误类型而非文案判重试)
· 编排 Agent:general_assistant orchestrate/persistence/spec_driver
· 联网搜索:internal/search(playwright)
· 网盘:backend + 前端
· 前端 UI:导航/路由/工作台若干页
· 交付文档:DELIVERY.md / AR04 / 部署文档的「无 Python」表述据实改写,
新增 eai_agentplatform-asr.service、asr.env、clonezilla-cleanup 清 ~/asr-poc
不分拆的原因:dev 早期,粒度不该打断工作节奏。且实测过——这些改动
**在编译上是同一个单元**(llm.go 的 ctx 签名变更牵动 12 个调用点,
chat_message.go 的 ctx 改动又与编排重写同处一个 hunk),拆出来的中间态编不过。
详见 TOP_CODING_RULES.md G14.5 与 bugs_and_errors.md E09。
Co-Authored-By: Claude Code <noreply@anthropic.com>
575 lines
16 KiB
Go
575 lines
16 KiB
Go
package api
|
||
|
||
import (
|
||
"crypto/rand"
|
||
"encoding/hex"
|
||
"errors"
|
||
"io"
|
||
"mime"
|
||
"os"
|
||
"path/filepath"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
|
||
"eai_agentplatform/backend/internal/dal"
|
||
"eai_agentplatform/backend/internal/middleware"
|
||
"eai_agentplatform/backend/internal/model"
|
||
"eai_agentplatform/backend/internal/web"
|
||
)
|
||
|
||
// 团队共享网盘:全公司共享一套目录与文件,company_id 固定为 1(单公司平台)。
|
||
const netdiskCompanyID = 1
|
||
|
||
// 目录 path 合法性相关常量(对齐 pj0034 asset_folder 红线)。
|
||
// 不可删除/改名的 system folder:根目录 `/`。
|
||
const netdiskRootPath = "/"
|
||
|
||
func init() {
|
||
netdiskFolderDAO = dal.NetdiskFolderDAO{}
|
||
netdiskFileDAO = dal.NetdiskFileDAO{}
|
||
}
|
||
|
||
var (
|
||
netdiskFolderDAO dal.NetdiskFolderDAO
|
||
netdiskFileDAO dal.NetdiskFileDAO
|
||
)
|
||
|
||
// netdiskRoot 返回网盘物理存储根目录(不存在则创建)。
|
||
func netdiskRoot() string {
|
||
dir := Cfg.NetdiskDataDir
|
||
if dir == "" {
|
||
dir = filepath.Join("data", "netdisk")
|
||
}
|
||
_ = os.MkdirAll(dir, 0o755)
|
||
return dir
|
||
}
|
||
|
||
// netdiskFilePath 由 StoredName 得到物理绝对路径。
|
||
func netdiskFilePath(storedName string) string {
|
||
return filepath.Join(netdiskRoot(), filepath.Base(storedName))
|
||
}
|
||
|
||
// ────────────────────────────────────────────────────────────
|
||
// 目录 path 工具(参考 pj0034 app/services/dam/folder.py)
|
||
// ────────────────────────────────────────────────────────────
|
||
|
||
var netdiskInvalidSegChars = func() func(r rune) bool {
|
||
invalid := "\\:*?\"<>|"
|
||
return func(r rune) bool { return strings.ContainsRune(invalid, r) }
|
||
}()
|
||
|
||
// netdiskNormalizePath 规范化:去尾 `/`、压缩多 `/`、去段内空白;返 `/` 起的绝对路径。
|
||
func netdiskNormalizePath(raw string) string {
|
||
s := strings.TrimSpace(strings.ReplaceAll(raw, "\\", "/"))
|
||
if s == "" {
|
||
return "/"
|
||
}
|
||
if !strings.HasPrefix(s, "/") {
|
||
s = "/" + s
|
||
}
|
||
for strings.Contains(s, "//") {
|
||
s = strings.ReplaceAll(s, "//", "/")
|
||
}
|
||
if len(s) > 1 {
|
||
s = strings.TrimRight(s, "/")
|
||
}
|
||
return s
|
||
}
|
||
|
||
// netdiskValidatePath 校验并返回规范化 path;不合法返回错误。
|
||
func netdiskValidatePath(raw string) (string, error) {
|
||
if strings.TrimSpace(raw) == "" {
|
||
return "", errors.New("目录路径不能为空")
|
||
}
|
||
p := netdiskNormalizePath(raw)
|
||
for _, seg := range strings.Split(p, "/") {
|
||
if seg == "" {
|
||
continue
|
||
}
|
||
if seg == "." || seg == ".." {
|
||
return "", errors.New("目录路径不允许 '.' 或 '..'")
|
||
}
|
||
if strings.ContainsFunc(seg, netdiskInvalidSegChars) {
|
||
return "", errors.New("目录名含非法字符 \\ : * ? \" < > |")
|
||
}
|
||
if len(seg) > 100 {
|
||
return "", errors.New("目录名「" + seg + "」超 100 字符")
|
||
}
|
||
}
|
||
if len(p) > 500 {
|
||
return "", errors.New("目录路径总长超 500 字符")
|
||
}
|
||
return p, nil
|
||
}
|
||
|
||
// netdiskIsRoot 是否根目录(system folder,不可删/改名)。
|
||
func netdiskIsRoot(path string) bool {
|
||
return netdiskNormalizePath(path) == netdiskRootPath
|
||
}
|
||
|
||
// netdiskDeriveDisplayName 由 path 末段派生显示名。
|
||
func netdiskDeriveDisplayName(path string) string {
|
||
p := netdiskNormalizePath(path)
|
||
if p == "/" {
|
||
return "/"
|
||
}
|
||
segs := strings.Split(p, "/")
|
||
return segs[len(segs)-1]
|
||
}
|
||
|
||
// netdiskReplacePrefix 把老前缀整体替换为新前缀(用于目录改名时联动后代)。
|
||
func netdiskReplacePrefix(path, oldPrefix, newPrefix string) string {
|
||
if path == oldPrefix {
|
||
return newPrefix
|
||
}
|
||
if strings.HasPrefix(path, oldPrefix+"/") {
|
||
return newPrefix + path[len(oldPrefix):]
|
||
}
|
||
return path
|
||
}
|
||
|
||
// netdiskParentPath 返回父目录路径(根为 "/")。
|
||
func netdiskParentPath(path string) string {
|
||
p := netdiskNormalizePath(path)
|
||
if p == "/" {
|
||
return "/"
|
||
}
|
||
if idx := strings.LastIndex(p, "/"); idx > 0 {
|
||
return p[:idx]
|
||
}
|
||
return "/"
|
||
}
|
||
|
||
// ────────────────────────────────────────────────────────────
|
||
// 目录 CRUD
|
||
// ────────────────────────────────────────────────────────────
|
||
|
||
// netdiskListFolders GET /api/netdisk/folders —— 列全部目录
|
||
func netdiskListFolders(c *gin.Context) {
|
||
folders := netdiskFolderDAO.ListDescendants(netdiskCompanyID, "")
|
||
web.OK(c, gin.H{"items": folders, "total": len(folders)})
|
||
}
|
||
|
||
// netdiskCreateFolder POST /api/netdisk/folders —— 新建目录 (mkdir)
|
||
func netdiskCreateFolder(c *gin.Context) {
|
||
u := middleware.CurrentUser(c)
|
||
var req struct {
|
||
Path string `json:"path"`
|
||
DisplayName string `json:"display_name"`
|
||
}
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
web.Fail(c, web.NewBadRequest("请求参数错误"))
|
||
return
|
||
}
|
||
path, err := netdiskValidatePath(req.Path)
|
||
if err != nil {
|
||
web.Fail(c, web.NewBadRequest(err.Error()))
|
||
return
|
||
}
|
||
if netdiskIsRoot(path) {
|
||
web.Fail(c, web.NewBadRequest("根目录已存在,无需创建"))
|
||
return
|
||
}
|
||
if _, found := netdiskFolderDAO.GetByPath(netdiskCompanyID, path); found {
|
||
web.Fail(c, web.NewBadRequest("目录「" + path + "」已存在"))
|
||
return
|
||
}
|
||
name := req.DisplayName
|
||
if name == "" {
|
||
name = netdiskDeriveDisplayName(path)
|
||
}
|
||
folder := model.NetdiskFolder{
|
||
CompanyID: netdiskCompanyID,
|
||
Path: path,
|
||
DisplayName: name,
|
||
CreatedByID: u.ID,
|
||
CreatedAt: time.Now(),
|
||
}
|
||
if !netdiskFolderDAO.Insert(&folder) {
|
||
web.Fail(c, web.NewBadRequest("创建目录失败"))
|
||
return
|
||
}
|
||
web.OK(c, folder)
|
||
}
|
||
|
||
// netdiskPatchFolder PATCH /api/netdisk/folders/:id —— 重命名 / 移动(联动后代与文件)
|
||
func netdiskPatchFolder(c *gin.Context) {
|
||
id, ok := parseID(c, "id")
|
||
if !ok {
|
||
return
|
||
}
|
||
var req struct {
|
||
Path *string `json:"path"`
|
||
DisplayName *string `json:"display_name"`
|
||
}
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
web.Fail(c, web.NewBadRequest("请求参数错误"))
|
||
return
|
||
}
|
||
folder, found := netdiskFolderDAO.GetByID(id)
|
||
if !found {
|
||
web.Fail(c, web.NewNotFoundError("目录不存在"))
|
||
return
|
||
}
|
||
if netdiskIsRoot(folder.Path) {
|
||
web.Fail(c, web.NewBadRequest("根目录不可改名或移动"))
|
||
return
|
||
}
|
||
|
||
if req.Path != nil {
|
||
newPath, err := netdiskValidatePath(*req.Path)
|
||
if err != nil {
|
||
web.Fail(c, web.NewBadRequest(err.Error()))
|
||
return
|
||
}
|
||
if netdiskNormalizePath(newPath) == netdiskNormalizePath(folder.Path) {
|
||
// 同名,跳过移动
|
||
} else {
|
||
if strings.HasPrefix(newPath+"/", folder.Path+"/") {
|
||
web.Fail(c, web.NewBadRequest("不能把目录移动到自己的子目录下"))
|
||
return
|
||
}
|
||
if _, dup := netdiskFolderDAO.GetByPath(netdiskCompanyID, newPath); dup {
|
||
web.Fail(c, web.NewBadRequest("目标目录「" + newPath + "」已存在"))
|
||
return
|
||
}
|
||
// 联动后代目录
|
||
desc := netdiskFolderDAO.ListDescendants(netdiskCompanyID, folder.Path)
|
||
for i := range desc {
|
||
if desc[i].ID == folder.ID {
|
||
continue
|
||
}
|
||
next := netdiskReplacePrefix(desc[i].Path, folder.Path, newPath)
|
||
if next != desc[i].Path {
|
||
desc[i].Path = next
|
||
netdiskFolderDAO.Update(&desc[i])
|
||
}
|
||
}
|
||
// 联动该目录下(含后代)的文件 folder_path
|
||
netdiskMigrateFilesOnFolderMove(folder.Path, newPath)
|
||
folder.Path = newPath
|
||
}
|
||
}
|
||
if req.DisplayName != nil && strings.TrimSpace(*req.DisplayName) != "" {
|
||
folder.DisplayName = strings.TrimSpace(*req.DisplayName)
|
||
}
|
||
if !netdiskFolderDAO.Update(&folder) {
|
||
web.Fail(c, web.NewBadRequest("更新目录失败"))
|
||
return
|
||
}
|
||
web.OK(c, folder)
|
||
}
|
||
|
||
// netdiskMigrateFilesOnFolderMove 目录改名/移动后,把落在该目录及其后代的文件 folder_path 同步改前缀。
|
||
func netdiskMigrateFilesOnFolderMove(oldPrefix, newPrefix string) {
|
||
// 取所有未被回收站软删的文件,逐个修正前缀。
|
||
folders := netdiskFolderDAO.ListDescendants(netdiskCompanyID, "")
|
||
affected := map[string]bool{}
|
||
for _, f := range folders {
|
||
if f.Path == oldPrefix || strings.HasPrefix(f.Path, oldPrefix+"/") {
|
||
affected[f.Path] = true
|
||
}
|
||
}
|
||
if len(affected) == 0 {
|
||
return
|
||
}
|
||
var files []model.NetdiskFile
|
||
var all []model.NetdiskFile
|
||
dal.New(&all).Find(&all)
|
||
for i := range all {
|
||
nf := all[i]
|
||
if nf.CompanyID != netdiskCompanyID || nf.DeletedAt != nil {
|
||
continue
|
||
}
|
||
next := netdiskReplacePrefix(nf.FolderPath, oldPrefix, newPrefix)
|
||
if next != nf.FolderPath {
|
||
nf.FolderPath = next
|
||
files = append(files, nf)
|
||
}
|
||
}
|
||
for i := range files {
|
||
netdiskFileDAO.Update(&files[i])
|
||
}
|
||
}
|
||
|
||
// netdiskDeleteFolder DELETE /api/netdisk/folders/:id —— 删除空目录(非空 409)
|
||
func netdiskDeleteFolder(c *gin.Context) {
|
||
id, ok := parseID(c, "id")
|
||
if !ok {
|
||
return
|
||
}
|
||
folder, found := netdiskFolderDAO.GetByID(id)
|
||
if !found {
|
||
web.Fail(c, web.NewNotFoundError("目录不存在"))
|
||
return
|
||
}
|
||
if netdiskIsRoot(folder.Path) {
|
||
web.Fail(c, web.NewBadRequest("根目录不可删除"))
|
||
return
|
||
}
|
||
if children := netdiskFolderDAO.CountChildren(netdiskCompanyID, folder.Path); children > 0 {
|
||
web.Fail(c, web.NewBadRequest("该目录下还有子目录,请先清空再删除"))
|
||
return
|
||
}
|
||
// 统计该目录下非回收站文件数
|
||
cnt := netdiskCountLiveFilesUnder(folder.Path)
|
||
if cnt > 0 {
|
||
web.Fail(c, web.NewBadRequest("该目录下还有 "+strconv.FormatInt(cnt, 10)+" 个文件,请先移走或删除"))
|
||
return
|
||
}
|
||
if !netdiskFolderDAO.Delete(id) {
|
||
web.Fail(c, web.NewBadRequest("删除失败"))
|
||
return
|
||
}
|
||
web.OK(c, gin.H{"deleted": id})
|
||
}
|
||
|
||
// netdiskCountLiveFilesUnder 统计某目录(含后代路径)下未软删除的文件数。
|
||
func netdiskCountLiveFilesUnder(folderPath string) int64 {
|
||
var all []model.NetdiskFile
|
||
dal.New(&all).Find(&all)
|
||
var n int64
|
||
for _, f := range all {
|
||
if f.CompanyID != netdiskCompanyID || f.DeletedAt != nil {
|
||
continue
|
||
}
|
||
if f.FolderPath == folderPath {
|
||
n++
|
||
}
|
||
}
|
||
return n
|
||
}
|
||
|
||
// ────────────────────────────────────────────────────────────
|
||
// 文件:列表 / 上传
|
||
// ────────────────────────────────────────────────────────────
|
||
|
||
// netdiskListFiles GET /api/netdisk/files?path=&status= —— 列某目录文件(默认只看 approved)
|
||
func netdiskListFiles(c *gin.Context) {
|
||
path := c.DefaultQuery("path", "/")
|
||
norm := netdiskNormalizePath(path)
|
||
status := c.DefaultQuery("status", "approved")
|
||
files := netdiskFileDAO.ListByFolder(netdiskCompanyID, norm, status)
|
||
web.OK(c, gin.H{"items": files, "total": len(files), "path": norm})
|
||
}
|
||
|
||
var blockedNetdiskExt = map[string]bool{
|
||
"exe": true, "bin": true, "dll": true, "so": true, "dylib": true,
|
||
"msi": true, "apk": true, "ipa": true, "deb": true, "rpm": true, "pkg": true, "appimage": true,
|
||
"bat": true, "cmd": true, "com": true, "scr": true, "sys": true, "drv": true,
|
||
"ps1": true, "psm1": true, "vbs": true, "vbe": true, "js": true, "jse": true, "wsf": true, "wsh": true,
|
||
"reg": true, "lnk": true, "iso": true, "img": true, "dmg": true,
|
||
}
|
||
|
||
func netdiskIsUploadable(ext string) bool {
|
||
return !blockedNetdiskExt[strings.ToLower(strings.TrimPrefix(ext, "."))]
|
||
}
|
||
|
||
func netdiskSizeLimit(ext string) int64 {
|
||
ext = strings.ToLower(strings.TrimPrefix(ext, "."))
|
||
switch ext {
|
||
case "mp4", "mov", "avi", "mkv", "webm", "m4v", "wmv", "flv":
|
||
return Cfg.FileMaxVideo
|
||
default:
|
||
return Cfg.FileMaxDoc
|
||
}
|
||
}
|
||
|
||
func netdiskRandomID() string {
|
||
b := make([]byte, 16)
|
||
if _, err := rand.Read(b); err != nil {
|
||
return strconv.FormatInt(time.Now().UnixNano(), 36)
|
||
}
|
||
return hex.EncodeToString(b)
|
||
}
|
||
|
||
// netdiskUpload POST /api/netdisk/upload —— 直传
|
||
func netdiskUpload(c *gin.Context) {
|
||
u := middleware.CurrentUser(c)
|
||
folderPath := netdiskNormalizePath(c.PostForm("folder_path"))
|
||
if _, err := netdiskValidatePath(folderPath); err != nil {
|
||
folderPath = "/"
|
||
}
|
||
// 若指定了不存在的目录,回退根目录
|
||
if folderPath != "/" {
|
||
if _, ok := netdiskFolderDAO.GetByPath(netdiskCompanyID, folderPath); !ok {
|
||
folderPath = "/"
|
||
}
|
||
}
|
||
|
||
file, header, err := c.Request.FormFile("file")
|
||
if err != nil {
|
||
web.Fail(c, web.NewBadRequest("缺少文件字段 file"))
|
||
return
|
||
}
|
||
defer file.Close()
|
||
|
||
ext := strings.ToLower(strings.TrimPrefix(filepath.Ext(header.Filename), "."))
|
||
if !netdiskIsUploadable(ext) {
|
||
web.Fail(c, web.NewBadRequest("不支持的文件类型"))
|
||
return
|
||
}
|
||
if header.Size > netdiskSizeLimit(ext) {
|
||
web.Fail(c, web.NewBadRequest("文件超过大小限制"))
|
||
return
|
||
}
|
||
|
||
source := "employee"
|
||
status := "pending"
|
||
if u.Role == "admin" {
|
||
source = "admin"
|
||
status = "approved" // 管理员上传自动通过
|
||
}
|
||
|
||
storedName := netdiskRandomID() + "." + ext
|
||
dst := filepath.Join(netdiskRoot(), storedName)
|
||
out, err := os.Create(dst)
|
||
if err != nil {
|
||
web.Fail(c, web.NewBadRequest("保存文件失败"))
|
||
return
|
||
}
|
||
if _, err := io.Copy(out, file); err != nil {
|
||
out.Close()
|
||
os.Remove(dst)
|
||
web.Fail(c, web.NewBadRequest("写入文件失败"))
|
||
return
|
||
}
|
||
out.Close()
|
||
|
||
m := model.NetdiskFile{
|
||
CompanyID: netdiskCompanyID,
|
||
FolderPath: folderPath,
|
||
Filename: header.Filename,
|
||
StoredName: storedName,
|
||
FileExt: ext,
|
||
MimeType: mime.TypeByExtension("." + ext),
|
||
FileSize: header.Size,
|
||
Status: status,
|
||
Source: source,
|
||
SubmitterID: u.ID,
|
||
CreatedAt: time.Now(),
|
||
}
|
||
if !netdiskFileDAO.Insert(&m) {
|
||
os.Remove(dst)
|
||
web.Fail(c, web.NewBadRequest("创建文件记录失败"))
|
||
return
|
||
}
|
||
web.OK(c, gin.H{"file_id": m.ID, "status": m.Status, "filename": m.Filename})
|
||
}
|
||
|
||
// netdiskDownload GET /api/netdisk/files/:id/download —— 下载(仅 approved)
|
||
func netdiskDownload(c *gin.Context) {
|
||
id, ok := parseID(c, "id")
|
||
if !ok {
|
||
return
|
||
}
|
||
f, found := netdiskFileDAO.GetByID(id)
|
||
if !found {
|
||
web.Fail(c, web.NewNotFoundError("文件不存在"))
|
||
return
|
||
}
|
||
if f.Status != "approved" {
|
||
web.Fail(c, web.NewForbiddenError("文件未通过审批,不可下载"))
|
||
return
|
||
}
|
||
c.FileAttachment(netdiskFilePath(f.StoredName), f.Filename)
|
||
}
|
||
|
||
// netdiskPreview GET /api/netdisk/files/:id/preview —— 在线预览(仅 approved)
|
||
func netdiskPreview(c *gin.Context) {
|
||
id, ok := parseID(c, "id")
|
||
if !ok {
|
||
return
|
||
}
|
||
f, found := netdiskFileDAO.GetByID(id)
|
||
if !found {
|
||
web.Fail(c, web.NewNotFoundError("文件不存在"))
|
||
return
|
||
}
|
||
if f.Status != "approved" {
|
||
web.Fail(c, web.NewForbiddenError("文件未通过审批,不可预览"))
|
||
return
|
||
}
|
||
web.OK(c, gin.H{
|
||
"preview_url": "/netdisk-file/" + f.StoredName,
|
||
"file_ext": f.FileExt,
|
||
"mime_type": f.MimeType,
|
||
})
|
||
}
|
||
|
||
// netdiskRenameFile PUT /api/netdisk/files/:id/rename —— 重命名(仅改名,不移动)
|
||
func netdiskRenameFile(c *gin.Context) {
|
||
id, ok := parseID(c, "id")
|
||
if !ok {
|
||
return
|
||
}
|
||
var req struct {
|
||
Filename string `json:"filename"`
|
||
}
|
||
if err := c.ShouldBindJSON(&req); err != nil || strings.TrimSpace(req.Filename) == "" {
|
||
web.Fail(c, web.NewBadRequest("文件名不能为空"))
|
||
return
|
||
}
|
||
f, found := netdiskFileDAO.GetByID(id)
|
||
if !found {
|
||
web.Fail(c, web.NewNotFoundError("文件不存在"))
|
||
return
|
||
}
|
||
if f.DeletedAt != nil {
|
||
web.Fail(c, web.NewBadRequest("文件在回收站中"))
|
||
return
|
||
}
|
||
name := strings.TrimSpace(req.Filename)
|
||
if netdiskNormalizePath(name) == "/" || strings.Contains(name, "/") || strings.Contains(name, "\\") {
|
||
web.Fail(c, web.NewBadRequest("文件名不合法"))
|
||
return
|
||
}
|
||
f.Filename = name
|
||
if !netdiskFileDAO.Update(&f) {
|
||
web.Fail(c, web.NewBadRequest("重命名失败"))
|
||
return
|
||
}
|
||
web.OK(c, f)
|
||
}
|
||
|
||
// netdiskMoveFile PUT /api/netdisk/files/:id/move —— 移动到目标目录
|
||
func netdiskMoveFile(c *gin.Context) {
|
||
id, ok := parseID(c, "id")
|
||
if !ok {
|
||
return
|
||
}
|
||
var req struct {
|
||
FolderPath string `json:"folder_path"`
|
||
}
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
web.Fail(c, web.NewBadRequest("请求参数错误"))
|
||
return
|
||
}
|
||
target, err := netdiskValidatePath(req.FolderPath)
|
||
if err != nil {
|
||
web.Fail(c, web.NewBadRequest(err.Error()))
|
||
return
|
||
}
|
||
if _, found := netdiskFolderDAO.GetByPath(netdiskCompanyID, target); !found && target != "/" {
|
||
web.Fail(c, web.NewNotFoundError("目标目录不存在"))
|
||
return
|
||
}
|
||
f, found := netdiskFileDAO.GetByID(id)
|
||
if !found {
|
||
web.Fail(c, web.NewNotFoundError("文件不存在"))
|
||
return
|
||
}
|
||
if f.DeletedAt != nil {
|
||
web.Fail(c, web.NewBadRequest("文件在回收站中"))
|
||
return
|
||
}
|
||
f.FolderPath = target
|
||
if !netdiskFileDAO.Update(&f) {
|
||
web.Fail(c, web.NewBadRequest("移动失败"))
|
||
return
|
||
}
|
||
web.OK(c, f)
|
||
} |