Browse Source

文件升级管理端

master
453530270@qq.com 2 years ago
parent
commit
4240998ddf
  1. 2
      fssc/README.md
  2. 73
      fssc/config/config.go
  3. 11
      fssc/go.mod
  4. 8
      fssc/go.sum
  5. 104
      fssc/internal/discovery/discovery.go
  6. 395
      fssc/internal/handler/handler.go
  7. 88
      fssc/internal/transfer/receive.go
  8. 118
      fssc/internal/transfer/send.go
  9. 233
      fssc/internal/util/util.go
  10. 83
      fssc/main.go
  11. 17
      xgfs/internal/handler/handler.go
  12. 9
      xgfs/web/download.tmpl

2
fssc/README.md

@ -0,0 +1,2 @@
## 文件升级的管理端
管理待升级的文件,可以勾选和其他的操作

73
fssc/config/config.go

@ -0,0 +1,73 @@
package config
import (
"fmt"
"net"
"os"
)
type Config struct {
DeviceName string
Port string
LocalIP string
MulticastAddress string
WildcardAddress string
FilePath string
Version string
}
var G Config
func (c *Config) SetConf(port string) error {
Hostname, err := os.Hostname()
if err != nil {
Hostname = "unknow device"
}
// 设备名称
c.DeviceName = Hostname
c.Port = port
c.LocalIP, err = getLocalIP()
if err != nil {
return err
}
// 多播地址
c.MulticastAddress = fmt.Sprintf("224.0.0.169:%s", port)
// 广播地址
c.WildcardAddress = fmt.Sprintf("0.0.0.0:%s", port)
c.FilePath = "./"
c.Version = "0.3.0"
return nil
}
// 本地ip
func getLocalIP() (string, error) {
addrs, err := net.InterfaceAddrs()
if err != nil {
return "", err
}
var ips []string
for _, address := range addrs {
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
ips = append(ips, ipnet.IP.String())
}
}
}
if len(ips) == 0 {
return "", fmt.Errorf("get local ip failed")
} else if len(ips) == 1 {
return ips[0], nil
} else {
// Select the one connected to the network
// when there are multiple network interfaces
// Is there a better way?
c, err := net.Dial("udp", "8.8.8.8:80")
if err != nil {
return ips[0], nil
}
defer c.Close()
return c.LocalAddr().(*net.UDPAddr).IP.String(), nil
}
}

11
fssc/go.mod

@ -0,0 +1,11 @@
module fssc
go 1.22.1
require github.com/urfave/cli/v2 v2.27.2
require (
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
)

8
fssc/go.sum

@ -0,0 +1,8 @@
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=

104
fssc/internal/discovery/discovery.go

@ -0,0 +1,104 @@
package discovery
import (
"fmt"
"net"
"strings"
"github.com/chyok/st/config"
"github.com/chyok/st/internal/transfer"
)
const separator = "|"
type Role string
const (
Sender Role = "sender"
Receiver Role = "receiver"
)
func Listen(role Role, filePath string) {
addr, err := net.ResolveUDPAddr("udp", config.G.MulticastAddress)
if err != nil {
fmt.Printf("Failed to resolve %s: %v\n", config.G.MulticastAddress, err)
return
}
conn, err := net.ListenMulticastUDP("udp", nil, addr)
if err != nil {
fmt.Printf("Failed to listen on %s: %v\n", config.G.MulticastAddress, err)
return
}
defer conn.Close()
buf := make([]byte, 1024)
for {
n, src, err := conn.ReadFromUDP(buf)
remoteAddr := src.IP.String() + ":" + config.G.Port
if err != nil {
fmt.Printf("Failed to read from %s: %v\n", remoteAddr, err)
continue
}
message := string(buf[:n])
parts := strings.Split(message, separator)
if len(parts) != 2 {
fmt.Printf("Received malformed message from %s: %s\n", remoteAddr, message)
continue
}
deviceName := parts[0]
remoteRole := Role(parts[1])
switch remoteRole {
case Sender:
if role == Sender {
// 发现发送端
fmt.Printf("Discovered Sender: %s (%s)\n", deviceName, remoteAddr)
go func() {
err := transfer.ReceiveFiles(remoteAddr)
if err != nil {
fmt.Printf("Receive file from %s error: %s\n", remoteAddr, err)
}
}()
}
case Receiver:
if role == Receiver {
// 发现接收端
fmt.Printf("Discovered Receiver: %s (%s)\n", deviceName, remoteAddr)
go func() {
err := transfer.SendFiles(filePath, fmt.Sprintf("http://%s", remoteAddr))
if err != nil {
fmt.Printf("Send file to %s error: %s\n", remoteAddr, err)
}
}()
}
}
}
}
func Send(role Role) {
addr, err := net.ResolveUDPAddr("udp", config.G.MulticastAddress)
if err != nil {
fmt.Printf("Failed to resolve %s: %v\n", config.G.MulticastAddress, err)
return
}
localAddr, err := net.ResolveUDPAddr("udp", config.G.LocalIP+":0")
if err != nil {
fmt.Printf("Failed to resolve %s: %v\n", config.G.MulticastAddress, err)
return
}
conn, err := net.DialUDP("udp", localAddr, addr)
if err != nil {
fmt.Printf("Failed to dial %s: %v\n", config.G.MulticastAddress, err)
return
}
defer conn.Close()
message := fmt.Sprintf("%s%s%s", config.G.DeviceName, separator, role)
conn.Write([]byte(message))
}

