diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 7e257db..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "recommendations": [] -} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 60a0819..50fad3c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -18,7 +18,7 @@ "type": "go", "request": "launch", "mode": "debug", - "program": "d:/local.git/ems.agt/restagent/", + "program": "d:/omc.git/be.ems/restagent/", "console": "integratedTerminal" }, { @@ -26,16 +26,7 @@ "type": "go", "request": "launch", "mode": "debug", - "program": "d:/local.git/ems.agt/sshsvc/sshsvc.go", - "console": "integratedTerminal" - }, - { - "name": "debug loadpconf", - "type": "go", - "request": "launch", - "mode": "debug", - "program": "d:/local.git/ems.agt/tools/loadpconf", - "args": ["-p","../../config/param/upf_param_config.yaml"], + "program": "d:/omc.git/be.ems/sshsvc/sshsvc.go", "console": "integratedTerminal" }, { @@ -43,8 +34,16 @@ "type": "go", "request": "launch", "mode": "debug", - "program": "d:/local.git/ems.agt/crontask", + "program": "d:/omc.git/be.ems/crontask", "console": "integratedTerminal" - } + }, + { + "name": "debug encyaml", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "d:/omc.git/be.ems/tools/encode", + "console": "integratedTerminal" + } ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index d844704..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "go.toolsEnvVars": { - "GOARCH": "amd64", - "GOOS": "windows" - }, - "go.testEnvVars": { - "GOARCH": "wasm", - "GOOS": "js" - }, - "commentTranslate.hover.enabled": true -} \ No newline at end of file diff --git a/features/pm/kpi_c_report/controller.go b/features/pm/kpi_c_report/controller.go index 1b88013..03f6ae2 100644 --- a/features/pm/kpi_c_report/controller.go +++ b/features/pm/kpi_c_report/controller.go @@ -40,13 +40,14 @@ func (k *KpiCReport) Get(c *gin.Context) { return } if querys.StartTime != "" { - conditions = append(conditions, "created_at >= ?") + conditions = append(conditions, "(UNIX_TIMESTAMP(created_at) * 1000) >= ?") params = append(params, querys.StartTime) } if querys.EndTime != "" { - conditions = append(conditions, "created_at <= ?") + conditions = append(conditions, "(UNIX_TIMESTAMP(created_at) * 1000) <= ?") params = append(params, querys.EndTime) } + conditions = append(conditions, "kpi_values != 'null'") whereSql := "" if len(conditions) > 0 { @@ -106,13 +107,14 @@ func (k *KpiCReport) GetReport2FE(c *gin.Context) { return } if querys.StartTime != "" { - conditions = append(conditions, "created_at >= ?") + conditions = append(conditions, "(UNIX_TIMESTAMP(created_at) * 1000) >= ?") params = append(params, querys.StartTime) } if querys.EndTime != "" { - conditions = append(conditions, "created_at <= ?") + conditions = append(conditions, "(UNIX_TIMESTAMP(created_at) * 1000) <= ?") params = append(params, querys.EndTime) } + conditions = append(conditions, "kpi_values != 'null'") whereSql := "" if len(conditions) > 0 { @@ -185,13 +187,14 @@ func (k *KpiCReport) GetTotalList(c *gin.Context) { dbg := dborm.DefaultDB().Table(tableName) if querys.StartTime != "" { - conditions = append(conditions, "created_at >= ?") + conditions = append(conditions, "(UNIX_TIMESTAMP(created_at) * 1000) >= ?") params = append(params, querys.StartTime) } if querys.EndTime != "" { - conditions = append(conditions, "created_at <= ?") + conditions = append(conditions, "(UNIX_TIMESTAMP(created_at) * 1000) <= ?") params = append(params, querys.EndTime) } + conditions = append(conditions, "kpi_values != 'null'") whereSql := "" if len(conditions) > 0 { @@ -253,13 +256,14 @@ func (k *KpiCReport) Total(c *gin.Context) { dbg := dborm.DefaultDB().Table(tableName) if querys.StartTime != "" { - conditions = append(conditions, "created_at >= ?") + conditions = append(conditions, "(UNIX_TIMESTAMP(created_at) * 1000) >= ?") params = append(params, querys.StartTime) } if querys.EndTime != "" { - conditions = append(conditions, "created_at <= ?") + conditions = append(conditions, "(UNIX_TIMESTAMP(created_at) * 1000) <= ?") params = append(params, querys.EndTime) } + conditions = append(conditions, "kpi_values != 'null'") whereSql := "" if len(conditions) > 0 { diff --git a/features/pm/kpi_c_title/controller.go b/features/pm/kpi_c_title/controller.go index aa2912c..1aadbcd 100644 --- a/features/pm/kpi_c_title/controller.go +++ b/features/pm/kpi_c_title/controller.go @@ -3,17 +3,23 @@ package kpi_c_title import ( "fmt" "net/http" + "regexp" + "strconv" "strings" "be.ems/lib/dborm" + "be.ems/lib/log" "be.ems/lib/services" + "be.ems/src/framework/utils/ctx" "github.com/gin-gonic/gin" ) +// get customize kpi total and list func (k *KpiCTitle) GetToalList(c *gin.Context) { var titles []KpiCTitle var conditions []string var params []any + i18n := ctx.AcceptLanguage(c) var querys KpiCTitleQuery if err := c.ShouldBindQuery(&querys); err != nil { @@ -30,7 +36,10 @@ func (k *KpiCTitle) GetToalList(c *gin.Context) { if status := querys.Status; status != "" { conditions = append(conditions, "status = ?") params = append(params, status) + } else { + conditions = append(conditions, "status != 'Deleted'") } + whereSql := "" if len(conditions) > 0 { whereSql += strings.Join(conditions, " and ") @@ -62,6 +71,8 @@ func (k *KpiCTitle) GetToalList(c *gin.Context) { return } + k.expressionAlias(titles, i18n) + c.JSON(http.StatusOK, services.TotalDataResp(titles, total)) //c.JSON(http.StatusOK, titles) } @@ -70,6 +81,7 @@ func (k *KpiCTitle) Get(c *gin.Context) { var titles []KpiCTitle var conditions []string var params []any + i18n := ctx.AcceptLanguage(c) // construct condition to get if neType := c.Query("neType"); neType != "" { @@ -79,7 +91,10 @@ func (k *KpiCTitle) Get(c *gin.Context) { if status := c.Query("status"); status != "" { conditions = append(conditions, "status = ?") params = append(params, status) + } else { + conditions = append(conditions, "status != 'Deleted'") } + whereSql := "" if len(conditions) > 0 { whereSql += strings.Join(conditions, " and ") @@ -89,10 +104,38 @@ func (k *KpiCTitle) Get(c *gin.Context) { return } + k.expressionAlias(titles, i18n) + c.JSON(http.StatusOK, services.DataResp(titles)) //c.JSON(http.StatusOK, titles) } +// alias customized kpi expression with cn/en title +func (k *KpiCTitle) expressionAlias(titles []KpiCTitle, i18n string) { + var title *KpiCTitle + for i := 0; i < len(titles); i++ { + title = &titles[i] + title.ExprAlias = *title.Expression + re := regexp.MustCompile(`'([^']+)'`) + matches := re.FindAllStringSubmatch(title.ExprAlias, -1) + + for _, match := range matches { + var alias, sql string + if i18n == "zh" { + sql = fmt.Sprintf("SELECT cn_title FROM kpi_title WHERE kpi_id='%s'", match[1]) + } else { + sql = fmt.Sprintf("SELECT en_title FROM kpi_title WHERE kpi_id='%s'", match[1]) + } + err := dborm.XCoreDB().QueryRow(sql).Scan(&alias) + if err != nil { + log.Warn("Failed to QueryRow:", err) + continue + } + title.ExprAlias = regexp.MustCompile(match[1]).ReplaceAllString(title.ExprAlias, alias) + } + } +} + func (k *KpiCTitle) Total(c *gin.Context) { var conditions []string var params []any @@ -105,7 +148,10 @@ func (k *KpiCTitle) Total(c *gin.Context) { if status := c.Query("status"); status != "" { conditions = append(conditions, "status = ?") params = append(params, status) + } else { + conditions = append(conditions, "status != 'Deleted'") } + whereSql := "" if len(conditions) > 0 { whereSql += strings.Join(conditions, " and ") @@ -120,17 +166,49 @@ func (k *KpiCTitle) Total(c *gin.Context) { } func (k *KpiCTitle) Post(c *gin.Context) { - var title KpiCTitle + var title, res KpiCTitle if err := c.ShouldBindJSON(&title); err != nil { c.JSON(http.StatusOK, services.ErrResp(err.Error())) return } + userName := ctx.LoginUserToUserName(c) + title.CreatedBy = &userName result := dborm.DefaultDB().Where("ne_type=? and (kpi_id=? or title=?)", title.NeType, title.KpiID, title.Title).First(&title) if result.RowsAffected > 0 { c.JSON(http.StatusOK, services.ErrResp("custom indicator already exist")) return } + + // Regexp match like AMF.C.01 + kpiIDRegexp := "^" + *title.NeType + "\\.C\\.[0-9]{2}$" + ret := dborm.DefaultDB().Table("kpi_c_title"). + Where("ne_type=? and kpi_id REGEXP ? ORDER BY kpi_id DESC LIMIT 1", title.NeType, kpiIDRegexp).Scan(&res) + if err := ret.Error; err != nil { + c.JSON(http.StatusOK, services.ErrResp(err.Error())) + return + } + newKpiID := *title.NeType + ".C" + ".01" + if ret.RowsAffected != 0 { + maxKpiID := *res.KpiID + prefix := maxKpiID[:len(maxKpiID)-2] + suffix := maxKpiID[len(maxKpiID)-2:] + suffixInt, err := strconv.Atoi(suffix) + if err != nil { + c.JSON(http.StatusOK, services.ErrResp(err.Error())) + return + } + if suffixInt >= MAX_KPI_C_ID { + err := fmt.Errorf("exceed the max customized KPI ID") + c.JSON(http.StatusOK, services.ErrResp(err.Error())) + return + } + + suffixInt++ + newSuffix := fmt.Sprintf("%02d", suffixInt) + newKpiID = prefix + newSuffix + } + title.KpiID = &newKpiID if err := dborm.DefaultDB().Create(&title).Error; err != nil { c.JSON(http.StatusOK, services.ErrResp(err.Error())) return @@ -178,8 +256,8 @@ func (k *KpiCTitle) Put(c *gin.Context) { func (k *KpiCTitle) Delete(c *gin.Context) { id := c.Param("id") - if err := dborm.DefaultDB().Delete(&KpiCTitle{}, id).Error; err != nil { - c.JSON(http.StatusOK, services.ErrResp("custom indicator not found")) + if err := dborm.DefaultDB().Table(k.TableName()).Where("id=?", id).Update("status", "Deleted").Error; err != nil { + c.JSON(http.StatusOK, services.ErrResp(err.Error())) return } diff --git a/features/pm/kpi_c_title/model.go b/features/pm/kpi_c_title/model.go index 77bb942..cdc2b5c 100644 --- a/features/pm/kpi_c_title/model.go +++ b/features/pm/kpi_c_title/model.go @@ -2,12 +2,17 @@ package kpi_c_title import "time" +const ( + MAX_KPI_C_ID = 99 +) + type KpiCTitle struct { ID int `gorm:"column:id;primary_key;auto_increment" json:"id"` NeType *string `gorm:"column:ne_type;default:NULL," json:"neType,omitempty"` KpiID *string `gorm:"column:kpi_id;default:NULL," json:"kpiId,omitempty"` Title *string `gorm:"column:title;default:NULL," json:"title,omitempty"` Expression *string `gorm:"column:expression;default:NULL," json:"expression,omitempty"` + ExprAlias string `gorm:"-" json:"exprAlias"` Status string `gorm:"column:status;default:'Active'" json:"status"` Unit *string `gorm:"column:unit" json:"unit,omitempty"` Description *string `gorm:"column:description;default:NULL," json:"description,omitempty"` diff --git a/features/state/getstate.go b/features/state/getstate.go index 9a79e50..61cb781 100644 --- a/features/state/getstate.go +++ b/features/state/getstate.go @@ -762,14 +762,14 @@ func GetStateFromNF(w http.ResponseWriter, r *http.Request) { SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). Get(requestURI2NF) if err != nil { - log.Error("Get system state from NF is failed:", err) + log.Error("Fail to get state:", err) + errorMessage := services.ErrorMessage{ ErrorCode: "1", ErrorInfo: "Internal server error, NF connnect refused", } - result["error"] = errorMessage - SN, Version, _ := dborm.XormGetNEStateInfo(ne.NeType, ne.NeId) - result["serialNum"] = SN - result["version"] = Version + systemState := make(map[string]interface{}) + systemState["error"] = errorMessage + result["systemState"] = systemState } else { systemState := make(map[string]interface{}) _ = json.Unmarshal(resp.Body(), &systemState) @@ -786,49 +786,62 @@ func GetStateFromNF(w http.ResponseWriter, r *http.Request) { response.Data = data services.ResponseWithJson(w, http.StatusOK, response) return - } - - if neType == "omc" { + } else if neType == "omc" { emsState := GetEMSState("127.0.0.1") services.ResponseWithJson(w, http.StatusOK, emsState) return } - var neList []dborm.NeInfo - err := dborm.XormGetNeInfoByNeType(neType, &neList) - if err != nil { - log.Error("Failed to dborm.XormGetNeInfoByNeType:", err) - services.ResponseInternalServerError500ProcessError(w, err) - return - } - data := make([]map[string]interface{}, 0) - for _, ne := range neList { - hostUri := fmt.Sprintf("http://%s:%v", ne.Ip, ne.Port) - requestURI2NF := fmt.Sprintf("%s/api/rest/systemManagement/v1/elementType/%s/objectType/systemState", - hostUri, strings.ToLower(ne.NeType)) - log.Debug("requestURI2NF:", requestURI2NF) + // only support omc and all elementType + err := fmt.Errorf("only support omc or all elementTypeValue") + log.Error("Fail to get state:", err) + services.ResponseInternalServerError500ProcessError(w, err) - resp, err := client.R(). - EnableTrace(). - SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}). - SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). - Get(requestURI2NF) - if err != nil { - log.Error("Get system state from NF is failed:", err) - } else { - systemState := make(map[string]interface{}) - _ = json.Unmarshal(resp.Body(), &systemState) - data = append(data, systemState) - } - } + // var neList []dborm.NeInfo + // err := dborm.XormGetNeInfoByNeType(neType, &neList) + // if err != nil { + // log.Error("Failed to dborm.XormGetNeInfoByNeType:", err) + // services.ResponseInternalServerError500ProcessError(w, err) + // return + // } + // var omcList []dborm.NeInfo + // err = dborm.XormGetNeInfoByNeType("omc", &omcList) + // if err != nil { + // log.Error("Failed to omc ne list:", err) + // services.ResponseInternalServerError500ProcessError(w, err) + // return + // } + // for i, ne := range neList { - if len(data) == 1 { - services.ResponseWithJson(w, http.StatusOK, data[0]) - return - } - var response Response - response.Data = data - services.ResponseWithJson(w, http.StatusOK, response) + // } + // data := make([]map[string]interface{}, 0) + // for _, ne := range neList { + // hostUri := fmt.Sprintf("http://%s:%v", ne.Ip, ne.Port) + // requestURI2NF := fmt.Sprintf("%s/api/rest/systemManagement/v1/elementType/%s/objectType/systemState", + // hostUri, strings.ToLower(ne.NeType)) + // log.Debug("requestURI2NF:", requestURI2NF) + + // resp, err := client.R(). + // EnableTrace(). + // SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}). + // SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). + // Get(requestURI2NF) + // if err != nil { + // log.Error("Get system state from NF is failed:", err) + // } else { + // systemState := make(map[string]interface{}) + // _ = json.Unmarshal(resp.Body(), &systemState) + // data = append(data, systemState) + // } + // } + + // if len(data) == 1 { + // services.ResponseWithJson(w, http.StatusOK, data[0]) + // return + // } + // var response Response + // response.Data = data + // services.ResponseWithJson(w, http.StatusOK, response) } // GetStateFromNF 旧函数 diff --git a/lib/core/conf/conf.go b/lib/core/conf/conf.go index c5e2586..7e22479 100644 --- a/lib/core/conf/conf.go +++ b/lib/core/conf/conf.go @@ -25,7 +25,7 @@ func InitConfig(configFile string) { // Get 获取配置信息 // -// Get("framework.name") +// Get("server.port") func Get(key string) any { return v.Get(key) } diff --git a/lib/eval/evaluate.go b/lib/eval/evaluate.go index d0ff37e..664d717 100644 --- a/lib/eval/evaluate.go +++ b/lib/eval/evaluate.go @@ -5,6 +5,7 @@ import ( "go/ast" "go/parser" "go/token" + "math" "regexp" "strconv" "strings" @@ -29,6 +30,9 @@ func CalcExpr(expr string, paramValues map[string]any) (float64, error) { // expression to evaluate result, err := evalExpr(expr) + if math.IsNaN(result) { + return 0.0, err + } return result, err } @@ -87,6 +91,10 @@ func evalNode(node ast.Node) (float64, error) { case token.MUL: result = left * right case token.QUO: + if right == 0 { + return math.NaN(), fmt.Errorf("divisor cannot be zero") + } + result = left / right } case *ast.BasicLit: diff --git a/lib/log/syslogger.go.bak b/lib/log/syslogger.go.bak new file mode 100644 index 0000000..e599020 --- /dev/null +++ b/lib/log/syslogger.go.bak @@ -0,0 +1,89 @@ +//go:build !windows && !nacl && !plan9 +// +build !windows,!nacl,!plan9 + +package log + +import ( + "fmt" + "log/syslog" +) + +var _ Logger = &SyslogLogger{} + +// SyslogLogger will be depricated +type SyslogLogger struct { + w *syslog.Writer +} + +// NewSyslogLogger implements Logger +func NewSyslogLogger(w *syslog.Writer) *SyslogLogger { + return &SyslogLogger{w: w} +} + +// Trace log content as Trace +func (s *SyslogLogger) Trace(v ...interface{}) { + _ = s.w.Trace(fmt.Sprint(v...)) +} + +// Tracef log content as Trace and format +func (s *SyslogLogger) Tracef(format string, v ...interface{}) { + _ = s.w.Trace(fmt.Sprintf(format, v...)) +} + +// Debug log content as Debug +func (s *SyslogLogger) Debug(v ...interface{}) { + _ = s.w.Debug(fmt.Sprint(v...)) +} + +// Debugf log content as Debug and format +func (s *SyslogLogger) Debugf(format string, v ...interface{}) { + _ = s.w.Debug(fmt.Sprintf(format, v...)) +} + +// Error log content as Error +func (s *SyslogLogger) Error(v ...interface{}) { + _ = s.w.Err(fmt.Sprint(v...)) +} + +// Errorf log content as Errorf and format +func (s *SyslogLogger) Errorf(format string, v ...interface{}) { + _ = s.w.Err(fmt.Sprintf(format, v...)) +} + +// Info log content as Info +func (s *SyslogLogger) Info(v ...interface{}) { + _ = s.w.Info(fmt.Sprint(v...)) +} + +// Infof log content as Infof and format +func (s *SyslogLogger) Infof(format string, v ...interface{}) { + _ = s.w.Info(fmt.Sprintf(format, v...)) +} + +// Warn log content as Warn +func (s *SyslogLogger) Warn(v ...interface{}) { + _ = s.w.Warn(fmt.Sprint(v...)) +} + +// Warnf log content as Warnf and format +func (s *SyslogLogger) Warnf(format string, v ...interface{}) { + _ = s.w.Warn(fmt.Sprintf(format, v...)) +} + +// Fatal log content as Fatal +func (s *SyslogLogger) Fatal(v ...interface{}) { + _ = s.w.Fatal(fmt.Sprint(v...)) +} + +// Fatalf log content as Fatalf and format +func (s *SyslogLogger) Fatalf(format string, v ...interface{}) { + _ = s.w.Fatal(fmt.Sprintf(format, v...)) +} + +// Level shows log level +func (s *SyslogLogger) Level() LogLevel { + return LOG_NODEF +} + +// SetLevel always return error, as current log/syslog package doesn't allow to set priority level after syslog.Writer created +func (s *SyslogLogger) SetLevel(l LogLevel) {} diff --git a/restagent/makefile b/restagent/makefile index a8c2f1b..b61249b 100644 --- a/restagent/makefile +++ b/restagent/makefile @@ -1,7 +1,7 @@ # Makefile for rest agent project PROJECT = OMC -VERSION = 2.2410.4 +VERSION = 2.2412.1 PLATFORM = amd64 ARMPLATFORM = aarch64 BUILDDIR = ../../build diff --git a/src/assets/dependency/iperf/deb/iperf_2.0.13+dfsg1-1build1_amd64.deb b/src/assets/dependency/iperf/deb/iperf_2.0.13+dfsg1-1build1_amd64.deb new file mode 100644 index 0000000..323ae58 Binary files /dev/null and b/src/assets/dependency/iperf/deb/iperf_2.0.13+dfsg1-1build1_amd64.deb differ diff --git a/src/assets/dependency/iperf/rpm/iperf-2.1.6-2.el8.aarch64.rpm b/src/assets/dependency/iperf/rpm/iperf-2.1.6-2.el8.aarch64.rpm new file mode 100644 index 0000000..ec6b931 Binary files /dev/null and b/src/assets/dependency/iperf/rpm/iperf-2.1.6-2.el8.aarch64.rpm differ diff --git a/src/assets/dependency/iperf/rpm/iperf3-3.6-6.ky10.aarch64.rpm b/src/assets/dependency/iperf/rpm/iperf3-3.6-6.ky10.aarch64.rpm new file mode 100644 index 0000000..b5d5a4a Binary files /dev/null and b/src/assets/dependency/iperf/rpm/iperf3-3.6-6.ky10.aarch64.rpm differ diff --git a/src/framework/config/config.go b/src/framework/config/config.go index 8974d4c..875acec 100644 --- a/src/framework/config/config.go +++ b/src/framework/config/config.go @@ -142,7 +142,7 @@ func RunTime() time.Time { // Get 获取配置信息 // -// Get("framework.name") +// Get("server.port") func Get(key string) any { return viper.Get(key) } diff --git a/src/framework/config/config/config.default.yaml b/src/framework/config/config/config.default.yaml index b9c60ae..13b1fff 100644 --- a/src/framework/config/config/config.default.yaml +++ b/src/framework/config/config/config.default.yaml @@ -1,8 +1,3 @@ -# 项目信息 -framework: - name: "OMC" - version: "2.2410.4" - # 应用服务配置 server: # 服务端口 @@ -181,6 +176,10 @@ aes: # 用户配置 user: + # 登录认证,默认打开 + loginAuth: true + # 接口加密,默认打开 + cryptoApi: true # 密码 password: # 密码最大错误次数 diff --git a/src/framework/datasource/datasource.go b/src/framework/datasource/datasource.go index 5b1a99c..736e48d 100644 --- a/src/framework/datasource/datasource.go +++ b/src/framework/datasource/datasource.go @@ -117,6 +117,9 @@ func DefaultDB() *gorm.DB { // 获取数据源 func DB(source string) *gorm.DB { + if source == "" { + source = config.Get("gorm.defaultDataSourceName").(string) + } return dbMap[source] } diff --git a/src/framework/middleware/crypto_api.go b/src/framework/middleware/crypto_api.go index bfb8f7a..661e970 100644 --- a/src/framework/middleware/crypto_api.go +++ b/src/framework/middleware/crypto_api.go @@ -24,6 +24,16 @@ import ( // 请将中间件放在最前置,对请求优先处理 func CryptoApi(requestDecrypt, responseEncrypt bool) gin.HandlerFunc { return func(c *gin.Context) { + // 登录认证,默认打开 + enable := true + if v := config.Get("user.cryptoApi"); v != nil && enable { + enable = v.(bool) + } + if !enable { + c.Next() + return + } + // 请求解密时对请求data注入 if requestDecrypt { method := c.Request.Method diff --git a/src/framework/middleware/pre_authorize.go b/src/framework/middleware/pre_authorize.go index 588ad29..564e45a 100644 --- a/src/framework/middleware/pre_authorize.go +++ b/src/framework/middleware/pre_authorize.go @@ -3,6 +3,7 @@ package middleware import ( "strings" + "be.ems/src/framework/config" AdminConstants "be.ems/src/framework/constants/admin" commonConstants "be.ems/src/framework/constants/common" "be.ems/src/framework/i18n" @@ -36,6 +37,22 @@ var URL_WHITE_LIST = []string{ // 同时匹配其中权限 "matchPerms": {"xxx"}, func PreAuthorize(options map[string][]string) gin.HandlerFunc { return func(c *gin.Context) { + // 登录认证,默认打开 + enable := true + if v := config.Get("user.loginAuth"); v != nil { + enable = v.(bool) + } + if !enable { + loginUser, _ := ctxUtils.LoginUser(c) + loginUser.UserID = "2" + loginUser.User.UserID = "2" + loginUser.User.UserName = "admin" + loginUser.User.NickName = "admin" + c.Set(commonConstants.CTX_LOGIN_USER, loginUser) + c.Next() + return + } + language := ctxUtils.AcceptLanguage(c) requestURI := c.Request.RequestURI diff --git a/src/framework/redis/conn.go b/src/framework/redis/conn.go index 96b83f6..e31ec90 100644 --- a/src/framework/redis/conn.go +++ b/src/framework/redis/conn.go @@ -59,3 +59,21 @@ func (c *ConnRedis) Close() { c.Client.Close() } } + +// RunCMD 执行单次命令 "GET key" +func (c *ConnRedis) RunCMD(cmd string) (any, error) { + if c.Client == nil { + return "", fmt.Errorf("redis client not connected") + } + // 写入命令 + cmdArr := strings.Fields(cmd) + if len(cmdArr) == 0 { + return "", fmt.Errorf("redis command is empty") + } + conn := *c.Client + args := make([]any, 0) + for _, v := range cmdArr { + args = append(args, v) + } + return conn.Do(context.Background(), args...).Result() +} diff --git a/src/framework/redis/redis.go b/src/framework/redis/redis.go index 9306b59..88e5cdd 100644 --- a/src/framework/redis/redis.go +++ b/src/framework/redis/redis.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "strings" + "sync" "time" "be.ems/src/framework/config" @@ -179,31 +180,22 @@ func GetExpire(source string, key string) (float64, error) { } // 获得缓存数据的key列表 -func GetKeys(source string, pattern string) ([]string, error) { +func GetKeys(source string, match string) ([]string, error) { // 数据源 rdb := DefaultRDB() if source != "" { rdb = RDB(source) } - // 初始化变量 - var keys []string - var cursor uint64 = 0 + keys := make([]string, 0) ctx := context.Background() - // 循环遍历获取匹配的键 - for { - // 使用 SCAN 命令获取匹配的键 - batchKeys, nextCursor, err := rdb.Scan(ctx, cursor, pattern, 1000).Result() - if err != nil { - logger.Errorf("Failed to scan keys: %v", err) - return keys, err - } - cursor = nextCursor - keys = append(keys, batchKeys...) - // 当 cursor 为 0,表示遍历完成 - if cursor == 0 { - break - } + iter := rdb.Scan(ctx, 0, match, 1000).Iterator() + if err := iter.Err(); err != nil { + logger.Errorf("Failed to scan keys: %v", err) + return keys, err + } + for iter.Next(ctx) { + keys = append(keys, iter.Val()) } return keys, nil } @@ -261,6 +253,89 @@ func GetHash(source, key string) (map[string]string, error) { return value, nil } +// 批量获得缓存数据 [key]result +func GetHashBatch(source string, keys []string) (map[string]map[string]string, error) { + result := make(map[string]map[string]string, 0) + if len(keys) == 0 { + return result, fmt.Errorf("not keys") + } + + // 数据源 + rdb := DefaultRDB() + if source != "" { + rdb = RDB(source) + } + + // 创建一个有限的并发控制信号通道 + sem := make(chan struct{}, 10) + var wg sync.WaitGroup + var mt sync.Mutex + batchSize := 1000 + total := len(keys) + if total < batchSize { + batchSize = total + } + + for i := 0; i < total; i += batchSize { + wg.Add(1) + go func(start int) { + ctx := context.Background() + // 并发控制,限制同时执行的 Goroutine 数量 + sem <- struct{}{} + defer func() { + <-sem + ctx.Done() + wg.Done() + }() + + // 检查索引是否越界 + end := start + batchSize + if end > total { + end = total + } + pipe := rdb.Pipeline() + for _, key := range keys[start:end] { + pipe.HGetAll(ctx, key) + } + + cmds, err := pipe.Exec(ctx) + if err != nil { + logger.Errorf("Failed to get hash batch exec err: %v", err) + return + } + + // 将结果添加到 result map 并发访问 + mt.Lock() + defer mt.Unlock() + + // 处理命令结果 + for _, cmd := range cmds { + if cmd.Err() != nil { + logger.Errorf("Failed to get hash batch cmds err: %v", cmd.Err()) + continue + } + // 将结果转换为 *redis.StringStringMapCmd 类型 + rcmd, ok := cmd.(*redis.MapStringStringCmd) + if !ok { + logger.Errorf("Failed to get hash batch type err: %v", cmd.Err()) + continue + } + + key := "-" + args := rcmd.Args() + if len(args) > 0 { + key = fmt.Sprint(args[1]) + } + + result[key] = rcmd.Val() + } + }(i) + } + + wg.Wait() + return result, nil +} + // 判断是否存在 func Has(source string, keys ...string) (bool, error) { // 数据源 diff --git a/src/modules/common/controller/index.go b/src/modules/common/controller/index.go index 020b5be..b61ce81 100644 --- a/src/modules/common/controller/index.go +++ b/src/modules/common/controller/index.go @@ -3,7 +3,7 @@ package controller import ( "fmt" - "be.ems/src/framework/config" + libGlobal "be.ems/lib/global" "be.ems/src/framework/vo/result" "github.com/gin-gonic/gin" @@ -21,8 +21,8 @@ type IndexController struct{} // // GET / func (s *IndexController) Handler(c *gin.Context) { - name := config.Get("framework.name").(string) - version := config.Get("framework.version").(string) + name := "OMC" + version := libGlobal.Version // str := "欢迎使用%s核心网管理平台,当前版本:%s,请通过前台地址访问。" str := "Welcome to the %s Core Network Management Platform, current version: %s, please access via the frontend address." c.JSON(200, result.OkMsg(fmt.Sprintf(str, name, version))) diff --git a/src/modules/common/service/commont.impl.go b/src/modules/common/service/commont.impl.go index 10ad3f8..b84b4d4 100644 --- a/src/modules/common/service/commont.impl.go +++ b/src/modules/common/service/commont.impl.go @@ -30,7 +30,6 @@ func (s *CommontImpl) SystemConfigInfo() map[string]string { // 获取打包注入的全局变量信息 infoMap["version"] = global.Version infoMap["buildTime"] = global.BuildTime - infoMap["goVer"] = global.GoVer // 系统首次使用标记 launchInfo := machine.LaunchInfo if launchInfo != nil { @@ -42,6 +41,8 @@ func (s *CommontImpl) SystemConfigInfo() map[string]string { } else { infoMap[common.LAUNCH_BOOTLOADER] = "true" } + // 用户登录认证 + infoMap["loginAuth"] = fmt.Sprint(config.Get("user.loginAuth")) // 序列号 infoMap["serialNum"] = fmt.Sprint(config.Get("omc.sn")) // 获取LOGO类型 diff --git a/src/modules/network_data/controller/udm_auth.go b/src/modules/network_data/controller/udm_auth.go index d72b6ba..90ee876 100644 --- a/src/modules/network_data/controller/udm_auth.go +++ b/src/modules/network_data/controller/udm_auth.go @@ -55,8 +55,8 @@ func (s *UDMAuthController) ResetData(c *gin.Context) { // GET /list func (s *UDMAuthController) List(c *gin.Context) { querys := ctx.QueryMap(c) - data := s.udmAuthService.SelectPage(querys) - c.JSON(200, result.Ok(data)) + total, rows := s.udmAuthService.SelectPage(querys) + c.JSON(200, result.Ok(map[string]any{"total": total, "rows": rows})) } // UDM鉴权用户信息 @@ -364,13 +364,12 @@ func (s *UDMAuthController) Export(c *gin.Context) { querys["pageNum"] = 1 querys["pageSize"] = 10000 - data := s.udmAuthService.SelectPage(querys) - if parse.Number(data["total"]) == 0 { + total, rows := s.udmAuthService.SelectPage(querys) + if total == 0 { // 导出数据记录为空 c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.exportEmpty"))) return } - rows := data["rows"].([]model.UDMAuthUser) // rows := s.udmAuthService.SelectList(model.UDMAuthUser{NeId: neId}) if len(rows) <= 0 { diff --git a/src/modules/network_data/controller/udm_sub.go b/src/modules/network_data/controller/udm_sub.go index 0b9150b..02ae5dc 100644 --- a/src/modules/network_data/controller/udm_sub.go +++ b/src/modules/network_data/controller/udm_sub.go @@ -54,8 +54,8 @@ func (s *UDMSubController) ResetData(c *gin.Context) { // GET /list func (s *UDMSubController) List(c *gin.Context) { querys := ctx.QueryMap(c) - data := s.udmSubService.SelectPage(querys) - c.JSON(200, result.Ok(data)) + total, rows := s.udmSubService.SelectPage(querys) + c.JSON(200, result.Ok(map[string]any{"total": total, "rows": rows})) } // UDM签约用户信息 @@ -188,10 +188,9 @@ func (s *UDMSubController) Adds(c *gin.Context) { // 发送MML cmd := fmt.Sprintf("baa udmuser:start_imsi=%s,start_msisdn=%s,sub_num=%s,", body.IMSI, body.MSISDN, num) cmd += s.udmSubService.ParseCommandParams(body) - // static_ip指给4G UE分配的静态IP,没有可不带此字段名,批量添加IP会自动递增 - if body.StaticIp != "" { - cmd += fmt.Sprintf(",static_ip=%s", body.StaticIp) - } + // 去除msisdn参数,避免重复 + omemsisdn := fmt.Sprintf(",msisdn=%s,", body.MSISDN) + cmd = strings.Replace(cmd, omemsisdn, ",", 1) data, err := telnet.ConvertToStr(telnetClient, cmd) if err != nil { c.JSON(200, result.ErrMsg(err.Error())) @@ -369,13 +368,12 @@ func (s *UDMSubController) Export(c *gin.Context) { querys["pageNum"] = 1 querys["pageSize"] = 10000 - data := s.udmSubService.SelectPage(querys) - if parse.Number(data["total"]) == 0 { + total, rows := s.udmSubService.SelectPage(querys) + if total == 0 { // 导出数据记录为空 c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.exportEmpty"))) return } - rows := data["rows"].([]model.UDMSubUser) // rows := s.udmSubService.SelectList(model.UDMSubUser{NeId: neId}) if len(rows) <= 0 { diff --git a/src/modules/network_data/model/cdr_event_ims.go b/src/modules/network_data/model/cdr_event_ims.go index ffe280c..60973fa 100644 --- a/src/modules/network_data/model/cdr_event_ims.go +++ b/src/modules/network_data/model/cdr_event_ims.go @@ -7,7 +7,7 @@ type CDREventIMS struct { ID string `json:"id" gorm:"column:id;primaryKey;autoIncrement"` NeType string `json:"neType" gorm:"column:ne_type"` NeName string `json:"neName" gorm:"column:ne_name"` - RmUID string `json:"rmUID" gorm:"column:rm_uid"` // 可能没有 + RmUID string `json:"rmUID" gorm:"column:rm_uid"` Timestamp int64 `json:"timestamp" gorm:"column:timestamp"` CDRJSONStr string `json:"cdrJSON" gorm:"column:cdr_json"` CreatedAt time.Time `json:"createdAt" gorm:"column:created_at;default:CURRENT_TIMESTAMP"` @@ -18,7 +18,7 @@ type CDREventIMSQuery struct { NeType string `json:"neType" form:"neType" binding:"required"` // 网元类型IMS NeID string `json:"neId" form:"neId" binding:"required"` RmUID string `json:"rmUID" form:"rmUID"` - RecordType string `json:"recordType" form:"recordType"` // 记录行为 MOC MTC MOSM MTSM + RecordType string `json:"recordType" form:"recordType"` // 记录行为 MOC MTC CallerParty string `json:"callerParty" form:"callerParty"` // 主叫号码 CalledParty string `json:"calledParty" form:"calledParty"` // 被叫号码 StartTime string `json:"startTime" form:"startTime"` diff --git a/src/modules/network_data/repository/udm_auth.go b/src/modules/network_data/repository/udm_auth.go index 582e957..980ef00 100644 --- a/src/modules/network_data/repository/udm_auth.go +++ b/src/modules/network_data/repository/udm_auth.go @@ -1,120 +1,57 @@ package repository import ( - "fmt" - "strings" - "be.ems/src/framework/datasource" "be.ems/src/framework/logger" - "be.ems/src/framework/utils/parse" "be.ems/src/framework/utils/repo" "be.ems/src/modules/network_data/model" ) // 实例化数据层 UDMAuthUser 结构体 -var NewUDMAuthUser = &UDMAuthUser{ - selectSql: `select id, imsi, ne_id, amf, status, ki, algo_index, opc from u_auth_user`, - - resultMap: map[string]string{ - "id": "ID", - "imsi": "IMSI", - "ne_id": "NeId", - "amf": "Amf", - "status": "Status", - "ki": "Ki", - "algo_index": "AlgoIndex", - "opc": "Opc", - }, -} +var NewUDMAuthUser = &UDMAuthUser{} // UDMAuthUser UDM鉴权信息表 数据层处理 -type UDMAuthUser struct { - // 查询视图对象SQL - selectSql string - // 结果字段与实体映射 - resultMap map[string]string -} - -// convertResultRows 将结果记录转实体结果组 -func (r *UDMAuthUser) convertResultRows(rows []map[string]any) []model.UDMAuthUser { - arr := make([]model.UDMAuthUser, 0) - for _, row := range rows { - item := model.UDMAuthUser{} - for key, value := range row { - if keyMapper, ok := r.resultMap[key]; ok { - repo.SetFieldValue(&item, keyMapper, value) - } - } - arr = append(arr, item) - } - return arr -} +type UDMAuthUser struct{} // ClearAndInsert 清空ne_id后新增实体 func (r *UDMAuthUser) ClearAndInsert(neId string, uArr []model.UDMAuthUser) int64 { // 不指定neID时,用 TRUNCATE 清空表快 // _, err := datasource.ExecDB("", "TRUNCATE TABLE u_auth_user", nil) - _, err := datasource.ExecDB("", "DELETE FROM u_auth_user WHERE ne_id = ?", []any{neId}) - if err != nil { - logger.Errorf("TRUNCATE err => %v", err) + result := datasource.DB("").Where("ne_id = ?", neId).Unscoped().Delete(&model.UDMAuthUser{}) + if result.Error != nil { + logger.Errorf("Delete err => %v", result.Error) } return r.Inserts(uArr) } // SelectPage 根据条件分页查询 -func (r *UDMAuthUser) SelectPage(query map[string]any) map[string]any { +func (r *UDMAuthUser) SelectPage(query map[string]any) (int64, []model.UDMAuthUser) { + tx := datasource.DB("").Model(&model.UDMAuthUser{}) // 查询条件拼接 - var conditions []string - var params []any if v, ok := query["imsi"]; ok && v != "" { - conditions = append(conditions, "imsi like concat(concat('%', ?), '%')") - params = append(params, strings.Trim(v.(string), " ")) + tx = tx.Where("imsi like concat(concat('%',?), '%')", v) } if v, ok := query["neId"]; ok && v != "" { - conditions = append(conditions, "ne_id = ?") - params = append(params, v) + tx = tx.Where("ne_id =?", v) } if v, ok := query["imsis"]; ok && v != "" { - placeholder := repo.KeyPlaceholderByQuery(len(v.([]any))) - conditions = append(conditions, fmt.Sprintf("imsi in (%s)", placeholder)) - for _, v := range v.([]any) { - params = append(params, v.(string)) - } + tx = tx.Where("imsi in ?", v) } - // 构建查询条件语句 - whereSql := "" - if len(conditions) > 0 { - whereSql += " where " + strings.Join(conditions, " and ") - } - - result := map[string]any{ - "total": 0, - "rows": []model.UDMAuthUser{}, - } + var total int64 = 0 + rows := []model.UDMAuthUser{} // 查询数量 长度为0直接返回 - totalSql := "select count(1) as 'total' from u_auth_user" - totalRows, err := datasource.RawDB("", totalSql+whereSql, params) - if err != nil { + if err := tx.Count(&total).Error; err != nil || total <= 0 { logger.Errorf("total err => %v", err) - return result - } - total := parse.Number(totalRows[0]["total"]) - if total == 0 { - return result - } else { - result["total"] = total + return total, rows } // 分页 pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) - pageSql := " limit ?,? " - params = append(params, pageNum*pageSize) - params = append(params, pageSize) + tx = tx.Offset(int(pageNum * pageSize)).Limit(int(pageSize)) // 排序 - orderSql := "" if v, ok := query["sortField"]; ok && v != "" { sortSql := v.(string) if o, ok := query["sortOrder"]; ok && o != nil && v != "" { @@ -124,71 +61,52 @@ func (r *UDMAuthUser) SelectPage(query map[string]any) map[string]any { sortSql += " asc " } } - orderSql = fmt.Sprintf(" order by %s ", sortSql) + tx = tx.Order(sortSql) } // 查询数据 - querySql := r.selectSql + whereSql + orderSql + pageSql - results, err := datasource.RawDB("", querySql, params) - if err != nil { + if err := tx.Find(&rows).Error; err != nil { logger.Errorf("query err => %v", err) } - // 转换实体 - result["rows"] = r.convertResultRows(results) - return result + return total, rows } // SelectList 根据实体查询 func (r *UDMAuthUser) SelectList(u model.UDMAuthUser) []model.UDMAuthUser { + tx := datasource.DB("").Model(&model.UDMAuthUser{}) // 查询条件拼接 - var conditions []string - var params []any if u.IMSI != "" { - conditions = append(conditions, "imsi = ?") - params = append(params, u.IMSI) + tx = tx.Where("imsi = ?", u.IMSI) } if u.NeId != "" { - conditions = append(conditions, "ne_id = ?") - params = append(params, u.NeId) - } - - // 构建查询条件语句 - whereSql := "" - if len(conditions) > 0 { - whereSql += " where " + strings.Join(conditions, " and ") + tx = tx.Where("ne_id = ?", u.NeId) } // 查询数据 - querySql := r.selectSql + whereSql + " order by imsi asc " - results, err := datasource.RawDB("", querySql, params) - if err != nil { + arr := []model.UDMAuthUser{} + if err := tx.Order("imsi asc").Find(&arr).Error; err != nil { logger.Errorf("query err => %v", err) } - - // 转换实体 - return r.convertResultRows(results) + return arr } // SelectByIMSIAndNeID 通过imsi和ne_id查询 func (r *UDMAuthUser) SelectByIMSIAndNeID(imsi, neId string) model.UDMAuthUser { - querySql := r.selectSql + " where imsi = ? and ne_id = ?" - results, err := datasource.RawDB("", querySql, []any{imsi, neId}) - if err != nil { + tx := datasource.DB("").Model(&model.UDMAuthUser{}) + item := model.UDMAuthUser{} + // 查询条件拼接 + tx = tx.Where("imsi = ? and ne_id = ?", imsi, neId) + // 查询数据 + if err := tx.Order("imsi asc").Limit(1).Find(&item).Error; err != nil { logger.Errorf("query err => %v", err) - return model.UDMAuthUser{} } - // 转换实体 - rows := r.convertResultRows(results) - if len(rows) > 0 { - return rows[0] - } - return model.UDMAuthUser{} + return item } // Insert 批量添加 func (r *UDMAuthUser) Inserts(uArr []model.UDMAuthUser) int64 { - tx := datasource.DefaultDB().CreateInBatches(uArr, 3000) + tx := datasource.DB("").CreateInBatches(uArr, 3000) if err := tx.Error; err != nil { logger.Errorf("CreateInBatches err => %v", err) } diff --git a/src/modules/network_data/repository/udm_sub.go b/src/modules/network_data/repository/udm_sub.go index b283a1c..7f57dc5 100644 --- a/src/modules/network_data/repository/udm_sub.go +++ b/src/modules/network_data/repository/udm_sub.go @@ -1,156 +1,60 @@ package repository import ( - "fmt" - "strings" - "be.ems/src/framework/datasource" "be.ems/src/framework/logger" - "be.ems/src/framework/utils/parse" "be.ems/src/framework/utils/repo" "be.ems/src/modules/network_data/model" ) // 实例化数据层 UDMSubUser 结构体 -var NewUDMSub = &UDMSubUser{ - selectSql: `select - id, imsi, msisdn, ne_id, - am_dat, ambr, nssai, rat, arfb, sar, cn_type, rfsp_index, reg_timer, ue_usage_type, active_time, mico, odb_ps, group_id, - eps_dat, eps_flag, eps_odb, hplmn_odb, ard, epstpl, context_id, apn_mum, apn_context, static_ip, - sm_data, smf_sel, cag - from u_sub_user`, - - resultMap: map[string]string{ - "id": "ID", - "imsi": "IMSI", - "msisdn": "MSISDN", - "ne_id": "NeId", - - "am_dat": "AmDat", - "ambr": "UeAmbrTpl", - "nssai": "NssaiTpl", - "rat": "RatRestrictions", - "arfb": "AreaForbiddenTpl", - "sar": "ServiceAreaRestrictionTpl", - "cn_type": "CnTypeRestrictions", - "rfsp_index": "RfspIndex", - "reg_timer": "SubsRegTime", - "ue_usage_type": "UeUsageType", - "active_time": "ActiveTime", - "mico": "MicoAllowed", - "odb_ps": "OdbPs", - "group_id": "GroupId", - - "eps_dat": "EpsDat", - "eps_flag": "EpsFlag", - "eps_odb": "EpsOdb", - "hplmn_odb": "HplmnOdb", - "ard": "Ard", - "epstpl": "Epstpl", - "context_id": "ContextId", - "apn_mum": "ApnNum", - "apn_context": "ApnContext", - "static_ip": "StaticIp", - - "sm_data": "SmData", - "smf_sel": "SmfSel", - "cag": "Cag", - }, -} +var NewUDMSub = &UDMSubUser{} // UDMSubUser UDM签约信息表 数据层处理 -type UDMSubUser struct { - // 查询视图对象SQL - selectSql string - // 结果字段与实体映射 - resultMap map[string]string -} - -// convertResultRows 将结果记录转实体结果组 -func (r *UDMSubUser) convertResultRows(rows []map[string]any) []model.UDMSubUser { - arr := make([]model.UDMSubUser, 0) - for _, row := range rows { - item := model.UDMSubUser{} - for key, value := range row { - if keyMapper, ok := r.resultMap[key]; ok { - repo.SetFieldValue(&item, keyMapper, value) - } - } - arr = append(arr, item) - } - return arr -} +type UDMSubUser struct{} // ClearAndInsert 清空ne_id后新增实体 func (r *UDMSubUser) ClearAndInsert(neId string, u []model.UDMSubUser) int64 { // 不指定neID时,用 TRUNCATE 清空表快 // _, err := datasource.ExecDB("", "TRUNCATE TABLE u_sub_user", nil) - _, err := datasource.ExecDB("", "DELETE FROM u_sub_user WHERE ne_id = ?", []any{neId}) - if err != nil { - logger.Errorf("TRUNCATE err => %v", err) + result := datasource.DB("").Where("ne_id = ?", neId).Unscoped().Delete(&model.UDMSubUser{}) + if result.Error != nil { + logger.Errorf("Delete err => %v", result.Error) } - return r.Inserts(u) } // SelectPage 根据条件分页查询字典类型 -func (r *UDMSubUser) SelectPage(query map[string]any) map[string]any { +func (r *UDMSubUser) SelectPage(query map[string]any) (int64, []model.UDMSubUser) { + tx := datasource.DB("").Model(&model.UDMSubUser{}) // 查询条件拼接 - var conditions []string - var params []any if v, ok := query["imsi"]; ok && v != "" { - conditions = append(conditions, "imsi like concat(concat('%', ?), '%')") - params = append(params, strings.Trim(v.(string), " ")) + tx = tx.Where("imsi like concat(concat('%', ?), '%')", v) } if v, ok := query["msisdn"]; ok && v != "" { - conditions = append(conditions, "msisdn like concat(concat('%', ?), '%')") - params = append(params, strings.Trim(v.(string), " ")) + tx = tx.Where("msisdn like concat(concat('%', ?), '%')", v) } if v, ok := query["neId"]; ok && v != "" { - conditions = append(conditions, "ne_id = ?") - params = append(params, v) + tx = tx.Where("ne_id =?", v) } if v, ok := query["imsis"]; ok && v != "" { - placeholder := repo.KeyPlaceholderByQuery(len(v.([]any))) - conditions = append(conditions, fmt.Sprintf("imsi in (%s)", placeholder)) - for _, v := range v.([]any) { - params = append(params, v.(string)) - } + tx = tx.Where("imsi in ?", v) } - // 构建查询条件语句 - whereSql := "" - if len(conditions) > 0 { - whereSql += " where " + strings.Join(conditions, " and ") - } - - result := map[string]any{ - "total": 0, - "rows": []model.UDMSubUser{}, - } + var total int64 = 0 + rows := []model.UDMSubUser{} // 查询数量 长度为0直接返回 - totalSql := "select count(1) as 'total' from u_sub_user" - totalRows, err := datasource.RawDB("", totalSql+whereSql, params) - if err != nil { + if err := tx.Count(&total).Error; err != nil || total <= 0 { logger.Errorf("total err => %v", err) - return result - } - total := parse.Number(totalRows[0]["total"]) - if total == 0 { - return result - } else { - result["total"] = total + return total, rows } // 分页 pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) - pageSql := " limit ?,? " - params = append(params, pageNum*pageSize) - params = append(params, pageSize) + tx = tx.Offset(int(pageNum * pageSize)).Limit(int(pageSize)) // 排序 - orderSql := "" if v, ok := query["sortField"]; ok && v != "" { sortSql := v.(string) if o, ok := query["sortOrder"]; ok && o != nil && v != "" { @@ -160,72 +64,52 @@ func (r *UDMSubUser) SelectPage(query map[string]any) map[string]any { sortSql += " asc " } } - orderSql = fmt.Sprintf(" order by %s ", sortSql) + tx = tx.Order(sortSql) } // 查询数据 - querySql := r.selectSql + whereSql + orderSql + pageSql - results, err := datasource.RawDB("", querySql, params) - if err != nil { + if err := tx.Find(&rows).Error; err != nil { logger.Errorf("query err => %v", err) - return result } - // 转换实体 - result["rows"] = r.convertResultRows(results) - return result + return total, rows } // SelectList 根据实体查询 func (r *UDMSubUser) SelectList(u model.UDMSubUser) []model.UDMSubUser { + tx := datasource.DB("").Model(&model.UDMSubUser{}) // 查询条件拼接 - var conditions []string - var params []any if u.IMSI != "" { - conditions = append(conditions, "imsi = ?") - params = append(params, u.IMSI) + tx = tx.Where("imsi = ?", u.IMSI) } if u.NeId != "" { - conditions = append(conditions, "ne_id = ?") - params = append(params, u.NeId) - } - - // 构建查询条件语句 - whereSql := "" - if len(conditions) > 0 { - whereSql += " where " + strings.Join(conditions, " and ") + tx = tx.Where("ne_id = ?", u.NeId) } // 查询数据 - querySql := r.selectSql + whereSql + " order by imsi asc " - results, err := datasource.RawDB("", querySql, params) - if err != nil { + arr := []model.UDMSubUser{} + if err := tx.Order("imsi asc").Find(&arr).Error; err != nil { logger.Errorf("query err => %v", err) } - - // 转换实体 - return r.convertResultRows(results) + return arr } // SelectByIMSIAndNeID 通过imsi和ne_id查询 func (r *UDMSubUser) SelectByIMSIAndNeID(imsi, neId string) model.UDMSubUser { - querySql := r.selectSql + " where imsi = ? and ne_id = ?" - results, err := datasource.RawDB("", querySql, []any{imsi, neId}) - if err != nil { + tx := datasource.DB("").Model(&model.UDMSubUser{}) + item := model.UDMSubUser{} + // 查询条件拼接 + tx = tx.Where("imsi = ? and ne_id = ?", imsi, neId) + // 查询数据 + if err := tx.Order("imsi asc").Limit(1).Find(&item).Error; err != nil { logger.Errorf("query err => %v", err) - return model.UDMSubUser{} } - // 转换实体 - rows := r.convertResultRows(results) - if len(rows) > 0 { - return rows[0] - } - return model.UDMSubUser{} + return item } // Insert 批量添加 func (r *UDMSubUser) Inserts(uArr []model.UDMSubUser) int64 { - tx := datasource.DefaultDB().CreateInBatches(uArr, 2000) + tx := datasource.DB("").CreateInBatches(uArr, 2000) if err := tx.Error; err != nil { logger.Errorf("CreateInBatches err => %v", err) } diff --git a/src/modules/network_data/service/udm_auth.go b/src/modules/network_data/service/udm_auth.go index 069c673..807b8be 100644 --- a/src/modules/network_data/service/udm_auth.go +++ b/src/modules/network_data/service/udm_auth.go @@ -43,14 +43,18 @@ func (r *UDMAuthUser) dataByRedis(imsi, neId string) []model.UDMAuthUser { if err != nil { return arr } - for _, key := range ausfArr { - m, err := redis.GetHash(source, key) - if err != nil { + mkv, err := redis.GetHashBatch(source, ausfArr) + if err != nil { + return arr + } + + for k, m := range mkv { + if k == "-" { continue } // 跳过-号数据 ausf:360000100000130 - imsi := key[5:] + imsi := k[5:] if strings.Contains(imsi, "-") { continue } @@ -97,7 +101,7 @@ func (r *UDMAuthUser) ParseInfo(imsi, neId string, data map[string]string) model } // SelectPage 分页查询数据库 -func (r *UDMAuthUser) SelectPage(query map[string]any) map[string]any { +func (r *UDMAuthUser) SelectPage(query map[string]any) (int64, []model.UDMAuthUser) { return r.udmAuthRepository.SelectPage(query) } diff --git a/src/modules/network_data/service/udm_sub.go b/src/modules/network_data/service/udm_sub.go index 2332ca0..ba9b37d 100644 --- a/src/modules/network_data/service/udm_sub.go +++ b/src/modules/network_data/service/udm_sub.go @@ -44,14 +44,24 @@ func (r *UDMSubUser) dataByRedis(imsi, neId string) []model.UDMSubUser { if err != nil { return arr } - for _, key := range udmsdArr { - m, err := redis.GetHash(source, key) - if err != nil { + mkv, err := redis.GetHashBatch(source, udmsdArr) + if err != nil { + return arr + } + + for k, m := range mkv { + if k == "-" { + continue + } + + // 跳过-号数据 udm-sd:360000100000130 + imsi := k[7:] + if strings.Contains(imsi, "-") { continue } a := model.UDMSubUser{ - IMSI: key[7:], // udm-sd:360000100000130 + IMSI: imsi, // udm-sd:360000100000130 MSISDN: m["gpsi"], // 8612300000130 NeId: neId, SmfSel: m["smf-sel"], // def_snssai @@ -170,7 +180,7 @@ func (r *UDMSubUser) ParseInfo(imsi, neId string, data map[string]string) model. } // SelectPage 分页查询数据库 -func (r *UDMSubUser) SelectPage(query map[string]any) map[string]any { +func (r *UDMSubUser) SelectPage(query map[string]any) (int64, []model.UDMSubUser) { return r.udmSubRepository.SelectPage(query) } @@ -337,6 +347,7 @@ func (r *UDMSubUser) ParseCommandParams(item model.UDMSubUser) string { if item.ApnContext != "" { conditions = append(conditions, fmt.Sprintf("apn_context=%s", item.ApnContext)) } + // static_ip指给4G UE分配的静态IP,没有可不带此字段名,批量添加IP会自动递增 if item.StaticIp != "" { conditions = append(conditions, fmt.Sprintf("static_ip=%s", item.StaticIp)) } @@ -348,8 +359,7 @@ func (r *UDMSubUser) ParseCommandParams(item model.UDMSubUser) string { if item.SmData != "" { conditions = append(conditions, fmt.Sprintf("sm_data=%s", item.SmData)) } - if item.Cag != "" { - conditions = append(conditions, fmt.Sprintf("cag=%s", item.Cag)) - } + conditions = append(conditions, fmt.Sprintf("cag=%s", item.Cag)) + return strings.Join(conditions, ",") } diff --git a/src/modules/network_element/controller/ne_config.go b/src/modules/network_element/controller/ne_config.go index c6c25be..a46da6f 100644 --- a/src/modules/network_element/controller/ne_config.go +++ b/src/modules/network_element/controller/ne_config.go @@ -199,16 +199,16 @@ func (s *NeConfigController) DataInfo(c *gin.Context) { } c.JSON(200, result.OkData(resData)) return - } else { - // 网元直连 - resData, err := neFetchlink.NeConfigInfo(neInfo, query.ParamName) - if err != nil { - c.JSON(200, result.ErrMsg(err.Error())) - return - } - - c.JSON(200, result.Ok(resData)) } + + // 网元直连 + resData, err := neFetchlink.NeConfigInfo(neInfo, query.ParamName) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.JSON(200, result.Ok(resData)) } // 网元参数配置数据修改 @@ -242,15 +242,15 @@ func (s *NeConfigController) DataEdit(c *gin.Context) { } c.JSON(200, result.OkData(resData)) return - } else { - // 网元直连 - resData, err := neFetchlink.NeConfigUpdate(neInfo, body.ParamName, body.Loc, body.ParamData) - if err != nil { - c.JSON(200, result.ErrMsg(err.Error())) - return - } - c.JSON(200, result.OkData(resData)) } + + // 网元直连 + resData, err := neFetchlink.NeConfigUpdate(neInfo, body.ParamName, body.Loc, body.ParamData) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + c.JSON(200, result.OkData(resData)) } // 网元参数配置数据新增(array) diff --git a/src/modules/network_element/controller/ne_host.go b/src/modules/network_element/controller/ne_host.go index 613a88a..e4719bc 100644 --- a/src/modules/network_element/controller/ne_host.go +++ b/src/modules/network_element/controller/ne_host.go @@ -79,6 +79,13 @@ func (s *NeHostController) Add(c *gin.Context) { return } + if body.GroupID == "1" { + // 主机信息操作【%s】失败,禁止操作网元 + msg := i18n.TKey(language, "neHost.banNE") + c.JSON(200, result.ErrMsg(msg)) + return + } + // 检查属性值唯一 uniqueHost := s.neHostService.CheckUniqueHostTitle(body.GroupID, body.Title, body.HostType, "") if !uniqueHost { diff --git a/src/modules/network_element/repository/ne_host.go b/src/modules/network_element/repository/ne_host.go index 2791978..7079466 100644 --- a/src/modules/network_element/repository/ne_host.go +++ b/src/modules/network_element/repository/ne_host.go @@ -111,8 +111,28 @@ func (r *NeHost) SelectPage(query map[string]any) map[string]any { params = append(params, pageNum*pageSize) params = append(params, pageSize) + // 排序 + orderSql := "" + if sv, ok := query["sortField"]; ok && sv != "" { + sortSql := fmt.Sprint(sv) + if sortSql == "updateTime" { + sortSql = "update_time" + } + if sortSql == "createTime" { + sortSql = "create_time" + } + if ov, ok := query["sortOrder"]; ok && ov != "" { + if fmt.Sprint(ov) == "desc" { + sortSql += " desc " + } else { + sortSql += " asc " + } + } + orderSql = fmt.Sprintf(" order by %s ", sortSql) + } + // 查询数据 - querySql := r.selectSql + whereSql + pageSql + querySql := r.selectSql + whereSql + orderSql + pageSql results, err := datasource.RawDB("", querySql, params) if err != nil { logger.Errorf("query err => %v", err) diff --git a/src/modules/network_element/repository/ne_info.go b/src/modules/network_element/repository/ne_info.go index 0b835c6..6e2a5cc 100644 --- a/src/modules/network_element/repository/ne_info.go +++ b/src/modules/network_element/repository/ne_info.go @@ -18,6 +18,7 @@ var neListSort = []string{ "IMS", "AMF", "AUSF", + "UDR", "UDM", "SMF", "PCF", diff --git a/src/modules/network_element/service/ne_config_backup.go b/src/modules/network_element/service/ne_config_backup.go index 9c2d6ba..80cfffb 100644 --- a/src/modules/network_element/service/ne_config_backup.go +++ b/src/modules/network_element/service/ne_config_backup.go @@ -120,6 +120,9 @@ func (r *NeConfigBackup) NeConfigLocalToNe(neInfo model.NeInfo, localFile string sshClient.RunCMD(fmt.Sprintf("sudo mkdir -p /usr/local/etc/iwf && sudo cp -rf %s/iwf/* /usr/local/etc/iwf && sudo chmod 755 /usr/local/etc/iwf/*.yaml", neDirTemp)) } else if neTypeLower == "omc" { sshClient.RunCMD(fmt.Sprintf("sudo mkdir -p /usr/local/omc/etc && sudo cp -rf %s/* /usr/local/omc/etc && sudo chmod 755 /usr/local/omc/etc/*.{yaml,conf}", neDirTemp)) + } else if neTypeLower == "smsc" { + chmodFile := "sudo chmod 755 /usr/local/etc/smsc/{*sys.conf,*conf.txt,conf/is41_operation.conf}" + sshClient.RunCMD(fmt.Sprintf("sudo mkdir -p /usr/local/etc/smsc/conf && sudo cp -rf %s/* /usr/local/etc/smsc && %s", neDirTemp, chmodFile)) } else { neEtcPath := fmt.Sprintf("/usr/local/etc/%s", neTypeLower) chmodFile := fmt.Sprintf("sudo chmod 755 %s/*.yaml", neEtcPath) @@ -175,6 +178,9 @@ func (r *NeConfigBackup) NeConfigNeToLocal(neInfo model.NeInfo) (string, error) sshClient.RunCMD(fmt.Sprintf("mkdir -p %s/iwf && sudo cp -rf /usr/local/etc/iwf/*.yaml %s/iwf", neDirTemp, neDirTemp)) } else if neTypeLower == "omc" { sshClient.RunCMD(fmt.Sprintf("mkdir -p %s && sudo cp -rf /usr/local/omc/etc/*.{yaml,conf} %s", neDirTemp, neDirTemp)) + } else if neTypeLower == "smsc" { + sshClient.RunCMD(fmt.Sprintf("mkdir -p %s && sudo cp -rf /usr/local/etc/smsc/{*.yaml,*.conf,*conf.txt} %s", neDirTemp, neDirTemp)) + sshClient.RunCMD(fmt.Sprintf("sudo cp -rf /usr/local/etc/smsc/conf %s/conf", neDirTemp)) } else { nePath := fmt.Sprintf("/usr/local/etc/%s/*.yaml", neTypeLower) if neTypeLower == "mme" { diff --git a/src/modules/network_element/service/ne_host.go b/src/modules/network_element/service/ne_host.go index c8ee1c2..fbedaa1 100644 --- a/src/modules/network_element/service/ne_host.go +++ b/src/modules/network_element/service/ne_host.go @@ -149,6 +149,13 @@ func (r *NeHost) DeleteByIds(hostIds []string) (int64, error) { return 0, fmt.Errorf("neHost.noData") } + for _, v := range ids { + if v.GroupID == "1" { + // 主机信息操作【%s】失败,禁止操作网元 + return 0, fmt.Errorf("neHost.banNE") + } + } + if len(ids) == len(hostIds) { rows := r.neHostRepository.DeleteByIds(hostIds) return rows, nil diff --git a/src/modules/network_element/service/ne_info.go b/src/modules/network_element/service/ne_info.go index cdde10e..e01f510 100644 --- a/src/modules/network_element/service/ne_info.go +++ b/src/modules/network_element/service/ne_info.go @@ -79,6 +79,28 @@ func (r *NeInfo) ClearNeCacheByNeType(neType string) bool { return delOk } +// SelectNeInfoByNeType 通过ne_type查询网元信息 +func (r *NeInfo) SelectNeInfoByNeType(neType string) []model.NeInfo { + neInfo := make([]model.NeInfo, 0) + key := fmt.Sprintf("%s%s:*", cachekey.NE_KEY, strings.ToUpper(neType)) + jsonStr, _ := redis.Get("", key) + if len(jsonStr) > 7 { + err := json.Unmarshal([]byte(jsonStr), &neInfo) + if err != nil { + return neInfo + } + } else { + neInfo = r.neInfoRepository.SelectList(model.NeInfo{NeType: neType}) + for _, v := range neInfo { + key := fmt.Sprintf("%s%s:%s", cachekey.NE_KEY, strings.ToUpper(v.NeType), v.NeId) + redis.Del("", key) + values, _ := json.Marshal(v) + redis.Set("", key, string(values)) + } + } + return neInfo +} + // SelectNeInfoByRmuid 通过rmUID查询网元信息 func (r *NeInfo) SelectNeInfoByRmuid(rmUid string) model.NeInfo { var neInfo model.NeInfo @@ -910,22 +932,25 @@ func (r *NeInfo) neConfPara5GDataConvert(content map[string]any) map[string]stri "DNN_IMS": basic["dnn_ims"].(string), // external - "N2_IP": external["amfn2_ip"].(string), - "UE_POOL": external["ue_pool"].(string), - "UE_IP": ueIP, - "UE_MASK": ueMask, - "UE_CIDR": ueCicr, - "UPF_TYPE": external["upf_type"].(string), // StandardUPF LightUPF - "N3_IP": n3IP, - "N3_MASK": n3Mask, - "N3_GW": external["upfn3_gw"].(string), - "N3_PCI": external["upfn3_pci"].(string), - "N3_MAC": external["upfn3_mac"].(string), - "N6_IP": n6IP, - "N6_MASK": n6Mask, - "N6_GW": external["upfn6_gw"].(string), - "N6_PCI": external["upfn6_pci"].(string), - "N6_MAC": external["upfn6_mac"].(string), + "N2_IP": external["amfn2_ip"].(string), + "UE_POOL": external["ue_pool"].(string), // 轻量版才用配置 + "UE_IP": ueIP, + "UE_MASK": ueMask, + "UE_CIDR": ueCicr, + "UPF_TYPE": external["upf_type"].(string), // 类型 StandardUPF LightUPF + "UPF_DRIVER_TYPE": external["upf_driver_type"].(string), // 网卡驱动 vmxnet3 host dpdk + "N3_IP": n3IP, + "N3_MASK": n3Mask, + "N3_GW": external["upfn3_gw"].(string), + "N3_PCI": external["upfn3_pci"].(string), + "N3_MAC": external["upfn3_mac"].(string), + "N3_NIC_NAME": external["upfn3_card_name"].(string), // 网卡名 eth0 + "N6_IP": n6IP, + "N6_MASK": n6Mask, + "N6_GW": external["upfn6_gw"].(string), + "N6_PCI": external["upfn6_pci"].(string), + "N6_MAC": external["upfn6_mac"].(string), + "N6_NIC_NAME": external["upfn6_card_name"].(string), // 网卡名 eth0 "SIP_IP": external["ims_sip_ip"].(string), diff --git a/src/modules/network_element/service/ne_version.go b/src/modules/network_element/service/ne_version.go index ea8152f..31aa4ad 100644 --- a/src/modules/network_element/service/ne_version.go +++ b/src/modules/network_element/service/ne_version.go @@ -239,7 +239,7 @@ func (r *NeVersion) operateCommand(action, neType string, neFilePaths []string) pkgCmdStr := fmt.Sprintf("sudo dpkg -i %s", strings.Join(neFilePaths, " ")) fileExt := filepath.Ext(strings.ToLower(neFilePaths[0])) if strings.HasSuffix(fileExt, "rpm") { - pkgCmdStr = fmt.Sprintf("sudo rpm -Uvh %s", strings.Join(neFilePaths, " ")) + pkgCmdStr = fmt.Sprintf("sudo rpm -Uvh --reinstall %s", strings.Join(neFilePaths, " ")) } // 组合命令输入 @@ -257,7 +257,7 @@ func (r *NeVersion) operateCommand(action, neType string, neFilePaths []string) // 升级软件包 pkgCmdStr = fmt.Sprintf("sudo M_PARAM=upgrade dpkg -i %s", strings.Join(neFilePaths, " ")) if strings.HasSuffix(fileExt, "rpm") { - pkgCmdStr = fmt.Sprintf("sudo M_PARAM=upgrade rpm -Uvh %s", strings.Join(neFilePaths, " ")) + pkgCmdStr = fmt.Sprintf("sudo M_PARAM=upgrade rpm -Uvh --reinstall %s", strings.Join(neFilePaths, " ")) } omcStrArr = append(omcStrArr, pkgCmdStr) } @@ -428,22 +428,35 @@ func (r *NeVersion) operateCommand(action, neType string, neFilePaths []string) cmdStrArr = append(cmdStrArr, "sudo cp /usr/local/etc/upf/default/upfForwarder_1.yaml /usr/local/etc/upf/upfForwarder_1.yaml \n") cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i \"s/172.16.5.190/%s/g\" /usr/local/etc/upf/upfcfg.yaml \n", para5GData["UPF_IP"])) cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i \"s/localhost/%s/g\" /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["UPF_IP"])) - // UE - cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: \"N6\"/,/ueIpv4: 10.2.1.0/s/ueIpv4: 10.2.1.0/ueIpv4: %s/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["UE_IP"])) - cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: \"N6\"/,/ueIpv4Mask: 255.255.255.0/s/ueIpv4Mask: 255.255.255.0/ueIpv4Mask: %s/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["UE_MASK"])) // N3 cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i \"s/192.168.8.190/%s/g\" /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["N3_IP"])) cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: \"N3\"/,/ipv4Mask: 255.255.240.0/s/ipv4Mask: 255.255.240.0/ipv4Mask: %s/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["N3_MASK"])) cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: \"N3\"/,/gatewayIpv4: 192.168.1.254/s/gatewayIpv4: 192.168.1.254/gatewayIpv4: %s/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["N3_GW"])) cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: \"N3\"/,/interfacePCI: \"0000:00:00.0\"/s/interfacePCI: \"0000:00:00.0\"/interfacePCI: \"%s\"/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["N3_PCI"])) cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: \"N3\"/,/macAddr: \"00:00:00:00:00:00\"/s/macAddr: \"00:00:00:00:00:00\"/macAddr: \"%s\"/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["N3_MAC"])) + cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: N3/,/ipv4Mask: 255.255.240.0/s/ipv4Mask: 255.255.240.0/ipv4Mask: %s/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["N3_MASK"])) + cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: N3/,/gatewayIpv4: 192.168.1.254/s/gatewayIpv4: 192.168.1.254/gatewayIpv4: %s/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["N3_GW"])) + cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: N3/,/interfacePCI: \"0000:00:00.0\"/s/interfacePCI: \"0000:00:00.0\"/interfacePCI: \"%s\"/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["N3_PCI"])) + cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: N3/,/macAddr: \"00:00:00:00:00:00\"/s/macAddr: \"00:00:00:00:00:00\"/macAddr: \"%s\"/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["N3_MAC"])) // 标准版 N6 if para5GData["UPF_TYPE"] == "StandardUPF" { cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i \"s/192.168.8.191/%s/g\" /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["N6_IP"])) + cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: \"N3\"/,/driverType: .*/s/driverType: .*/driverType: \"%s\"/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["UPF_DRIVER_TYPE"])) + cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: \"N3\"/,/systemNetworkCardName: .*/s/systemNetworkCardName: .*/systemNetworkCardName: \"%s\"/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["N3_NIC_NAME"])) + cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: \"N6\"/,/driverType: .*/s/driverType: .*/driverType: \"%s\"/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["UPF_DRIVER_TYPE"])) + cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: \"N6\"/,/systemNetworkCardName: .*/s/systemNetworkCardName: .*/systemNetworkCardName: \"%s\"/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["N6_NIC_NAME"])) cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: \"N6\"/,/ipv4Mask: 255.255.240.0/s/ipv4Mask: 255.255.240.0/ipv4Mask: %s/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["N6_MASK"])) cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: \"N6\"/,/gatewayIpv4: 192.168.1.254/s/gatewayIpv4: 192.168.1.254/gatewayIpv4: %s/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["N6_GW"])) cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: \"N6\"/,/interfacePCI: \"0000:00:00.0\"/s/interfacePCI: \"0000:00:00.0\"/interfacePCI: \"%s\"/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["N6_PCI"])) cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: \"N6\"/,/macAddr: \"00:00:00:00:00:00\"/s/macAddr: \"00:00:00:00:00:00\"/macAddr: \"%s\"/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["N6_MAC"])) + cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: N3/,/driverType: .*/s/driverType: .*/driverType: \"%s\"/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["UPF_DRIVER_TYPE"])) + cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: N3/,/systemNetworkCardName: .*/s/systemNetworkCardName: .*/systemNetworkCardName: \"%s\"/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["N3_NIC_NAME"])) + cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: N6/,/driverType: .*/s/driverType: .*/driverType: \"%s\"/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["UPF_DRIVER_TYPE"])) + cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: N6/,/systemNetworkCardName: .*/s/systemNetworkCardName: .*/systemNetworkCardName: \"%s\"/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["N6_NIC_NAME"])) + cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: N6/,/ipv4Mask: 255.255.240.0/s/ipv4Mask: 255.255.240.0/ipv4Mask: %s/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["N6_MASK"])) + cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: N6/,/gatewayIpv4: 192.168.1.254/s/gatewayIpv4: 192.168.1.254/gatewayIpv4: %s/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["N6_GW"])) + cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: N6/,/interfacePCI: \"0000:00:00.0\"/s/interfacePCI: \"0000:00:00.0\"/interfacePCI: \"%s\"/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["N6_PCI"])) + cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: N6/,/macAddr: \"00:00:00:00:00:00\"/s/macAddr: \"00:00:00:00:00:00\"/macAddr: \"%s\"/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["N6_MAC"])) // 路由 cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo ip route add '%s/%s' via '%s' \n", para5GData["UE_IP"], para5GData["UE_CIDR"], para5GData["N6_IP"])) } @@ -451,7 +464,12 @@ func (r *NeVersion) operateCommand(action, neType string, neFilePaths []string) if para5GData["UPF_TYPE"] == "LightUPF" { cmdStrArr = append(cmdStrArr, "sudo sed -i \"s/192.168.8.191/0.0.0.0/g\" /usr/local/etc/upf/upfForwarder_1.yaml \n") cmdStrArr = append(cmdStrArr, "sudo sed -i \"s/type: upfd/type: tun/g\" /usr/local/etc/upf/upfForwarder_1.yaml \n") - cmdStrArr = append(cmdStrArr, "sudo sed -i 's/driverType: vmxnet3/driverType: \"\"/g' /usr/local/etc/upf/upfForwarder_1.yaml \n") + cmdStrArr = append(cmdStrArr, "sudo sed -i 's/driverType: .*/driverType: \"\"/g' /usr/local/etc/upf/upfForwarder_1.yaml \n") + // UE + cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: N6/,/ueIpv4: 10.2.1.0/s/ueIpv4: 10.2.1.0/ueIpv4: %s/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["UE_IP"])) + cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: N6/,/ueIpv4Mask: 255.255.255.0/s/ueIpv4Mask: 255.255.255.0/ueIpv4Mask: %s/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["UE_MASK"])) + cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: \"N6\"/,/ueIpv4: 10.2.1.0/s/ueIpv4: 10.2.1.0/ueIpv4: %s/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["UE_IP"])) + cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i '/- interfaceType: \"N6\"/,/ueIpv4Mask: 255.255.255.0/s/ueIpv4Mask: 255.255.255.0/ueIpv4Mask: %s/' /usr/local/etc/upf/upfForwarder_1.yaml \n", para5GData["UE_MASK"])) } cmdStrArr = append(cmdStrArr, fmt.Sprintf("grep -qxF '%s upf' /etc/hosts || echo '%s upf' | sudo tee -a /etc/hosts \n", para5GData["UPF_IP"], para5GData["UPF_IP"])) cmdStrArr = append(cmdStrArr, fmt.Sprintf("grep -qxF '%s upfn3' /etc/hosts || echo '%s upfn3' | sudo tee -a /etc/hosts \n", para5GData["N3_IP"], para5GData["N3_IP"])) @@ -539,14 +557,14 @@ func (r *NeVersion) operateCommand(action, neType string, neFilePaths []string) if strings.Contains(pkgCmdStr, "adb") { para5GData := NewNeInfo.Para5GData cmdStrArr = append(cmdStrArr, "sudo cp /usr/local/etc/adb/default/adb.conf /usr/local/etc/adb/adb.conf \n") - cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i \"s/bind 127.0.0.1/bind 127.0.0.1 %s/g\" /usr/local/etc/adb/adb.conf \n", para5GData["DB_IP"])) + cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i \"s/bind 127.0.0.1/bind %s/g\" /usr/local/etc/adb/adb.conf \n", para5GData["DB_IP"])) cmdStrArr = append(cmdStrArr, "sudo service adb restart \n") } // kvdb if strings.Contains(pkgCmdStr, "kvdb") { para5GData := NewNeInfo.Para5GData cmdStrArr = append(cmdStrArr, "sudo cp /usr/local/etc/kvdb/default/kvdb.conf /usr/local/etc/kvdb/kvdb.conf \n") - cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i \"s/bind 127.0.0.1/bind 127.0.0.1 %s/g\" /usr/local/etc/kvdb/kvdb.conf \n", para5GData["DB_IP"])) + cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo sed -i \"s/bind 127.0.0.1/bind %s/g\" /usr/local/etc/kvdb/kvdb.conf \n", para5GData["DB_IP"])) cmdStrArr = append(cmdStrArr, "sudo service kvdb restart \n") } } @@ -671,17 +689,20 @@ func (r *NeVersion) operateDome(action string, neVersion model.NeVersion) error smscHost := fmt.Sprintf("%s smsc.ims.%s.3gppnetwork.org", para5GData["SMSC_IP"], mnc_mcc) smscHostCMD := fmt.Sprintf("grep -qxF '%s' /etc/hosts || echo '%s' | sudo tee -a /etc/hosts \n", smscHost, smscHost) smscIPCMD := fmt.Sprintf("grep -qxF '%s smsc' /etc/hosts || echo '%s smsc' | sudo tee -a /etc/hosts \n", para5GData["SMSC_IP"], para5GData["SMSC_IP"]) + smsHost := fmt.Sprintf("sudo sed -i '/^%s smsc.*smsc$/c\\' /etc/hosts", para5GData["SIP_IP"]) + // IMS 配置 imsNEs := NewNeInfo.SelectList(model.NeInfo{NeType: "IMS"}, false, false) for _, v := range imsNEs { NewNeInfo.NeRunSSHCmd(v.NeType, v.NeId, smscIPCMD) NewNeInfo.NeRunSSHCmd(v.NeType, v.NeId, smscHostCMD) + NewNeInfo.NeRunSSHCmd(v.NeType, v.NeId, smsHost) NewNeInfo.NeRunSSHCmd(v.NeType, v.NeId, "sudo sed -i '/^#!define WITH_SMS/ s/^/#/' /usr/local/etc/ims/vars.cfg") NewNeInfo.NeRunSSHCmd(v.NeType, v.NeId, "ims-stop || true && ims-start") } // UDM 配置 - smscASName := fmt.Sprintf("sudo sed -i '/- name: sms_as/{n;s|serverName: .*|serverName: sip:%s:5060|}' /usr/local/etc/udm/as.yaml", para5GData["SMSC_IP"]) - smscASAddress := fmt.Sprintf("sudo sed -i '/- name: sms_as/{n;s|diameterAddress: .*|diameterAddress: smsc.ims.%s.3gppnetwork.org|}' /usr/local/etc/udm/as.yaml", mnc_mcc) + smscASName := fmt.Sprintf("sudo sed -i \"/- name: 'sms_as'/{n;s|serverName: .*|serverName: 'sip:%s:5060'|}\" /usr/local/etc/udm/as.yaml", para5GData["SMSC_IP"]) + smscASAddress := fmt.Sprintf("sudo sed -i \"/- name: 'sms_as'/{n;n;n;s|diameterAddress: .*|diameterAddress: 'smsc.ims.%s.3gppnetwork.org'|}\" /usr/local/etc/udm/as.yaml", mnc_mcc) udmNEs := NewNeInfo.SelectList(model.NeInfo{NeType: "UDM"}, false, false) for _, v := range udmNEs { NewNeInfo.NeRunSSHCmd(v.NeType, v.NeId, smscIPCMD) diff --git a/src/modules/tool/controller/iperf.go b/src/modules/tool/controller/iperf.go index 71413d6..c684974 100644 --- a/src/modules/tool/controller/iperf.go +++ b/src/modules/tool/controller/iperf.go @@ -37,15 +37,16 @@ type IPerfController struct { func (s *IPerfController) Version(c *gin.Context) { language := ctx.AcceptLanguage(c) var query struct { - NeType string `form:"neType" binding:"required"` // 网元类型 - NeID string `form:"neId" binding:"required"` // 网元ID + NeType string `form:"neType" binding:"required"` // 网元类型 + NeId string `form:"neId" binding:"required"` // 网元ID + Version string `form:"version" binding:"required,oneof=V2 V3"` // 版本 } if err := c.ShouldBindQuery(&query); err != nil { c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) return } - output, err := s.iperfService.Version(query.NeType, query.NeID) + output, err := s.iperfService.Version(query.NeType, query.NeId, query.Version) if err != nil { c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error()))) return @@ -60,15 +61,16 @@ func (s *IPerfController) Version(c *gin.Context) { func (s *IPerfController) Install(c *gin.Context) { language := ctx.AcceptLanguage(c) var body struct { - NeType string `json:"neType" binding:"required"` // 网元类型 - NeID string `json:"neId" binding:"required"` // 网元ID + NeType string `json:"neType" binding:"required"` // 网元类型 + NeId string `json:"neId" binding:"required"` // 网元ID + Version string `form:"version" binding:"required,oneof=V2 V3"` // 版本 } if err := c.ShouldBindBodyWithJSON(&body); err != nil { c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) return } - if err := s.iperfService.Install(body.NeType, body.NeID); err != nil { + if err := s.iperfService.Install(body.NeType, body.NeId, body.Version); err != nil { c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error()))) return } diff --git a/src/modules/tool/service/iperf.go b/src/modules/tool/service/iperf.go index 75ffaae..bdb3b7e 100644 --- a/src/modules/tool/service/iperf.go +++ b/src/modules/tool/service/iperf.go @@ -22,17 +22,39 @@ var NewIPerf = &IPerf{} type IPerf struct{} // Version 查询版本信息 -func (s *IPerf) Version(meType, neId string) (string, error) { - // 检查是否安装iperf3 - output, err := neService.NewNeInfo.NeRunSSHCmd(meType, neId, "iperf3 --version") +func (s *IPerf) Version(meType, neId, version string) (string, error) { + if version != "V2" && version != "V3" { + return "", fmt.Errorf("iperf version is required V2 or V3") + } + cmd := "iperf3 --version" + if version == "V2" { + cmd = "iperf -v" + } + + // 网元主机的SSH客户端 + sshClient, err := neService.NewNeInfo.NeRunSSHClient(meType, neId) if err != nil { - return "", fmt.Errorf("iperf3 not installed") + return "", err + } + defer sshClient.Close() + + // 检查是否安装iperf + output, err := sshClient.RunCMD(cmd) + if err != nil { + if version == "V2" && strings.HasSuffix(err.Error(), "status 1") { // V2 版本 + return strings.TrimSpace(output), nil + } + return "", fmt.Errorf("iperf %s not installed", version) } return strings.TrimSpace(output), err } // Install 安装iperf3 -func (s *IPerf) Install(meType, neId string) error { +func (s *IPerf) Install(meType, neId, version string) error { + if version != "V2" && version != "V3" { + return fmt.Errorf("iperf version is required V2 or V3") + } + // 网元主机的SSH客户端 sshClient, err := neService.NewNeInfo.NeRunSSHClient(meType, neId) if err != nil { @@ -56,9 +78,16 @@ func (s *IPerf) Install(meType, neId string) error { depDir = "assets/dependency/iperf3/deb" // sudo apt remove iperf3 libiperf0 libsctp1 libsctp-dev lksctp-tools } else if _, err := sshClient.RunCMD("sudo yum --version"); err == nil { - depPkg = "sudo rpm -Uvh --force" + depPkg = "sudo rpm -Uvh --nosignature --reinstall --force" depDir = "assets/dependency/iperf3/rpm" // yum remove iperf3 iperf3-help.noarch + } else { + return fmt.Errorf("iperf %s not supported install", version) + } + + // V2版本和V3版本的安装包路径不同 + if version == "V2" { + depDir = strings.Replace(depDir, "iperf3", "iperf", 1) } // 从 embed.FS 中读取默认配置文件内容 @@ -72,19 +101,19 @@ func (s *IPerf) Install(meType, neId string) error { // 打开本地文件 localFile, err := assetsDir.Open(fmt.Sprintf("%s/%s", depDir, d.Name())) if err != nil { - return fmt.Errorf("iperf3 file local error") + return fmt.Errorf("iperf %s file local error", version) } defer localFile.Close() // 创建远程文件 remotePath := fmt.Sprintf("%s/%s", nePath, d.Name()) remoteFile, err := sftpClient.Client.Create(remotePath) if err != nil { - return fmt.Errorf("iperf3 file remote error") + return fmt.Errorf("iperf %s file remote error", version) } defer remoteFile.Close() // 使用 io.Copy 将嵌入的文件内容复制到目标文件 if _, err := io.Copy(remoteFile, localFile); err != nil { - return fmt.Errorf("iperf3 file copy error") + return fmt.Errorf("iperf %s file copy error", version) } neFilePaths = append(neFilePaths, remotePath) } @@ -98,7 +127,7 @@ func (s *IPerf) Install(meType, neId string) error { // 安装软件包 pkgInstall := fmt.Sprintf("%s %s", depPkg, strings.Join(neFilePaths, " ")) if _, err := sshClient.RunCMD(pkgInstall); err != nil { - return fmt.Errorf("iperf3 install error") + return fmt.Errorf("iperf %s install error", version) } return err } @@ -108,7 +137,7 @@ func (s *IPerf) Run(client *wsModel.WSClient, reqMsg wsModel.WSRequest) { // 必传requestId确认消息 if reqMsg.RequestID == "" { msg := "message requestId is required" - logger.Infof("ws IPerf3 Run UID %s err: %s", client.BindUid, msg) + logger.Infof("ws IPerf Run UID %s err: %s", client.BindUid, msg) msgByte, _ := json.Marshal(result.ErrMsg(msg)) client.MsgChan <- msgByte return @@ -126,7 +155,7 @@ func (s *IPerf) Run(client *wsModel.WSClient, reqMsg wsModel.WSRequest) { time.Sleep(1 * time.Second) client.StopChan <- struct{}{} return - case "iperf3": + case "iperf": // SSH会话消息接收写入会话 var command string command, err = s.parseOptions(reqMsg.Data) @@ -155,7 +184,7 @@ func (s *IPerf) Run(client *wsModel.WSClient, reqMsg wsModel.WSRequest) { } if err != nil { - logger.Warnf("ws IPerf3 Run UID %s err: %s", client.BindUid, err.Error()) + logger.Warnf("ws IPerf Run UID %s err: %s", client.BindUid, err.Error()) msgByte, _ := json.Marshal(result.ErrMsg(err.Error())) client.MsgChan <- msgByte if err == io.EOF { @@ -175,7 +204,8 @@ func (s *IPerf) parseOptions(reqData any) (string, error) { msgByte, _ := json.Marshal(reqData) var data struct { Command string `json:"command"` // 命令字符串 - Client bool `json:"client"` // 服务端或客户端,默认服务端 + Version string `json:"version"` // 服务版本,默认V3 + Mode string `json:"mode"` // 服务端或客户端,默认客户端client Host string `json:"host"` // 客户端连接到的服务端IP地址 // Server or Client Port int `json:"port"` // 服务端口 @@ -183,17 +213,25 @@ func (s *IPerf) parseOptions(reqData any) (string, error) { // Server OneOff bool `json:"oneOff"` // 只进行一次连接 // Client - UDP bool `json:"udp"` // use UDP rather than TCP - Time int `json:"time"` // 以秒为单位的传输时间(默认为 10 秒) - Reverse bool `json:"reverse"` // 以反向模式运行(服务器发送,客户端接收) - Window string `json:"window"` // 设置窗口大小/套接字缓冲区大小 + UDP bool `json:"udp"` // use UDP rather than TCP + Time int `json:"time"` // 以秒为单位的传输时间(默认为 10 秒) + Reverse bool `json:"reverse"` // 以反向模式运行(服务器发送,客户端接收) + Window string `json:"window"` // 设置窗口大小/套接字缓冲区大小 + Parallel int `json:"parallel"` // 运行的并行客户端流数量 + Bitrate int `json:"bitrate"` // 以比特/秒为单位(0 表示无限制) } if err := json.Unmarshal(msgByte, &data); err != nil { logger.Warnf("ws processor parseClient err: %s", err.Error()) return "", fmt.Errorf("query data structure error") } + if data.Version != "V3" && data.Version != "V2" { + return "", fmt.Errorf("query data version support V3 or V2") + } command := []string{"iperf3"} + if data.Version == "V2" { + command = []string{"iperf"} + } // 命令字符串高优先级 if data.Command != "" { command = append(command, data.Command) @@ -201,16 +239,14 @@ func (s *IPerf) parseOptions(reqData any) (string, error) { return strings.Join(command, " "), nil } - if data.Client && data.Host == "" { + if data.Mode != "client" && data.Mode != "server" { + return "", fmt.Errorf("query data mode support client or server") + } + if data.Mode == "client" && data.Host == "" { return "", fmt.Errorf("query data client host empty") } - if !data.Client { - command = append(command, "-s") - // Server - if data.OneOff { - command = append(command, "-1") - } - } else { + + if data.Mode == "client" { command = append(command, "-c") command = append(command, data.Host) // Client @@ -220,6 +256,12 @@ func (s *IPerf) parseOptions(reqData any) (string, error) { if data.Time > 0 { command = append(command, fmt.Sprintf("-t %d", data.Time)) } + if data.Bitrate > 0 { + command = append(command, fmt.Sprintf("-b %d", data.Bitrate)) + } + if data.Parallel > 0 { + command = append(command, fmt.Sprintf("-P %d", data.Parallel)) + } if data.Reverse { command = append(command, "-R") } @@ -227,6 +269,13 @@ func (s *IPerf) parseOptions(reqData any) (string, error) { command = append(command, fmt.Sprintf("-w %s", data.Window)) } } + if data.Mode == "server" { + command = append(command, "-s") + // Server + if data.OneOff { + command = append(command, "-1") + } + } // Server or Client if data.Port > 0 { diff --git a/src/modules/ws/controller/ws_redis.go b/src/modules/ws/controller/ws_redis.go new file mode 100644 index 0000000..febcd3c --- /dev/null +++ b/src/modules/ws/controller/ws_redis.go @@ -0,0 +1,69 @@ +package controller + +import ( + "be.ems/src/framework/i18n" + "be.ems/src/framework/logger" + "be.ems/src/framework/redis" + "be.ems/src/framework/utils/ctx" + "be.ems/src/framework/vo/result" + neService "be.ems/src/modules/network_element/service" + + "github.com/gin-gonic/gin" +) + +// Redis 终端 +// +// GET /redis?hostId=1 +func (s *WSController) Redis(c *gin.Context) { + language := ctx.AcceptLanguage(c) + var query struct { + HostId string `form:"hostId" binding:"required"` // 连接主机ID + } + if err := c.ShouldBindQuery(&query); err != nil { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 登录用户信息 + loginUser, err := ctx.LoginUser(c) + if err != nil { + c.JSON(401, result.CodeMsg(401, i18n.TKey(language, err.Error()))) + return + } + + neHost := neService.NewNeHost.SelectById(query.HostId) + if neHost.HostID != query.HostId || neHost.HostType != "redis" { + // 没有可访问主机信息数据! + c.JSON(200, result.ErrMsg(i18n.TKey(language, "neHost.noData"))) + return + } + + // 创建链接Redis客户端 + var connRedis redis.ConnRedis + neHost.CopyTo(&connRedis) + client, err := connRedis.NewClient() + if err != nil { + // 连接主机失败,请检查连接参数后重试 + c.JSON(200, result.ErrMsg(i18n.TKey(language, "neHost.errByHostInfo"))) + return + } + defer client.Close() + + // 将 HTTP 连接升级为 WebSocket 连接 + wsConn := s.wsService.UpgraderWs(c.Writer, c.Request) + if wsConn == nil { + return + } + defer wsConn.Close() + + wsClient := s.wsService.ClientCreate(loginUser.UserID, nil, wsConn, client) + go s.wsService.ClientWriteListen(wsClient) + go s.wsService.ClientReadListen(wsClient, s.wsReceiveService.Redis) + + // 等待停止信号 + for value := range wsClient.StopChan { + s.wsService.ClientClose(wsClient.ID) + logger.Infof("ws Stop Client UID %s %s", wsClient.BindUid, value) + return + } +} diff --git a/src/modules/ws/service/ws_receive.go b/src/modules/ws/service/ws_receive.go index f374b3f..1b3b516 100644 --- a/src/modules/ws/service/ws_receive.go +++ b/src/modules/ws/service/ws_receive.go @@ -4,9 +4,12 @@ import ( "encoding/json" "fmt" "io" + "reflect" + "strings" "time" "be.ems/src/framework/logger" + "be.ems/src/framework/redis" "be.ems/src/framework/telnet" "be.ems/src/framework/utils/ssh" "be.ems/src/framework/vo/result" @@ -104,7 +107,7 @@ func (s *WSReceive) Shell(client *model.WSClient, reqMsg model.WSRequest) { command := reqMsg.Data.(string) sshClientSession := client.ChildConn.(*ssh.SSHClientSession) _, err = sshClientSession.Write(command) - case "ssh_resize": + case "resize": // SSH会话窗口重置 msgByte, _ := json.Marshal(reqMsg.Data) var data struct { @@ -225,7 +228,7 @@ func (s *WSReceive) Telnet(client *model.WSClient, reqMsg model.WSRequest) { command := reqMsg.Data.(string) telnetClientSession := client.ChildConn.(*telnet.TelnetClientSession) _, err = telnetClientSession.Write(command) - case "telnet_resize": + case "resize": // Telnet会话窗口重置 msgByte, _ := json.Marshal(reqMsg.Data) var data struct { @@ -256,3 +259,76 @@ func (s *WSReceive) Telnet(client *model.WSClient, reqMsg model.WSRequest) { client.MsgChan <- resByte } } + +// Redis 接收终端交互业务处理 +func (s *WSReceive) Redis(client *model.WSClient, reqMsg model.WSRequest) { + // 必传requestId确认消息 + if reqMsg.RequestID == "" { + msg := "message requestId is required" + logger.Infof("ws Shell UID %s err: %s", client.BindUid, msg) + msgByte, _ := json.Marshal(result.ErrMsg(msg)) + client.MsgChan <- msgByte + return + } + + var resByte []byte + var err error + + switch reqMsg.Type { + case "close": + s.close(client) + return + case "redis": + // Redis会话消息接收写入会话 + command := fmt.Sprint(reqMsg.Data) + redisClientSession := client.ChildConn.(*redis.ConnRedis) + output, err := redisClientSession.RunCMD(command) + dataStr := "" + if err != nil { + dataStr = fmt.Sprintf("%s \r\n", err.Error()) + } else { + // 获取结果的反射类型 + resultType := reflect.TypeOf(output) + switch resultType.Kind() { + case reflect.Slice: + // 如果是切片类型需要进一步判断是否是 []string 或 []interface{} + if resultType.Elem().Kind() == reflect.String { + dataStr = fmt.Sprintf("%s \r\n", strings.Join(output.([]string), "\r\n")) + } else if resultType.Elem().Kind() == reflect.Interface { + arr := []string{} + for _, v := range output.([]any) { + arr = append(arr, fmt.Sprintf("%s", v)) + } + dataStr = fmt.Sprintf("%s \r\n", strings.Join(arr, "\r\n")) + } + case reflect.Ptr: + dataStr = "\r\n" + case reflect.String, reflect.Int64: + dataStr = fmt.Sprintf("%s \r\n", output) + default: + dataStr = fmt.Sprintf("%s \r\n", output) + } + } + resByte, _ = json.Marshal(result.Ok(map[string]any{ + "requestId": reqMsg.RequestID, + "data": dataStr, + })) + default: + err = fmt.Errorf("message type %s not supported", reqMsg.Type) + } + + if err != nil { + logger.Warnf("ws Shell UID %s err: %s", client.BindUid, err.Error()) + msgByte, _ := json.Marshal(result.ErrMsg(err.Error())) + client.MsgChan <- msgByte + if err == io.EOF { + // 等待1s后关闭连接 + time.Sleep(1 * time.Second) + client.StopChan <- struct{}{} + } + return + } + if len(resByte) > 0 { + client.MsgChan <- resByte + } +} diff --git a/src/modules/ws/ws.go b/src/modules/ws/ws.go index 976b901..35e63f8 100644 --- a/src/modules/ws/ws.go +++ b/src/modules/ws/ws.go @@ -35,6 +35,11 @@ func Setup(router *gin.Engine) { collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.ws", collectlogs.BUSINESS_TYPE_OTHER)), controller.NewWSController.Telnet, ) + wsGroup.GET("/redis", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.ws", collectlogs.BUSINESS_TYPE_OTHER)), + controller.NewWSController.Redis, + ) wsGroup.GET("/view", middleware.PreAuthorize(nil), collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.ws", collectlogs.BUSINESS_TYPE_OTHER)),