Files
mayfly-go/server/internal/machine/api/machine_file.go

448 lines
14 KiB
Go
Raw Normal View History

2021-09-11 14:04:09 +08:00
package api
import (
"fmt"
"io"
"io/fs"
2022-09-09 18:26:08 +08:00
"mayfly-go/internal/machine/api/form"
"mayfly-go/internal/machine/api/vo"
"mayfly-go/internal/machine/application"
"mayfly-go/internal/machine/application/dto"
"mayfly-go/internal/machine/config"
2022-09-09 18:26:08 +08:00
"mayfly-go/internal/machine/domain/entity"
2024-11-20 22:43:53 +08:00
"mayfly-go/internal/machine/imsg"
2023-10-30 17:34:56 +08:00
"mayfly-go/internal/machine/mcm"
2023-07-03 21:42:04 +08:00
msgapp "mayfly-go/internal/msg/application"
2023-10-10 17:39:46 +08:00
msgdto "mayfly-go/internal/msg/application/dto"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/errorx"
2024-11-20 22:43:53 +08:00
"mayfly-go/pkg/i18n"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/model"
2023-01-14 16:29:52 +08:00
"mayfly-go/pkg/req"
2023-10-20 21:31:46 +08:00
"mayfly-go/pkg/utils/anyx"
"mayfly-go/pkg/utils/collx"
"mayfly-go/pkg/utils/timex"
"mime/multipart"
"os"
"path/filepath"
2022-09-26 18:08:12 +08:00
"sort"
"strings"
"sync"
"github.com/may-fly/cast"
"github.com/pkg/sftp"
)
type MachineFile struct {
machineFileApp application.MachineFile `inject:"T"`
msgApp msgapp.Msg `inject:"T"`
}
func (mf *MachineFile) ReqConfs() *req.Confs {
reqs := [...]*req.Conf{
// 获取指定机器文件列表
req.NewGet(":machineId/files", mf.MachineFiles),
req.NewPost(":machineId/files", mf.SaveMachineFiles).Log(req.NewLogSaveI(imsg.LogMachineFileConfSave)).RequiredPermissionCode("machine:file:add"),
req.NewDelete(":machineId/files/:fileId", mf.DeleteFile).Log(req.NewLogSaveI(imsg.LogMachineFileConfDelete)).RequiredPermissionCode("machine:file:del"),
req.NewGet(":machineId/files/:fileId/read", mf.ReadFileContent).Log(req.NewLogSaveI(imsg.LogMachineFileRead)),
req.NewGet(":machineId/files/:fileId/download", mf.DownloadFile).NoRes().Log(req.NewLogSaveI(imsg.LogMachineFileDownload)),
req.NewGet(":machineId/files/:fileId/read-dir", mf.GetDirEntry),
req.NewGet(":machineId/files/:fileId/dir-size", mf.GetDirSize),
req.NewGet(":machineId/files/:fileId/file-stat", mf.GetFileStat),
req.NewPost(":machineId/files/:fileId/write", mf.WriteFileContent).Log(req.NewLogSaveI(imsg.LogMachineFileModify)).RequiredPermissionCode("machine:file:write"),
req.NewPost(":machineId/files/:fileId/create-file", mf.CreateFile).Log(req.NewLogSaveI(imsg.LogMachineFileCreate)),
req.NewPost(":machineId/files/:fileId/upload", mf.UploadFile).Log(req.NewLogSaveI(imsg.LogMachineFileUpload)).RequiredPermissionCode("machine:file:upload"),
req.NewPost(":machineId/files/:fileId/upload-folder", mf.UploadFolder).Log(req.NewLogSaveI(imsg.LogMachineFileUploadFolder)).RequiredPermissionCode("machine:file:upload"),
req.NewPost(":machineId/files/:fileId/remove", mf.RemoveFile).Log(req.NewLogSaveI(imsg.LogMachineFileDelete)).RequiredPermissionCode("machine:file:rm"),
req.NewPost(":machineId/files/:fileId/cp", mf.CopyFile).Log(req.NewLogSaveI(imsg.LogMachineFileCopy)).RequiredPermissionCode("machine:file:rm"),
req.NewPost(":machineId/files/:fileId/mv", mf.MvFile).Log(req.NewLogSaveI(imsg.LogMachineFileMove)).RequiredPermissionCode("machine:file:rm"),
req.NewPost(":machineId/files/:fileId/rename", mf.Rename).Log(req.NewLogSaveI(imsg.LogMachineFileRename)).RequiredPermissionCode("machine:file:write"),
}
return req.NewConfs("machines", reqs[:]...)
}
const (
file = "-"
dir = "d"
link = "l"
max_read_size = 1 * 1024 * 1024
)
2023-01-14 16:29:52 +08:00
func (m *MachineFile) MachineFiles(rc *req.Ctx) {
condition := &entity.MachineFile{MachineId: GetMachineId(rc)}
res, err := m.machineFileApp.GetPageList(condition, rc.GetPageParam())
biz.ErrIsNil(err)
rc.ResData = model.PageResultConv[*entity.MachineFile, *vo.MachineFileVO](res)
}
2023-01-14 16:29:52 +08:00
func (m *MachineFile) SaveMachineFiles(rc *req.Ctx) {
fileForm := new(form.MachineFileForm)
entity := req.BindJsonAndCopyTo[*entity.MachineFile](rc, fileForm, new(entity.MachineFile))
rc.ReqParam = fileForm
biz.ErrIsNil(m.machineFileApp.Save(rc.MetaCtx, entity))
}
2023-01-14 16:29:52 +08:00
func (m *MachineFile) DeleteFile(rc *req.Ctx) {
biz.ErrIsNil(m.machineFileApp.DeleteById(rc.MetaCtx, GetMachineFileId(rc)))
}
/*** sftp相关操作 */
2023-01-14 16:29:52 +08:00
func (m *MachineFile) CreateFile(rc *req.Ctx) {
opForm := req.BindJsonAndValid(rc, new(form.CreateFileForm))
path := opForm.Path
2023-10-12 12:14:56 +08:00
attrs := collx.Kvs("path", path)
2023-10-30 17:34:56 +08:00
var mi *mcm.MachineInfo
var err error
if opForm.Type == dir {
2024-11-20 22:43:53 +08:00
attrs["type"] = "Folder"
mi, err = m.machineFileApp.MkDir(rc.MetaCtx, opForm.MachineFileOp)
} else {
2024-11-20 22:43:53 +08:00
attrs["type"] = "File"
mi, err = m.machineFileApp.CreateFile(rc.MetaCtx, opForm.MachineFileOp)
}
attrs["machine"] = mi
rc.ReqParam = attrs
2024-11-20 22:43:53 +08:00
biz.ErrIsNil(err)
}
2023-01-14 16:29:52 +08:00
func (m *MachineFile) ReadFileContent(rc *req.Ctx) {
opForm := req.BindQuery(rc, new(dto.MachineFileOp))
readPath := opForm.Path
2024-11-20 22:43:53 +08:00
ctx := rc.MetaCtx
// 特殊处理rdp文件
if opForm.Protocol == entity.MachineProtocolRdp {
path := m.machineFileApp.GetRdpFilePath(rc.GetLoginAccount(), opForm.Path)
fi, err := os.Stat(path)
2024-11-20 22:43:53 +08:00
biz.ErrIsNil(err)
biz.IsTrueI(ctx, fi.Size() < max_read_size, imsg.ErrFileTooLargeUseDownload)
datas, err := os.ReadFile(path)
2024-11-20 22:43:53 +08:00
biz.ErrIsNil(err)
rc.ResData = string(datas)
return
}
sftpFile, mi, err := m.machineFileApp.ReadFile(rc.MetaCtx, opForm)
2023-10-12 12:14:56 +08:00
rc.ReqParam = collx.Kvs("machine", mi, "path", readPath)
2024-11-20 22:43:53 +08:00
biz.ErrIsNil(err)
defer sftpFile.Close()
fileInfo, _ := sftpFile.Stat()
filesize := fileInfo.Size()
2024-11-20 22:43:53 +08:00
biz.IsTrueI(ctx, filesize < max_read_size, imsg.ErrFileTooLargeUseDownload)
datas, err := io.ReadAll(sftpFile)
2024-11-20 22:43:53 +08:00
biz.ErrIsNil(err)
rc.ResData = string(datas)
}
func (m *MachineFile) DownloadFile(rc *req.Ctx) {
opForm := req.BindQuery(rc, new(dto.MachineFileOp))
readPath := opForm.Path
// 截取文件名,如/usr/local/test.java -》 test.java
path := strings.Split(readPath, "/")
fileName := path[len(path)-1]
if opForm.Protocol == entity.MachineProtocolRdp {
path := m.machineFileApp.GetRdpFilePath(rc.GetLoginAccount(), opForm.Path)
file, err := os.Open(path)
if err != nil {
return
}
defer file.Close()
rc.Download(file, fileName)
return
}
sftpFile, mi, err := m.machineFileApp.ReadFile(rc.MetaCtx, opForm)
rc.ReqParam = collx.Kvs("machine", mi, "path", readPath)
2024-11-20 22:43:53 +08:00
biz.ErrIsNilAppendErr(err, "open file error: %s")
defer sftpFile.Close()
rc.Download(sftpFile, fileName)
}
2023-01-14 16:29:52 +08:00
func (m *MachineFile) GetDirEntry(rc *req.Ctx) {
opForm := req.BindQuery(rc, new(dto.MachineFileOp))
readPath := opForm.Path
2023-08-04 12:22:21 +08:00
rc.ReqParam = fmt.Sprintf("path: %s", readPath)
fis, err := m.machineFileApp.ReadDir(rc.MetaCtx, opForm)
2024-11-20 22:43:53 +08:00
biz.ErrIsNilAppendErr(err, "read dir error: %s")
fisVO := make([]vo.MachineFileInfo, 0)
for _, fi := range fis {
name := fi.Name()
if !strings.HasPrefix(name, "/") {
name = "/" + name
}
path := name
if readPath != "/" && readPath != "" {
path = readPath + name
}
mfi := vo.MachineFileInfo{
Name: fi.Name(),
Size: fi.Size(),
Path: path,
Type: getFileType(fi.Mode()),
Mode: fi.Mode().String(),
ModTime: timex.DefaultFormat(fi.ModTime()),
}
if sftpFs, ok := fi.Sys().(*sftp.FileStat); ok {
mfi.UID = sftpFs.UID
mfi.GID = sftpFs.GID
}
fisVO = append(fisVO, mfi)
}
2022-09-26 18:08:12 +08:00
sort.Sort(vo.MachineFileInfos(fisVO))
rc.ResData = fisVO
}
2023-07-05 00:26:00 +08:00
func (m *MachineFile) GetDirSize(rc *req.Ctx) {
opForm := req.BindQuery(rc, new(dto.MachineFileOp))
2023-07-05 00:26:00 +08:00
size, err := m.machineFileApp.GetDirSize(rc.MetaCtx, opForm)
biz.ErrIsNil(err)
rc.ResData = size
2023-07-05 00:26:00 +08:00
}
func (m *MachineFile) GetFileStat(rc *req.Ctx) {
opForm := req.BindQuery(rc, new(dto.MachineFileOp))
res, err := m.machineFileApp.FileStat(rc.MetaCtx, opForm)
biz.ErrIsNil(err, res)
rc.ResData = res
2023-07-05 00:26:00 +08:00
}
2023-01-14 16:29:52 +08:00
func (m *MachineFile) WriteFileContent(rc *req.Ctx) {
opForm := req.BindJsonAndValid(rc, new(form.WriteFileContentForm))
path := opForm.Path
mi, err := m.machineFileApp.WriteFileContent(rc.MetaCtx, opForm.MachineFileOp, []byte(opForm.Content))
2023-10-12 12:14:56 +08:00
rc.ReqParam = collx.Kvs("machine", mi, "path", path)
2024-11-20 22:43:53 +08:00
biz.ErrIsNilAppendErr(err, "open file error: %s")
}
2023-01-14 16:29:52 +08:00
func (m *MachineFile) UploadFile(rc *req.Ctx) {
2024-02-25 12:46:18 +08:00
path := rc.PostForm("path")
protocol := cast.ToInt(rc.PostForm("protocol"))
machineId := cast.ToUint64(rc.PostForm("machineId"))
authCertName := rc.PostForm("authCertName")
2024-02-25 12:46:18 +08:00
fileheader, err := rc.FormFile("file")
2024-11-20 22:43:53 +08:00
biz.ErrIsNilAppendErr(err, "read form file error: %s")
ctx := rc.MetaCtx
maxUploadFileSize := config.GetMachine().UploadMaxFileSize
2024-11-20 22:43:53 +08:00
biz.IsTrueI(ctx, fileheader.Size <= maxUploadFileSize, imsg.ErrUploadFileOutOfLimit, "size", maxUploadFileSize)
file, _ := fileheader.Open()
defer file.Close()
la := rc.GetLoginAccount()
defer func() {
2023-10-20 21:31:46 +08:00
if anyx.ToString(recover()) != "" {
2024-11-20 22:43:53 +08:00
logx.Errorf("upload file error: %s", err)
m.msgApp.CreateAndSend(la, msgdto.ErrSysMsg(i18n.TC(ctx, imsg.ErrFileUploadFail), fmt.Sprintf("%s: \n<-e : %s", i18n.TC(ctx, imsg.ErrFileUploadFail), err)))
}
}()
opForm := &dto.MachineFileOp{
MachineId: machineId,
AuthCertName: authCertName,
Protocol: protocol,
Path: path,
}
mi, err := m.machineFileApp.UploadFile(ctx, opForm, fileheader.Filename, file)
2023-10-12 12:14:56 +08:00
rc.ReqParam = collx.Kvs("machine", mi, "path", fmt.Sprintf("%s/%s", path, fileheader.Filename))
2024-11-20 22:43:53 +08:00
biz.ErrIsNilAppendErr(err, "upload file error: %s")
// 保存消息并发送文件上传成功通知
m.msgApp.CreateAndSend(la, msgdto.SuccessSysMsg(i18n.TC(ctx, imsg.MsgUploadFileSuccess), fmt.Sprintf("[%s] -> %s[%s:%s]", fileheader.Filename, mi.Name, mi.Ip, path)))
}
type FolderFile struct {
Dir string
Fileheader *multipart.FileHeader
}
func (m *MachineFile) UploadFolder(rc *req.Ctx) {
2024-02-25 12:46:18 +08:00
mf, err := rc.MultipartForm()
2024-11-20 22:43:53 +08:00
biz.ErrIsNilAppendErr(err, "get multipart form error: %s")
basePath := mf.Value["basePath"][0]
2024-11-20 22:43:53 +08:00
biz.NotEmpty(basePath, "basePath cannot be empty")
fileheaders := mf.File["files"]
2024-11-20 22:43:53 +08:00
biz.IsTrue(len(fileheaders) > 0, "files cannot be empty")
allFileSize := collx.ArrayReduce(fileheaders, 0, func(i int64, fh *multipart.FileHeader) int64 {
return i + fh.Size
})
2024-11-20 22:43:53 +08:00
ctx := rc.MetaCtx
maxUploadFileSize := config.GetMachine().UploadMaxFileSize
2024-11-20 22:43:53 +08:00
biz.IsTrueI(ctx, allFileSize <= maxUploadFileSize, imsg.ErrUploadFileOutOfLimit, "size", maxUploadFileSize)
paths := mf.Value["paths"]
authCertName := mf.Value["authCertName"][0]
machineId := cast.ToUint64(mf.Value["machineId"][0])
// protocol
protocol := cast.ToInt(mf.Value["protocol"][0])
opForm := &dto.MachineFileOp{
MachineId: machineId,
Protocol: protocol,
AuthCertName: authCertName,
}
if protocol == entity.MachineProtocolRdp {
m.machineFileApp.UploadFiles(ctx, opForm, basePath, fileheaders, paths)
return
}
folderName := filepath.Dir(paths[0])
mcli, err := m.machineFileApp.GetMachineCli(authCertName)
biz.ErrIsNil(err)
defer mcm.PutMachineCli(mcli)
2023-10-30 17:34:56 +08:00
mi := mcli.Info
sftpCli, err := mcli.GetSftpCli()
biz.ErrIsNil(err)
2023-10-12 12:14:56 +08:00
rc.ReqParam = collx.Kvs("machine", mi, "path", fmt.Sprintf("%s/%s", basePath, folderName))
folderFiles := make([]FolderFile, len(paths))
// 先创建目录并将其包装为folderFile结构
mkdirs := make(map[string]bool, 0)
for i, path := range paths {
dir := filepath.Dir(path)
// 目录已建,则无需重复建
if !mkdirs[dir] {
2024-11-20 22:43:53 +08:00
biz.ErrIsNilAppendErr(sftpCli.MkdirAll(basePath+"/"+dir), "create dir error: %s")
mkdirs[dir] = true
}
folderFiles[i] = FolderFile{
Dir: dir,
Fileheader: fileheaders[i],
}
}
// 分组处理
groupNum := 30
chunks := collx.ArraySplit(folderFiles, groupNum)
var wg sync.WaitGroup
// 设置要等待的协程数量
wg.Add(len(chunks))
2023-10-30 17:34:56 +08:00
isSuccess := true
la := rc.GetLoginAccount()
for _, chunk := range chunks {
go func(files []FolderFile, wg *sync.WaitGroup) {
defer func() {
// 协程执行完成后调用Done方法
wg.Done()
if err := recover(); err != nil {
2023-10-30 17:34:56 +08:00
isSuccess = false
2024-11-20 22:43:53 +08:00
logx.Errorf("upload file error: %s", err)
switch t := err.(type) {
2024-10-23 17:30:05 +08:00
case *errorx.BizError:
m.msgApp.CreateAndSend(la, msgdto.ErrSysMsg(i18n.TC(ctx, imsg.ErrFileUploadFail), fmt.Sprintf("%s: \n<-e errCode: %d, errMsg: %s", i18n.TC(ctx, imsg.ErrFileUploadFail), t.Code(), t.Error())))
}
}
}()
for _, file := range files {
fileHeader := file.Fileheader
dir := file.Dir
file, _ := fileHeader.Open()
defer file.Close()
2024-11-20 22:43:53 +08:00
logx.Debugf("upload folder: dir=%s -> filename=%s", dir, fileHeader.Filename)
createfile, err := sftpCli.Create(fmt.Sprintf("%s/%s/%s", basePath, dir, fileHeader.Filename))
2024-11-20 22:43:53 +08:00
biz.ErrIsNilAppendErr(err, "create file error: %s")
defer createfile.Close()
io.Copy(createfile, file)
}
}(chunk, &wg)
}
// 等待所有协程执行完成
wg.Wait()
2023-10-30 17:34:56 +08:00
if isSuccess {
// 保存消息并发送文件上传成功通知
m.msgApp.CreateAndSend(la, msgdto.SuccessSysMsg(i18n.TC(ctx, imsg.MsgUploadFileSuccess), fmt.Sprintf("[%s] -> %s[%s:%s]", folderName, mi.Name, mi.Ip, basePath)))
2023-10-30 17:34:56 +08:00
}
}
2023-01-14 16:29:52 +08:00
func (m *MachineFile) RemoveFile(rc *req.Ctx) {
opForm := req.BindJsonAndValid(rc, new(form.RemoveFileForm))
mi, err := m.machineFileApp.RemoveFile(rc.MetaCtx, opForm.MachineFileOp, opForm.Paths...)
rc.ReqParam = collx.Kvs("machine", mi, "path", opForm)
2024-11-20 22:43:53 +08:00
biz.ErrIsNilAppendErr(err, "remove file error: %s")
}
func (m *MachineFile) CopyFile(rc *req.Ctx) {
opForm := req.BindJsonAndValid(rc, new(form.CopyFileForm))
mi, err := m.machineFileApp.Copy(rc.MetaCtx, opForm.MachineFileOp, opForm.ToPath, opForm.Paths...)
2024-11-20 22:43:53 +08:00
biz.ErrIsNilAppendErr(err, "file copy error: %s")
rc.ReqParam = collx.Kvs("machine", mi, "cp", opForm)
}
func (m *MachineFile) MvFile(rc *req.Ctx) {
opForm := req.BindJsonAndValid(rc, new(form.CopyFileForm))
mi, err := m.machineFileApp.Mv(rc.MetaCtx, opForm.MachineFileOp, opForm.ToPath, opForm.Paths...)
rc.ReqParam = collx.Kvs("machine", mi, "mv", opForm)
2024-11-20 22:43:53 +08:00
biz.ErrIsNilAppendErr(err, "file move error: %s")
}
func (m *MachineFile) Rename(rc *req.Ctx) {
renameForm := req.BindJsonAndValid(rc, new(form.RenameForm))
mi, err := m.machineFileApp.Rename(rc.MetaCtx, renameForm.MachineFileOp, renameForm.Newname)
rc.ReqParam = collx.Kvs("machine", mi, "rename", renameForm)
2024-11-20 22:43:53 +08:00
biz.ErrIsNilAppendErr(err, "file rename error: %s")
}
func getFileType(fm fs.FileMode) string {
if fm.IsDir() {
return dir
}
if fm.IsRegular() {
return file
}
return dir
}
func GetMachineFileId(rc *req.Ctx) uint64 {
2024-02-25 12:46:18 +08:00
fileId := rc.PathParamInt("fileId")
2024-11-20 22:43:53 +08:00
biz.IsTrue(fileId != 0, "fileId error")
return uint64(fileId)
}