395
fssc/internal/handler/handler.go

@ -0,0 +1,395 @@
package handler
import (
"encoding/json"
"fmt"
"html/template"
"io"
"mime"
"net"
"path"
"strings"
"time"
"net/http"
"net/rpc"
"os"
"path/filepath"
"github.com/chyok/st/config"
"github.com/chyok/st/internal/transfer"
"github.com/chyok/st/internal/util"
"github.com/chyok/st/web"
)
// rpc功能 压缩包的结构体
// 文件路径 文件名
type Args struct {
Zpfile, Fname string
}
// 获取文件大小的接口
type Size interface {
Size() int64
}
// 获取文件信息的接口
type Stat interface {
Stat() (os.FileInfo, error)
}
// 显示状态
func ReceiveStatus(w http.ResponseWriter, r *http.Request) {
switch r.Method {
// 显示当前传送文件的大小
case http.MethodPost:
file, _, err := r.FormFile("file")
if err != nil {
http.Error(w, err.Error(), 500)
return
}
if statInterface, ok := file.(Stat); ok {
fileInfo, _ := statInterface.Stat()
fmt.Fprintf(w, "上传文件的大小为: %d", fileInfo.Size())
}
if sizeInterface, ok := file.(Size); ok {
fmt.Fprintf(w, "上传文件的大小为: %d", sizeInterface.Size())
}
return
}
// fmt.Fprintf(w, r.Method)
}
// 接收 上传
func ReceiveHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
// serve upload page for receive
// tmpl, err := template.New("index").Parse(web.UploadPage)
// if err != nil {
// http.Error(w, err.Error(), http.StatusInternalServerError)
// return
// }
// // 获取当前设备名称
// err = tmpl.Execute(w, config.G.DeviceName)
// if err != nil {
// http.Error(w, err.Error(), http.StatusInternalServerError)
// }
//
fmt.Fprintf(w, "%s:接收文件中...", r.Host)
case http.MethodPost:
// receive file and save
file, header, err := r.FormFile("file")
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
defer file.Close()
_, params, err := mime.ParseMediaType(header.Header.Get("Content-Disposition"))
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
filename := filepath.FromSlash(params["filename"])
fmt.Printf("Downloading [%s]...\n", filename)
dirPath := filepath.Dir(filename)
err = os.MkdirAll(dirPath, os.ModePerm)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
out, err := os.Create(filename)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer out.Close()
_, err = io.Copy(out, file)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 如果收到的是zip文件,自动给解压缩
suf := strings.Split(filename, ".")
if suf[1] == "zip" {
go util.DecompressZip(filename)
}
fmt.Printf("[√] Download [%s] Success.\n", filename)
//
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
// 输出接收结果
fmt.Fprintf(w, "接收成功,并已经完成解压缩")
}
// 文件服务
func FileServerHandler(w http.ResponseWriter, r *http.Request) {
currentPath := config.G.FilePath
fileInfo, err := os.Stat(currentPath)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
basePath := filepath.Base(currentPath)
if fileInfo.IsDir() {
path := r.URL.Path[len("/download/"+basePath):]
fullPath := filepath.Join(currentPath, path)
http.ServeFile(w, r, fullPath)
} else {
http.ServeFile(w, r, currentPath)
}
}
// udp 方式发送zip文件
func SendZip(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
// 选择文件,并生成zip包
// 文件
zipfarr := r.Form["zipfiles"]
// 服务器ip地址
serip := r.Form["serverip"]
if serip[0] == "" {
http.Error(w, "remote server ip is blank!", http.StatusInternalServerError)
return
}
// 选中的路径,可以为空
wtculpath := r.Form["curpath"]
// 实际路径
realFilePath := filepath.Join(config.G.FilePath, wtculpath[0])
// zip 文件名
zpFileName := "BIU_" + time.Now().Format("20060102_150405") + ".zip"
// 创建zip 异步?
taskId := make(chan string)
go func() {
util.CompressToZip(zpFileName, realFilePath, zipfarr)
taskId <- "arcok"
// fmt.Fprintln(w, "create archive:", err)
}()
// go util.CompressToZip(zpFileName, realFilePath, zipfarr)
fmt.Println("archive is createding...")
// 当前运行的目录
// ZIP 文件的实际路径
ziprl := path.Join("./files/", zpFileName)
// zip 创建成功后
rest := <-taskId
// 有压缩包 才可以操作
if strings.EqualFold(strings.ToLower(rest), "arcok") {
fmt.Println("archive is sending...")
// 创建udp 渠道发送数据
// 1、获取udp addr
remoteAddr, err := net.ResolveUDPAddr("udp", serip[0]+":9099")
if err != nil {
fmt.Printf("Failed to resolve %s: %v\n", serip[0], err)
return
}
// 2、 监听端口
conn, err := net.DialUDP("udp", nil, remoteAddr)
if err != nil {
fmt.Printf("Failed to dial %s: %v\n", serip[0], err)
return
}
defer conn.Close()
// 3、在端口发送数据
message := fmt.Sprintf("%s%s%s", config.G.DeviceName, "|", "sender")
// 向链接通道发送数据 数据包头
conn.Write([]byte(message))
// 发送文件
go func() {
err := transfer.SendFiles(ziprl, fmt.Sprintf("http://%s", remoteAddr))
if err != nil {
fmt.Printf("Send file to %s error: %s\n", remoteAddr, err)
}
}()
// 页面上显示
fmt.Fprintf(w, "File:%s,been sent successfully.", zpFileName)
} else {
fmt.Println("archive is not exist!!!")
}
}
// 下载zip
func Downzip(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
// 选择文件,并生成zip包
// 文件
zipfarr := r.Form["zipfiles"]
// 服务器ip地址
serip := r.Form["serverip"]
if serip[0] == "" {
http.Error(w, "remote server ip is blank!", http.StatusInternalServerError)
return
}
// fmt.Println(r.Form)
// 选中的路径
wtculpath := r.Form["curpath"]
// 实际路径
realFilePath := filepath.Join(config.G.FilePath, wtculpath[0])
// zip 文件名
zpFileName := "BIU_" + time.Now().Format("20060102_150405") + ".zip"
// 创建zip
err := util.CompressToZip(zpFileName, realFilePath, zipfarr)
if err != nil {
fmt.Println("create zip error", err)
}
// 停止下,检查zip是否创建成功
time.Sleep(1200)
_, err = os.Stat(realFilePath + "/" + zpFileName)
if err != nil {
// 调用httpd rpc
fmt.Println("RPC Server IP:", serip[0])
rpcServer := serip[0] + ":9080"
client, err := rpc.DialHTTP("tcp", rpcServer)
if err != nil {
fmt.Println("Http rpc error :", err)
}
// 组装url 静态文件
// url := "http://" + config.G.LocalIP + ":" + config.G.Port + "/files/" + zpFileName
// 动态
url := "http://" + config.G.LocalIP + ":" + config.G.Port + "/files?zp=" + zpFileName
args := Args{url, zpFileName}
var reply int
err = client.Call("Rbup.DecompressZip", args, &reply)
if err != nil {
fmt.Println("rpc call error :", err)
// 输出执行结果
fmt.Fprintln(w, "rpc call error:", err)
} else {
// 输出下载链接
fmt.Fprintf(w, "<a href='%s'>%s</a><br/>\n", url, url)
fmt.Fprintf(w, "RPC execute result :%d<br/>\n", reply)
}
} else {
fmt.Fprintf(w, "archive is created faild")
}
}
// 文件下载
func Dfiles(w http.ResponseWriter, r *http.Request) {
// url中获取参数
query := r.URL.Query()
zpfile := query.Get("zp")
// 实际路径
zpFileName := filepath.Join(config.G.FilePath, "/files/", zpfile)
// fileInfo, err := os.Stat(zpFileName)
// if err != nil {
// http.Error(w, err.Error(), http.StatusInternalServerError)
// return
// }
// fmt.Println(fileInfo)
http.ServeFile(w, r, zpFileName)
}
// 发送拦截
func SendHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
// serve download page for send
realFilePath := filepath.Join(config.G.FilePath, r.URL.Path[1:])
downloadPath := filepath.Join(filepath.Base(config.G.FilePath), r.URL.Path[1:])
fileInfo, err := os.Stat(realFilePath)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := struct {
DeviceName string
//Rundir string
IsDir bool
FileName string
DownloadPath string
UrlPath string
Files []os.DirEntry
}{
DeviceName: config.G.DeviceName,
//Rundir: config.G.FilePath,
DownloadPath: downloadPath,
UrlPath: strings.TrimSuffix(r.URL.Path, "/"),
}
if fileInfo.IsDir() {
data.IsDir = true
// 遍历目录
files, err := os.ReadDir(realFilePath)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data.Files = files
} else {
data.FileName = filepath.Base(realFilePath)
}
//
fmt.Print("fdata:%v", data)
// 文件列表模板
tmpl, err := template.New("download").Parse(web.DownloadPage)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = tmpl.Execute(w, data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
case http.MethodPost:
// return file or folder information in JSON format for convenient send to the recipient
currentPath := config.G.FilePath
fileInfo, err := os.Stat(currentPath)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var pathInfo struct {
Type string `json:"type"`
Paths []string `json:"paths"`
}
if fileInfo.IsDir() {
files, err := util.GetDirFilePaths(currentPath, true)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
pathInfo.Paths = files
pathInfo.Type = "dir"
} else {
pathInfo.Paths = []string{filepath.Base(currentPath)}
pathInfo.Type = "file"
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(pathInfo)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}

88
fssc/internal/transfer/receive.go

@ -0,0 +1,88 @@
package transfer
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
)
// 接收文件
func ReceiveFiles(remoteAddr string) error {
resp, err := http.Post(fmt.Sprintf("http://%s/", remoteAddr), "", nil)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to get file info: %s", resp.Status)
}
var pathInfo struct {
Type string `json:"type"`
Paths []string `json:"paths"`
}
if err := json.NewDecoder(resp.Body).Decode(&pathInfo); err != nil {
return err
}
if pathInfo.Type == "dir" {
fmt.Printf("Found directory with %d paths:\n", len(pathInfo.Paths))
for _, path := range pathInfo.Paths {
fmt.Printf("- %s\n", path)
}
fmt.Print("Do you want to download the entire directory? [y/n] ")
var confirm string
if _, err := fmt.Scanln(&confirm); err != nil || (confirm != "y" && confirm != "Y") {
return nil
}
for _, path := range pathInfo.Paths {
if err := downloadFile(remoteAddr, path); err != nil {
return err
}
}
} else {
if len(pathInfo.Paths) != 1 {
return fmt.Errorf("unexpected number of paths: %d", len(pathInfo.Paths))
}
if err := downloadFile(remoteAddr, pathInfo.Paths[0]); err != nil {
return err
}
}
return nil
}
// 下载文件
func downloadFile(remoteAddr, path string) error {
path = filepath.ToSlash(path)
resp, err := http.Get(fmt.Sprintf("http://%s/download/%s", remoteAddr, path))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to download file: %s", resp.Status)
}
fmt.Printf("Downloading [%s]...\n", path)
out, err := os.Create(path)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
if err != nil {
return err
}
fmt.Printf("[✅] Download [%s] Success.\n", path)
return nil
}

