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
|
|||
|
|
}
|