Files
tyapi-server/internal/application/product/ui_component_file_service.go
2025-12-19 17:05:09 +08:00

342 lines
9.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}