118
fssc/internal/transfer/send.go

@ -0,0 +1,118 @@
package transfer
import (
"bytes"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"github.com/chyok/st/internal/util"
"github.com/schollz/progressbar/v3"
)
// 发送文件
func SendFiles(filePath string, url string) error {
filePath = filepath.ToSlash(filePath)
fileInfo, err := os.Stat(filePath)
if err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("file [%s] not exist", filePath)
}
return fmt.Errorf("file [%s] error: %w", filePath, err)
}
if fileInfo.IsDir() {
return postDirectory(filePath, url)
}
return postFile(filePath, path.Base(filePath), url)
}
// 传送文件夹
func postDirectory(dirPath string, url string) error {
files, err := util.GetDirFilePaths(dirPath, false)
if err != nil {
return err
}
fmt.Println("\nAll files in folder:")
for _, file := range files {
fmt.Println(file)
}
var confirm string
fmt.Print("\nTransfer all files? [Y/N] ")
fmt.Scanln(&confirm)
if strings.ToLower(confirm) != "y" {
fmt.Print("\nCancel send all files ")
return nil
}
for _, file := range files {
fileName, _ := filepath.Rel(dirPath, file)
fileName = filepath.Join(filepath.Base(dirPath), fileName)
err := postFile(file, fileName, url)
if err != nil {
return err
}
}
fmt.Printf("Send folder %s success.\n", dirPath)
return nil
}
// 传送文件
func postFile(filePath string, filename string, url string) error {
payload := &bytes.Buffer{}
writer := multipart.NewWriter(payload)
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close()
part, err := writer.CreateFormFile("file", filepath.ToSlash(filename))
if err != nil {
return err
}
fileInfo, _ := file.Stat()
bar := progressbar.DefaultBytes(
fileInfo.Size(),
fmt.Sprintf("Uploading [%s]", filename),
)
_, err = io.Copy(io.MultiWriter(part, bar), file)
if err != nil {
return err
}
err = writer.Close()
if err != nil {
return err
}
req, err := http.NewRequest(http.MethodPost, url, payload)
if err != nil {
return err
}
req.Header.Set("Content-Type", writer.FormDataContentType())
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("upload failed with status code: %d", resp.StatusCode)
}
return nil
}

