From 955aba902b80eda6d511dda09e914464423b2216 Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Fri, 27 Oct 2023 09:40:53 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=90=88=E5=B9=B6=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- features/cm/ne.go | 20 +- features/cm/software.go | 5 +- features/trace/tcpdump.go | 10 +- features/udm_user/api_udm_user.go | 7 +- restagent/config/config.go | 1 + src/app.go | 30 +- .../template/excel/user_import_template.xlsx | Bin 0 -> 9441 bytes src/framework/cmd/cmd.go | 201 ++++++++++++++ src/framework/config/config.go | 10 + src/framework/utils/file/csv.go | 88 ++++++ src/framework/utils/file/txt.go | 79 ++++++ src/framework/utils/ssh/scp.go | 49 ++++ src/modules/crontask/crontask.go | 5 +- src/modules/net_element/controller/ne_info.go | 39 +++ src/modules/net_element/model/ne_info.go | 19 ++ src/modules/net_element/net_element.go | 23 ++ src/modules/net_element/repository/ne_info.go | 11 + .../net_element/repository/ne_info.impl.go | 69 +++++ src/modules/net_element/service/ne_info.go | 9 + .../net_element/service/ne_info.impl.go | 22 ++ src/modules/system/controller/sys_user.go | 36 ++- src/modules/system/service/sys_user.impl.go | 32 +-- src/modules/trace/controller/tcpdump.go | 262 ++++++++++++++++++ src/modules/trace/service/tcpdump.go | 7 + src/modules/trace/service/tcpdump.impl.go | 41 +++ src/modules/trace/trace.go | 35 +++ 26 files changed, 1057 insertions(+), 53 deletions(-) create mode 100644 src/assets/template/excel/user_import_template.xlsx create mode 100644 src/framework/cmd/cmd.go create mode 100644 src/framework/utils/file/csv.go create mode 100644 src/framework/utils/file/txt.go create mode 100644 src/framework/utils/ssh/scp.go create mode 100644 src/modules/net_element/controller/ne_info.go create mode 100644 src/modules/net_element/model/ne_info.go create mode 100644 src/modules/net_element/net_element.go create mode 100644 src/modules/net_element/repository/ne_info.go create mode 100644 src/modules/net_element/repository/ne_info.impl.go create mode 100644 src/modules/net_element/service/ne_info.go create mode 100644 src/modules/net_element/service/ne_info.impl.go create mode 100644 src/modules/trace/controller/tcpdump.go create mode 100644 src/modules/trace/service/tcpdump.go create mode 100644 src/modules/trace/service/tcpdump.impl.go create mode 100644 src/modules/trace/trace.go diff --git a/features/cm/ne.go b/features/cm/ne.go index 329ac9d..fd480da 100644 --- a/features/cm/ne.go +++ b/features/cm/ne.go @@ -423,7 +423,13 @@ func ExportCmFromNF(w http.ResponseWriter, r *http.Request) { var scpCmd string ipType := global.ParseIPAddr(neInfo.Ip) - if neTypeLower != "omc" { + omcNetypeLower := strings.ToLower(config.GetYamlConfig().OMC.NeType) + etcListIMS := "{*.yaml,mmtel,vars.cfg}" + if config.GetYamlConfig().NE.EtcListIMS != "" { + etcListIMS = config.GetYamlConfig().NE.EtcListIMS + } + switch neTypeLower { + case omcNetypeLower: if ipType == global.IsIPv4 { scpCmd = fmt.Sprintf("scp -r %s@%s:%s/%s/*.yaml %s/etc/%s", config.GetYamlConfig().NE.User, neInfo.Ip, config.GetYamlConfig().NE.EtcDir, @@ -433,7 +439,17 @@ func ExportCmFromNF(w http.ResponseWriter, r *http.Request) { neInfo.Ip, config.GetYamlConfig().NE.EtcDir, neTypeLower, config.GetYamlConfig().OMC.Backup, neTypeLower) } - } else { + case "ims": + if ipType == global.IsIPv4 { + scpCmd = fmt.Sprintf("scp -r %s@%s:%s/%s/%s %s/etc/%s", config.GetYamlConfig().NE.User, + neInfo.Ip, config.GetYamlConfig().NE.EtcDir, neTypeLower, + etcListIMS, config.GetYamlConfig().OMC.Backup, neTypeLower) + } else { + scpCmd = fmt.Sprintf("scp -r %s@[%s]:%s/%s/%s %s/etc/%s", config.GetYamlConfig().NE.User, + neInfo.Ip, config.GetYamlConfig().NE.EtcDir, neTypeLower, + etcListIMS, config.GetYamlConfig().OMC.Backup, neTypeLower) + } + default: if ipType == global.IsIPv4 { scpCmd = fmt.Sprintf("scp -r %s@%s:%s/etc/*.yaml %s/etc/%s", config.GetYamlConfig().NE.User, neInfo.Ip, config.GetYamlConfig().NE.OmcDir, config.GetYamlConfig().OMC.Backup, neTypeLower) diff --git a/features/cm/software.go b/features/cm/software.go index d264b9c..535a0fe 100644 --- a/features/cm/software.go +++ b/features/cm/software.go @@ -698,7 +698,7 @@ func ActiveSoftwareToNF(w http.ResponseWriter, r *http.Request) { return } } else if fileType == 2 { - dpkgCmd := fmt.Sprintf("sudo dpkg -i '%s'", filePath) + dpkgCmd := fmt.Sprintf("sudo dpkg -i --force-all '%s'", filePath) cmd := exec.Command("ssh", sshHost, dpkgCmd) out, err := cmd.CombinedOutput() log.Debugf("Exec output: %v", string(out)) @@ -744,7 +744,6 @@ func ActiveSoftwareToNF(w http.ResponseWriter, r *http.Request) { services.ResponseInternalServerError500ProcessError(w, err) return } - } } @@ -839,7 +838,7 @@ func RollBackSoftwareToNF(w http.ResponseWriter, r *http.Request) { return } } else if fileType == 2 { - dpkgCmd := fmt.Sprintf("sudo dpkg -i '%s'", filePath) + dpkgCmd := fmt.Sprintf("sudo dpkg -i --force-all '%s'", filePath) cmd := exec.Command("ssh", sshHost, dpkgCmd) out, err := cmd.CombinedOutput() log.Debugf("Exec output: %v", string(out)) diff --git a/features/trace/tcpdump.go b/features/trace/tcpdump.go index 06bc9f5..a4c6b4a 100644 --- a/features/trace/tcpdump.go +++ b/features/trace/tcpdump.go @@ -7,14 +7,14 @@ import ( "strings" "time" - "ems.agt/lib/core/cmd" "ems.agt/lib/core/conf" - "ems.agt/lib/core/file" "ems.agt/lib/core/utils/ctx" "ems.agt/lib/core/vo/result" "ems.agt/lib/dborm" "ems.agt/lib/log" "ems.agt/restagent/config" + "ems.agt/src/framework/cmd" + "ems.agt/src/framework/utils/ssh" ) var ( @@ -101,7 +101,7 @@ func TcpdumpPcapDownload(w http.ResponseWriter, r *http.Request) { nePath := fmt.Sprintf("/tmp/%s", body.FileName) localPath := fmt.Sprintf("%s/tcpdump/pcap/%s", conf.Get("ne.omcdir"), body.FileName) - err = file.FileSCPNeToLocal(neInfo.Ip, nePath, localPath) + err = ssh.FileSCPNeToLocal(neInfo.Ip, nePath, localPath) if err != nil { ctx.JSON(w, 200, result.ErrMsg(err.Error())) return @@ -218,7 +218,7 @@ func TcpdumpNeUPFTask(w http.ResponseWriter, r *http.Request) { fileLogName := fmt.Sprintf("tmp_%s_%s.log", body.NeType, body.NeId) filePcapName := fmt.Sprintf("tmp_%s_%s.pcap", body.NeType, body.NeId) // 复制文件到网元上 - err := file.FileSCPLocalToNe(neInfo.Ip, "C:\\AMP\\Probject\\ems_backend\\restagent\\backup\\upf_pcap", "/tmp") + err := ssh.FileSCPLocalToNe(neInfo.Ip, "C:\\AMP\\Probject\\ems_backend\\restagent\\backup\\upf_pcap", "/tmp") if err != nil { ctx.JSON(w, 200, result.ErrMsg(err.Error())) return @@ -245,7 +245,7 @@ func TcpdumpNeUPFTask(w http.ResponseWriter, r *http.Request) { fileLogName := fmt.Sprintf("tmp_%s_%s.log", body.NeType, body.NeId) filePcapName := fmt.Sprintf("tmp_%s_%s.pcap", body.NeType, body.NeId) // cmdStr := "cd /tmp \nexpect /tmp/cat.sh " - err := file.FileSCPLocalToNe(neInfo.Ip, "C:\\AMP\\Probject\\ems_backend\\restagent\\backup\\upf_pcap", "/tmp") + err := ssh.FileSCPLocalToNe(neInfo.Ip, "C:\\AMP\\Probject\\ems_backend\\restagent\\backup\\upf_pcap", "/tmp") if err != nil { ctx.JSON(w, 200, result.ErrMsg(err.Error())) return diff --git a/features/udm_user/api_udm_user.go b/features/udm_user/api_udm_user.go index c441085..dd58d08 100644 --- a/features/udm_user/api_udm_user.go +++ b/features/udm_user/api_udm_user.go @@ -10,7 +10,6 @@ import ( "ems.agt/features/udm_user/model" "ems.agt/features/udm_user/service" "ems.agt/lib/core/conf" - "ems.agt/lib/core/file" mmlclient "ems.agt/lib/core/mml_client" "ems.agt/lib/core/utils/ctx" "ems.agt/lib/core/utils/parse" @@ -21,6 +20,8 @@ import ( "ems.agt/lib/services" "ems.agt/restagent/config" "ems.agt/src/framework/middleware/collectlogs" + "ems.agt/src/framework/utils/file" + "ems.agt/src/framework/utils/ssh" ) // UDM 用户信息接口添加到路由 @@ -588,7 +589,7 @@ func (s *UdmUserApi) UdmAuthUserImport(w http.ResponseWriter, r *http.Request) { } // 复制到远程 - err = file.FileSCPLocalToNe(neInfo.Ip, localPath, nePath) + err = ssh.FileSCPLocalToNe(neInfo.Ip, localPath, nePath) if err != nil { ctx.JSON(w, 200, result.ErrMsg(err.Error())) return @@ -1179,7 +1180,7 @@ func (s *UdmUserApi) UdmSubUserImport(w http.ResponseWriter, r *http.Request) { } // 复制到远程 - err = file.FileSCPLocalToNe(neInfo.Ip, localPath, nePath) + err = ssh.FileSCPLocalToNe(neInfo.Ip, localPath, nePath) if err != nil { ctx.JSON(w, 200, result.ErrMsg(err.Error())) return diff --git a/restagent/config/config.go b/restagent/config/config.go index 29aba63..2e5fd60 100644 --- a/restagent/config/config.go +++ b/restagent/config/config.go @@ -114,6 +114,7 @@ type YamlConfig struct { OmcDir string `yaml:"omcdir"` ScpDir string `yaml:"scpdir"` LicenseDir string `yaml:"licensedir"` + EtcListIMS string `yaml:"etcListIMS"` } `yaml:"ne"` Auth struct { diff --git a/src/app.go b/src/app.go index 2e7260c..05f0a4e 100644 --- a/src/app.go +++ b/src/app.go @@ -1,6 +1,7 @@ package src import ( + "embed" "fmt" "ems.agt/src/framework/config" @@ -10,11 +11,16 @@ import ( "ems.agt/src/modules/common" "ems.agt/src/modules/crontask" "ems.agt/src/modules/monitor" + netelement "ems.agt/src/modules/net_element" "ems.agt/src/modules/system" + "ems.agt/src/modules/trace" "github.com/gin-gonic/gin" ) +//go:embed assets/* +var assetsDir embed.FS + // 路由函数句柄,交给由 http.ListenAndServe(addr, router) func AppEngine() *gin.Engine { app := initAppEngine() @@ -25,12 +31,11 @@ func AppEngine() *gin.Engine { // 初始模块路由 initModulesRoute(app) + // 设置程序内全局资源访问 + config.SetAssetsDirFS(assetsDir) + // 读取服务配置 app.ForwardedByClientIP = config.Get("server.proxy").(bool) - addr := fmt.Sprintf(":%d", config.Get("server.port").(int)) - - // 启动服务 - fmt.Printf("\nopen http://localhost%s \n\n", addr) return app } @@ -43,13 +48,7 @@ func AppEngine() *gin.Engine { // } // } func RunServer() error { - app := initAppEngine() - - // 初始全局默认 - initDefeat(app) - - // 初始模块路由 - initModulesRoute(app) + app := AppEngine() // 读取服务配置 app.ForwardedByClientIP = config.Get("server.proxy").(bool) @@ -113,9 +112,16 @@ func initDefeat(app *gin.Engine) { // 初始模块路由 func initModulesRoute(app *gin.Engine) { - + // 通用模块 common.Setup(app) + // 监控模块 monitor.Setup(app) + // 系统模块 system.Setup(app) + // 网元模块 + netelement.Setup(app) + // 跟踪模块 + trace.Setup(app) + // 调度任务模块--暂无接口 crontask.Setup(app) } diff --git a/src/assets/template/excel/user_import_template.xlsx b/src/assets/template/excel/user_import_template.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..758a4b5a6333e2c98c7444a10cc8af58556ce05c GIT binary patch literal 9441 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 KT@TR monitor 模块路由") + logger.Infof("开始加载 ====> crontask 模块路由") - // 启动时需要的初始参数 + // 初始定时任务队列 InitCronQueue() - } // InitCronQueue 初始定时任务队列 diff --git a/src/modules/net_element/controller/ne_info.go b/src/modules/net_element/controller/ne_info.go new file mode 100644 index 0000000..75b32f8 --- /dev/null +++ b/src/modules/net_element/controller/ne_info.go @@ -0,0 +1,39 @@ +package controller + +import ( + "ems.agt/src/framework/vo/result" + netElementService "ems.agt/src/modules/net_element/service" + "github.com/gin-gonic/gin" +) + +// 实例化控制层 NeInfoController 结构体 +var NewNeInfo = &NeInfoController{ + NeInfoService: netElementService.NewNeInfoImpl, +} + +// 网元信息请求 +// +// PATH /ne-info +type NeInfoController struct { + // 网元信息服务 + NeInfoService netElementService.INeInfo +} + +// 网元neType和neID查询 +// +// GET /info +func (s *NeInfoController) NeTypeAndID(c *gin.Context) { + neType := c.Query("neType") + neId := c.Query("neId") + if neType == "" || neId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + data := s.NeInfoService.SelectNeInfoByNeTypeAndNeID(neType, neId) + if data.NeType == neType { + c.JSON(200, result.OkData(data)) + return + } + c.JSON(200, result.Err(nil)) +} diff --git a/src/modules/net_element/model/ne_info.go b/src/modules/net_element/model/ne_info.go new file mode 100644 index 0000000..71844c1 --- /dev/null +++ b/src/modules/net_element/model/ne_info.go @@ -0,0 +1,19 @@ +package model + +// NeInfo 网元信息对象 +type NeInfo struct { + ID int64 `json:"id"` + NeType string `json:"neType"` + NeId string `json:"neId"` + RmUID string `json:"rmUid"` + NeName string `json:"neName"` + IP string `json:"ip"` + Port int64 `json:"port"` + PvFlag string `json:"pvFlag"` // enum('PNF','VNF') + Province string `json:"province"` + VendorName string `json:"vendorName"` + Dn string `json:"dn"` + NeAddress string `json:"neAddress"` + Status string `json:"status"` // 0: 在线 1: 下线 2: 备用 3: 工程 + UpdateTime string `json:"updateTime"` +} diff --git a/src/modules/net_element/net_element.go b/src/modules/net_element/net_element.go new file mode 100644 index 0000000..b7c0049 --- /dev/null +++ b/src/modules/net_element/net_element.go @@ -0,0 +1,23 @@ +package netelement + +import ( + "ems.agt/src/framework/logger" + "ems.agt/src/framework/middleware" + "ems.agt/src/modules/net_element/controller" + + "github.com/gin-gonic/gin" +) + +// 模块路由注册 +func Setup(router *gin.Engine) { + logger.Infof("开始加载 ====> net_element 模块路由") + + // 网元信息 + netInfoGroup := router.Group("/ne-info") + { + netInfoGroup.GET("/info", + middleware.PreAuthorize(nil), + controller.NewNeInfo.NeTypeAndID, + ) + } +} diff --git a/src/modules/net_element/repository/ne_info.go b/src/modules/net_element/repository/ne_info.go new file mode 100644 index 0000000..fd02208 --- /dev/null +++ b/src/modules/net_element/repository/ne_info.go @@ -0,0 +1,11 @@ +package repository + +import ( + "ems.agt/src/modules/net_element/model" +) + +// 网元信息 数据层接口 +type INeInfo interface { + // SelectNeInfoByNeTypeAndNeID 通过ne_type和ne_id查询网元信息 + SelectNeInfoByNeTypeAndNeID(neType, neID string) model.NeInfo +} diff --git a/src/modules/net_element/repository/ne_info.impl.go b/src/modules/net_element/repository/ne_info.impl.go new file mode 100644 index 0000000..af95725 --- /dev/null +++ b/src/modules/net_element/repository/ne_info.impl.go @@ -0,0 +1,69 @@ +package repository + +import ( + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/net_element/model" +) + +// 实例化数据层 NeInfoImpl 结构体 +var NewNeInfoImpl = &NeInfoImpl{ + selectSql: `select id, ne_type, ne_id, rm_uid, ne_name, ip, port, pv_flag, province, vendor_name, dn, ne_address, status, update_time from ne_info`, + + resultMap: map[string]string{ + "id": "ID", + "ne_type": "NeType", + "ne_id": "NeId", + "rm_uid": "RmUID", + "ne_name": "NeName", + "ip": "IP", + "port": "Port", + "pv_flag": "PvFlag", + "province": "Province", + "vendor_name": "VendorName", + "dn": "Dn", + "ne_address": "NeAddress", + "status": "Status", + "update_time": "UpdateTime", + }, +} + +// NeInfoImpl 网元信息表 数据层处理 +type NeInfoImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *NeInfoImpl) convertResultRows(rows []map[string]any) []model.NeInfo { + arr := make([]model.NeInfo, 0) + for _, row := range rows { + item := model.NeInfo{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&item, keyMapper, value) + } + } + arr = append(arr, item) + } + return arr +} + +// SelectNeInfoByNeTypeAndNeID 通过ne_type和ne_id查询网元信息 +func (r *NeInfoImpl) SelectNeInfoByNeTypeAndNeID(neType, neID string) model.NeInfo { + querySql := r.selectSql + " where ne_type = ? and ne_id = ?" + results, err := datasource.RawDB("", querySql, []any{neType, neID}) + if err != nil { + logger.Errorf("query err => %v", err) + return model.NeInfo{} + } + // 转换实体 + rows := r.convertResultRows(results) + if len(rows) > 0 { + return rows[0] + } + return model.NeInfo{} +} diff --git a/src/modules/net_element/service/ne_info.go b/src/modules/net_element/service/ne_info.go new file mode 100644 index 0000000..5efc53f --- /dev/null +++ b/src/modules/net_element/service/ne_info.go @@ -0,0 +1,9 @@ +package service + +import "ems.agt/src/modules/net_element/model" + +// 网元信息 服务层接口 +type INeInfo interface { + // SelectNeInfoByNeTypeAndNeID 通过ne_type和ne_id查询网元信息 + SelectNeInfoByNeTypeAndNeID(neType, neID string) model.NeInfo +} diff --git a/src/modules/net_element/service/ne_info.impl.go b/src/modules/net_element/service/ne_info.impl.go new file mode 100644 index 0000000..d76e81a --- /dev/null +++ b/src/modules/net_element/service/ne_info.impl.go @@ -0,0 +1,22 @@ +package service + +import ( + "ems.agt/src/modules/net_element/model" + "ems.agt/src/modules/net_element/repository" +) + +// 实例化服务层 NeInfoImpl 结构体 +var NewNeInfoImpl = &NeInfoImpl{ + NeInfoRepository: repository.NewNeInfoImpl, +} + +// 网元信息 服务层处理 +type NeInfoImpl struct { + // 网元信息数据信息 + NeInfoRepository repository.INeInfo +} + +// SelectNeInfoByNeTypeAndNeID 通过ne_type和ne_id查询网元信息 +func (r *NeInfoImpl) SelectNeInfoByNeTypeAndNeID(neType, neID string) model.NeInfo { + return r.NeInfoRepository.SelectNeInfoByNeTypeAndNeID(neType, neID) +} diff --git a/src/modules/system/controller/sys_user.go b/src/modules/system/controller/sys_user.go index 138d4cd..c6eafc6 100644 --- a/src/modules/system/controller/sys_user.go +++ b/src/modules/system/controller/sys_user.go @@ -400,10 +400,11 @@ func (s *SysUserController) Export(c *gin.Context) { "E1": "手机号码", "F1": "用户性别", "G1": "帐号状态", - "H1": "最后登录IP", - "I1": "最后登录时间", - "J1": "部门名称", - "K1": "部门负责人", + "H1": "部门编号", + "I1": "部门名称", + "J1": "部门负责人", + "K1": "最后登录IP", + "L1": "最后登录时间", } // 读取用户性别字典数据 dictSysUserSex := s.sysDictDataService.SelectDictDataByType("sys_user_sex") @@ -432,10 +433,11 @@ func (s *SysUserController) Export(c *gin.Context) { "E" + idx: row.PhoneNumber, "F" + idx: sysUserSex, "G" + idx: statusValue, - "H" + idx: row.LoginIP, - "I" + idx: date.ParseDateToStr(row.LoginDate, date.YYYY_MM_DD_HH_MM_SS), - "J" + idx: row.Dept.DeptName, - "K" + idx: row.Dept.Leader, + "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), }) } @@ -455,7 +457,23 @@ func (s *SysUserController) Export(c *gin.Context) { func (s *SysUserController) Template(c *gin.Context) { fileName := fmt.Sprintf("user_import_template_%d.xlsx", time.Now().UnixMilli()) asserPath := "assets/template/excel/user_import_template.xlsx" - c.FileAttachment(asserPath, fileName) + + // 从 embed.FS 中读取默认配置文件内容 + assetsDir := config.GetAssetsDirFS() + + // 读取内嵌文件 + fileData, err := assetsDir.ReadFile(asserPath) + if err != nil { + c.String(500, "Failed to read file") + return + } + + // 设置响应头 + c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", fileName)) + c.Header("Content-Type", "application/octet-stream") + + // 返回响应体 + c.Data(200, "application/octet-stream", fileData) } // 用户信息列表导入 diff --git a/src/modules/system/service/sys_user.impl.go b/src/modules/system/service/sys_user.impl.go index 9f2cfa4..666fbaf 100644 --- a/src/modules/system/service/sys_user.impl.go +++ b/src/modules/system/service/sys_user.impl.go @@ -198,7 +198,7 @@ func (r *SysUserImpl) ImportUser(rows []map[string]string, isUpdateSupport bool, failureNum := 0 successMsgArr := []string{} failureMsgArr := []string{} - mustItemArr := []string{"C", "D"} + mustItemArr := []string{"B", "C"} for _, row := range rows { // 检查必填列 ownItem := true @@ -218,13 +218,13 @@ func (r *SysUserImpl) ImportUser(rows []map[string]string, isUpdateSupport bool, // 用户性别转值 sysUserSex := "0" for _, v := range dictSysUserSex { - if row["G"] == v.DictLabel { + if row["F"] == v.DictLabel { sysUserSex = v.DictValue break } } sysUserStatus := common.STATUS_NO - if row["H"] == "正常" { + if row["G"] == "正常" { sysUserStatus = common.STATUS_YES } @@ -232,11 +232,11 @@ func (r *SysUserImpl) ImportUser(rows []map[string]string, isUpdateSupport bool, newSysUser := model.SysUser{ UserType: "sys", Password: initPassword, - DeptID: row["B"], - UserName: row["C"], - NickName: row["D"], - PhoneNumber: row["F"], - Email: row["E"], + DeptID: row["H"], + UserName: row["B"], + NickName: row["C"], + PhoneNumber: row["E"], + Email: row["D"], Status: sysUserStatus, Sex: sysUserSex, } @@ -246,13 +246,13 @@ func (r *SysUserImpl) ImportUser(rows []map[string]string, isUpdateSupport bool, if regular.ValidMobile(newSysUser.PhoneNumber) { uniquePhone := r.CheckUniquePhone(newSysUser.PhoneNumber, "") if !uniquePhone { - msg := fmt.Sprintf("序号:%s 手机号码 %s 已存在", row["A"], row["F"]) + msg := fmt.Sprintf("序号:%s 手机号码 %s 已存在", row["A"], row["E"]) failureNum++ failureMsgArr = append(failureMsgArr, msg) continue } } else { - msg := fmt.Sprintf("序号:%s 手机号码 %s 格式错误", row["A"], row["F"]) + msg := fmt.Sprintf("序号:%s 手机号码 %s 格式错误", row["A"], row["E"]) failureNum++ failureMsgArr = append(failureMsgArr, msg) continue @@ -264,13 +264,13 @@ func (r *SysUserImpl) ImportUser(rows []map[string]string, isUpdateSupport bool, if regular.ValidEmail(newSysUser.Email) { uniqueEmail := r.CheckUniqueEmail(newSysUser.Email, "") if !uniqueEmail { - msg := fmt.Sprintf("序号:%s 用户邮箱 %s 已存在", row["A"], row["E"]) + msg := fmt.Sprintf("序号:%s 用户邮箱 %s 已存在", row["A"], row["D"]) failureNum++ failureMsgArr = append(failureMsgArr, msg) continue } } else { - msg := fmt.Sprintf("序号:%s 用户邮箱 %s 格式错误", row["A"], row["E"]) + msg := fmt.Sprintf("序号:%s 用户邮箱 %s 格式错误", row["A"], row["D"]) failureNum++ failureMsgArr = append(failureMsgArr, msg) continue @@ -283,11 +283,11 @@ func (r *SysUserImpl) ImportUser(rows []map[string]string, isUpdateSupport bool, newSysUser.CreateBy = operName insertId := r.InsertUser(newSysUser) if insertId != "" { - msg := fmt.Sprintf("序号:%s 登录名称 %s 导入成功", row["A"], row["C"]) + msg := fmt.Sprintf("序号:%s 登录名称 %s 导入成功", row["A"], row["B"]) successNum++ successMsgArr = append(successMsgArr, msg) } else { - msg := fmt.Sprintf("序号:%s 登录名称 %s 导入失败", row["A"], row["E"]) + msg := fmt.Sprintf("序号:%s 登录名称 %s 导入失败", row["A"], row["B"]) failureNum++ failureMsgArr = append(failureMsgArr, msg) } @@ -300,11 +300,11 @@ func (r *SysUserImpl) ImportUser(rows []map[string]string, isUpdateSupport bool, newSysUser.UpdateBy = operName rows := r.UpdateUser(newSysUser) if rows > 0 { - msg := fmt.Sprintf("序号:%s 登录名称 %s 更新成功", row["A"], row["C"]) + msg := fmt.Sprintf("序号:%s 登录名称 %s 更新成功", row["A"], row["B"]) successNum++ successMsgArr = append(successMsgArr, msg) } else { - msg := fmt.Sprintf("序号:%s 登录名称 %s 更新失败", row["A"], row["E"]) + msg := fmt.Sprintf("序号:%s 登录名称 %s 更新失败", row["A"], row["B"]) failureNum++ failureMsgArr = append(failureMsgArr, msg) } diff --git a/src/modules/trace/controller/tcpdump.go b/src/modules/trace/controller/tcpdump.go new file mode 100644 index 0000000..83e8b9e --- /dev/null +++ b/src/modules/trace/controller/tcpdump.go @@ -0,0 +1,262 @@ +package controller + +import ( + "fmt" + "strings" + + "ems.agt/src/framework/cmd" + "ems.agt/src/framework/config" + "ems.agt/src/framework/utils/ssh" + "ems.agt/src/framework/vo/result" + netElementService "ems.agt/src/modules/net_element/service" + traceService "ems.agt/src/modules/trace/service" + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 TcpdumpController 结构体 +var NewTcpdump = &TcpdumpController{ + NeInfoService: netElementService.NewNeInfoImpl, + TcpdumpService: traceService.NewTcpdumpImpl, +} + +// 信令抓包请求 +// +// PATH /tcpdump +type TcpdumpController struct { + // 网元信息服务 + NeInfoService netElementService.INeInfo + // 信令抓包服务 + TcpdumpService traceService.ITcpdump +} + +// 网元发送执行 pcap +// +// POST /ne +func (s *TcpdumpController) NeTask(c *gin.Context) { + var body struct { + NeType string `json:"neType" binding:"required"` // 网元类型 + NeId string `json:"neId" binding:"required"` // 网元ID + Timeout int `json:"timeout" binding:"required"` // 超时时间 + Cmd string `json:"cmd" binding:"required"` // 命令 + Timestamp string `json:"timestamp" binding:"required"` // 时间戳 + } + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查网元信息 + neInfo := s.NeInfoService.SelectNeInfoByNeTypeAndNeID(body.NeType, body.NeId) + if neInfo.NeId != body.NeId { + msg := fmt.Sprintf("找不到 %s %s 对应网元信息", body.NeType, body.NeId) + c.JSON(200, result.ErrMsg(msg)) + return + } + + filePcapName := fmt.Sprintf("%s_%s_%s.pcap", body.Timestamp, body.NeType, body.NeId) + fileLogName := fmt.Sprintf("%s_%s_%s.log", body.Timestamp, body.NeType, body.NeId) + writeLog := fmt.Sprintf(" > %s 2>&1 \ncat %s", fileLogName, fileLogName) // 执行信息写入日志文件,放置弹出code 127 + cmdStr := fmt.Sprintf("cd /tmp \nsudo timeout %d tcpdump -i any %s -s0 -w %s", body.Timeout, body.Cmd, filePcapName) + usernameNe := config.Get("ne.user").(string) // 网元统一用户 + sshHost := fmt.Sprintf("%s@%s", usernameNe, neInfo.IP) + msg, err := cmd.ExecWithCheck("ssh", sshHost, cmdStr+writeLog) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.JSON(200, result.OkData(map[string]any{ + "cmd": cmdStr, + "msg": msg, + "fileName": filePcapName, + })) +} + +// 网元抓包pcap文件下载 +// +// POST /download +func (s *TcpdumpController) Download(c *gin.Context) { + var body struct { + NeType string `json:"neType" binding:"required"` // 网元类型 + NeId string `json:"neId" binding:"required"` // 网元ID + FileName string `form:"fileName" ` // 文件名 + } + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查网元信息 + neInfo := s.NeInfoService.SelectNeInfoByNeTypeAndNeID(body.NeType, body.NeId) + if neInfo.NeId != body.NeId { + msg := fmt.Sprintf("找不到 %s %s 对应网元信息", body.NeType, body.NeId) + c.JSON(200, result.ErrMsg(msg)) + return + } + + nePath := fmt.Sprintf("/tmp/%s", body.FileName) + localPath := fmt.Sprintf("%s/tcpdump/%s", config.Get("ne.scpdir"), body.FileName) + err = ssh.FileSCPNeToLocal(neInfo.IP, nePath, localPath) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.FileAttachment(localPath, body.FileName) +} + +// 网元发送执行 pcap +// +// POST /neUPF +func (s *TcpdumpController) NeUPFTask(c *gin.Context) { + var body struct { + NeType string `json:"neType" binding:"required"` // 网元类型 + NeId string `json:"neId" binding:"required"` // 网元ID + RunType string `json:"runType" binding:"required"` // 执行开始start还是停止stop + Cmd string `json:"cmd" binding:"required"` // 命令 + Timestamp string `json:"timestamp" binding:"required"` // 时间戳 + } + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查网元信息 + neInfo := s.NeInfoService.SelectNeInfoByNeTypeAndNeID(body.NeType, body.NeId) + if neInfo.NeId != body.NeId { + msg := fmt.Sprintf("找不到 %s %s 对应网元信息", body.NeType, body.NeId) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 开始telnet + if body.RunType == "start_telnet" { + filePcapName := fmt.Sprintf("%s_%s_%s.pcap", body.Timestamp, body.NeType, body.NeId) + cmdStr := fmt.Sprintf("%s file %s", body.Cmd, filePcapName) + // 进行连接telnet + resultStr, err := s.TcpdumpService.UPFTelnet(neInfo.IP, cmdStr) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + // 处理结果 + s := strings.Index(resultStr, "pcap dispatch trace:") + if s == -1 { + s = strings.Index(resultStr, "Write ") + } + if s != -1 { + e := strings.Index(resultStr, "\r\nupfd1#") + resultStr = resultStr[s:e] + } else { + resultStr = "No stoppable found" + } + c.JSON(200, result.OkData(map[string]any{ + "cmd": cmdStr, + "msg": resultStr, + "fileName": filePcapName, + })) + return + } + // 停止telnet + if body.RunType == "stop_telnet" { + filePcapName := fmt.Sprintf("%s_%s_%s.pcap", body.Timestamp, body.NeType, body.NeId) + cmdStr := "pcap dispatch trace off" + // 进行连接telnet + resultStr, err := s.TcpdumpService.UPFTelnet(neInfo.IP, cmdStr) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + // 处理结果 + s := strings.Index(resultStr, "pcap dispatch trace:") + if s != -1 { + e := strings.Index(resultStr, "\r\nupfd1#") + resultStr = resultStr[s:e] + } else { + resultStr = "Executed, please stop before proceeding" + } + c.JSON(200, result.OkData(map[string]any{ + "cmd": cmdStr, + "msg": resultStr, + "fileName": filePcapName, + })) + return + } + + // 开始-脚本字符串 + if body.RunType == "start_str" { + fileLogName := fmt.Sprintf("%s_%s_%s.log", body.Timestamp, body.NeType, body.NeId) + filePcapName := fmt.Sprintf("%s_%s_%s.pcap", body.Timestamp, body.NeType, body.NeId) + scriptStr := "set capcmd [lindex $argv 0]\nspawn telnet localhost 5002\nexpect \"upfd1# \"\nsend \"$capcmd\\n\"\nexpect \"upfd1# \"\nsend \"quit\\n\"\nexpect \"eof\"" + writeLog := fmt.Sprintf(" > %s 2>&1 \ncat %s", fileLogName, fileLogName) // 执行信息写入日志文件输出,避免弹出code 127 + + capCmdStr := fmt.Sprintf("%s file %s", body.Cmd, filePcapName) + + cmdStr := fmt.Sprintf("cd /tmp\n\necho '%s' > cap.sh\n\nchmod +x cap.sh\n\nexpect ./cap.sh '%s'%s", scriptStr, capCmdStr, writeLog) + usernameNe := config.Get("ne.user").(string) // 网元统一用户 + sshHost := fmt.Sprintf("%s@%s", usernameNe, neInfo.IP) + msg, err := cmd.ExecWithCheck("ssh", sshHost, cmdStr) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + s := strings.Index(msg, "pcap dispatch trace:") + if s != -1 { + e := strings.Index(msg, "\r\nupfd1#") + msg = msg[s:e] + } else { + msg = "Executed, please stop before proceeding" + } + c.JSON(200, result.OkData(map[string]any{ + "cmd": capCmdStr, + "msg": msg, + "fileName": filePcapName, + })) + return + } + // 停止-脚本字符串 + if body.RunType == "stop_str" { + fileLogName := fmt.Sprintf("%s_%s_%s.log", body.Timestamp, body.NeType, body.NeId) + filePcapName := fmt.Sprintf("%s_%s_%s.pcap", body.Timestamp, body.NeType, body.NeId) + scriptStr := "set capcmd [lindex $argv 0]\nspawn telnet localhost 5002\nexpect \"upfd1# \"\nsend \"$capcmd\\n\"\nexpect \"upfd1# \"\nsend \"quit\\n\"\nexpect \"eof\"" + writeLog := fmt.Sprintf(" > %s 2>&1 \ncat %s", fileLogName, fileLogName) // 执行信息写入日志文件输出,避免弹出code 127 + + capCmdStr := body.Cmd + + cmdStr := fmt.Sprintf("cd /tmp\n\necho '%s' > cap.sh\n\nchmod +x cap.sh\n\nexpect ./cap.sh '%s'%s", scriptStr, capCmdStr, writeLog) + + usernameNe := config.Get("ne.user").(string) // 网元统一用户 + sshHost := fmt.Sprintf("%s@%s", usernameNe, neInfo.IP) + msg, err := cmd.ExecWithCheck("ssh", sshHost, cmdStr) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + s := strings.Index(msg, "pcap dispatch trace:") + if s == -1 { + s = strings.Index(msg, "Write ") + // 停止写入的文件名 + startIndex := strings.LastIndex(msg, "/") + 1 + endIndex := strings.LastIndex(msg, ",") + filePcapName = msg[startIndex:endIndex] + } + if s != -1 { + e := strings.Index(msg, "\r\nupfd1#") + msg = msg[s:e] + } else { + msg = "No stoppable found" + } + c.JSON(200, result.OkData(map[string]any{ + "cmd": capCmdStr, + "msg": msg, + "fileName": filePcapName, + })) + return + } + + c.JSON(200, result.ErrMsg("请选择RunType执行start_telnet/stop_telnet/start_str/stop_str")) +} diff --git a/src/modules/trace/service/tcpdump.go b/src/modules/trace/service/tcpdump.go new file mode 100644 index 0000000..d7cf7b2 --- /dev/null +++ b/src/modules/trace/service/tcpdump.go @@ -0,0 +1,7 @@ +package service + +// 通用请求 服务层接口 +type ITcpdump interface { + // UPFTelnetStart UPF进行telnet抓包 + UPFTelnet(neIp, cmdStr string) (string, error) +} diff --git a/src/modules/trace/service/tcpdump.impl.go b/src/modules/trace/service/tcpdump.impl.go new file mode 100644 index 0000000..2b81dda --- /dev/null +++ b/src/modules/trace/service/tcpdump.impl.go @@ -0,0 +1,41 @@ +package service + +import ( + "fmt" + "net" + "time" + + netElementService "ems.agt/src/modules/net_element/service" +) + +// 实例化服务层 TcpdumpImpl 结构体 +var NewTcpdumpImpl = &TcpdumpImpl{ + neInfoService: netElementService.NewNeInfoImpl, +} + +// 通用请求 服务层处理 +type TcpdumpImpl struct { + // 网元信息服务 + neInfoService netElementService.INeInfo +} + +// UPFTelnetStart UPF进行telnet抓包 +func (s *TcpdumpImpl) UPFTelnet(neIp, cmdStr string) (string, error) { + // 创建TCP连接 + conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", neIp, 5002)) + if err != nil { + return "", err + } + defer conn.Close() + + fmt.Fprintln(conn, cmdStr) + + // 读取内容 + time.Sleep(time.Duration(300) * time.Millisecond) + buf := make([]byte, 1024*8) + n, err := conn.Read(buf) + if err != nil { + return "", err + } + return string(buf[0:n]), nil +} diff --git a/src/modules/trace/trace.go b/src/modules/trace/trace.go new file mode 100644 index 0000000..f26f604 --- /dev/null +++ b/src/modules/trace/trace.go @@ -0,0 +1,35 @@ +package trace + +import ( + "ems.agt/src/framework/logger" + "ems.agt/src/framework/middleware" + "ems.agt/src/framework/middleware/collectlogs" + "ems.agt/src/modules/trace/controller" + + "github.com/gin-gonic/gin" +) + +// 模块路由注册 +func Setup(router *gin.Engine) { + logger.Infof("开始加载 ====> trace 模块路由") + + // 信令抓包 + tcpdumpGroup := router.Group("/tcpdump") + { + tcpdumpGroup.POST("/ne", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("信令抓包", collectlogs.BUSINESS_TYPE_OTHER)), + controller.NewTcpdump.NeTask, + ) + tcpdumpGroup.POST("/neUPF", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("信令抓包", collectlogs.BUSINESS_TYPE_OTHER)), + controller.NewTcpdump.NeUPFTask, + ) + tcpdumpGroup.POST("/download", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("信令抓包", collectlogs.BUSINESS_TYPE_IMPORT)), + controller.NewTcpdump.Download, + ) + } +}