feat(asr): 本地语音转写接入为一级路由 + 并行工作流合并提交
按用户指示做**一包提交**,不按工作流拆分。本提交刻意混合了多条并行线:
· 本地 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>
This commit is contained in:
@@ -0,0 +1,575 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user