refactor(file,shell,process): 去 gin 化——标准库 http.HandlerFunc + webhttp.Router 适配层
- 新增 webhttp 包: Router 最小路由接口(5 动词) + ServeMuxRouter 适配器 + WriteJSON 统一响应 - file/shell/process 三个被控能力包改以标准库 handler 形态注册, 不再绑死 gin - 由此 gin 及其连带重依赖(quic-go/http3、mongo bson、ugorji codec、goccy、validator)退出被控端二进制 - shell: recDir 死参数修复——原实现硬编码 ./rec, 传入的录制目录不生效; 现录制写入与记录列表同源 - shell: 终端创建失败由 panic 改为记日志后关连接(net/http 按连接兜底恢复, 不再依赖框架 Recovery) - file: 上传落盘不再沿用 SaveUploadedFile 的 MkdirAll+Chmod(Windows 盘根 Chmod 报参数不正确), 保留 MkdirAll 去掉 Chmod - 消费方: remote-client 纯 ServeMux 承载; remote-service 保留 gin 栈经 gin.WrapH 桥接(middle 链照常生效) 破坏性变更: RegisterRouters* 形参 gin.RouterGroup → webhttp.Router, 升级 tag v0.5.0
This commit is contained in:
+164
-247
@@ -1,216 +1,149 @@
|
||||
package file
|
||||
|
||||
// 移植自 remote-backend-server internal/handlers/file(去 DB 依赖)。
|
||||
// 标准库 http.HandlerFunc 形态, 路由注册经 webhttp.Router 适配(去 gin 化)。
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jaypipes/ghw"
|
||||
"github.com/shirou/gopsutil/v3/disk"
|
||||
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"git.zeroonesoft.cn/golib/remote-core/common"
|
||||
"strconv"
|
||||
|
||||
"github.com/jaypipes/ghw"
|
||||
"github.com/shirou/gopsutil/v3/disk"
|
||||
|
||||
"git.zeroonesoft.cn/golib/remote-core/common"
|
||||
"git.zeroonesoft.cn/golib/remote-core/webhttp"
|
||||
)
|
||||
|
||||
// RegisterRoutersGatewayFile 注册路由
|
||||
func RegisterRoutersGatewayFile(r *gin.RouterGroup) {
|
||||
r.GET("/file/list", getGatewayFileList())
|
||||
r.POST("/file/list", postGatewayFileList())
|
||||
r.DELETE("/file", deleteGatewayFile())
|
||||
r.PUT("/file/name", putGatewayFileName())
|
||||
r.GET("/file/download", getGatewayFileDownload())
|
||||
r.HEAD("/file/download", headGatewayFileDownload())
|
||||
r.POST("/file/upload", postGatewayFileUpload())
|
||||
r.POST("/file/dir", postGatewayFileDir())
|
||||
// RegisterRoutersGatewayFile 注册路由。
|
||||
// 下载注册的是 GET, ServeMux 模式下 HEAD 自动兼容(响应头一致、无响应体);
|
||||
// 原 gin 独立 HEAD handler 在文件缺失时会因 fi 空指针 panic, 一并消失。
|
||||
func RegisterRoutersGatewayFile(r webhttp.Router) {
|
||||
r.GET("/file/list", getGatewayFileList)
|
||||
r.POST("/file/list", postGatewayFileList)
|
||||
r.DELETE("/file", deleteGatewayFile)
|
||||
r.PUT("/file/name", putGatewayFileName)
|
||||
r.GET("/file/download", getGatewayFileDownload)
|
||||
r.HEAD("/file/download", headGatewayFileDownload)
|
||||
r.POST("/file/upload", postGatewayFileUpload)
|
||||
r.POST("/file/dir", postGatewayFileDir)
|
||||
}
|
||||
|
||||
type PathData struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
func fail(w http.ResponseWriter, msg string) {
|
||||
webhttp.WriteJSON(w, http.StatusOK, map[string]any{
|
||||
"code": 1,
|
||||
"msg": msg,
|
||||
"data": "OK",
|
||||
})
|
||||
}
|
||||
|
||||
func ok(w http.ResponseWriter, data any) {
|
||||
webhttp.WriteJSON(w, http.StatusOK, map[string]any{
|
||||
"code": 0,
|
||||
"msg": "操作成功",
|
||||
"data": data,
|
||||
})
|
||||
}
|
||||
|
||||
// 传递文件路径不方便 比如 路径中包含 #
|
||||
func getGatewayFileList() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
dir := c.Query("path")
|
||||
data, err := getFileList(dir)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 1,
|
||||
"msg": "error:" + err.Error(),
|
||||
"data": "OK",
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"msg": "操作成功",
|
||||
"data": gin.H{
|
||||
"list": data,
|
||||
"count": len(data),
|
||||
"path": dir,
|
||||
},
|
||||
})
|
||||
func getGatewayFileList(w http.ResponseWriter, r *http.Request) {
|
||||
dir := r.URL.Query().Get("path")
|
||||
data, err := getFileList(dir)
|
||||
if err != nil {
|
||||
fail(w, "error:"+err.Error())
|
||||
return
|
||||
}
|
||||
ok(w, map[string]any{
|
||||
"list": data,
|
||||
"count": len(data),
|
||||
"path": dir,
|
||||
})
|
||||
}
|
||||
|
||||
func deleteGatewayFile() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
path := c.Query("path")
|
||||
ok, err := common.PathExists(path)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 1,
|
||||
"msg": "error:" + err.Error(),
|
||||
"data": "OK",
|
||||
})
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 1,
|
||||
"msg": "error:" + "文件不存在",
|
||||
"data": "OK",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = os.Remove(path)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 1,
|
||||
"msg": "error:" + err.Error(),
|
||||
"data": "OK",
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"msg": "操作成功",
|
||||
"data": nil,
|
||||
})
|
||||
func deleteGatewayFile(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Query().Get("path")
|
||||
exists, err := common.PathExists(path)
|
||||
if err != nil {
|
||||
fail(w, "error:"+err.Error())
|
||||
return
|
||||
}
|
||||
if !exists {
|
||||
fail(w, "error:文件不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if err = os.Remove(path); err != nil {
|
||||
fail(w, "error:"+err.Error())
|
||||
return
|
||||
}
|
||||
ok(w, nil)
|
||||
}
|
||||
|
||||
func putGatewayFileName() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
oldPath := c.Query("old")
|
||||
newFile := c.Query("new")
|
||||
func putGatewayFileName(w http.ResponseWriter, r *http.Request) {
|
||||
oldPath := r.URL.Query().Get("old")
|
||||
newFile := r.URL.Query().Get("new")
|
||||
|
||||
fileInfo, err := os.Stat(oldPath)
|
||||
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 1,
|
||||
"msg": "error:" + "文件不存在",
|
||||
"data": "OK",
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 1,
|
||||
"msg": "error:" + err.Error(),
|
||||
"data": "OK",
|
||||
})
|
||||
if _, err := os.Stat(oldPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
fail(w, "error:文件不存在")
|
||||
return
|
||||
}
|
||||
|
||||
dir := filepath.Dir(oldPath)
|
||||
|
||||
newPath := filepath.Join(dir, newFile)
|
||||
|
||||
fmt.Println(fileInfo)
|
||||
fmt.Println(oldPath, newPath)
|
||||
|
||||
err = os.Rename(oldPath, newPath)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 1,
|
||||
"msg": "error:" + err.Error(),
|
||||
"data": "OK",
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"msg": "操作成功",
|
||||
"data": nil,
|
||||
})
|
||||
fail(w, "error:"+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
dir := filepath.Dir(oldPath)
|
||||
newPath := filepath.Join(dir, newFile)
|
||||
|
||||
if err := os.Rename(oldPath, newPath); err != nil {
|
||||
fail(w, "error:"+err.Error())
|
||||
return
|
||||
}
|
||||
ok(w, nil)
|
||||
}
|
||||
|
||||
func postGatewayFileDir() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
dir := c.Query("dir")
|
||||
ok, err := common.PathExists(dir)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 1,
|
||||
"msg": "error:" + err.Error(),
|
||||
"data": "OK",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if ok {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 1,
|
||||
"msg": "error:" + "文件不存在",
|
||||
"data": "OK",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
err = os.MkdirAll(dir, 0750)
|
||||
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 1,
|
||||
"msg": "error:" + err.Error(),
|
||||
"data": "OK",
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"msg": "操作成功",
|
||||
"data": nil,
|
||||
})
|
||||
func postGatewayFileDir(w http.ResponseWriter, r *http.Request) {
|
||||
dir := r.URL.Query().Get("dir")
|
||||
exists, err := common.PathExists(dir)
|
||||
if err != nil {
|
||||
fail(w, "error:"+err.Error())
|
||||
return
|
||||
}
|
||||
if exists {
|
||||
fail(w, "error:文件不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if err = os.MkdirAll(dir, 0750); err != nil {
|
||||
fail(w, "error:"+err.Error())
|
||||
return
|
||||
}
|
||||
ok(w, nil)
|
||||
}
|
||||
|
||||
func postGatewayFileList() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
func postGatewayFileList(w http.ResponseWriter, r *http.Request) {
|
||||
dir := PathData{}
|
||||
// body 解析失败按空路径处理(列盘符), 与原 gin BindJSON 忽略错误的语义一致
|
||||
_ = json.NewDecoder(r.Body).Decode(&dir)
|
||||
|
||||
dir := PathData{}
|
||||
err := c.BindJSON(&dir)
|
||||
fmt.Println("dir", dir)
|
||||
|
||||
data, err := getFileList(dir.Path)
|
||||
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 1,
|
||||
"msg": "error:" + err.Error(),
|
||||
"data": "OK",
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"msg": "操作成功",
|
||||
"data": gin.H{
|
||||
"list": data,
|
||||
"count": len(data),
|
||||
"path": dir.Path,
|
||||
},
|
||||
})
|
||||
data, err := getFileList(dir.Path)
|
||||
if err != nil {
|
||||
fail(w, "error:"+err.Error())
|
||||
return
|
||||
}
|
||||
ok(w, map[string]any{
|
||||
"list": data,
|
||||
"count": len(data),
|
||||
"path": dir.Path,
|
||||
})
|
||||
}
|
||||
|
||||
type FileInfo struct {
|
||||
@@ -227,17 +160,6 @@ type FileInfo struct {
|
||||
|
||||
func getFileList(dir string) ([]FileInfo, error) {
|
||||
list := make([]FileInfo, 0)
|
||||
/*filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error {
|
||||
list = append(list, FileInfo{
|
||||
Name: info.Name(),
|
||||
Size: info.Size(),
|
||||
Mode: info.Mode().String(),
|
||||
ModTime: time.Time{},
|
||||
IsDir: info.IsDir(),
|
||||
})
|
||||
fmt.Println(info.Mode().Type())
|
||||
return nil
|
||||
})*/
|
||||
|
||||
if dir == "" {
|
||||
partitions, err := disk.Partitions(false)
|
||||
@@ -306,35 +228,25 @@ func getFileList(dir string) ([]FileInfo, error) {
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func getGatewayFileDownload() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
filePath := c.Query("filename")
|
||||
fileName := filepath.Base(filePath)
|
||||
c.Header("Content-Type", "application/octet-stream")
|
||||
c.Header("Content-Disposition", "attachment; filename="+fileName)
|
||||
c.Header("Content-Transfer-Encoding", "binary")
|
||||
fi, err := os.Stat(filePath)
|
||||
if err == nil {
|
||||
c.Header("Content-Length", strconv.FormatInt(fi.Size(), 10))
|
||||
}
|
||||
c.File(filePath)
|
||||
}
|
||||
func getGatewayFileDownload(w http.ResponseWriter, r *http.Request) {
|
||||
filePath := r.URL.Query().Get("filename")
|
||||
fileName := filepath.Base(filePath)
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.Header().Set("Content-Disposition", "attachment; filename="+fileName)
|
||||
w.Header().Set("Content-Transfer-Encoding", "binary")
|
||||
http.ServeFile(w, r, filePath)
|
||||
}
|
||||
|
||||
func headGatewayFileDownload() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
filePath := c.Query("filename")
|
||||
fileName := filepath.Base(filePath)
|
||||
c.Header("Content-Type", "application/octet-stream")
|
||||
c.Header("Content-Disposition", "attachment; filename="+fileName)
|
||||
c.Header("Content-Transfer-Encoding", "binary")
|
||||
fmt.Println(filePath)
|
||||
fi, err := os.Stat(filePath)
|
||||
if err == nil {
|
||||
fmt.Println("file size is ", fi.Size(), err)
|
||||
}
|
||||
c.Header("Content-Length", strconv.FormatInt(fi.Size(), 10))
|
||||
func headGatewayFileDownload(w http.ResponseWriter, r *http.Request) {
|
||||
filePath := r.URL.Query().Get("filename")
|
||||
fileName := filepath.Base(filePath)
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.Header().Set("Content-Disposition", "attachment; filename="+fileName)
|
||||
w.Header().Set("Content-Transfer-Encoding", "binary")
|
||||
if fi, err := os.Stat(filePath); err == nil {
|
||||
w.Header().Set("Content-Length", strconv.FormatInt(fi.Size(), 10))
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
type Win32_Volume struct {
|
||||
@@ -350,49 +262,54 @@ type FileData struct {
|
||||
Md5 string `json:"md5"`
|
||||
}
|
||||
|
||||
func postGatewayFileUpload() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
dir := c.Query("dir")
|
||||
file, err := HttpUploadFile(c, dir)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 1,
|
||||
"msg": "上传失败:" + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"msg": "success",
|
||||
"status": "done",
|
||||
"file": file,
|
||||
func postGatewayFileUpload(w http.ResponseWriter, r *http.Request) {
|
||||
dir := r.URL.Query().Get("dir")
|
||||
file, err := HttpUploadFile(r, dir)
|
||||
if err != nil {
|
||||
webhttp.WriteJSON(w, http.StatusOK, map[string]any{
|
||||
"code": 1,
|
||||
"msg": "上传失败:" + err.Error(),
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
webhttp.WriteJSON(w, http.StatusOK, map[string]any{
|
||||
"code": 0,
|
||||
"msg": "success",
|
||||
"status": "done",
|
||||
"file": file,
|
||||
})
|
||||
}
|
||||
|
||||
func HttpUploadFile(c *gin.Context, saveDir string) (*FileData, error) {
|
||||
file, err := c.FormFile("file")
|
||||
// HttpUploadFile 接收 multipart 上传并落盘。
|
||||
// 不沿用 gin SaveUploadedFile 的 MkdirAll+Chmod: Windows 盘根上 Chmod 报
|
||||
// "参数不正确" 导致盘根上传必败(remote-service 已用流式接管绕开此坑, 这里
|
||||
// 是非 multipart 请求的兜底路径), 目录创建保留、Chmod 去掉。
|
||||
func HttpUploadFile(r *http.Request, saveDir string) (*FileData, error) {
|
||||
fh, head, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
fileName := file.Filename
|
||||
// 保存路径 绝对路径
|
||||
savePath := filepath.Join(saveDir, file.Filename)
|
||||
|
||||
err = c.SaveUploadedFile(file, savePath)
|
||||
if err != nil {
|
||||
savePath := filepath.Join(saveDir, head.Filename)
|
||||
if err = os.MkdirAll(filepath.Dir(savePath), 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fileData := &FileData{
|
||||
OriginalFileName: file.Filename,
|
||||
FileName: fileName,
|
||||
out, err := os.Create(savePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer out.Close()
|
||||
if _, err = io.Copy(out, fh); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &FileData{
|
||||
OriginalFileName: head.Filename,
|
||||
FileName: head.Filename,
|
||||
FilePath: savePath,
|
||||
Size: file.Size,
|
||||
}
|
||||
|
||||
return fileData, nil
|
||||
Size: head.Size,
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user