233
fssc/internal/util/util.go

@ -0,0 +1,233 @@
package util
import (
"archive/zip"
"fmt"
"io"
"io/fs"
"os"
"path"
"path/filepath"
"strings"
)
// 遍历目录下的文件
func GetDirFilePaths(dirPath string, relativeOnly bool) ([]string, error) {
var filePaths []string
err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
if relativeOnly {
fileName := filepath.Base(path)
relativePath := filepath.ToSlash(filepath.Join(filepath.Base(dirPath), fileName))
filePaths = append(filePaths, relativePath)
} else {
filePaths = append(filePaths, filepath.ToSlash(path))
}
}
return nil
})
if err != nil {
return nil, err
}
return filePaths, nil
}
// dest:目的地址以及压缩文件名 eg:/data/logZip/2022-12-12-log.zip
// paths:需要压缩的所有文件所组成的集合
func CompressToZip(dest string, currentPath string, paths []string) error {
// fmt.Println("real path", currentPath)
// 打开files 目录
filesPath := ""
if dir, err := os.Getwd(); err == nil {
filesPath = dir + "/files/"
}
if err := os.MkdirAll(filesPath, 0755); err != nil {
fmt.Println(err.Error())
}
// ToSlash 过滤windows的斜杠引起的bug
zfile, err := os.Create(path.Join("./files", "/", dest))
if err != nil {
return err
}
defer zfile.Close()
zipWriter := zip.NewWriter(zfile)
defer zipWriter.Close()
// 遍历带压缩的目录信息
for _, src := range paths {
// 替换文件名,并且去除前后 "\" 或 "/"
// path = strings.Trim(path, string(filepath.Separator))
// 从配置中读取到源目录
src = path.Join(currentPath, "/", src)
// 删除尾随路径(如果它是目录)
src := strings.TrimSuffix(src, string(os.PathSeparator))
err = filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// 创建本地文件头
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
// 将压缩方法设置为deflate
header.Method = zip.Deflate
// 在zip存档中设置文件的相对路径
header.Name, err = filepath.Rel(filepath.Dir(src), path)
if err != nil {
return err
}
// 目录需要拼上一个 "/" ,否则会出现一个和目录一样的文件在压缩包中
if info.IsDir() {
// header.Name += string(os.PathSeparator)
header.Name += "/"
} else {
// 替换一下分隔符,zip不支持 "\\"
header.Name = strings.ReplaceAll(header.Name, "\\", "/")
}
// 创建写入头的writer
headerWriter, err := zipWriter.CreateHeader(header)
if err != nil {
return err
}
if info.IsDir() {
return nil
}
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(headerWriter, f)
return err
})
if err != nil {
return err
}
}
// 存到指定位置
os.Rename(dest, path.Join("./files/", dest))
return nil
}
// 解压缩zip文件
// 这个方法必须是首字母大写的,才能被注册
// src: 需要解压的zip文件
func DecompressZip(zpFname string) error {
// 下载文件
archive, err := zip.OpenReader(zpFname)
if err != nil {
return err
}
dir := filepath.Dir(zpFname)
defer archive.Close()
// 遍历目录
for _, f := range archive.File {
filePath := filepath.Join(dir, f.Name)
if f.FileInfo().IsDir() {
os.MkdirAll(filePath, os.ModePerm)
continue
}
// 父文件夹开始闯将目录
if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
return fmt.Errorf("failed to make directory (%v)", err)
}
// 文件原有模式
dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return fmt.Errorf("failed to create file (%v)", err)
}
fileInArchive, err := f.Open()
if err != nil {
return fmt.Errorf("failed to open file in zip (%v)", err)
}
if _, err := io.Copy(dstFile, fileInArchive); err != nil {
return fmt.Errorf("failed to copy file in zip (%v)", err)
}
dstFile.Close()
fileInArchive.Close()
}
return nil
}
// Zip 压缩文件或目录
// @params dst io.Writer 压缩文件可写流
// @params src string 待压缩源文件/目录路径
func Zip(dst io.Writer, src string) error {
// 强转一下路径
src = filepath.Clean(src)
// 提取最后一个文件或目录的名称
baseFile := filepath.Base(src)
// 判断src是否存在
_, err := os.Stat(src)
if err != nil {
return err
}
// 通文件流句柄创建一个ZIP压缩包
zw := zip.NewWriter(dst)
// 延迟关闭这个压缩包
defer zw.Close()
// 通过filepath封装的Walk来递归处理源路径到压缩文件中
return filepath.Walk(src, func(path string, info fs.FileInfo, err error) error {
// 是否存在异常
if err != nil {
return err
}
// 通过原始文件头信息,创建zip文件头信息
zfh, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
// 赋值默认的压缩方法,否则不压缩
zfh.Method = zip.Deflate
// 移除绝对路径
tmpPath := path
index := strings.Index(tmpPath, baseFile)
if index > -1 {
tmpPath = tmpPath[index:]
}
// 替换文件名,并且去除前后 "\" 或 "/"
tmpPath = strings.Trim(tmpPath, string(filepath.Separator))
// 替换一下分隔符,zip不支持 "\\"
zfh.Name = strings.ReplaceAll(tmpPath, "\\", "/")
// 目录需要拼上一个 "/" ,否则会出现一个和目录一样的文件在压缩包中
if info.IsDir() {
zfh.Name += "/"
}
// 写入文件头信息,并返回一个ZIP文件写入句柄
zfw, err := zw.CreateHeader(zfh)
if err != nil {
return err
}
// 仅在他是标准文件时进行文件内容写入
if zfh.Mode().IsRegular() {
// 打开要压缩的文件
sfr, err := os.Open(path)
if err != nil {
return err
}
defer sfr.Close()
// 将srcFileReader拷贝到zipFilWrite中
_, err = io.Copy(zfw, sfr)
if err != nil {
return err
}
}
// 搞定
return nil
})
}

