342 lines
9.1 KiB
Go
342 lines
9.1 KiB
Go
package product
|
||
|
||
import (
|
||
"archive/zip"
|
||
"context"
|
||
"fmt"
|
||
"io"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"time"
|
||
|
||
"go.uber.org/zap"
|
||
)
|
||
|
||
// UIComponentFileService UI组件文件服务接口
|
||
type UIComponentFileService interface {
|
||
// 上传并解压UI组件文件
|
||
UploadAndExtract(ctx context.Context, componentID, componentCode string, file io.Reader, filename string) error
|
||
|
||
// 批量上传UI组件文件(支持文件夹结构)
|
||
UploadMultipleFiles(ctx context.Context, componentID, componentCode string, files []io.Reader, filenames []string, paths []string) error
|
||
|
||
// 根据组件编码创建文件夹
|
||
CreateFolderByCode(componentCode string) (string, error)
|
||
|
||
// 删除组件文件夹
|
||
DeleteFolder(folderPath string) error
|
||
|
||
// 检查文件夹是否存在
|
||
FolderExists(folderPath string) bool
|
||
|
||
// 获取文件夹内容
|
||
GetFolderContent(folderPath string) ([]FileInfo, error)
|
||
}
|
||
|
||
// FileInfo 文件信息
|
||
type FileInfo struct {
|
||
Name string `json:"name"`
|
||
Path string `json:"path"`
|
||
Size int64 `json:"size"`
|
||
Type string `json:"type"` // "file" or "folder"
|
||
Modified time.Time `json:"modified"`
|
||
}
|
||
|
||
// UIComponentFileServiceImpl UI组件文件服务实现
|
||
type UIComponentFileServiceImpl struct {
|
||
basePath string
|
||
logger *zap.Logger
|
||
}
|
||
|
||
// NewUIComponentFileService 创建UI组件文件服务
|
||
func NewUIComponentFileService(basePath string, logger *zap.Logger) UIComponentFileService {
|
||
// 确保基础路径存在
|
||
if err := os.MkdirAll(basePath, 0755); err != nil {
|
||
logger.Error("创建基础存储目录失败", zap.Error(err), zap.String("path", basePath))
|
||
}
|
||
|
||
return &UIComponentFileServiceImpl{
|
||
basePath: basePath,
|
||
logger: logger,
|
||
}
|
||
}
|
||
|
||
// UploadAndExtract 上传并解压UI组件文件
|
||
func (s *UIComponentFileServiceImpl) UploadAndExtract(ctx context.Context, componentID, componentCode string, file io.Reader, filename string) error {
|
||
// 直接使用基础路径作为文件夹路径,不再创建组件编码子文件夹
|
||
folderPath := s.basePath
|
||
|
||
// 确保基础目录存在
|
||
if err := os.MkdirAll(folderPath, 0755); err != nil {
|
||
return fmt.Errorf("创建基础目录失败: %w", err)
|
||
}
|
||
|
||
// 保存上传的文件
|
||
filePath := filepath.Join(folderPath, filename)
|
||
savedFile, err := os.Create(filePath)
|
||
if err != nil {
|
||
return fmt.Errorf("创建文件失败: %w", err)
|
||
}
|
||
defer savedFile.Close()
|
||
|
||
// 复制文件内容
|
||
if _, err := io.Copy(savedFile, file); err != nil {
|
||
// 删除部分写入的文件
|
||
_ = os.Remove(filePath)
|
||
return fmt.Errorf("保存文件失败: %w", err)
|
||
}
|
||
|
||
// 仅对ZIP文件执行解压逻辑
|
||
if strings.HasSuffix(strings.ToLower(filename), ".zip") {
|
||
// 解压文件到基础目录
|
||
if err := s.extractZipFile(filePath, folderPath); err != nil {
|
||
// 删除ZIP文件
|
||
_ = os.Remove(filePath)
|
||
return fmt.Errorf("解压文件失败: %w", err)
|
||
}
|
||
|
||
// 删除ZIP文件
|
||
_ = os.Remove(filePath)
|
||
|
||
s.logger.Info("UI组件文件上传并解压成功",
|
||
zap.String("componentID", componentID),
|
||
zap.String("componentCode", componentCode),
|
||
zap.String("folderPath", folderPath))
|
||
} else {
|
||
s.logger.Info("UI组件文件上传成功(未解压)",
|
||
zap.String("componentID", componentID),
|
||
zap.String("componentCode", componentCode),
|
||
zap.String("filePath", filePath))
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// UploadMultipleFiles 批量上传UI组件文件(支持文件夹结构)
|
||
func (s *UIComponentFileServiceImpl) UploadMultipleFiles(ctx context.Context, componentID, componentCode string, files []io.Reader, filenames []string, paths []string) error {
|
||
// 直接使用基础路径作为文件夹路径,不再创建组件编码子文件夹
|
||
folderPath := s.basePath
|
||
|
||
// 确保基础目录存在
|
||
if err := os.MkdirAll(folderPath, 0755); err != nil {
|
||
return fmt.Errorf("创建基础目录失败: %w", err)
|
||
}
|
||
|
||
// 处理每个文件
|
||
for i, file := range files {
|
||
filename := filenames[i]
|
||
path := paths[i]
|
||
|
||
// 如果有路径信息,创建对应的子文件夹
|
||
if path != "" && path != filename {
|
||
// 获取文件所在目录
|
||
dir := filepath.Dir(path)
|
||
if dir != "." {
|
||
// 创建子文件夹
|
||
subDirPath := filepath.Join(folderPath, dir)
|
||
if err := os.MkdirAll(subDirPath, 0755); err != nil {
|
||
return fmt.Errorf("创建子文件夹失败: %w", err)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 确定文件保存路径
|
||
var filePath string
|
||
if path != "" && path != filename {
|
||
// 有路径信息,使用完整路径
|
||
filePath = filepath.Join(folderPath, path)
|
||
} else {
|
||
// 没有路径信息,直接保存在根目录
|
||
filePath = filepath.Join(folderPath, filename)
|
||
}
|
||
|
||
// 保存上传的文件
|
||
savedFile, err := os.Create(filePath)
|
||
if err != nil {
|
||
return fmt.Errorf("创建文件失败: %w", err)
|
||
}
|
||
defer savedFile.Close()
|
||
|
||
// 复制文件内容
|
||
if _, err := io.Copy(savedFile, file); err != nil {
|
||
// 删除部分写入的文件
|
||
_ = os.Remove(filePath)
|
||
return fmt.Errorf("保存文件失败: %w", err)
|
||
}
|
||
|
||
// 对ZIP文件执行解压逻辑
|
||
if strings.HasSuffix(strings.ToLower(filename), ".zip") {
|
||
// 确定解压目录
|
||
var extractDir string
|
||
if path != "" && path != filename {
|
||
// 有路径信息,解压到对应目录
|
||
dir := filepath.Dir(path)
|
||
if dir != "." {
|
||
extractDir = filepath.Join(folderPath, dir)
|
||
} else {
|
||
extractDir = folderPath
|
||
}
|
||
} else {
|
||
// 没有路径信息,解压到根目录
|
||
extractDir = folderPath
|
||
}
|
||
|
||
// 解压文件
|
||
if err := s.extractZipFile(filePath, extractDir); err != nil {
|
||
// 删除ZIP文件
|
||
_ = os.Remove(filePath)
|
||
return fmt.Errorf("解压文件失败: %w", err)
|
||
}
|
||
|
||
// 删除ZIP文件
|
||
_ = os.Remove(filePath)
|
||
|
||
s.logger.Info("UI组件文件上传并解压成功",
|
||
zap.String("componentID", componentID),
|
||
zap.String("componentCode", componentCode),
|
||
zap.String("filePath", filePath),
|
||
zap.String("extractDir", extractDir))
|
||
} else {
|
||
s.logger.Info("UI组件文件上传成功(未解压)",
|
||
zap.String("componentID", componentID),
|
||
zap.String("componentCode", componentCode),
|
||
zap.String("filePath", filePath))
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// CreateFolderByCode 根据组件编码创建文件夹
|
||
func (s *UIComponentFileServiceImpl) CreateFolderByCode(componentCode string) (string, error) {
|
||
folderPath := filepath.Join(s.basePath, componentCode)
|
||
|
||
// 创建文件夹(如果不存在)
|
||
if err := os.MkdirAll(folderPath, 0755); err != nil {
|
||
return "", fmt.Errorf("创建文件夹失败: %w", err)
|
||
}
|
||
|
||
return folderPath, nil
|
||
}
|
||
|
||
// DeleteFolder 删除组件文件夹
|
||
func (s *UIComponentFileServiceImpl) DeleteFolder(folderPath string) error {
|
||
if !s.FolderExists(folderPath) {
|
||
return nil // 文件夹不存在,不视为错误
|
||
}
|
||
|
||
if err := os.RemoveAll(folderPath); err != nil {
|
||
return fmt.Errorf("删除文件夹失败: %w", err)
|
||
}
|
||
|
||
s.logger.Info("删除组件文件夹成功", zap.String("folderPath", folderPath))
|
||
return nil
|
||
}
|
||
|
||
// FolderExists 检查文件夹是否存在
|
||
func (s *UIComponentFileServiceImpl) FolderExists(folderPath string) bool {
|
||
info, err := os.Stat(folderPath)
|
||
if err != nil {
|
||
return false
|
||
}
|
||
return info.IsDir()
|
||
}
|
||
|
||
// GetFolderContent 获取文件夹内容
|
||
func (s *UIComponentFileServiceImpl) GetFolderContent(folderPath string) ([]FileInfo, error) {
|
||
var files []FileInfo
|
||
|
||
err := filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error {
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 跳过根目录
|
||
if path == folderPath {
|
||
return nil
|
||
}
|
||
|
||
// 获取相对路径
|
||
relPath, err := filepath.Rel(folderPath, path)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
fileType := "file"
|
||
if info.IsDir() {
|
||
fileType = "folder"
|
||
}
|
||
|
||
files = append(files, FileInfo{
|
||
Name: info.Name(),
|
||
Path: relPath,
|
||
Size: info.Size(),
|
||
Type: fileType,
|
||
Modified: info.ModTime(),
|
||
})
|
||
|
||
return nil
|
||
})
|
||
|
||
if err != nil {
|
||
return nil, fmt.Errorf("扫描文件夹失败: %w", err)
|
||
}
|
||
|
||
return files, nil
|
||
}
|
||
|
||
// extractZipFile 解压ZIP文件
|
||
func (s *UIComponentFileServiceImpl) extractZipFile(zipPath, destPath string) error {
|
||
reader, err := zip.OpenReader(zipPath)
|
||
if err != nil {
|
||
return fmt.Errorf("打开ZIP文件失败: %w", err)
|
||
}
|
||
defer reader.Close()
|
||
|
||
for _, file := range reader.File {
|
||
path := filepath.Join(destPath, file.Name)
|
||
|
||
// 防止路径遍历攻击
|
||
if !strings.HasPrefix(filepath.Clean(path), filepath.Clean(destPath)+string(os.PathSeparator)) {
|
||
return fmt.Errorf("无效的文件路径: %s", file.Name)
|
||
}
|
||
|
||
if file.FileInfo().IsDir() {
|
||
// 创建目录
|
||
if err := os.MkdirAll(path, file.Mode()); err != nil {
|
||
return fmt.Errorf("创建目录失败: %w", err)
|
||
}
|
||
continue
|
||
}
|
||
|
||
// 创建文件
|
||
fileReader, err := file.Open()
|
||
if err != nil {
|
||
return fmt.Errorf("打开ZIP内文件失败: %w", err)
|
||
}
|
||
|
||
// 确保父目录存在
|
||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||
fileReader.Close()
|
||
return fmt.Errorf("创建父目录失败: %w", err)
|
||
}
|
||
|
||
destFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
|
||
if err != nil {
|
||
fileReader.Close()
|
||
return fmt.Errorf("创建目标文件失败: %w", err)
|
||
}
|
||
|
||
_, err = io.Copy(destFile, fileReader)
|
||
fileReader.Close()
|
||
destFile.Close()
|
||
|
||
if err != nil {
|
||
return fmt.Errorf("写入文件失败: %w", err)
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|