feat: 文件备份/CDR/LOG本地文件列表功能接口
This commit is contained in:
@@ -90,11 +90,18 @@ func Setup(router *gin.Engine) {
|
||||
// 文件操作处理
|
||||
fileGroup := router.Group("/file")
|
||||
{
|
||||
fileGroup.GET("/download/:filePath", middleware.PreAuthorize(nil), controller.NewFile.Download)
|
||||
fileGroup.POST("/upload", middleware.PreAuthorize(nil), controller.NewFile.Upload)
|
||||
fileGroup.POST("/chunkCheck", middleware.PreAuthorize(nil), controller.NewFile.ChunkCheck)
|
||||
fileGroup.POST("/chunkUpload", middleware.PreAuthorize(nil), controller.NewFile.ChunkUpload)
|
||||
fileGroup.POST("/chunkMerge", middleware.PreAuthorize(nil), controller.NewFile.ChunkMerge)
|
||||
fileGroup.POST("/transferStaticFile", middleware.PreAuthorize(nil), controller.NewCommont.TransferStaticFile)
|
||||
|
||||
fileGroup.POST("/upload", middleware.PreAuthorize(nil), controller.NewFile.Upload)
|
||||
fileGroup.POST("/chunk-check", middleware.PreAuthorize(nil), controller.NewFile.ChunkCheck)
|
||||
fileGroup.POST("/chunk-upload", middleware.PreAuthorize(nil), controller.NewFile.ChunkUpload)
|
||||
fileGroup.POST("/chunk-merge", middleware.PreAuthorize(nil), controller.NewFile.ChunkMerge)
|
||||
fileGroup.GET("/download/:filePath", middleware.PreAuthorize(nil), controller.NewFile.Download)
|
||||
fileGroup.GET("/list", middleware.PreAuthorize(nil), controller.NewFile.List)
|
||||
fileGroup.GET("", middleware.PreAuthorize(nil), controller.NewFile.File)
|
||||
fileGroup.DELETE("", middleware.PreAuthorize(nil), controller.NewFile.Remove)
|
||||
fileGroup.POST("/transfer-static-file", middleware.PreAuthorize(nil), controller.NewFile.TransferStaticFile)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,18 +4,19 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"be.ems/src/framework/config"
|
||||
"be.ems/src/framework/constants/uploadsubpath"
|
||||
"be.ems/src/framework/constants"
|
||||
"be.ems/src/framework/i18n"
|
||||
"be.ems/src/framework/utils/ctx"
|
||||
"be.ems/src/framework/reqctx"
|
||||
"be.ems/src/framework/resp"
|
||||
"be.ems/src/framework/utils/file"
|
||||
"be.ems/src/framework/vo/result"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
)
|
||||
|
||||
// 实例化控制层 FileController 结构体
|
||||
@@ -30,16 +31,16 @@ type FileController struct{}
|
||||
//
|
||||
// GET /download/:filePath
|
||||
func (s *FileController) Download(c *gin.Context) {
|
||||
language := ctx.AcceptLanguage(c)
|
||||
language := reqctx.AcceptLanguage(c)
|
||||
filePath := c.Param("filePath")
|
||||
if len(filePath) < 8 {
|
||||
c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
|
||||
c.JSON(422, resp.CodeMsg(422002, i18n.TKey(language, "app.common.err400")))
|
||||
return
|
||||
}
|
||||
// base64解析出地址
|
||||
decodedBytes, err := base64.StdEncoding.DecodeString(filePath)
|
||||
if err != nil {
|
||||
c.JSON(400, result.CodeMsg(400, err.Error()))
|
||||
c.JSON(422, resp.CodeMsg(422002, err.Error()))
|
||||
return
|
||||
}
|
||||
routerPath := string(decodedBytes)
|
||||
@@ -48,7 +49,7 @@ func (s *FileController) Download(c *gin.Context) {
|
||||
headerRange := c.GetHeader("Range")
|
||||
resultMap, err := file.ReadUploadFileStream(routerPath, headerRange)
|
||||
if err != nil {
|
||||
c.JSON(200, result.ErrMsg(err.Error()))
|
||||
c.JSON(200, resp.ErrMsg(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -83,39 +84,41 @@ func (s *FileController) Download(c *gin.Context) {
|
||||
// @Description Upload a file, interface param use <fileName>
|
||||
// @Router /file/upload [post]
|
||||
func (s *FileController) Upload(c *gin.Context) {
|
||||
language := ctx.AcceptLanguage(c)
|
||||
// 上传的文件
|
||||
formFile, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
|
||||
c.JSON(422, resp.CodeMsg(422002, "bind err: file is empty"))
|
||||
return
|
||||
}
|
||||
// 子路径
|
||||
// 子路径需要在指定范围内
|
||||
subPath := c.PostForm("subPath")
|
||||
if _, ok := uploadsubpath.UploadSubpath[subPath]; !ok {
|
||||
c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
|
||||
_, ok := constants.UPLOAD_SUB_PATH[subPath]
|
||||
if subPath != "" && !ok {
|
||||
c.JSON(422, resp.CodeMsg(422002, "bind err: subPath not in range"))
|
||||
return
|
||||
}
|
||||
if subPath == "" {
|
||||
subPath = constants.UPLOAD_COMMON
|
||||
}
|
||||
|
||||
// 上传文件转存
|
||||
upFilePath, err := file.TransferUploadFile(formFile, subPath, nil)
|
||||
uploadFilePath, err := file.TransferUploadFile(formFile, subPath, []string{})
|
||||
if err != nil {
|
||||
c.JSON(200, result.ErrMsg(err.Error()))
|
||||
c.JSON(200, resp.ErrMsg(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
newFileName := upFilePath[strings.LastIndex(upFilePath, "/")+1:]
|
||||
c.JSON(200, result.OkData(map[string]string{
|
||||
"url": "//" + c.Request.Host + upFilePath,
|
||||
"fileName": upFilePath,
|
||||
"newFileName": newFileName,
|
||||
c.JSON(200, resp.OkData(map[string]string{
|
||||
"url": "//" + c.Request.Host + uploadFilePath,
|
||||
"filePath": uploadFilePath,
|
||||
"newFileName": filepath.Base(uploadFilePath),
|
||||
"originalFileName": formFile.Filename,
|
||||
}))
|
||||
}
|
||||
|
||||
// 切片文件检查
|
||||
//
|
||||
// POST /chunkCheck
|
||||
// POST /chunk-check
|
||||
//
|
||||
// @Tags common/file
|
||||
// @Accept json
|
||||
@@ -125,33 +128,30 @@ func (s *FileController) Upload(c *gin.Context) {
|
||||
// @Security TokenAuth
|
||||
// @Summary Slice file checking
|
||||
// @Description Slice file checking
|
||||
// @Router /file/chunkCheck [post]
|
||||
// @Router /file/chunk-check [post]
|
||||
func (s *FileController) ChunkCheck(c *gin.Context) {
|
||||
language := ctx.AcceptLanguage(c)
|
||||
var body struct {
|
||||
// 唯一标识
|
||||
Identifier string `json:"identifier" binding:"required"`
|
||||
// 文件名
|
||||
FileName string `json:"fileName" binding:"required"`
|
||||
Identifier string `json:"identifier" binding:"required"` // 唯一标识
|
||||
FileName string `json:"fileName" binding:"required"` // 文件名
|
||||
}
|
||||
err := c.ShouldBindJSON(&body)
|
||||
if err != nil {
|
||||
c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
|
||||
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
|
||||
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
|
||||
c.JSON(422, resp.CodeMsg(422001, errMsgs))
|
||||
return
|
||||
}
|
||||
|
||||
// 读取标识目录
|
||||
chunks, err := file.ChunkCheckFile(body.Identifier, body.FileName)
|
||||
if err != nil {
|
||||
c.JSON(200, result.ErrMsg(err.Error()))
|
||||
c.JSON(200, resp.ErrMsg(err.Error()))
|
||||
return
|
||||
}
|
||||
c.JSON(200, result.OkData(chunks))
|
||||
c.JSON(200, resp.OkData(chunks))
|
||||
}
|
||||
|
||||
// 切片文件合并
|
||||
//
|
||||
// POST /chunkMerge
|
||||
// POST /chunk-merge
|
||||
//
|
||||
// @Tags common/file
|
||||
// @Accept json
|
||||
@@ -161,46 +161,45 @@ func (s *FileController) ChunkCheck(c *gin.Context) {
|
||||
// @Security TokenAuth
|
||||
// @Summary Slice file merge
|
||||
// @Description Slice file merge
|
||||
// @Router /file/chunkMerge [post]
|
||||
// @Router /file/chunk-merge [post]
|
||||
func (s *FileController) ChunkMerge(c *gin.Context) {
|
||||
language := ctx.AcceptLanguage(c)
|
||||
var body struct {
|
||||
// 唯一标识
|
||||
Identifier string `json:"identifier" binding:"required"`
|
||||
// 文件名
|
||||
FileName string `json:"fileName" binding:"required"`
|
||||
// 子路径类型
|
||||
SubPath string `json:"subPath" binding:"required"`
|
||||
Identifier string `json:"identifier" binding:"required"` // 唯一标识
|
||||
FileName string `json:"fileName" binding:"required"` // 文件名
|
||||
SubPath string `json:"subPath"` // 子路径类型
|
||||
}
|
||||
err := c.ShouldBindJSON(&body)
|
||||
if err != nil {
|
||||
c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
|
||||
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
|
||||
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
|
||||
c.JSON(422, resp.CodeMsg(422001, errMsgs))
|
||||
return
|
||||
}
|
||||
if _, ok := uploadsubpath.UploadSubpath[body.SubPath]; !ok {
|
||||
c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
|
||||
// 子路径需要在指定范围内
|
||||
if _, ok := constants.UPLOAD_SUB_PATH[body.SubPath]; body.SubPath != "" && !ok {
|
||||
c.JSON(422, resp.CodeMsg(422002, "bind err: subPath not in range"))
|
||||
return
|
||||
}
|
||||
if body.SubPath == "" {
|
||||
body.SubPath = constants.UPLOAD_COMMON
|
||||
}
|
||||
|
||||
// 切片文件合并
|
||||
mergeFilePath, err := file.ChunkMergeFile(body.Identifier, body.FileName, body.SubPath)
|
||||
if err != nil {
|
||||
c.JSON(200, result.ErrMsg(err.Error()))
|
||||
c.JSON(200, resp.ErrMsg(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
newFileName := mergeFilePath[strings.LastIndex(mergeFilePath, "/")+1:]
|
||||
c.JSON(200, result.OkData(map[string]string{
|
||||
c.JSON(200, resp.OkData(map[string]string{
|
||||
"url": "//" + c.Request.Host + mergeFilePath,
|
||||
"fileName": mergeFilePath,
|
||||
"newFileName": newFileName,
|
||||
"filePath": mergeFilePath,
|
||||
"newFileName": filepath.Base(mergeFilePath),
|
||||
"originalFileName": body.FileName,
|
||||
}))
|
||||
}
|
||||
|
||||
// 切片文件上传
|
||||
//
|
||||
// POST /chunkUpload
|
||||
// POST /chunk-upload
|
||||
//
|
||||
// @Tags common/file
|
||||
// @Accept multipart/form-data
|
||||
@@ -212,41 +211,214 @@ func (s *FileController) ChunkMerge(c *gin.Context) {
|
||||
// @Security TokenAuth
|
||||
// @Summary Sliced file upload
|
||||
// @Description Sliced file upload
|
||||
// @Router /file/chunkUpload [post]
|
||||
// @Router /file/chunk-upload [post]
|
||||
func (s *FileController) ChunkUpload(c *gin.Context) {
|
||||
language := ctx.AcceptLanguage(c)
|
||||
// 切片编号
|
||||
index := c.PostForm("index")
|
||||
// 切片唯一标识
|
||||
identifier := c.PostForm("identifier")
|
||||
if index == "" || identifier == "" {
|
||||
c.JSON(422, resp.CodeMsg(422002, "bind err: index and identifier must be set"))
|
||||
return
|
||||
}
|
||||
// 上传的文件
|
||||
formFile, err := c.FormFile("file")
|
||||
if index == "" || identifier == "" || err != nil {
|
||||
c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
|
||||
if err != nil {
|
||||
c.JSON(422, resp.CodeMsg(422002, "bind err: file is empty"))
|
||||
return
|
||||
}
|
||||
|
||||
// 上传文件转存
|
||||
chunkFilePath, err := file.TransferChunkUploadFile(formFile, index, identifier)
|
||||
if err != nil {
|
||||
c.JSON(200, result.ErrMsg(err.Error()))
|
||||
c.JSON(200, resp.ErrMsg(err.Error()))
|
||||
return
|
||||
}
|
||||
c.JSON(206, result.OkData(chunkFilePath))
|
||||
c.JSON(206, resp.OkData(chunkFilePath))
|
||||
}
|
||||
|
||||
// 本地文件列表
|
||||
//
|
||||
// GET /list
|
||||
//
|
||||
// @Tags common/file
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param path query string true "file path" default(/var/log)
|
||||
// @Param pageNum query number true "pageNum" default(1)
|
||||
// @Param pageSize query number true "pageSize" default(10)
|
||||
// @Param search query string false "search prefix" default(upf)
|
||||
// @Success 200 {object} object "Response Results"
|
||||
// @Security TokenAuth
|
||||
// @Summary Local file list
|
||||
// @Description Local file list
|
||||
// @Router /file/list [get]
|
||||
func (s *FileController) List(c *gin.Context) {
|
||||
var querys struct {
|
||||
Path string `form:"path" binding:"required"`
|
||||
PageNum int64 `form:"pageNum" binding:"required"`
|
||||
PageSize int64 `form:"pageSize" binding:"required"`
|
||||
Search string `form:"search"`
|
||||
}
|
||||
if err := c.ShouldBindQuery(&querys); err != nil {
|
||||
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
|
||||
c.JSON(422, resp.CodeMsg(422001, errMsgs))
|
||||
return
|
||||
}
|
||||
|
||||
// 获取文件列表
|
||||
localFilePath := querys.Path
|
||||
if runtime.GOOS == "windows" {
|
||||
localFilePath = fmt.Sprintf("C:%s", localFilePath)
|
||||
}
|
||||
rows, err := file.FileList(localFilePath, querys.Search)
|
||||
if err != nil {
|
||||
c.JSON(200, resp.OkData(map[string]any{
|
||||
"path": querys.Path,
|
||||
"total": len(rows),
|
||||
"rows": []file.FileListRow{},
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
// 对数组进行切片分页
|
||||
lenNum := int64(len(rows))
|
||||
start := (querys.PageNum - 1) * querys.PageSize
|
||||
end := start + querys.PageSize
|
||||
var splitRows []file.FileListRow
|
||||
if start >= lenNum {
|
||||
splitRows = []file.FileListRow{}
|
||||
} else if end >= lenNum {
|
||||
splitRows = rows[start:]
|
||||
} else {
|
||||
splitRows = rows[start:end]
|
||||
}
|
||||
|
||||
c.JSON(200, resp.OkData(map[string]any{
|
||||
"path": querys.Path,
|
||||
"total": lenNum,
|
||||
"rows": splitRows,
|
||||
}))
|
||||
}
|
||||
|
||||
// 本地文件获取下载
|
||||
//
|
||||
// DELETE /
|
||||
//
|
||||
// @Tags common/file
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param path query string true "file path" default(/var/log)
|
||||
// @Param fileName query string true "file name" default(omc.log)
|
||||
// @Success 200 {object} object "Response Results"
|
||||
// @Security TokenAuth
|
||||
// @Summary Local files for download
|
||||
// @Description Local files for download
|
||||
// @Router /file [get]
|
||||
func (s *FileController) File(c *gin.Context) {
|
||||
var querys struct {
|
||||
Path string `form:"path" binding:"required"`
|
||||
Filename string `form:"fileName" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindQuery(&querys); err != nil {
|
||||
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
|
||||
c.JSON(422, resp.CodeMsg(422001, errMsgs))
|
||||
return
|
||||
}
|
||||
|
||||
// 检查路径是否在允许的目录范围内
|
||||
allowedPaths := []string{"/var/log", "/tmp", "/usr/local/omc/backup"}
|
||||
allowed := false
|
||||
for _, p := range allowedPaths {
|
||||
if strings.HasPrefix(querys.Path, p) {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !allowed {
|
||||
c.JSON(200, resp.ErrMsg("operation path is not within the allowed range"))
|
||||
return
|
||||
}
|
||||
|
||||
// 获取文件路径并下载
|
||||
localFilePath := filepath.Join(querys.Path, querys.Filename)
|
||||
if runtime.GOOS == "windows" {
|
||||
localFilePath = fmt.Sprintf("C:%s", localFilePath)
|
||||
}
|
||||
if _, err := os.Stat(localFilePath); os.IsNotExist(err) {
|
||||
c.JSON(200, resp.ErrMsg("file does not exist"))
|
||||
return
|
||||
}
|
||||
c.FileAttachment(localFilePath, querys.Filename)
|
||||
}
|
||||
|
||||
// 本地文件删除
|
||||
//
|
||||
// DELETE /
|
||||
//
|
||||
// @Tags common/file
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param path query string true "file path" default(/var/log)
|
||||
// @Param fileName query string true "file name" default(omc.log)
|
||||
// @Success 200 {object} object "Response Results"
|
||||
// @Security TokenAuth
|
||||
// @Summary Local file deletion
|
||||
// @Description Local file deletion
|
||||
// @Router /file [delete]
|
||||
func (s *FileController) Remove(c *gin.Context) {
|
||||
var querys struct {
|
||||
Path string `form:"path" binding:"required"`
|
||||
Filename string `form:"fileName" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindQuery(&querys); err != nil {
|
||||
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
|
||||
c.JSON(422, resp.CodeMsg(422001, errMsgs))
|
||||
return
|
||||
}
|
||||
|
||||
// 检查路径是否在允许的目录范围内
|
||||
allowedPaths := []string{"/tmp", "/usr/local/omc/backup"}
|
||||
allowed := false
|
||||
for _, p := range allowedPaths {
|
||||
if strings.HasPrefix(querys.Path, p) {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !allowed {
|
||||
c.JSON(200, resp.ErrMsg("operation path is not within the allowed range"))
|
||||
return
|
||||
}
|
||||
|
||||
// 获取文件路径并删除
|
||||
localFilePath := filepath.Join(querys.Path, querys.Filename)
|
||||
if runtime.GOOS == "windows" {
|
||||
localFilePath = fmt.Sprintf("C:%s", localFilePath)
|
||||
}
|
||||
if _, err := os.Stat(localFilePath); os.IsNotExist(err) {
|
||||
c.JSON(200, resp.ErrMsg("file does not exist"))
|
||||
return
|
||||
}
|
||||
if err := os.Remove(localFilePath); err != nil {
|
||||
c.JSON(200, resp.ErrMsg(err.Error()))
|
||||
return
|
||||
}
|
||||
c.JSON(200, resp.Ok(nil))
|
||||
}
|
||||
|
||||
// 转存指定对应文件到静态目录
|
||||
//
|
||||
// POST /transferStaticFile
|
||||
func (s *CommontController) TransferStaticFile(c *gin.Context) {
|
||||
language := ctx.AcceptLanguage(c)
|
||||
// POST /transfer-static-file
|
||||
func (s *FileController) TransferStaticFile(c *gin.Context) {
|
||||
var body struct {
|
||||
UploadPath string `json:"uploadPath" binding:"required"`
|
||||
StaticPath string `json:"staticPath" binding:"required"`
|
||||
Language string `json:"language" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindBodyWith(&body, binding.JSON); err != nil {
|
||||
c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
|
||||
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
|
||||
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
|
||||
c.JSON(422, resp.CodeMsg(422001, errMsgs))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -257,7 +429,7 @@ func (s *CommontController) TransferStaticFile(c *gin.Context) {
|
||||
static := config.Get("staticFile.default").(map[string]any)
|
||||
dir, err := filepath.Abs(static["dir"].(string))
|
||||
if err != nil {
|
||||
c.JSON(400, result.CodeMsg(400, err.Error()))
|
||||
c.JSON(200, resp.ErrMsg(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -267,10 +439,10 @@ func (s *CommontController) TransferStaticFile(c *gin.Context) {
|
||||
|
||||
err = file.CopyUploadFile(body.UploadPath, newFile)
|
||||
if err != nil {
|
||||
c.JSON(400, result.CodeMsg(400, err.Error()))
|
||||
c.JSON(200, resp.ErrMsg(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
urlPath := strings.Replace(newFile, dir, static["prefix"].(string), 1)
|
||||
c.JSON(200, result.OkData(filepath.ToSlash(urlPath)))
|
||||
c.JSON(200, resp.OkData(filepath.ToSlash(urlPath)))
|
||||
}
|
||||
|
||||
101
src/modules/network_data/controller/all_backup.go
Normal file
101
src/modules/network_data/controller/all_backup.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"be.ems/src/framework/reqctx"
|
||||
"be.ems/src/framework/resp"
|
||||
"be.ems/src/modules/network_data/model"
|
||||
"be.ems/src/modules/network_data/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// 实例化控制层 BackupController 结构体
|
||||
var NewBackup = &BackupController{
|
||||
backupService: service.NewBackup,
|
||||
}
|
||||
|
||||
// 备份数据
|
||||
//
|
||||
// PATH /backup
|
||||
type BackupController struct {
|
||||
backupService *service.Backup // 备份相关服务
|
||||
}
|
||||
|
||||
// 备份文件-更新FTP配置
|
||||
//
|
||||
// PUT /ftp
|
||||
func (s BackupController) FTPUpdate(c *gin.Context) {
|
||||
var body model.BackupDataFTP
|
||||
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
|
||||
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
|
||||
c.JSON(422, resp.CodeMsg(422001, errMsgs))
|
||||
return
|
||||
}
|
||||
|
||||
byteData, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
c.JSON(200, resp.ErrMsg(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
up := s.backupService.FTPConfigUpdate(string(byteData), reqctx.LoginUserToUserName(c))
|
||||
if up <= 0 {
|
||||
c.JSON(200, resp.ErrMsg("update failed"))
|
||||
return
|
||||
}
|
||||
c.JSON(200, resp.Ok(nil))
|
||||
}
|
||||
|
||||
// 备份文件-获取FTP配置
|
||||
//
|
||||
// GET /ftp
|
||||
func (s BackupController) FTPInfo(c *gin.Context) {
|
||||
info := s.backupService.FTPConfigInfo()
|
||||
c.JSON(200, resp.OkData(info))
|
||||
}
|
||||
|
||||
// 备份文件-文件FTP发送
|
||||
//
|
||||
// POST /ftp
|
||||
func (s BackupController) FTPPush(c *gin.Context) {
|
||||
var body struct {
|
||||
Path string `form:"path" binding:"required"` // 路径必须是 BACKUP_DIR 开头的路径
|
||||
Filename string `form:"fileName" binding:"required"`
|
||||
Tag string `form:"tag" binding:"required"` // 标签,用于区分不同的备份文件
|
||||
}
|
||||
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
|
||||
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
|
||||
c.JSON(422, resp.CodeMsg(422001, errMsgs))
|
||||
return
|
||||
}
|
||||
// 判断路径是否合法
|
||||
if !strings.HasPrefix(body.Path, s.backupService.BACKUP_DIR) {
|
||||
c.JSON(200, resp.ErrMsg("operation path is not within the allowed range"))
|
||||
return
|
||||
}
|
||||
|
||||
// 判断文件是否存在
|
||||
localFilePath := filepath.Join(body.Path, body.Filename)
|
||||
if runtime.GOOS == "windows" {
|
||||
localFilePath = fmt.Sprintf("C:%s", localFilePath)
|
||||
}
|
||||
if _, err := os.Stat(localFilePath); os.IsNotExist(err) {
|
||||
c.JSON(200, resp.ErrMsg("file does not exist"))
|
||||
return
|
||||
}
|
||||
|
||||
// 发送文件
|
||||
err := s.backupService.FTPPushFile(localFilePath, body.Tag)
|
||||
if err != nil {
|
||||
c.JSON(200, resp.ErrMsg(err.Error()))
|
||||
return
|
||||
}
|
||||
c.JSON(200, resp.Ok(nil))
|
||||
}
|
||||
11
src/modules/network_data/model/backup.go
Normal file
11
src/modules/network_data/model/backup.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package model
|
||||
|
||||
// BackupDataFTP 备份数据FTP服务参数结构体
|
||||
type BackupDataFTP struct {
|
||||
Password string `json:"password"` // FTP密码
|
||||
Username string `json:"username" binding:"required"` // FTP用户名
|
||||
ToIp string `json:"toIp" binding:"required"` // FTP服务器IP
|
||||
ToPort int64 `json:"toPort" binding:"required"` // FTP服务器端口
|
||||
Dir string `json:"dir" binding:"required"` // FTP服务器目录
|
||||
Enable bool `json:"enable"` // 是否启用
|
||||
}
|
||||
86
src/modules/network_data/service/backup.go
Normal file
86
src/modules/network_data/service/backup.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"be.ems/src/framework/ssh"
|
||||
"be.ems/src/modules/network_data/model"
|
||||
neService "be.ems/src/modules/network_element/service"
|
||||
systemService "be.ems/src/modules/system/service"
|
||||
)
|
||||
|
||||
// 实例化数据层 Backup 结构体
|
||||
var NewBackup = &Backup{
|
||||
BACKUP_DIR: "/usr/local/omc/backup",
|
||||
neInfoService: neService.NewNeInfo,
|
||||
sysConfigService: systemService.NewSysConfigImpl,
|
||||
}
|
||||
|
||||
// Backup 备份相关 服务层处理
|
||||
type Backup struct {
|
||||
BACKUP_DIR string // 备份目录
|
||||
neInfoService *neService.NeInfo // 网元信息服务
|
||||
sysConfigService *systemService.SysConfigImpl // 参数配置服务
|
||||
}
|
||||
|
||||
// FTPConfigUpdate 更新FTP配置信息
|
||||
func (r Backup) FTPConfigUpdate(value, updateBy string) int64 {
|
||||
cfg := r.sysConfigService.SelectConfigByKey("neData.backupDataFTP")
|
||||
if cfg.ConfigID == "" {
|
||||
return 0
|
||||
}
|
||||
cfg.ConfigValue = value
|
||||
cfg.UpdateBy = updateBy
|
||||
return r.sysConfigService.UpdateEncryptValue(cfg)
|
||||
}
|
||||
|
||||
// FTPConfigInfo 获取FTP配置信息
|
||||
func (r Backup) FTPConfigInfo() model.BackupDataFTP {
|
||||
info := model.BackupDataFTP{}
|
||||
// 获取配置
|
||||
cfg := r.sysConfigService.FindByKeyDecryptValue("neData.backupDataFTP")
|
||||
if cfg.ConfigID != "" && cfg.ConfigValue != "" {
|
||||
if err := json.Unmarshal([]byte(cfg.ConfigValue), &info); err != nil {
|
||||
return info
|
||||
}
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
// FTPPushFile 推送文件到FTP
|
||||
func (r Backup) FTPPushFile(localFilePath, tag string) error {
|
||||
cfgData := r.FTPConfigInfo()
|
||||
if !cfgData.Enable {
|
||||
return fmt.Errorf("setting remote backup ftp is disabled")
|
||||
}
|
||||
|
||||
connSSH := ssh.ConnSSH{
|
||||
User: cfgData.Username,
|
||||
Password: cfgData.Password,
|
||||
Addr: cfgData.ToIp,
|
||||
Port: cfgData.ToPort,
|
||||
AuthMode: "0",
|
||||
}
|
||||
sshClient, err := connSSH.NewClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sshClient.Close()
|
||||
// 网元主机的SSH客户端进行文件传输
|
||||
sftpClient, err := sshClient.NewClientSFTP()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sftpClient.Close()
|
||||
|
||||
remotePath := strings.Replace(localFilePath, r.BACKUP_DIR, tag, 1)
|
||||
remotePath = filepath.Join(cfgData.Dir, remotePath)
|
||||
// 复制到远程
|
||||
if err = sftpClient.CopyFileLocalToRemote(localFilePath, remotePath); err != nil {
|
||||
return fmt.Errorf("error uploading file")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -3,8 +3,10 @@ package service
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"be.ems/src/framework/config"
|
||||
"be.ems/src/framework/constants/cachekey"
|
||||
"be.ems/src/framework/database/redis"
|
||||
"be.ems/src/framework/utils/crypto"
|
||||
"be.ems/src/modules/system/model"
|
||||
"be.ems/src/modules/system/repository"
|
||||
)
|
||||
@@ -168,3 +170,40 @@ func (r *SysConfigImpl) SelectConfigByKey(configKey string) model.SysConfig {
|
||||
}
|
||||
return model.SysConfig{}
|
||||
}
|
||||
|
||||
// FindByKey 查询配置信息BY键
|
||||
func (s SysConfigImpl) FindByKey(configKey string) model.SysConfig {
|
||||
sysConf := s.sysConfigRepository.SelectConfigList(model.SysConfig{
|
||||
ConfigKey: configKey,
|
||||
})
|
||||
if len(sysConf) > 0 {
|
||||
return sysConf[0]
|
||||
}
|
||||
return model.SysConfig{}
|
||||
}
|
||||
|
||||
// UpdateEncryptValue 更新并加密配置值信息
|
||||
func (s SysConfigImpl) UpdateEncryptValue(sysConfig model.SysConfig) int64 {
|
||||
appKey := config.Get("aes.appKey").(string)
|
||||
bodyEn, err := crypto.AESEncryptBase64(sysConfig.ConfigValue, appKey)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
sysConfig.ConfigValue = bodyEn
|
||||
return s.UpdateConfig(sysConfig)
|
||||
}
|
||||
|
||||
// FindByKeyDecryptValue 获取并解密配置值信息
|
||||
func (s SysConfigImpl) FindByKeyDecryptValue(configKey string) model.SysConfig {
|
||||
item := s.FindByKey(configKey)
|
||||
if item.ConfigKey != configKey {
|
||||
return item
|
||||
}
|
||||
appKey := config.Get("aes.appKey").(string)
|
||||
bodyDe, err := crypto.AESDecryptBase64(item.ConfigValue, appKey)
|
||||
if err != nil {
|
||||
return item
|
||||
}
|
||||
item.ConfigValue = bodyDe
|
||||
return item
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user