83
fssc/main.go

@ -0,0 +1,83 @@
package fssc
import (
"fmt"
"fssc/config"
"net/http"
"os"
"path/filepath"
"fssc/internal/discovery"
"github.com/urfave/cli/v2"
)
// 初始化配置
func initConfig(c *cli.Context) error {
return config.G.SetConf(port)
}
// 发送端
func sendClient() error {
// 发送者角色
go discovery.Send(discovery.Sender)
//udp 监听指定路径下的文件
go discovery.Listen(discovery.Receiver, config.G.FilePath)
url := fmt.Sprintf("http://%s:%s", config.G.LocalIP, config.G.Port)
fmt.Printf("Server address: %s\n", url)
http.HandleFunc("/", handler.SendHandler)
// 加载静态资源
http.Handle("/static/", http.StripPrefix("/static/",
http.FileServer(http.FS(web.StaticFs))))
http.HandleFunc("/download/", handler.FileServerHandler)
// 下载压缩包
http.HandleFunc("/dlzip", handler.Downzip)
// udp 传送文件
http.HandleFunc("/sendZip", handler.SendZip)
// 已经打包的zip存放位置
http.HandleFunc("/files", handler.Dfiles)
fmt.Println("send file to server...")
return http.ListenAndServe(config.G.WildcardAddress, nil)
}
// 入口函数
func main() {
app := &cli.App{
Name: "fssc",
Usage: "file transfer tool",
UsageText: "fssc [global options] [filename|foldername]",
Description: "fssc is a simple command-line tool send file/folder",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "port",
Value: "9099",
Usage: "server port",
Aliases: []string{"p"},
Destination: &port,
},
&cli.BoolFlag{
Name: "version",
Aliases: []string{"v"},
Usage: "print the version",
},
},
Action: func(c *cli.Context) error {
if c.NArg() > 0 {
currentPath := filepath.ToSlash(c.Args().Get(0))
config.G.FilePath = currentPath
return sendClient()
}
//return receiveClient()
},
Before: initConfig,
}
err := app.Run(os.Args)
if err != nil {
fmt.Println(err.Error())
}
}

