Files
zmkgC/api/v1/common/coryCommon/zip.go
2025-07-07 20:11:59 +08:00

622 lines
16 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 coryCommon
import (
"archive/zip"
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
//解压文件、文件夹复制
// FileZipFunc 对文件进行解压
func FileZipFunc(relativePath string, filenPath string, template string) (fileName string, err error) {
// 打开压缩文件
zipFile, err := zip.OpenReader(relativePath)
if err != nil {
fmt.Println("无法打开压缩文件只支持ZIP:", err)
return
}
var i = 0
var name = ""
//进入的压缩文件,判断压缩文件中的第一层是否包含文件,如果有就在当前随机产生一个文件夹,返回就返回到随机文件夹的位置
randomFolder := ""
// 遍历压缩文件中的文件头信息
for _, f := range zipFile.File {
//path, _ := gbkDecode(f.Name)
fmt.Println("头? ", f.Name)
path, _ := IsGB18030(f.Name)
// 判断是否为顶层文件夹
if !hasSlash(path) {
randomFolder = FileName("/randomFolder")
template = template + randomFolder
filenPath = filenPath + randomFolder
break
}
}
// 遍历压缩文件中的文件
for _, file := range zipFile.File {
fmt.Println("ti ", file.Name)
// 解决文件名编码问题
if i == 0 {
gb18030, _ := IsGB18030(file.Name)
if err != nil {
return "", err
}
name = gb18030
}
file.Name, err = IsGB18030(file.Name)
if err != nil {
return "", err
}
//对所有文件的名称进行空格替换
file.Name = strings.Replace(file.Name, " ", "", -1)
if err != nil {
return file.Name, err
}
// 获取文件的相对路径,根据原本路径来操作还是根据新路径来filenPath
extractedFilePath := ""
extractedFilePathTwo := ""
if filenPath != "" {
extractedFilePath = filepath.Join(FileToFunc(filenPath, 2), "/"+file.Name)
split := strings.Split(filenPath, "/")
extractedFilePathTwo = extractedFilePath + "/" + split[len(split)-1]
} else {
extractedFilePath = filepath.ToSlash(filepath.Join(GetCWD()+template+"/"+".", file.Name))
extractedFilePathTwo = extractedFilePath + "/"
}
//判断文件夹是否存在存在就退出i==0 是为了只判断最外层那一文件夹路径)
_, err := os.Stat(filepath.Dir(extractedFilePathTwo))
if err == nil && i == 0 {
zipFile.Close()
// 删除压缩文件
err = os.Remove(relativePath)
err = errors.New("当前文件夹已经存在,导入无效!")
return "", err
}
i = i + 1
// 检查是否为文件
if !file.FileInfo().IsDir() {
// 创建文件的目录结构
err = os.MkdirAll(filepath.Dir(extractedFilePath), os.ModePerm)
if err != nil {
fmt.Println("无法创建目录:", err)
return "", err
}
// 打开压缩文件中的文件
zippedFile, err := file.Open()
if err != nil {
fmt.Println("无法打开压缩文件中的文件:", err)
return "", err
}
defer zippedFile.Close()
// 创建目标文件
extractedFile, err := os.Create(extractedFilePath)
if err != nil {
fmt.Println("无法创建目标文件:", err)
return "", err
}
defer extractedFile.Close()
// 将压缩文件中的内容复制到目标文件
_, err = io.Copy(extractedFile, zippedFile)
if err != nil {
fmt.Println("无法解压缩文件:", err)
return "", err
}
}
}
zipFile.Close()
// 删除压缩文件
err = os.Remove(relativePath)
if err != nil {
fmt.Println("无法删除压缩文件:", err)
return
}
fileName = strings.Split(name, "/")[0]
if randomFolder != "" {
fileName = ""
} else {
fileName = "/" + fileName
}
if filenPath != "" {
return FileToFunc(filenPath, 2) + fileName, err
} else {
return GetCWD() + template + fileName, err
}
}
// IsGB18030 判断字符串是否是 GB18030 编码
func IsGB18030(name string) (string, error) {
// 创建 GB18030 解码器
decoder := simplifiedchinese.GB18030.NewDecoder()
// 使用 transform 解码数据
_, err := io.ReadAll(transform.NewReader(bytes.NewReader([]byte(name)), decoder))
if err == nil {
return name, nil
} else {
fileName, errName := simplifiedchinese.GB18030.NewDecoder().String(name)
return fileName, errName
}
}
// gbkDecode 解决文件名乱码
func gbkDecode(s string) (string, error) {
gbkDecoder := simplifiedchinese.GBK.NewDecoder()
decodedName, _, err := transform.String(gbkDecoder, s)
return decodedName, err
}
type DocumentListPublicRes struct {
Id int64 `json:"id"`
IdStr string `json:"idStr"`
Pid string `json:"pid"`
Name string `json:"name"`
FilenPath string `json:"filenPath"`
FilenPathCoding string `json:"filenPathCoding"`
Suffix string `json:"suffix"`
Type string `json:"type"`
CreateBy string `json:"createBy"`
CreatedAt *gtime.Time `json:"createdAt"`
IsDuplicate bool `json:"isDuplicate"`
ProjectId int64 `json:"projectId"`
}
// Traversal 遍历文件夹 ctx绝对路径上级文件夹可有可无表名存储位置,项目id,模板1or资料2
//
// one, err := coryCommon.Traversal(ctx, path, req.Pid, dao.DocumentCompletion.Table(), dataFolder, req.ProjectId, "2") //遍历解压后的文件,插入数据
// if err != nil {
// liberr.ErrIsNil(ctx, err)
// return
// }
// _, err = g.DB().Model(dao.DocumentCompletion.Table()).Ctx(ctx).Insert(one)
// liberr.ErrIsNil(ctx, err, "新增失败!")
func Traversal(ctx context.Context, root string, pidstr string, tableName string, templatePath string, projectId int64, num string) (dataOne []*DocumentListPublicRes, err error) {
template := strings.Replace(templatePath, "/resource/public", "/file", 1)
err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// 获取相对路径
relativePath, err := filepath.Rel(root, path)
if err != nil {
return err
}
// 根目录下创建,还是指定文件夹下面创建?
p := ""
if pidstr != "" {
value, _ := g.DB().Model(tableName).Ctx(ctx).Where("id_str", pidstr).Fields("filen_path").Value()
split := strings.Split(root, "/")
p = value.String() + "/" + split[len(split)-1] + "/" + relativePath
} else {
p = template + "/" + relativePath
}
p = strings.ReplaceAll(p, "\\", "/")
// 获取当前项的深度
depth := strings.Count(relativePath, string(filepath.Separator))
// 判断父子关系并打印结果
if depth == 0 && info.IsDir() {
if relativePath == "." {
split := strings.Split(root, "/")
n := split[len(split)-1]
// 根目录下创建,还是指定文件夹下面创建?
p := ""
if pidstr != "" {
value, _ := g.DB().Model(tableName).Ctx(ctx).Where("id_str", pidstr).Fields("filen_path").Value()
p = value.String() + "/" + n
} else {
p = template + "/" + n
}
p = strings.ReplaceAll(p, "\\", "/")
template = template + "/" + n
var dataTwo = new(DocumentListPublicRes)
dataTwo.IdStr = SHA256(p)
if pidstr != "" {
dataTwo.Pid = pidstr
} else {
dataTwo.Pid = "0"
}
dataTwo.Name = n
dataTwo.FilenPath = p
////如果文件夹路径重复,就提示 解压文件夹的时候就已经判断了,这里就不需要了
//err := IsFolderExist(ctx, p)
//if err != nil {
// return err
//}
dataTwo.Type = "2"
dataOne = append(dataOne, dataTwo)
} else {
dir, n := filepath.Split(p)
dir = strings.TrimSuffix(dir, "/")
var dataTwo = new(DocumentListPublicRes)
dataTwo.IdStr = SHA256(p)
dataTwo.Pid = SHA256(dir)
dataTwo.Name = n
dataTwo.FilenPath = p
////如果文件夹路径重复,就提示
//err := IsFolderExist(ctx, p)
//if err != nil {
// return err
//}
dataTwo.Type = "2"
dataOne = append(dataOne, dataTwo)
}
} else if info.IsDir() {
// 子文件夹
dir, n := filepath.Split(p)
dir = strings.TrimSuffix(dir, "/")
var dataTwo = new(DocumentListPublicRes)
dataTwo.IdStr = SHA256(p)
dataTwo.Pid = SHA256(dir)
dataTwo.Name = n
dataTwo.FilenPath = p
dataTwo.Type = "2"
dataOne = append(dataOne, dataTwo)
} else {
dir, n := filepath.Split(p)
dir = strings.TrimSuffix(dir, "/")
var dataTwo = new(DocumentListPublicRes)
dataTwo.Pid = SHA256(dir)
lastDotIndex := strings.LastIndex(n, ".")
if lastDotIndex == -1 || lastDotIndex == 0 {
dataTwo.Name = strings.Split(n, ".")[0]
} else {
dataTwo.Name = n[:lastDotIndex]
}
dataTwo.Suffix = n[lastDotIndex:]
dataTwo.FilenPath = p
dataTwo.Type = "1"
//文件只能是这三种类型,其他类型进不来
s := n[lastDotIndex:]
if num == "1" { //资料有格式限制
if strings.EqualFold(s, ".xls") || strings.EqualFold(s, ".xlsx") || strings.EqualFold(s, ".docx") || strings.EqualFold(s, ".doc") || strings.EqualFold(s, ".pptx") || strings.EqualFold(s, ".ppt") {
dataOne = append(dataOne, dataTwo)
}
} else {
dataOne = append(dataOne, dataTwo)
}
}
return err
})
if err != nil {
//fmt.Println("遍历文件夹时发生错误:", err)
return nil, err
}
// 有项目id表示资料 无表模板
if projectId > 0 {
for i := range dataOne {
dataOne[i].ProjectId = projectId
}
}
return
}
func SHA256(str string) (hx string) {
// 创建 SHA-256 哈希对象
hash := sha256.New()
// 将字符串转换为字节数组并进行哈希计算
hash.Write([]byte(str))
// 计算 SHA-256 哈希值
hashedBytes := hash.Sum(nil)
// 将哈希值转换为十六进制字符串
hashStr := hex.EncodeToString(hashedBytes)
return hashStr
}
// CopyFile 文件复制
func CopyFile(src, dst string) error {
// 打开源文件
inputFile, err := os.Open(src)
if err != nil {
return err
}
defer inputFile.Close()
// 创建目标文件
outputFile, err := os.Create(dst)
if err != nil {
return err
}
defer outputFile.Close()
// 通过 Copy 函数实现拷贝功能
_, err = io.Copy(outputFile, inputFile)
if err != nil {
return err
}
// 确保文件内容被刷新到磁盘上
err = outputFile.Sync()
if err != nil {
return err
}
return nil
}
// CopyDirectory 文件夹复制
func CopyDirectory(src string, dest string) error {
// 检查源文件夹是否存在
_, err := os.Stat(src)
if err != nil {
return err
}
// 检查目标文件夹是否存在,不存在则创建
err = os.MkdirAll(dest, 0755)
if err != nil {
return err
}
// 遍历源文件夹
files, err := os.ReadDir(src)
if err != nil {
return err
}
for _, file := range files {
srcPath := src + "/" + file.Name()
destPath := dest + "/" + file.Name()
// 判断文件类型
if file.IsDir() {
// 如果是文件夹,则递归调用 copyDirectory 函数复制文件夹及其子文件
err = CopyDirectory(srcPath, destPath)
if err != nil {
return err
}
} else {
// 如果是文件,则复制文件到目标文件夹
inputFile, err := os.Open(srcPath)
if err != nil {
return err
}
defer inputFile.Close()
outputFile, err := os.Create(destPath)
if err != nil {
return err
}
defer outputFile.Close()
_, err = io.Copy(outputFile, inputFile)
if err != nil {
return err
}
}
}
return nil
}
// MoveFile 文件移动
func MoveFile(source, destination string) (err error) {
// 执行移动操作
err = os.Rename(source, destination)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("File moved successfully!")
}
return err
}
// MoveFolder 文件夹下面的文件及子文件全部移动到新文件夹下
func MoveFolder(srcPath, destPath string) error {
// 获取源文件夹下的所有文件和子文件夹
fileList := []string{}
err := filepath.Walk(srcPath, func(path string, info os.FileInfo, err error) error {
fileList = append(fileList, path)
return nil
})
if err != nil {
return err
}
// 移动每个文件和子文件夹
for _, file := range fileList {
// 获取相对路径
relPath, err := filepath.Rel(srcPath, file)
if err != nil {
return err
}
// 构建目标路径
destFile := filepath.Join(destPath, relPath)
// 判断是文件还是文件夹
if fileInfo, err := os.Stat(file); err == nil && fileInfo.IsDir() {
// 如果是文件夹,创建目标文件夹
err := os.MkdirAll(destFile, os.ModePerm)
if err != nil {
return err
}
} else {
// 如果是文件,复制文件
err := copyFile(file, destFile)
if err != nil {
return err
}
}
}
// 移动完成后删除源文件夹
return os.RemoveAll(srcPath)
}
func copyFile(src, dest string) error {
srcFile, err := os.Open(src)
if err != nil {
return err
}
defer srcFile.Close()
destFile, err := os.Create(dest)
if err != nil {
return err
}
defer destFile.Close()
_, err = io.Copy(destFile, srcFile)
return err
}
// FolderToZip 将给定文件夹压缩成压缩包存储到另外一个路径(参数:源数据、目标路径)
func FolderToZip(folderToZip, zipFile string) (err error) {
// 创建一个新的压缩包文件
newZipFile, err := os.Create(zipFile)
if err != nil {
return err
}
defer newZipFile.Close()
// 创建一个 zip.Writer 来向压缩包中写入内容
zipWriter := zip.NewWriter(newZipFile)
defer zipWriter.Close()
// 递归遍历文件夹并将其中的文件和目录添加到压缩包中
err = filepath.Walk(folderToZip, func(filePath string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// 获取当前文件的相对路径
relativePath, err := filepath.Rel(folderToZip, filePath)
if err != nil {
return err
}
// 如果是目录,则创建一个目录项
if info.IsDir() {
_, err = zipWriter.Create(relativePath + "/")
if err != nil {
return err
}
return nil
}
// 如果是文件,则创建一个文件项并写入文件内容
fileData, err := ioutil.ReadFile(filePath)
if err != nil {
return err
}
file, err := zipWriter.Create(relativePath)
if err != nil {
return err
}
_, err = file.Write(fileData)
if err != nil {
return err
}
return nil
})
return
}
// zipDirFiles 压缩文件
func ZipDirFiles(src_dir string, zip_file_name string) error {
// 检查并创建目标目录
err := os.MkdirAll(filepath.Dir(zip_file_name), os.ModePerm)
if err != nil {
return err
}
// 删除空的zip而不是直接使用os.RemoveAll以提高安全性
err = os.Remove(zip_file_name)
if err != nil && !os.IsNotExist(err) {
return err
}
// 创建新的zip文件
zipfile, err := os.Create(zip_file_name)
if err != nil {
return err
}
defer zipfile.Close()
// 初始化zip写入器
archive := zip.NewWriter(zipfile)
defer archive.Close()
// 遍历源目录下的文件和子目录
return filepath.Walk(src_dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// 计算相对于源目录的路径
relPath, _ := filepath.Rel(src_dir, path)
if path == src_dir {
// 如果是源目录本身,跳过
return nil
}
// 创建zip文件头
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
header.Name = filepath.ToSlash(relPath)
// 标记目录
if info.IsDir() {
header.Name += "/"
return nil
}
// 处理文件
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
writer, err := archive.CreateHeader(header)
if err != nil {
return err
}
_, err = io.Copy(writer, file)
return err
})
}
// 压缩后删除源文件
func DeleteFolderToZip(srcpath, destpathzip string) (err error) {
//srcpath := filepath.ToSlash(`D:\GiteeProject\gsproject\zmkg-back\resource\public\temporary\del`)
//destpathzip := filepath.ToSlash(`D:\GiteeProject\gsproject\zmkg-back\resource\public\temporary\yy.zip`)
err = ZipDirFiles(srcpath, destpathzip) // 文件压缩 压缩文件目录 和压缩文件zip
if err != nil {
return err
}
// 删除原文件
if err := os.RemoveAll(srcpath); err != nil {
if !os.IsNotExist(err) {
fmt.Printf("Error removing existing file %s: %v\n", srcpath, err)
return err
}
}
return err
}
// 判断字符串中是否包含斜杠
func hasSlash(s string) bool {
for _, c := range s {
if c == '/' || c == '\\' {
return true
}
}
return false
}