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 0000000..758a4b5a Binary files /dev/null and b/src/assets/template/excel/user_import_template.xlsx differ diff --git a/src/framework/cmd/cmd.go b/src/framework/cmd/cmd.go new file mode 100644 index 0000000..085494d --- /dev/null +++ b/src/framework/cmd/cmd.go @@ -0,0 +1,201 @@ +package cmd + +import ( + "bytes" + "context" + "fmt" + "os/exec" + "strings" + "time" +) + +func Exec(cmdStr string) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + defer cancel() + cmd := exec.Command("bash", "-c", cmdStr) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + if ctx.Err() == context.DeadlineExceeded { + return "", fmt.Errorf("errCmdTimeout %v", err) + } + if err != nil { + errMsg := "" + if len(stderr.String()) != 0 { + errMsg = fmt.Sprintf("stderr: %s", stderr.String()) + } + if len(stdout.String()) != 0 { + if len(errMsg) != 0 { + errMsg = fmt.Sprintf("%s; stdout: %s", errMsg, stdout.String()) + } else { + errMsg = fmt.Sprintf("stdout: %s", stdout.String()) + } + } + return errMsg, err + } + return stdout.String(), nil +} + +func ExecWithTimeOut(cmdStr string, timeout time.Duration) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + cmd := exec.Command("bash", "-c", cmdStr) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + if ctx.Err() == context.DeadlineExceeded { + return "", fmt.Errorf("errCmdTimeout %v", err) + } + if err != nil { + errMsg := "" + if len(stderr.String()) != 0 { + errMsg = fmt.Sprintf("stderr: %s", stderr.String()) + } + if len(stdout.String()) != 0 { + if len(errMsg) != 0 { + errMsg = fmt.Sprintf("%s; stdout: %s", errMsg, stdout.String()) + } else { + errMsg = fmt.Sprintf("stdout: %s", stdout.String()) + } + } + return errMsg, err + } + return stdout.String(), nil +} + +func ExecCronjobWithTimeOut(cmdStr string, workdir string, timeout time.Duration) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + cmd := exec.Command("bash", "-c", cmdStr) + cmd.Dir = workdir + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + if ctx.Err() == context.DeadlineExceeded { + return "", fmt.Errorf("errCmdTimeout %v", err) + } + + errMsg := "" + if len(stderr.String()) != 0 { + errMsg = fmt.Sprintf("stderr:\n %s", stderr.String()) + } + if len(stdout.String()) != 0 { + if len(errMsg) != 0 { + errMsg = fmt.Sprintf("%s \n\n; stdout:\n %s", errMsg, stdout.String()) + } else { + errMsg = fmt.Sprintf("stdout:\n %s", stdout.String()) + } + } + return errMsg, err +} + +func Execf(cmdStr string, a ...interface{}) (string, error) { + cmd := exec.Command("bash", "-c", fmt.Sprintf(cmdStr, a...)) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + if err != nil { + errMsg := "" + if len(stderr.String()) != 0 { + errMsg = fmt.Sprintf("stderr: %s", stderr.String()) + } + if len(stdout.String()) != 0 { + if len(errMsg) != 0 { + errMsg = fmt.Sprintf("%s; stdout: %s", errMsg, stdout.String()) + } else { + errMsg = fmt.Sprintf("stdout: %s", stdout.String()) + } + } + return errMsg, err + } + return stdout.String(), nil +} + +func ExecWithCheck(name string, a ...string) (string, error) { + cmd := exec.Command(name, a...) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + if err != nil { + errMsg := "" + if len(stderr.String()) != 0 { + errMsg = fmt.Sprintf("stderr: %s", stderr.String()) + } + if len(stdout.String()) != 0 { + if len(errMsg) != 0 { + errMsg = fmt.Sprintf("%s; stdout: %s", errMsg, stdout.String()) + } else { + errMsg = fmt.Sprintf("stdout: %s", stdout.String()) + } + } + return errMsg, err + } + return stdout.String(), nil +} + +func ExecScript(scriptPath, workDir string) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) + defer cancel() + cmd := exec.Command("bash", scriptPath) + cmd.Dir = workDir + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + if ctx.Err() == context.DeadlineExceeded { + return "", fmt.Errorf("errCmdTimeout %v", err) + } + if err != nil { + errMsg := "" + if len(stderr.String()) != 0 { + errMsg = fmt.Sprintf("stderr: %s", stderr.String()) + } + if len(stdout.String()) != 0 { + if len(errMsg) != 0 { + errMsg = fmt.Sprintf("%s; stdout: %s", errMsg, stdout.String()) + } else { + errMsg = fmt.Sprintf("stdout: %s", stdout.String()) + } + } + return errMsg, err + } + return stdout.String(), nil +} + +func CheckIllegal(args ...string) bool { + if args == nil { + return false + } + for _, arg := range args { + if strings.Contains(arg, "&") || strings.Contains(arg, "|") || strings.Contains(arg, ";") || + strings.Contains(arg, "$") || strings.Contains(arg, "'") || strings.Contains(arg, "`") || + strings.Contains(arg, "(") || strings.Contains(arg, ")") || strings.Contains(arg, "\"") { + return true + } + } + return false +} + +func HasNoPasswordSudo() bool { + cmd2 := exec.Command("sudo", "-n", "ls") + err2 := cmd2.Run() + return err2 == nil +} + +func SudoHandleCmd() string { + cmd := exec.Command("sudo", "-n", "ls") + if err := cmd.Run(); err == nil { + return "sudo " + } + return "" +} + +func Which(name string) bool { + _, err := exec.LookPath(name) + return err == nil +} diff --git a/src/framework/config/config.go b/src/framework/config/config.go index dfe60eb..ce196ee 100644 --- a/src/framework/config/config.go +++ b/src/framework/config/config.go @@ -147,6 +147,16 @@ func Get(key string) any { return viper.Get(key) } +// GetAssetsDirFS 访问程序内全局资源访问 +func GetAssetsDirFS() embed.FS { + return viper.Get("AssetsDir").(embed.FS) +} + +// SetAssetsDirFS 设置程序内全局资源访问 +func SetAssetsDirFS(assetsDir embed.FS) { + viper.Set("AssetsDir", assetsDir) +} + // IsAdmin 用户是否为管理员 func IsAdmin(userID string) bool { if userID == "" { diff --git a/src/framework/utils/file/csv.go b/src/framework/utils/file/csv.go new file mode 100644 index 0000000..4ab4b34 --- /dev/null +++ b/src/framework/utils/file/csv.go @@ -0,0 +1,88 @@ +package file + +import ( + "encoding/csv" + "os" + "path/filepath" + "strings" + + "ems.agt/src/framework/logger" +) + +// 写入CSV文件,需要转换数据 +// 例如: +// data := [][]string{} +// data = append(data, []string{"姓名", "年龄", "城市"}) +// data = append(data, []string{"1", "2", "3"}) +// err := file.WriterCSVFile(data, filePath) +func WriterCSVFile(data [][]string, filePath string) error { + // 获取文件所在的目录路径 + dirPath := filepath.Dir(filePath) + + // 确保文件夹路径存在 + err := os.MkdirAll(dirPath, os.ModePerm) + if err != nil { + logger.Errorf("创建文件夹失败 CreateFile %v", err) + } + + // 创建或打开文件 + file, err := os.Create(filePath) + if err != nil { + return err + } + defer file.Close() + + // 创建CSV编写器 + writer := csv.NewWriter(file) + defer writer.Flush() + + // 写入数据 + for _, row := range data { + writer.Write(row) + } + return nil +} + +// 读取CSV文件,转换map数据 +func ReadCSVFile(filePath string) []map[string]string { + // 创建 map 存储 CSV 数据 + arr := make([]map[string]string, 0) + + // 打开 CSV 文件 + file, err := os.Open(filePath) + if err != nil { + logger.Errorf("无法打开 CSV 文件:%v", err) + return arr + } + defer file.Close() + + // 创建 CSV Reader + reader := csv.NewReader(file) + + // 读取 CSV 头部行 + header, err := reader.Read() + if err != nil { + logger.Errorf("无法读取 CSV 头部行:%v", err) + return arr + } + + // 遍历 CSV 数据行 + for { + // 读取一行数据 + record, err := reader.Read() + if err != nil { + // 到达文件末尾或遇到错误时退出循环 + break + } + + // 将 CSV 数据插入到 map 中 + data := make(map[string]string) + for i, value := range record { + key := strings.ToLower(header[i]) + data[key] = value + } + arr = append(arr, data) + } + + return arr +} diff --git a/src/framework/utils/file/txt.go b/src/framework/utils/file/txt.go new file mode 100644 index 0000000..bb09144 --- /dev/null +++ b/src/framework/utils/file/txt.go @@ -0,0 +1,79 @@ +package file + +import ( + "bufio" + "fmt" + "os" + "path/filepath" + "strings" + + "ems.agt/src/framework/logger" +) + +// 写入Txt文件用,号分割 需要转换数据 +// 例如: +// data := [][]string{} +// data = append(data, []string{"姓名", "年龄", "城市"}) +// data = append(data, []string{"1", "2", "3"}) +// err := file.WriterCSVFile(data, filePath) +func WriterTxtFile(data [][]string, filePath string) error { + // 获取文件所在的目录路径 + dirPath := filepath.Dir(filePath) + + // 确保文件夹路径存在 + err := os.MkdirAll(dirPath, os.ModePerm) + if err != nil { + logger.Errorf("CreateFile MkdirAll %v", err) + } + + // 创建或打开文件 + file, err := os.Create(filePath) + if err != nil { + return err + } + defer file.Close() + + // 创建一个 Writer 对象,用于将数据写入文件 + writer := bufio.NewWriter(file) + for _, row := range data { + line := strings.Join(row, ",") + fmt.Fprintln(writer, line) + } + + // 将缓冲区中的数据刷新到文件中 + err = writer.Flush() + if err != nil { + logger.Errorf("CreateFile Flush %v", err) + return err + } + return nil +} + +// 读取Txt文件,用,号分割 转换数组数据 +func ReadTxtFile(filePath string) [][]string { + // 创建 map 存储 CSV 数据 + arr := make([][]string, 0) + + // 打开文本文件 + file, err := os.Open(filePath) + if err != nil { + logger.Errorf("ReadTxtFile Open %v", err) + return arr + } + defer file.Close() + + // 创建一个 Scanner 对象,用于逐行读取文件内容 + scanner := bufio.NewScanner(file) + if scanner.Err() != nil { + logger.Errorf("ReadTxtFile NewScanner %v", scanner.Err()) + return arr + } + + for scanner.Scan() { + line := scanner.Text() + fields := strings.Split(line, ",") + arr = append(arr, fields) + } + + return arr +} diff --git a/src/framework/utils/ssh/scp.go b/src/framework/utils/ssh/scp.go new file mode 100644 index 0000000..de16b50 --- /dev/null +++ b/src/framework/utils/ssh/scp.go @@ -0,0 +1,49 @@ +package ssh + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + + "ems.agt/lib/core/conf" + "ems.agt/lib/log" +) + +// 网元NE 文件复制到远程文件 +func FileSCPLocalToNe(neIp, localPath, nePath string) error { + usernameNe := conf.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() + if err != nil { + 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("创建文件夹失败 CreateFile %v", err) + return err + } + + usernameNe := conf.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() + if err != nil { + return err + } + log.Infof("FileSCPNeToLocal %s", string(out)) + return nil +} diff --git a/src/modules/crontask/crontask.go b/src/modules/crontask/crontask.go index 4011eca..0eea775 100644 --- a/src/modules/crontask/crontask.go +++ b/src/modules/crontask/crontask.go @@ -12,11 +12,10 @@ import ( // Setup 模块路由注册 func Setup(router *gin.Engine) { - logger.Infof("开始加载 ====> 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, + ) + } +}