17
xgfs/internal/handler/handler.go

@ -158,13 +158,15 @@ func SendZip(w http.ResponseWriter, r *http.Request) {
// 选择文件,并生成zip包
// 文件
zipfarr := r.Form["zipfiles"]
// 服务器ip地址
serip := r.Form["serverip"]
if serip[0] == "" {
http.Error(w, "remote server ip is blank!", http.StatusInternalServerError)
return
}
// 选中的路径
// 选中的路径,可以为空
wtculpath := r.Form["curpath"]
// 实际路径
@ -183,6 +185,8 @@ func SendZip(w http.ResponseWriter, r *http.Request) {
// go util.CompressToZip(zpFileName, realFilePath, zipfarr)
fmt.Println("archive is createding...")
// 当前运行的目录
// ZIP 文件的实际路径
ziprl := path.Join("./files/", zpFileName)
@ -316,16 +320,16 @@ func SendHandler(w http.ResponseWriter, r *http.Request) {
}
data := struct {
DeviceName string
Rundir string
DeviceName string
//Rundir string
IsDir bool
FileName string
DownloadPath string
UrlPath string
Files []os.DirEntry
}{
DeviceName: config.G.DeviceName,
Rundir: config.G.FilePath,
DeviceName: config.G.DeviceName,
//Rundir: config.G.FilePath,
DownloadPath: downloadPath,
UrlPath: strings.TrimSuffix(r.URL.Path, "/"),
}
@ -343,6 +347,9 @@ func SendHandler(w http.ResponseWriter, r *http.Request) {
data.FileName = filepath.Base(realFilePath)
}
//
fmt.Print("fdata:%v", data)
// 文件列表模板
tmpl, err := template.New("download").Parse(web.DownloadPage)
if err != nil {

9
xgfs/web/download.tmpl

@ -107,7 +107,7 @@
<h2>{{ .DownloadPath }}</h2>
<form action="/sendZip" method="post" class="form-inline">
<div class="row">
<p class="lead">运行目录:{{.Rundir}}</p>
<p class="lead">运行目录:{{ .DownloadPath }}</p>
</div>
<div class="row">
@ -119,14 +119,14 @@
<div class="col-md-5">
<div class="form-group">
<label for="serverip">服务器ip</label>
<input type="text" class="form-control" id="serverip" placeholder="eg:192.168.66.100:9099">
<label for="sip">服务器ip</label>
<input type="text" name="serverip" class="form-control" id="sip" placeholder="eg:192.168.66.100:9099">
</div>
</div>
<div class="col-md-5">
<div class="form-group">
<label for="curpath">相对路径</label>
<label>相对路径</label>
<input type="text" class="form-control" disabled="disabled" name="curpath" value="{{ .UrlPath }}"/>
</div>
</div>
@ -160,7 +160,6 @@
{{ end }}
</li>
{{ end }}
</ul>
{{ end }}
</form>

Loading…
Cancel
Save