2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 05:31:44 +08:00
Files
panel/pkg/storage/ftp.go
2025-09-18 14:48:46 +08:00

366 lines
6.9 KiB
Go

package storage
import (
"bytes"
"fmt"
"io"
"mime"
"path/filepath"
"strings"
"time"
"github.com/jlaffaye/ftp"
)
type FTPConfig struct {
Host string // FTP 服务器地址
Port int // FTP 端口,默认 21
Username string // 用户名
Password string // 密码
BasePath string // 基础路径
}
type FTP struct {
config FTPConfig
}
func NewFTP(config FTPConfig) (Storage, error) {
if config.Port == 0 {
config.Port = 21
}
config.BasePath = strings.Trim(config.BasePath, "/")
f := &FTP{
config: config,
}
if err := f.ensureBasePath(); err != nil {
return nil, fmt.Errorf("failed to ensure base path: %w", err)
}
return f, nil
}
// connect 建立 FTP 连接
func (f *FTP) connect() (*ftp.ServerConn, error) {
addr := fmt.Sprintf("%s:%d", f.config.Host, f.config.Port)
conn, err := ftp.Dial(addr)
if err != nil {
return nil, err
}
err = conn.Login(f.config.Username, f.config.Password)
if err != nil {
_ = conn.Quit()
return nil, err
}
return conn, nil
}
// ensureBasePath 确保基础路径存在
func (f *FTP) ensureBasePath() error {
conn, err := f.connect()
if err != nil {
return err
}
defer func(conn *ftp.ServerConn) {
_ = conn.Quit()
}(conn)
// 递归创建路径
parts := strings.Split(f.config.BasePath, "/")
currentPath := ""
for _, part := range parts {
if part == "" {
continue
}
if currentPath == "" {
currentPath = part
} else {
currentPath = currentPath + "/" + part
}
_ = conn.MakeDir(currentPath)
}
return nil
}
// getRemotePath 获取远程路径
func (f *FTP) getRemotePath(path string) string {
path = strings.TrimPrefix(path, "/")
if f.config.BasePath == "" {
return path
}
if path == "" {
return f.config.BasePath
}
return fmt.Sprintf("%s/%s", f.config.BasePath, path)
}
// MakeDirectory 创建目录
func (f *FTP) MakeDirectory(directory string) error {
conn, err := f.connect()
if err != nil {
return err
}
defer func(conn *ftp.ServerConn) {
_ = conn.Quit()
}(conn)
remotePath := f.getRemotePath(directory)
// 递归创建目录
parts := strings.Split(remotePath, "/")
currentPath := ""
for _, part := range parts {
if part == "" {
continue
}
if currentPath == "" {
currentPath = part
} else {
currentPath = currentPath + "/" + part
}
// 尝试创建目录
_ = conn.MakeDir(currentPath)
}
return nil
}
// DeleteDirectory 删除目录
func (f *FTP) DeleteDirectory(directory string) error {
conn, err := f.connect()
if err != nil {
return err
}
defer func(conn *ftp.ServerConn) {
_ = conn.Quit()
}(conn)
remotePath := f.getRemotePath(directory)
return conn.RemoveDir(remotePath)
}
// Copy 复制文件到新位置
func (f *FTP) Copy(oldFile, newFile string) error {
// FTP 不支持直接复制,需要下载再上传
data, err := f.Get(oldFile)
if err != nil {
return err
}
return f.Put(newFile, string(data))
}
// Delete 删除文件
func (f *FTP) Delete(files ...string) error {
conn, err := f.connect()
if err != nil {
return err
}
defer func(conn *ftp.ServerConn) {
_ = conn.Quit()
}(conn)
for _, file := range files {
remotePath := f.getRemotePath(file)
if err := conn.Delete(remotePath); err != nil {
return err
}
}
return nil
}
// Exists 检查文件是否存在
func (f *FTP) Exists(file string) bool {
conn, err := f.connect()
if err != nil {
return false
}
defer func(conn *ftp.ServerConn) {
_ = conn.Quit()
}(conn)
remotePath := f.getRemotePath(file)
_, err = conn.FileSize(remotePath)
return err == nil
}
// Files 获取目录下的所有文件
func (f *FTP) Files(path string) ([]string, error) {
conn, err := f.connect()
if err != nil {
return nil, err
}
defer func(conn *ftp.ServerConn) {
_ = conn.Quit()
}(conn)
remotePath := f.getRemotePath(path)
entries, err := conn.List(remotePath)
if err != nil {
return nil, err
}
var files []string
for _, entry := range entries {
if entry.Type == ftp.EntryTypeFile {
files = append(files, entry.Name)
}
}
return files, nil
}
// Get 读取文件内容
func (f *FTP) Get(file string) ([]byte, error) {
conn, err := f.connect()
if err != nil {
return nil, err
}
defer func(conn *ftp.ServerConn) {
_ = conn.Quit()
}(conn)
remotePath := f.getRemotePath(file)
resp, err := conn.Retr(remotePath)
if err != nil {
return nil, err
}
defer func(resp *ftp.Response) {
_ = resp.Close()
}(resp)
return io.ReadAll(resp)
}
// LastModified 获取文件最后修改时间
func (f *FTP) LastModified(file string) (time.Time, error) {
conn, err := f.connect()
if err != nil {
return time.Time{}, err
}
defer func(conn *ftp.ServerConn) {
_ = conn.Quit()
}(conn)
remotePath := f.getRemotePath(file)
entries, err := conn.List(filepath.Dir(remotePath))
if err != nil {
return time.Time{}, err
}
fileName := filepath.Base(remotePath)
for _, entry := range entries {
if entry.Name == fileName {
return entry.Time, nil
}
}
return time.Time{}, fmt.Errorf("file not found: %s", file)
}
// MimeType 获取文件的 MIME 类型
func (f *FTP) MimeType(file string) (string, error) {
ext := filepath.Ext(file)
mimeType := mime.TypeByExtension(ext)
if mimeType == "" {
return "application/octet-stream", nil
}
return mimeType, nil
}
// Missing 检查文件是否不存在
func (f *FTP) Missing(file string) bool {
return !f.Exists(file)
}
// Move 移动文件到新位置
func (f *FTP) Move(oldFile, newFile string) error {
conn, err := f.connect()
if err != nil {
return err
}
defer func(conn *ftp.ServerConn) {
_ = conn.Quit()
}(conn)
oldPath := f.getRemotePath(oldFile)
newPath := f.getRemotePath(newFile)
// 确保目标目录存在
newDir := filepath.Dir(newPath)
if newDir != "." {
f.createDirectoryPath(conn, newDir)
}
return conn.Rename(oldPath, newPath)
}
// createDirectoryPath 递归创建目录路径
func (f *FTP) createDirectoryPath(conn *ftp.ServerConn, path string) {
parts := strings.Split(path, "/")
currentPath := ""
for _, part := range parts {
if part == "" {
continue
}
if currentPath == "" {
currentPath = part
} else {
currentPath = currentPath + "/" + part
}
_ = conn.MakeDir(currentPath)
}
}
// Path 获取文件的完整路径
func (f *FTP) Path(file string) string {
return fmt.Sprintf("ftp://%s:%d/%s", f.config.Host, f.config.Port, f.getRemotePath(file))
}
// Put 写入文件内容
func (f *FTP) Put(file, content string) error {
conn, err := f.connect()
if err != nil {
return err
}
defer func(conn *ftp.ServerConn) {
_ = conn.Quit()
}(conn)
remotePath := f.getRemotePath(file)
// 确保目录存在
remoteDir := filepath.Dir(remotePath)
if remoteDir != "." {
f.createDirectoryPath(conn, remoteDir)
}
return conn.Stor(remotePath, bytes.NewReader([]byte(content)))
}
// Size 获取文件大小
func (f *FTP) Size(file string) (int64, error) {
conn, err := f.connect()
if err != nil {
return 0, err
}
defer func(conn *ftp.ServerConn) {
_ = conn.Quit()
}(conn)
remotePath := f.getRemotePath(file)
return conn.FileSize(remotePath)
}