mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 05:31:44 +08:00
366 lines
6.9 KiB
Go
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)
|
|
}
|