diff --git a/restagent/etc/restconf.yaml b/restagent/etc/restconf.yaml index 167382c..a1c4c01 100644 --- a/restagent/etc/restconf.yaml +++ b/restagent/etc/restconf.yaml @@ -58,7 +58,7 @@ redis: # UDM网元用户库 udmuser: port: 6379 # Redis port - host: "192.168.1.187" + host: "192.168.5.57" password: "" db: 0 # Redis db_num # 多个数据源时可以用这个指定默认的数据源 diff --git a/src/assets/template/excel/user_import_template_en.xlsx b/src/assets/template/excel/user_import_template_en.xlsx index 5acb1c4..90f88c4 100644 Binary files a/src/assets/template/excel/user_import_template_en.xlsx and b/src/assets/template/excel/user_import_template_en.xlsx differ diff --git a/src/assets/template/excel/user_import_template_zh.xlsx b/src/assets/template/excel/user_import_template_zh.xlsx index 4f4713b..0f034a3 100644 Binary files a/src/assets/template/excel/user_import_template_zh.xlsx and b/src/assets/template/excel/user_import_template_zh.xlsx differ diff --git a/src/framework/config/config/config.default.yaml b/src/framework/config/config/config.default.yaml index 0515333..ee82bb5 100644 --- a/src/framework/config/config/config.default.yaml +++ b/src/framework/config/config/config.default.yaml @@ -1,19 +1,19 @@ # 项目信息 framework: - name: "ems_agt" - version: "0.0.1" + name: "CN EMS" + version: "2.2312.9" # 应用服务配置 server: # 服务端口 - port: 3040 + port: 3030 # 是否开启代理 proxy: false # 日志 logger: - fileDir: "/usr/local/omc/log" - fileName: "ems_agt.log" + fileDir: "/var/log" + fileName: "omc.log" level: 2 # 日志记录的等级 0:silent<1:info<2:warn<3:error maxDay: 180 # 日志会保留 180 天 maxSize: 10 # 调整按 10MB 大小的切割 diff --git a/src/framework/utils/ssh/files.go b/src/framework/utils/ssh/files.go new file mode 100644 index 0000000..23604a8 --- /dev/null +++ b/src/framework/utils/ssh/files.go @@ -0,0 +1,104 @@ +package ssh + +import ( + "fmt" + "strings" + + "ems.agt/src/framework/cmd" + "ems.agt/src/framework/config" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/parse" +) + +// FileListRow 文件列表行数据 +type FileListRow struct { + FileType string `json:"fileType"` // 文件类型 + FileMode string `json:"fileMode"` // 文件的权限 + LinkCount int64 `json:"linkCount"` // 硬链接数目 + Owner string `json:"owner"` // 所属用户 + Group string `json:"group"` // 所属组 + Size string `json:"size"` // 文件的大小 + ModifiedTime int64 `json:"modifiedTime"` // 最后修改时间,单位为秒 + FileName string `json:"fileName"` // 文件的名称 +} + +// 文件列表 +// neIp 网元IP空字符串为本地 +// search 文件名后模糊* +// +// return 目录大小,行记录,异常 +func FileList(path, neIp, search string) (string, []FileListRow, error) { + totalSize := "" + var rows []FileListRow + rowStr := "" + + // 发送命令 + searchStr := "" + if search != "" { + searchStr = search + "*" + } + pathStr := fmt.Sprintf("cd %s \n", path) + cmdStr := fmt.Sprintf("ls -lht --time-style=+%%s %s \n", searchStr) + + // 是否远程读取 + if neIp != "" { + usernameNe := config.Get("ne.user").(string) // 网元统一用户 + sshHost := fmt.Sprintf("%s@%s", usernameNe, neIp) + resultStr, err := cmd.ExecWithCheck("ssh", sshHost, pathStr, cmdStr) + if err != nil { + logger.Errorf("Ne FileList Path: %s, Search: %s, Error:%s", path, search, err.Error()) + return totalSize, rows, err + } + rowStr = resultStr + } else { + resultStr, err := cmd.Execf(pathStr, cmdStr) + if err != nil { + logger.Errorf("Ne FileList Path: %s, Search: %s, Error:%s", path, search, err.Error()) + return totalSize, rows, err + } + rowStr = resultStr + } + + // 遍历组装 + rowStrList := strings.Split(rowStr, "\n") + for i, rowStr := range rowStrList { + if rowStr == "" { + continue + } + // 使用空格对字符串进行切割 + fields := strings.Fields(rowStr) + + // 无查询过滤会有total总计 + if i == 0 && searchStr == "" { + totalSize = fields[1] + continue + } + + // 拆分不足7位跳过 + if len(fields) != 7 { + continue + } + + // 文件类型 + fileMode := fields[0] + fileType := "file" + if fileMode[0] == 'd' { + fileType = "dir" + } else if fileMode[0] == 'l' { + fileType = "symlink" + } + + // 提取各个字段的值 + rows = append(rows, FileListRow{ + FileMode: fileMode, + FileType: fileType, + LinkCount: parse.Number(fields[1]), + Owner: fields[2], + Group: fields[3], + Size: fields[4], + ModifiedTime: parse.Number(fields[5]), + FileName: fields[6], + }) + } + return totalSize, rows, nil +} diff --git a/src/framework/utils/ssh/scp.go b/src/framework/utils/ssh/scp.go index b303a9d..2a50d78 100644 --- a/src/framework/utils/ssh/scp.go +++ b/src/framework/utils/ssh/scp.go @@ -6,44 +6,39 @@ import ( "os/exec" "path/filepath" - "ems.agt/lib/core/conf" - "ems.agt/lib/log" + "ems.agt/src/framework/config" + "ems.agt/src/framework/logger" ) // 网元NE 文件复制到远程文件 func FileSCPLocalToNe(neIp, localPath, nePath string) error { - usernameNe := conf.Get("ne.user").(string) + usernameNe := config.Get("ne.user").(string) // scp /path/to/local/file.txt user@remote-server:/path/to/remote/directory/ neDir := fmt.Sprintf("%s@%s:%s", usernameNe, neIp, nePath) cmd := exec.Command("scp", "-r", localPath, neDir) - out, err := cmd.CombinedOutput() + _, err := cmd.CombinedOutput() if err != nil { + logger.Errorf("FileSCPLocalToNe %s", err.Error()) return err } - log.Infof("FileSCPLocalToNe %s", string(out)) return nil } // 网元NE 远程文件复制到本地文件 func FileSCPNeToLocal(neIp, nePath, localPath string) error { - // 获取文件所在的目录路径 - dirPath := filepath.Dir(localPath) - // 确保文件夹路径存在 - err := os.MkdirAll(dirPath, os.ModePerm) - if err != nil { - log.Errorf("FileSCPNeToLocal MkdirAll err %v", err) + if err := os.MkdirAll(filepath.Dir(localPath), 0750); err != nil { + logger.Errorf("FileSCPNeToLocal MkdirAll err %v", err) return err } - - usernameNe := conf.Get("ne.user").(string) + usernameNe := config.Get("ne.user").(string) // scp user@remote-server:/path/to/remote/directory/ /path/to/local/file.txt neDir := fmt.Sprintf("%s@%s:%s", usernameNe, neIp, nePath) cmd := exec.Command("scp", "-r", neDir, localPath) - out, err := cmd.CombinedOutput() + _, err := cmd.CombinedOutput() if err != nil { + logger.Errorf("FileSCPNeToLocal %s", err.Error()) return err } - log.Infof("FileSCPNeToLocal %s", string(out)) return nil } diff --git a/src/modules/common/common.go b/src/modules/common/common.go index 6890f58..419a7ec 100644 --- a/src/modules/common/common.go +++ b/src/modules/common/common.go @@ -3,7 +3,6 @@ package common import ( "ems.agt/src/framework/logger" "ems.agt/src/framework/middleware" - "ems.agt/src/framework/middleware/collectlogs" "ems.agt/src/modules/common/controller" "github.com/gin-gonic/gin" @@ -26,12 +25,6 @@ func Setup(router *gin.Engine) { // 系统可暴露的配置信息 indexGroup.GET("/sys-conf", controller.NewCommont.SysConfig) - // 转存上传文件到静态资源 - indexGroup.POST("/transferStaticFile", - middleware.PreAuthorize(nil), - collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.helpDoc", collectlogs.BUSINESS_TYPE_UPDATE)), - controller.NewCommont.TransferStaticFile, - ) // 验证码操作处理 indexGroup.GET("/captchaImage", @@ -92,5 +85,6 @@ func Setup(router *gin.Engine) { 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) } } diff --git a/src/modules/common/controller/common.go b/src/modules/common/controller/common.go index a47d295..4ba3776 100644 --- a/src/modules/common/controller/common.go +++ b/src/modules/common/controller/common.go @@ -1,18 +1,11 @@ package controller import ( - "fmt" - "path/filepath" - "strings" - - "ems.agt/src/framework/config" "ems.agt/src/framework/i18n" "ems.agt/src/framework/utils/ctx" - "ems.agt/src/framework/utils/file" "ems.agt/src/framework/vo/result" commonService "ems.agt/src/modules/common/service" "github.com/gin-gonic/gin" - "github.com/gin-gonic/gin/binding" ) // 实例化控制层 CommontController 结构体 @@ -76,43 +69,3 @@ func (s *CommontController) SysConfig(c *gin.Context) { c.JSON(200, result.OkData(data)) } - -// 转存指定对应文件 -// -// POST /transferStaticFile -func (s *CommontController) TransferStaticFile(c *gin.Context) { - language := ctx.AcceptLanguage(c) - 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"))) - return - } - - // 取语言前缀 - lang := strings.SplitN(body.Language, "_", 2)[0] - - // 默认静态资源 - 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())) - return - } - - delPrefix := strings.Replace(body.StaticPath, static["prefix"].(string), "", 1) - staticPath := strings.Replace(delPrefix, "{language}", lang, 1) - newFile := fmt.Sprintf("%s%s", dir, staticPath) - - err = file.CopyUploadFile(body.UploadPath, newFile) - if err != nil { - c.JSON(400, result.CodeMsg(400, err.Error())) - return - } - - urlPath := strings.Replace(newFile, dir, static["prefix"].(string), 1) - c.JSON(200, result.OkData(filepath.ToSlash(urlPath))) -} diff --git a/src/modules/common/controller/file.go b/src/modules/common/controller/file.go index 352fee7..d925221 100644 --- a/src/modules/common/controller/file.go +++ b/src/modules/common/controller/file.go @@ -4,8 +4,10 @@ import ( "encoding/base64" "fmt" "net/url" + "path/filepath" "strings" + "ems.agt/src/framework/config" "ems.agt/src/framework/constants/uploadsubpath" "ems.agt/src/framework/i18n" "ems.agt/src/framework/utils/ctx" @@ -13,6 +15,7 @@ import ( "ems.agt/src/framework/vo/result" "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" ) // 实例化控制层 FileController 结构体 @@ -190,3 +193,43 @@ func (s *FileController) ChunkUpload(c *gin.Context) { } c.JSON(206, result.OkData(chunkFilePath)) } + +// 转存指定对应文件到静态目录 +// +// POST /transferStaticFile +func (s *CommontController) TransferStaticFile(c *gin.Context) { + language := ctx.AcceptLanguage(c) + 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"))) + return + } + + // 取语言前缀 + lang := strings.SplitN(body.Language, "_", 2)[0] + + // 默认静态资源 + 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())) + return + } + + delPrefix := strings.Replace(body.StaticPath, static["prefix"].(string), "", 1) + staticPath := strings.Replace(delPrefix, "{language}", lang, 1) + newFile := fmt.Sprintf("%s%s", dir, staticPath) + + err = file.CopyUploadFile(body.UploadPath, newFile) + if err != nil { + c.JSON(400, result.CodeMsg(400, err.Error())) + return + } + + urlPath := strings.Replace(newFile, dir, static["prefix"].(string), 1) + c.JSON(200, result.OkData(filepath.ToSlash(urlPath))) +} diff --git a/src/modules/network_element/controller/ne_action.go b/src/modules/network_element/controller/ne_action.go index f001b63..d56408a 100644 --- a/src/modules/network_element/controller/ne_action.go +++ b/src/modules/network_element/controller/ne_action.go @@ -66,3 +66,93 @@ func (s *NeActionController) PushFile(c *gin.Context) { neFilePath := fmt.Sprintf("%s/%s", nePath, fileName) c.JSON(200, result.OkData(filepath.ToSlash(neFilePath))) } + +// 从网元端获取文件 +// +// GET /pullFile +func (s *NeActionController) PullFile(c *gin.Context) { + language := ctx.AcceptLanguage(c) + var querys struct { + NeType string `form:"neType" binding:"required"` + NeID string `form:"neId" binding:"required"` + Path string `form:"path" binding:"required"` + FileName string `form:"fileName" binding:"required"` + } + if err := c.ShouldBindQuery(&querys); err != nil { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID(querys.NeType, querys.NeID) + if neInfo.NeId != querys.NeID || neInfo.IP == "" { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + + nePath := fmt.Sprintf("%s/%s", querys.Path, querys.FileName) + localPath := fmt.Sprintf("/tmp/omc/pullFile/%s", querys.FileName) + err := ssh.FileSCPNeToLocal(neInfo.IP, nePath, localPath) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + c.FileAttachment(localPath, querys.FileName) +} + +// 网元端文件列表 +// +// GET /files +func (s *NeActionController) Files(c *gin.Context) { + language := ctx.AcceptLanguage(c) + var querys struct { + NeType string `form:"neType" binding:"required"` + NeID string `form:"neId" binding:"required"` + 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 { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID(querys.NeType, querys.NeID) + if neInfo.NeId != querys.NeID || neInfo.IP == "" { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + + totalSize, rows, err := ssh.FileList(querys.Path, neInfo.IP, querys.Search) + if err != nil { + c.JSON(200, result.Ok(map[string]any{ + "path": querys.Path, + "totalSize": totalSize, + "total": len(rows), + "rows": []ssh.FileListRow{}, + })) + return + } + + // 对数组进行切片分页 + lenNum := int64(len(rows)) + start := (querys.PageNum - 1) * querys.PageSize + end := start + querys.PageSize + var splitRows []ssh.FileListRow + if start >= lenNum { + splitRows = []ssh.FileListRow{} + } else if end >= lenNum { + splitRows = rows[start:] + } else { + splitRows = rows[start:end] + } + + c.JSON(200, result.Ok(map[string]any{ + "path": querys.Path, + "totalSize": totalSize, + "total": lenNum, + "rows": splitRows, + })) +} diff --git a/src/modules/network_element/network_element.go b/src/modules/network_element/network_element.go index d156c50..866095e 100644 --- a/src/modules/network_element/network_element.go +++ b/src/modules/network_element/network_element.go @@ -25,7 +25,14 @@ func Setup(router *gin.Engine) { // 网元处理 neActionGroup := neGroup.Group("/action") { - // 发送文件到网元服务器 + neActionGroup.GET("/files", + middleware.PreAuthorize(nil), + controller.NewNeAction.Files, + ) + neActionGroup.GET("/pullFile", + middleware.PreAuthorize(nil), + controller.NewNeAction.PullFile, + ) neActionGroup.POST("/pushFile", middleware.PreAuthorize(nil), collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.neAction", collectlogs.BUSINESS_TYPE_IMPORT)), diff --git a/src/modules/system/controller/sys_user.go b/src/modules/system/controller/sys_user.go index 6bf77cd..63202a8 100644 --- a/src/modules/system/controller/sys_user.go +++ b/src/modules/system/controller/sys_user.go @@ -507,11 +507,12 @@ func (s *SysUserController) Export(c *gin.Context) { "E1": i18n.TKey(language, "user.export.phone"), "F1": i18n.TKey(language, "user.export.sex"), "G1": i18n.TKey(language, "user.export.status"), - "H1": i18n.TKey(language, "user.export.deptID"), - "I1": i18n.TKey(language, "user.export.deptName"), - "J1": i18n.TKey(language, "user.export.deptLeader"), - "K1": i18n.TKey(language, "user.export.loginIP"), - "L1": i18n.TKey(language, "user.export.loginDate"), + "H1": i18n.TKey(language, "user.export.role"), + "I1": i18n.TKey(language, "user.export.deptID"), + "J1": i18n.TKey(language, "user.export.deptName"), + "K1": i18n.TKey(language, "user.export.deptLeader"), + "L1": i18n.TKey(language, "user.export.loginIP"), + "M1": i18n.TKey(language, "user.export.loginDate"), } // 读取用户性别字典数据 dictSysUserSex := s.sysDictDataService.SelectDictDataByType("sys_user_sex") @@ -532,6 +533,13 @@ func (s *SysUserController) Export(c *gin.Context) { if row.Status == "1" { statusValue = i18n.TKey(language, "dictData.normal") } + // 用户角色, 默认导出首个 + userRole := "" + if len(row.Roles) > 0 { + roleID := row.Roles[0].RoleID + roleName := i18n.TKey(language, row.Roles[0].RoleName) + userRole = fmt.Sprintf("%s-%s", roleID, roleName) + } dataCells = append(dataCells, map[string]any{ "A" + idx: row.UserID, "B" + idx: row.UserName, @@ -540,11 +548,12 @@ func (s *SysUserController) Export(c *gin.Context) { "E" + idx: row.PhoneNumber, "F" + idx: sysUserSex, "G" + idx: statusValue, - "H" + idx: row.Dept.DeptID, - "I" + idx: row.Dept.DeptName, - "J" + idx: row.Dept.Leader, - "K" + idx: row.LoginIP, - "L" + idx: date.ParseDateToStr(row.LoginDate, date.YYYY_MM_DD_HH_MM_SS), + "H" + idx: userRole, + "I" + idx: row.Dept.DeptID, + "J" + idx: row.Dept.DeptName, + "K" + idx: row.Dept.Leader, + "L" + idx: row.LoginIP, + "M" + idx: date.ParseDateToStr(row.LoginDate, date.YYYY_MM_DD_HH_MM_SS), }) } @@ -655,22 +664,32 @@ func (s *SysUserController) ImportData(c *gin.Context) { break } } + // 用户状态 sysUserStatus := common.STATUS_NO - if row["G"] == "Normal" { + if row["G"] == "正常" || row["G"] == "Normal" { sysUserStatus = common.STATUS_YES } + // 用户角色 拿编号 + sysUserRole := "" + if v, ok := row["H"]; ok && v != "" { + sysUserRole = strings.SplitN(v, "-", 2)[0] + if sysUserRole == "1" { + sysUserRole = "" + } + } // 构建用户实体信息 newSysUser := model.SysUser{ UserType: "sys", Password: initPassword, - DeptID: row["H"], + DeptID: row["I"], UserName: row["B"], NickName: row["C"], PhoneNumber: row["E"], Email: row["D"], Status: sysUserStatus, Sex: sysUserSex, + RoleIDs: []string{sysUserRole}, } // 检查手机号码格式并判断是否唯一 @@ -725,7 +744,7 @@ func (s *SysUserController) ImportData(c *gin.Context) { successMsgArr = append(successMsgArr, msg) } else { // 用户编号:%s 登录名称 %s 导入失败 - msg := i18n.TTemplate(language, "user.import.fail", map[string]any{"id": row["A"], "email": newSysUser.UserName}) + msg := i18n.TTemplate(language, "user.import.fail", map[string]any{"id": row["A"], "name": newSysUser.UserName}) failureNum++ failureMsgArr = append(failureMsgArr, msg) } @@ -739,12 +758,12 @@ func (s *SysUserController) ImportData(c *gin.Context) { rows := s.sysUserService.UpdateUser(newSysUser) if rows > 0 { // 用户编号:%s 登录名称 %s 更新成功 - msg := i18n.TTemplate(language, "user.import.successUpdate", map[string]any{"id": row["A"], "email": newSysUser.UserName}) + msg := i18n.TTemplate(language, "user.import.successUpdate", map[string]any{"id": row["A"], "name": newSysUser.UserName}) successNum++ successMsgArr = append(successMsgArr, msg) } else { // 用户编号:%s 登录名称 %s 更新失败 - msg := i18n.TTemplate(language, "user.import.failUpdate", map[string]any{"id": row["A"], "email": newSysUser.UserName}) + msg := i18n.TTemplate(language, "user.import.failUpdate", map[string]any{"id": row["A"], "name": newSysUser.UserName}) failureNum++ failureMsgArr = append(failureMsgArr, msg) } diff --git a/src/modules/trace/controller/tcpdump.go b/src/modules/trace/controller/tcpdump.go index 6924de5..bb7f444 100644 --- a/src/modules/trace/controller/tcpdump.go +++ b/src/modules/trace/controller/tcpdump.go @@ -104,7 +104,7 @@ func (s *TcpdumpController) Download(c *gin.Context) { } nePath := fmt.Sprintf("/tmp/%s", body.FileName) - localPath := fmt.Sprintf("%s/tcpdump/%s", config.Get("ne.scpdir"), body.FileName) + localPath := fmt.Sprintf("/tmp/omc/tcpdump/%s", body.FileName) err = ssh.FileSCPNeToLocal(neInfo.IP, nePath, localPath) if err != nil { c.JSON(200, result.ErrMsg(err.Error()))