diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000..7e257db
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,3 @@
+{
+ "recommendations": []
+}
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..3ce5d98
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,59 @@
+{
+ // 使用 IntelliSense 了解相关属性。
+ // 悬停以查看现有属性的描述。
+ // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "调试模式",
+ "type": "go",
+ "request": "launch",
+ "mode": "debug",
+ "program": "./restagent/restagent.go",
+ "console": "integratedTerminal",
+ "args": ["--env", "local"] // 走开发配置
+ },
+ {
+ "name": "DEBUG restagent",
+ "type": "go",
+ "request": "launch",
+ "mode": "debug",
+ "program": "d:/local.git/be.ems/restagent/",
+ "console": "integratedTerminal"
+ },
+ {
+ "name": "debug sshsvc",
+ "type": "go",
+ "request": "launch",
+ "mode": "debug",
+ "program": "d:/local.git/be.ems/sshsvc/sshsvc.go",
+ "console": "integratedTerminal"
+ },
+ {
+ "name": "debug loadpconf",
+ "type": "go",
+ "request": "launch",
+ "mode": "debug",
+ "program": "d:/local.git/be.ems/tools/loadpconf",
+ "args": ["-p","../../config/param/smf_param_config.yaml"],
+ "console": "integratedTerminal"
+ },
+ {
+ "name": "debug crontask",
+ "type": "go",
+ "request": "launch",
+ "mode": "debug",
+ "program": "d:/local.git/be.ems/crontask",
+ "console": "integratedTerminal"
+ },
+ {
+ "name": "debug encyaml",
+ "type": "go",
+ "request": "launch",
+ "mode": "debug",
+ "program": "d:/local.git/be.ems/tools/encode",
+ //"args": ["-p","../../config/param/smf_param_config.yaml"],
+ "console": "integratedTerminal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..d844704
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,11 @@
+{
+ "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/README.md b/README.md
new file mode 100644
index 0000000..0c3be0d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,43 @@
+# nms_nbi
+
+网络管理系统-北向接口
+
+## redis 配置文件相关
+
+```conf
+# IP绑定与端口
+bind 0.0.0.0
+port 6379
+# 安全设置
+protected-mode yes
+requirepass ""
+# 从节点只能读取数据,不能直接写入数据
+slaveof 192.168.114.114 6379
+# 允许从节点进行读写操作
+replica-read-only no
+```
+
+## 开发
+
+```sh
+# 安装 Go版本 1.21.6 后进入项目根目录
+cd ./restagent
+# 下载依赖
+go mod download
+
+# 调试启动
+go run restagent.go -c ./restagent/etc/restconf.yaml --env local
+
+# 打包命名 restagent
+go build -ldflags="-s -w" -o restagent
+```
+
+## 打包
+
+```sh
+go env -w GOOS=linux
+
+go env -w GOOS=windows
+
+go build -o restagent -v -ldflags "-X 'nms_nbi/lib/global.Version=2.2311.8' -X 'nms_nbi/lib/global.BuildTime=`date`' -X 'nms_nbi/lib/global.GoVer=`go version`'"
+```
diff --git a/a b/a
deleted file mode 100644
index 2e65efe..0000000
--- a/a
+++ /dev/null
@@ -1 +0,0 @@
-a
\ No newline at end of file
diff --git a/features/aaaa/aaaa.go b/features/aaaa/aaaa.go
new file mode 100644
index 0000000..5ea5719
--- /dev/null
+++ b/features/aaaa/aaaa.go
@@ -0,0 +1,142 @@
+package aaaa
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/go-resty/resty/v2"
+
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/oauth"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+)
+
+var (
+ UriAAAASSO = config.DefaultUriPrefix + "/aaaa/{apiVersion}/security/sso" // for 4A external
+
+ CustomUriAAAASSO = config.UriPrefix + "/aaaa/{apiVersion}/security/sso" // for 4A external
+)
+
+var client = resty.New()
+
+func init() {
+ /*
+ client.
+ SetTimeout(10 * time.Second).
+ SetRetryCount(1).
+ SetRetryWaitTime(1 * time.Second).
+ SetRetryMaxWaitTime(2 * time.Second).
+ SetRetryAfter(func(client *resty.Client, resp *resty.Response) (time.Duration, error) {
+ return 0, errors.New("quota exceeded")
+ })
+ */
+ client.SetTimeout(3 * time.Second)
+}
+
+type AAAATicket struct {
+ Ticket string `json:"ticket"`
+}
+
+type SSOResult struct {
+ SSO struct {
+ Result string `json:"result"`
+ ResultMsg string `json:"result_msg"`
+ Ticket string `json:"ticket"`
+ ResultMsgcode string `json:"result_msgcode"`
+ Account []struct {
+ Accid string `json:"accid"`
+ } `json:"account"`
+ } `json:"sso"`
+}
+
+// Get system state from NF/NFs
+func GetSSOFromAAAA(w http.ResponseWriter, r *http.Request) {
+ log.Info("GetSSOFromAAAA processing... ")
+
+ vars := r.URL.Query()
+ ticket := vars["ticket"]
+ if len(ticket) == 0 {
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ log.Debug("ticket:", ticket)
+
+ log.Debugf("r.RemoteAddr:%s r.Host: %s", r.RemoteAddr, r.Host)
+
+ aaaaIp := r.RemoteAddr[:strings.Index(r.RemoteAddr, ":")]
+ omcIp := r.Host[:strings.Index(r.Host, ":")]
+
+ log.Debugf("aaaaIp=%s omcIp=%s", aaaaIp, omcIp)
+ requestURI2NF := fmt.Sprintf("http://%s:8080/qryUserByTicket", aaaaIp)
+
+ log.Debug("requestURI2NF:", requestURI2NF)
+
+ aaaaTicket := &AAAATicket{
+ Ticket: ticket[0],
+ }
+
+ body, err := json.Marshal(aaaaTicket)
+ if err != nil {
+ log.Error("Failed to json.Marshal:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ response, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ SetBody(body).
+ Post(requestURI2NF)
+ if err != nil {
+ log.Error("Failed to Post:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Debug("response:", response)
+
+ switch response.StatusCode() {
+ case http.StatusOK, http.StatusCreated, http.StatusNoContent, http.StatusAccepted:
+ ssoResult := new(SSOResult)
+ json.Unmarshal(response.Body(), ssoResult)
+ var accid string
+ if len(ssoResult.SSO.Account) != 0 {
+ accid = ssoResult.SSO.Account[0].Accid
+ }
+
+ log.Debug("accid:", accid)
+ exist, err := dborm.XormIsExistUser(accid)
+ if err != nil {
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ token := oauth.GenRandToken("aaaa") // Generate new token to session ID
+ affected, err := dborm.XormInsertSession(accid, r.RemoteAddr, token,
+ config.GetExpiresFromConfig(), config.GetYamlConfig().Auth.Session)
+ if err != nil {
+ log.Error("Failed to XormInsertSession:", err)
+ if affected == -1 {
+ services.ResponseForbidden403MultiLoginNotAllowed(w)
+ } else {
+ services.ResponseBadRequest400IncorrectLogin(w)
+ }
+ return
+ }
+ if exist == true {
+ redirectUrl := fmt.Sprintf("http://%s:8888/home.html?user=%s&token=%s", omcIp, accid, token)
+ services.ResponseRedirect(w, redirectUrl, accid, token)
+ return
+ } else {
+ services.ResponseBadRequest400IncorrectLogin(w)
+ return
+ }
+ default:
+ services.ResponseForbidden403NotPermission(w)
+ return
+ }
+}
diff --git a/features/cdr/cdrevent.go b/features/cdr/cdrevent.go
new file mode 100644
index 0000000..329fee6
--- /dev/null
+++ b/features/cdr/cdrevent.go
@@ -0,0 +1,67 @@
+package cdr
+
+import (
+ "encoding/json"
+ "io"
+ "net/http"
+
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/global"
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+ wsService "nms_nbi/src/modules/ws/service"
+)
+
+var (
+ UriCDREvent = config.DefaultUriPrefix + "/cdrManagement/v1/elementType/{elementTypeValue}/objectType/cdrEvent"
+ UriCDRFile = config.DefaultUriPrefix + "/cdrManagement/v1/elementType/{elementTypeValue}/objectType/cdrFile"
+
+ CustomUriCDREvent = config.UriPrefix + "/cdrManagement/v1/elementType/{elementTypeValue}/objectType/cdrEvent"
+ CustomUriCDRFile = config.UriPrefix + "/cdrManagement/v1/elementType/{elementTypeValue}/objectType/cdrFile"
+)
+
+type CDREvent struct {
+ NeType string `json:"neType" xorm:"ne_type"`
+ NeName string `json:"neName" xorm:"ne_name"`
+ RmUID string `json:"rmUID" xorm:"rm_uid"`
+ Timestamp int `json:"timestamp" xorm:"timestamp"`
+ CDR map[string]any `json:"CDR" xorm:"cdr_json"`
+}
+
+func PostCDREventFromNF(w http.ResponseWriter, r *http.Request) {
+ log.Info("PostCDREventFromNF processing... ")
+
+ // body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen))
+ body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen))
+ if err != nil {
+ log.Error("Faile to io.ReadAll: ", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ cdrEvent := new(CDREvent)
+ err = json.Unmarshal(body, &cdrEvent)
+ if cdrEvent.NeType == "" || err != nil {
+ log.Error("Failed to Unmarshal cdrEvent:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Trace("cdrEvent:", cdrEvent)
+
+ affected, err := dborm.XormInsertTableOne("cdr_event", cdrEvent)
+ if err != nil && affected <= 0 {
+ log.Error("Failed to insert cdr_event:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ // 推送到ws订阅组
+ if v, ok := cdrEvent.CDR["recordType"]; ok {
+ if v == "MOC" || v == "MTSM" {
+ wsService.NewWSSendImpl.ByGroupID(wsService.GROUP_IMS_CDR, cdrEvent)
+ }
+ }
+
+ services.ResponseStatusOK204NoContent(w)
+}
diff --git a/features/cm/exec_linux.go b/features/cm/exec_linux.go
new file mode 100644
index 0000000..392e6c3
--- /dev/null
+++ b/features/cm/exec_linux.go
@@ -0,0 +1,95 @@
+//go:build linux
+// +build linux
+
+package cm
+
+import (
+ "bytes"
+ "context"
+ "os/exec"
+ "time"
+
+ "nms_nbi/lib/log"
+)
+
+func ExecCmd(command string) error {
+ log.Debug("Exec command:", command)
+
+ cmd := exec.Command("/bin/bash", "-c", command)
+ out, err := cmd.CombinedOutput()
+ log.Tracef("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("exe cmd error: ", err)
+ return err
+ }
+ /*
+ if err := cmd.Start(); err != nil {
+ log.Error("Start error: ", err)
+ return err
+ }
+ if err := cmd.Wait(); err != nil {
+ log.Error("Wait error: ", err)
+ return err
+ }
+ */
+ return nil
+}
+
+func ExecShell(command string) error {
+ in := bytes.NewBuffer(nil)
+ cmd := exec.Command("sh")
+ cmd.Stdin = in
+ in.WriteString(command)
+ in.WriteString("exit\n")
+ if err := cmd.Start(); err != nil {
+ return err
+ }
+ return nil
+}
+
+func ExecOsCmd(command, os string) error {
+ log.Debugf("Exec %s command:%s", os, command)
+
+ var cmd *exec.Cmd
+ switch os {
+ case "Linux":
+ cmd = exec.Command(command)
+ case "Windows":
+ cmd = exec.Command("cmd", "/C", command)
+ }
+
+ out, err := cmd.CombinedOutput()
+ log.Tracef("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("exe cmd error: ", err)
+ return err
+ }
+ return nil
+}
+
+func StartSSHCmdWithTimeout(duration int, sshHost, cmdStr string) error {
+ timeout := time.Duration(duration) * time.Second
+ ctx, cancel := context.WithTimeout(context.Background(), timeout) // 设置超时
+ defer cancel()
+ cmd := exec.CommandContext(ctx, "ssh", sshHost, cmdStr)
+ var stdout, stderr bytes.Buffer
+ cmd.Stdout = &stdout
+ cmd.Stderr = &stderr
+ err := cmd.Start()
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func RunSSHCmd(sshHost, cmdStr string) error {
+ cmd := exec.Command("ssh", sshHost, cmdStr)
+ var stdout, stderr bytes.Buffer
+ cmd.Stdout = &stdout
+ cmd.Stderr = &stderr
+ err := cmd.Run()
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/features/cm/exec_windows.go b/features/cm/exec_windows.go
new file mode 100644
index 0000000..a32f05f
--- /dev/null
+++ b/features/cm/exec_windows.go
@@ -0,0 +1,83 @@
+//go:build windows
+// +build windows
+
+package cm
+
+import (
+ "bytes"
+ "context"
+ "os/exec"
+ "time"
+
+ "nms_nbi/lib/log"
+)
+
+func ExecCmd(command string) error {
+ log.Debug("Exec command:", command)
+
+ cmd := exec.Command("cmd", "/C", command)
+ out, err := cmd.CombinedOutput()
+ log.Tracef("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("exe cmd error: ", err)
+ return err
+ }
+ /*
+ if err := cmd.Start(); err != nil {
+ log.Error("Start error: ", err)
+ return err
+ }
+ if err := cmd.Wait(); err != nil {
+ log.Error("Wait error: ", err)
+ return err
+ }
+ */
+ return nil
+}
+
+func ExecOsCmd(command, os string) error {
+ log.Debugf("Exec %s command:%s", os, command)
+
+ var cmd *exec.Cmd
+ switch os {
+ case "Linux":
+ cmd = exec.Command(command)
+ case "Windows":
+ cmd = exec.Command("cmd", "/C", command)
+ }
+
+ out, err := cmd.CombinedOutput()
+ log.Tracef("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("exe cmd error: ", err)
+ return err
+ }
+ return nil
+}
+
+func StartSSHCmdWithTimeout(duration int, sshHost, cmdStr string) error {
+ timeout := time.Duration(duration) * time.Second
+ ctx, cancel := context.WithTimeout(context.Background(), timeout) // 设置超时
+ defer cancel()
+ cmd := exec.CommandContext(ctx, "ssh", sshHost, cmdStr)
+ var stdout, stderr bytes.Buffer
+ cmd.Stdout = &stdout
+ cmd.Stderr = &stderr
+ err := cmd.Start()
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func RunSSHCmd(sshHost, cmdStr string) error {
+ cmd := exec.Command("ssh", sshHost, cmdStr)
+ var stdout, stderr bytes.Buffer
+ cmd.Stdout = &stdout
+ cmd.Stderr = &stderr
+ err := cmd.Run()
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/features/cm/license.go b/features/cm/license.go
new file mode 100644
index 0000000..4389156
--- /dev/null
+++ b/features/cm/license.go
@@ -0,0 +1,524 @@
+package cm
+
+import (
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "os/exec"
+ "strings"
+ "time"
+
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+
+ "github.com/gorilla/mux"
+)
+
+var (
+ // General License URI
+ UriLicense = config.DefaultUriPrefix + "/systemManagement/{apiVersion}/{elementTypeValue}/license"
+ UriLicenseExt = config.DefaultUriPrefix + "/systemManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/license"
+
+ CustomUriLicense = config.UriPrefix + "/systemManagement/{apiVersion}/{elementTypeValue}/license"
+ CustomUriLicenseExt = config.UriPrefix + "/systemManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/license"
+)
+
+func UploadLicenseFile(w http.ResponseWriter, r *http.Request) {
+ log.Debug("UploadLicenseFile processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Http request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["neType"]
+ if neType == "" {
+ log.Error("neType is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ // neTypeUpper := strings.ToUpper(neType)
+ // neTypeLower := strings.ToLower(neType)
+
+ services.ResponseStatusOK204NoContent(w)
+}
+
+func DownloadLicenseFile(w http.ResponseWriter, r *http.Request) {
+ log.Debug("DownloadLicenseFile processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["neType"]
+ if neType == "" {
+ log.Error("neType is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ // // neTypeUpper := strings.ToUpper(neType)
+ // //neTypeLower := strings.ToLower(neType)
+
+ // version := vars["version"]
+ // if version == "" {
+ // log.Error("version is empty")
+ // services.ResponseNotFound404UriNotExist(w, r)
+ // return
+ // }
+
+ // sql := fmt.Sprintf("select * from ne_software where ne_type='%s' and version='%s'", neTypeUpper, version)
+ // neSoftware, err := dborm.XormGetDataBySQL(sql)
+ // if err != nil {
+ // log.Error("Faile to XormGetDataBySQL:", err)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // } else if len(*neSoftware) == 0 {
+ // err := global.ErrCMNotFoundTargetSoftware
+ // log.Error(err)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // }
+
+ // fileName := (*neSoftware)[0]["file_name"]
+ // path := (*neSoftware)[0]["path"]
+ // md5Sum := (*neSoftware)[0]["md5_sum"]
+
+ // services.ResponseFileWithNameAndMD5(w, http.StatusOK, fileName, path, md5Sum)
+}
+
+func DeleteLcenseFile(w http.ResponseWriter, r *http.Request) {
+ log.Debug("DeleteLcenseFile processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["neType"]
+ if neType == "" {
+ log.Error("neType is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ // neTypeUpper := strings.ToUpper(neType)
+ // //neTypeLower := strings.ToLower(neType)
+
+ // version := vars["version"]
+ // if version == "" {
+ // log.Error("version is empty")
+ // services.ResponseNotFound404UriNotExist(w, r)
+ // return
+ // }
+
+ // sql := fmt.Sprintf("select * from ne_software where ne_type='%s' and version='%s'", neTypeUpper, version)
+ // neSoftware, err := dborm.XormGetDataBySQL(sql)
+ // if err != nil {
+ // log.Error("Faile to XormGetDataBySQL:", err)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // } else if len(*neSoftware) == 0 {
+ // err := global.ErrCMNotFoundTargetSoftware
+ // log.Error(err)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // }
+
+ // where := fmt.Sprintf("ne_type='%s' and version='%s'", neTypeUpper, version)
+ // affected, err := dborm.XormDeleteDataByWhere(where, "ne_software")
+ // if err != nil || affected == 0 {
+ // log.Error("Faile to XormGetDataBySQL:", err)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // }
+
+ // fileName := (*neSoftware)[0]["file_name"]
+ // path := (*neSoftware)[0]["path"]
+ // filePath := fmt.Sprintf("%s/%s", path, fileName)
+ // err = os.Remove(filePath)
+ // if err != nil {
+ // log.Error("Faile to Remove:", err)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // }
+
+ services.ResponseStatusOK204NoContent(w)
+}
+
+type MMLRequest struct {
+ MML []string `json:"mml"`
+}
+
+var TIME_DELAY_AFTER_WRITE time.Duration = 200
+var TIME_DEAD_LINE time.Duration = 10
+
+func init() {
+ if config.GetYamlConfig().MML.Sleep != 0 {
+ TIME_DELAY_AFTER_WRITE = time.Duration(config.GetYamlConfig().MML.Sleep)
+ }
+ if config.GetYamlConfig().MML.DeadLine != 0 {
+ TIME_DEAD_LINE = time.Duration(config.GetYamlConfig().MML.DeadLine)
+ }
+}
+
+func UploadLicenseFileData(w http.ResponseWriter, r *http.Request) {
+ log.Info("UploadLicenseFileData processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Http request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+ if neType == "" {
+ log.Error("elementTypeValue is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ neTypeUpper := strings.ToUpper(neType)
+ neTypeLower := strings.ToLower(neType)
+
+ //md5Param := services.GetUriParamString(r, "md5Sum", ",", false, false)
+
+ neId := services.GetUriParamString(r, "neId", ",", false, false)
+
+ neInfo, err := dborm.XormGetNeInfo(neType, neId)
+ if err != nil {
+ log.Errorf("Failed to get ne_info:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Debug("neInfo:", neInfo)
+
+ licensePath := fmt.Sprintf("%s/%s", config.GetYamlConfig().OMC.License, neTypeLower)
+ err = os.MkdirAll(licensePath, os.ModePerm)
+ if err != nil {
+ log.Error("Failed to Mkdir:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ //fileName, err := services.HandleUploadFile(r, softwarePath, "")
+
+ // 解析multipart/form-data请求
+ err = r.ParseMultipartForm(10 << 20) // 10MB
+ if err != nil {
+ log.Error("Faile to ParseMultipartForm:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ // 获取文件和数据
+ licFile := r.MultipartForm.File["file"]
+ data := r.MultipartForm.Value["comment"]
+
+ var licenseFileName, comment string
+
+ // 处理license文件
+ if len(licFile) > 0 {
+ file := licFile[0]
+ // 打开文件
+ f, err := file.Open()
+ if err != nil {
+ log.Error("Faile to Open:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ defer f.Close()
+
+ // 创建本地文件
+ dst, err := os.Create(licensePath + "/" + file.Filename)
+ if err != nil {
+ log.Error("Faile to Create:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ defer dst.Close()
+
+ licenseFileName = file.Filename
+ // 将文件内容拷贝到本地文件
+ _, err = io.Copy(dst, f)
+ if err != nil {
+ log.Error("Faile to Copy:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ }
+
+ // 处理数据
+ if len(data) > 0 {
+ comment = data[0]
+ }
+
+ neLicensePath := strings.ReplaceAll(config.GetYamlConfig().NE.LicenseDir, "{neType}", neTypeLower)
+
+ srcFile := fmt.Sprintf("%s/%s", licensePath, licenseFileName)
+ scpDir := fmt.Sprintf("%s@%s:%s", config.GetYamlConfig().NE.User, neInfo.Ip, config.GetYamlConfig().NE.ScpDir)
+ cmd := exec.Command("scp", "-r", srcFile, scpDir)
+ out, err := cmd.CombinedOutput()
+ log.Debugf("Exec output: %v", string(out))
+ if err != nil {
+ log.Errorf("Faile to scp NF: neType=%s, neId=%s, ip=%s", neType, neId, neInfo.Ip)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ // backup system.ini to system.ini.bak
+ sshHost := fmt.Sprintf("%s@%s", config.GetYamlConfig().NE.User, neInfo.Ip)
+ cpCmd := fmt.Sprintf("sudo cp -f %s/system.ini %s/system.ini.bak", neLicensePath, neLicensePath)
+ cmd = exec.Command("ssh", sshHost, cpCmd)
+ out, err = cmd.CombinedOutput()
+ log.Debugf("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("Faile to execute cp command:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ // replace system.ini
+ neFilePath := config.GetYamlConfig().NE.ScpDir + "/" + licenseFileName
+ cpCmd = fmt.Sprintf("sudo mv -f %s %s/system.ini", neFilePath, neLicensePath)
+ cmd = exec.Command("ssh", sshHost, cpCmd)
+ out, err = cmd.CombinedOutput()
+ log.Debugf("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("Faile to execute cp command:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ /*
+ // judge license if expired
+ isRestart := false
+ hostUri := fmt.Sprintf("http://%s:%v", neInfo.Ip, neInfo.Port)
+ requestURI2NF := fmt.Sprintf("%s/api/rest/systemManagement/v1/elementType/%s/objectType/systemState",
+ hostUri, neTypeLower)
+ log.Debug("requestURI2NF:", requestURI2NF)
+
+ resp, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}).
+ //SetHeaders(map[string]string{"accessToken": token}).
+ 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("Failed to get system state:", err)
+ isRestart = true
+ } else {
+ systemState := make(map[string]interface{})
+ _ = json.Unmarshal(resp.Body(), &systemState)
+ expiryDate := fmt.Sprintf("%v", systemState["expiryDate"])
+ t1_expiry, _ := time.ParseInLocation(time.DateOnly, expiryDate, time.Local)
+ nowDate := time.Now().Local()
+ nowDate.Format(time.DateOnly)
+ isRestart = t1_expiry.Before(nowDate)
+ }
+ // case non-expired license: send NE reload license MML
+ if !isRestart {
+ // send reload license MML
+ var buf [20 * 1024]byte
+ //buf := make([]byte, 0)
+ var n int
+ if neInfo != nil {
+ switch strings.ToLower(neType) {
+ case "ims":
+ hostMML := fmt.Sprintf("%s:%d", neInfo.Ip, config.GetYamlConfig().MML.Port)
+ conn, err := net.Dial("tcp", hostMML)
+ if err != nil {
+ errMsg := fmt.Sprintf("Failed to dial %s: %v", hostMML, err)
+ log.Error(errMsg)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ defer conn.Close()
+
+ conn.SetDeadline(time.Now().Add(TIME_DEAD_LINE * time.Second))
+
+ _, err = conn.Write([]byte(config.GetYamlConfig().MML.User + "\r\n"))
+ if err != nil {
+ log.Error("Failed to write:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ time.Sleep(time.Millisecond * TIME_DELAY_AFTER_WRITE)
+
+ n, err = conn.Read(buf[0:])
+ if err != nil {
+ log.Error("Failed to read:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Trace(string(buf[0:n]))
+
+ _, err = conn.Write([]byte(config.GetYamlConfig().MML.Password + "\r\n"))
+ if err != nil {
+ log.Error("Failed to write:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ time.Sleep(time.Millisecond * TIME_DELAY_AFTER_WRITE)
+
+ n, err = conn.Read(buf[0:])
+ if err != nil {
+ log.Error("Failed to read:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Trace(string(buf[0 : n-len(neType)-2]))
+
+ mmlCommand := "check lic\r\n"
+
+ _, err = conn.Write([]byte(mmlCommand))
+ if err != nil {
+ log.Error("Failed to write:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ time.Sleep(time.Millisecond * TIME_DELAY_AFTER_WRITE)
+
+ n, err = conn.Read(buf[0:])
+ if err != nil {
+ log.Error("Failed to read:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Trace(string(buf[0 : n-len(neType)-2]))
+
+ re1 := regexp.MustCompile(`\x1B\[[0-9;]*[a-zA-Z]`) // 匹配包含␛的控制字符
+ //re2 := regexp.MustCompile(`\x00`) // 匹配空字符
+ re2 := regexp.MustCompile(`[\x00-\x08\x0B\x0C\x0E-\x1F\x7F\x1B]`) // 匹配空字符和包含␛的控制字符
+ //re := regexp.MustCompile(`[\x00-\x1F\x7F]`)
+ // upf telnet buffer只能读取一次,需要去掉前面的多余字符
+ result := re1.ReplaceAllString(string(buf[0:n-len(neType)-2]), "")
+ result = re2.ReplaceAllString(result, "")
+ if !strings.Contains(result, "COMMAND OK") {
+ err = fmt.Errorf("failed to check license, %s", result)
+ log.Error(err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ default:
+ hostMML := fmt.Sprintf("%s:%d", neInfo.Ip, config.GetYamlConfig().MML.Port)
+ conn, err := net.Dial("tcp", hostMML)
+ if err != nil {
+ errMsg := fmt.Sprintf("Failed to dial %s: %v", hostMML, err)
+ log.Error(errMsg)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ defer conn.Close()
+
+ conn.SetDeadline(time.Now().Add(TIME_DEAD_LINE * time.Second))
+ loginStr := fmt.Sprintf("%s\n%s\n", config.GetYamlConfig().MML.User, config.GetYamlConfig().MML.Password)
+ _, err = conn.Write([]byte(loginStr))
+ if err != nil {
+ log.Error("Failed to write:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ time.Sleep(time.Millisecond * TIME_DELAY_AFTER_WRITE)
+
+ n, err = conn.Read(buf[0:])
+ if err != nil {
+ log.Error("Failed to read:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Trace(string(buf[0:n]))
+
+ mmlCommand := "check lic\n"
+ _, err = conn.Write([]byte(mmlCommand))
+ if err != nil {
+ log.Error("Failed to write:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ time.Sleep(time.Millisecond * TIME_DELAY_AFTER_WRITE)
+
+ n, err = conn.Read(buf[0:])
+ if err != nil {
+ log.Error("Failed to read:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Trace(string(buf[0 : n-len(neType)-2]))
+ re1 := regexp.MustCompile(`\x1B\[[0-9;]*[a-zA-Z]`) // 匹配包含␛的控制字符
+ //re2 := regexp.MustCompile(`\x00`) // 匹配空字符
+ re2 := regexp.MustCompile(`[\x00-\x08\x0B\x0C\x0E-\x1F\x7F\x1B]`) // 匹配空字符和包含␛的控制字符
+ //re := regexp.MustCompile(`[\x00-\x1F\x7F]`)
+ result := re1.ReplaceAllString(string(buf[0:n-len(neType)-2]), "")
+ result = re2.ReplaceAllString(result, "")
+ if !strings.Contains(result, "COMMAND OK") {
+ err = fmt.Errorf("failed to check license, %s", result)
+ log.Error(err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ }
+ }
+ } else {
+ // case expired license: restart NE service
+ switch neTypeLower {
+ case "omc":
+ restartCmd := fmt.Sprintf("sudo %s/bin/omcsvc.sh restart", config.GetYamlConfig().NE.OmcDir)
+ cmd := exec.Command("ssh", sshHost, restartCmd)
+ out, err := cmd.CombinedOutput()
+ log.Debugf("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("Faile to execute ssh restart omc:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ case "ims":
+ restartCmd := "sudo ims-stop && sudo ims-start"
+ cmd := exec.Command("ssh", sshHost, restartCmd)
+ out, err := cmd.CombinedOutput()
+ log.Debugf("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("Faile to execute ssh sudo systemctl command:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ default:
+ restartCmd := fmt.Sprintf("sudo systemctl restart %s.service", neTypeLower)
+ cmd := exec.Command("ssh", sshHost, restartCmd)
+ out, err := cmd.CombinedOutput()
+ log.Debugf("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("Faile to execute ssh sudo systemctl command:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ }
+ }
+ */
+ neLicense := dborm.NeLicense{
+ NeType: neTypeUpper,
+ NeID: neId,
+ Status: "ACTIVE",
+ Path: licensePath,
+ FileName: licenseFileName,
+ Comment: comment,
+ }
+
+ log.Debug("neLicense:", neLicense)
+ _, err = dborm.XormInsertTableOne("ne_license", neLicense)
+ if err != nil {
+ log.Error("Faile to XormInsertTableOne:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ services.ResponseStatusOK204NoContent(w)
+}
diff --git a/features/cm/ne.go b/features/cm/ne.go
new file mode 100644
index 0000000..99e18ab
--- /dev/null
+++ b/features/cm/ne.go
@@ -0,0 +1,983 @@
+package cm
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "os/exec"
+ "strings"
+ "time"
+
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/global"
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+ tokenConst "nms_nbi/src/framework/constants/token"
+ neService "nms_nbi/src/modules/network_element/service"
+
+ "github.com/go-resty/resty/v2"
+ "github.com/gorilla/mux"
+)
+
+var (
+ // NE CM export/import
+ NeCmUri = config.DefaultUriPrefix + "/systemManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/cm"
+ // NE info
+ UriNeInfo = config.DefaultUriPrefix + "/systemManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/neInfo"
+ // NE backup file
+ UriNeCmFile = config.DefaultUriPrefix + "/systemManagement/{apiVersion}/{neType}/neBackup/{fileName}"
+ // service action uri, action: start/stop/restart
+ UriNeService = config.DefaultUriPrefix + "/systemManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/service/{action}"
+ // nf instance action uri, action: start/stop/restart
+ UriNeInstance = config.DefaultUriPrefix + "/systemManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/instance/{action}"
+
+ CustomNeCmUri = config.UriPrefix + "/systemManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/cm"
+ CustomUriNeInfo = config.UriPrefix + "/systemManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/neInfo"
+ CustomUriNeCmFile = config.UriPrefix + "/systemManagement/{apiVersion}/{neType}/neBackup/{fileName}"
+ // service action uri, action: start/stop/restart
+ CustomUriNeService = config.UriPrefix + "/systemManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/service/{action}"
+ // nf instance action uri, action: start/stop/restart
+ CustomUriNeInstance = config.UriPrefix + "/systemManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/instance/{action}"
+)
+
+const (
+ NEStatusActive = 0
+ NEStatusOffline = 1
+ NEStatusStandby = 2
+ NEStatusMaintain = 3
+)
+
+var client = resty.New()
+
+func init() {
+ /*
+ client.
+ SetTimeout(10 * time.Second).
+ SetRetryCount(1).
+ SetRetryWaitTime(1 * time.Second).
+ SetRetryMaxWaitTime(2 * time.Second).
+ SetRetryAfter(func(client *resty.Client, resp *resty.Response) (time.Duration, error) {
+ return 0, errors.New("quota exceeded")
+ })
+ */
+ client.
+ SetTimeout(time.Duration(1 * time.Second))
+}
+
+func GetNeInfo(w http.ResponseWriter, r *http.Request) {
+ log.Debug("GetNeInfo processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+ if neType == "" {
+ log.Error("elementTypeValue is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ neId := services.GetUriParamString(r, "ne_id", ",", false, false)
+
+ // no, _ := strconv.ParseInt(neId, 10, 64)
+ neInfo, err := dborm.XormGetNeInfo(neType, neId)
+ if err != nil {
+ log.Error("dborm.XormGetNeInfo is failed:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ var response services.DataResponse
+ response.Data = neInfo
+ services.ResponseWithJson(w, http.StatusOK, response)
+}
+
+type OmcNeConfig struct {
+ NeId string `json:"neId" xorm:"ne_id"` // 网元标识(内部),
+ RmUID string `json:"rmUID" xorm:"rm_uid"` // rmUID 网元唯一标识
+ NeName string `json:"neName" xorm:"ne_name"` // 网元名称(内部)/友好名称(北向资源/性能等使用)
+ PvFlag string `json:"pvFlag" xorm:"pv_flag"` // 网元虚实性标识 VNF/PNF: 虚拟/物理
+ Province string `json:"province" xorm:"province"` // 网元所在省份
+ VendorName string `json:"vendorName" xorm:"vendor_name"` // 厂商名称
+ // ManagedBy string `json:"managedBy" xorm:"managed_by"` // 管理ManagedElement的ManagementNode对象类的DN值
+ Dn string `json:"dn" xorm:"dn"` // 资源里边的ManagedBy,性能的Dn,网络唯一标识
+}
+
+func PostNeInfo(w http.ResponseWriter, r *http.Request) {
+ log.Debug("PostNeInfo processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+ if neType == "" {
+ log.Error("elementTypeValue is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ neTypeUpper := strings.ToUpper(neType)
+ syncFlag := services.GetUriParamString(r, "sync2ne", ",", false, false)
+ body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen))
+ if err != nil {
+ log.Error("Failed to o.ReadAll:", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ log.Trace("Body:", string(body))
+
+ neInfo := new(dborm.NeInfo)
+ err = json.Unmarshal(body, neInfo)
+ if err != nil {
+ log.Error("Failed to json.Unmarshal:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ neInfo.UpdateTime = time.Now().Format(time.DateTime)
+ log.Debug("NE info:", neInfo)
+
+ //if !config.GetYamlConfig().OMC.Chk2Ne {
+ if syncFlag == "false" || neTypeUpper == config.GetYamlConfig().OMC.NeType {
+ neInfo.Status = NEStatusMaintain
+ affected, err := dborm.XormInsertNeInfo(neInfo)
+ if err != nil {
+ log.Error("Failed to insert Ne info:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ // 刷新缓存,不存在结构体网元Id空字符串
+ neService.NewNeInfoImpl.RefreshByNeTypeAndNeID(neInfo.NeType, neInfo.NeId)
+
+ mapRow := make(map[string]interface{})
+ row := map[string]interface{}{"affectedRows": affected}
+ mapRow["data"] = row
+ services.ResponseWithJson(w, http.StatusOK, mapRow)
+ return
+ } else {
+ hostUri := global.CombineHostUri(neInfo.Ip, neInfo.Port)
+ //hostUri := fmt.Sprintf("http://%s:%v", neInfo.Ip, neInfo.Port)
+ apiUri := fmt.Sprintf("%s/systemManagement/v1/elementType/%s/objectType/config/omcNeConfig", config.DefaultUriPrefix, strings.ToLower(neInfo.NeType))
+ requestURI2NF := fmt.Sprintf("%s%s", hostUri, apiUri)
+ log.Debug("requestURI2NF:", requestURI2NF)
+
+ omcNeConfig := &OmcNeConfig{
+ NeId: neInfo.NeId,
+ RmUID: neInfo.RmUID,
+ NeName: neInfo.NeName,
+ PvFlag: neInfo.PvFlag,
+ Province: neInfo.Province,
+ VendorName: neInfo.VendorName,
+ Dn: neInfo.Dn,
+ }
+ body, _ = json.Marshal(omcNeConfig)
+ response, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ SetBody(body).
+ Put(requestURI2NF)
+ if err != nil {
+ log.Error("Failed to Put:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Info("StatusCode: ", response.StatusCode())
+
+ respMsg := make(map[string]interface{})
+ switch response.StatusCode() {
+ case http.StatusOK, http.StatusCreated, http.StatusNoContent, http.StatusAccepted:
+ neInfo.Status = NEStatusActive
+ affected, err := dborm.XormInsertNeInfo(neInfo)
+ if err != nil {
+ log.Error("Failed to dborm.XormInsertNeInfo:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else if affected <= 0 {
+ log.Infof("Not record affected to insert ne_info")
+ }
+
+ // 刷新缓存,不存在结构体网元Id空字符串
+ neService.NewNeInfoImpl.RefreshByNeTypeAndNeID(neInfo.NeType, neInfo.NeId)
+
+ services.ResponseStatusOK204NoContent(w)
+ return
+ default:
+ log.Info("response body:", string(response.Body()))
+ body := new(map[string]interface{})
+ _ = json.Unmarshal(response.Body(), &body)
+ respMsg["error"] = body
+ }
+
+ services.ResponseWithJson(w, response.StatusCode(), respMsg)
+ return
+ }
+}
+
+func PutNeInfo(w http.ResponseWriter, r *http.Request) {
+ log.Debug("PutNeInfo processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+ if neType == "" {
+ log.Error("elementTypeValue is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ neTypeLower := strings.ToLower(neType)
+ neTypeUpper := strings.ToUpper(neType)
+ syncFlag := services.GetUriParamString(r, "sync2ne", ",", false, false)
+ body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen))
+ if err != nil {
+ log.Error("Failed to io.ReadAll:", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ neInfo := new(dborm.NeInfo)
+ _ = json.Unmarshal(body, neInfo)
+ neInfo.NeType = strings.ToUpper(neType)
+ neInfo.UpdateTime = time.Now().Format(time.DateTime)
+ log.Debug("NE info:", neInfo)
+
+ //if !config.GetYamlConfig().OMC.Chk2Ne {
+ if syncFlag == "false" || neTypeUpper == config.GetYamlConfig().OMC.NeType {
+ neInfo.Status = NEStatusMaintain
+ affected, err := dborm.XormUpdateNeInfo(neInfo)
+ if err != nil {
+ log.Error("Failed to update Ne info:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ // 刷新缓存,不存在结构体网元Id空字符串
+ neService.NewNeInfoImpl.RefreshByNeTypeAndNeID(neInfo.NeType, neInfo.NeId)
+
+ mapRow := make(map[string]interface{})
+ row := map[string]interface{}{"affectedRows": affected}
+ mapRow["data"] = row
+ services.ResponseWithJson(w, http.StatusOK, mapRow)
+ return
+ } else {
+ hostUri := global.CombineHostUri(neInfo.Ip, neInfo.Port)
+ //hostUri := fmt.Sprintf("http://%s:%v", neInfo.Ip, neInfo.Port)
+ apiUri := fmt.Sprintf("%s/systemManagement/v1/elementType/%s/objectType/config/omcNeConfig", config.DefaultUriPrefix, neTypeLower)
+ requestURI2NF := fmt.Sprintf("%s%s", hostUri, apiUri)
+ log.Debug("requestURI2NF:", requestURI2NF)
+
+ omcNeConfig := &OmcNeConfig{
+ NeId: neInfo.NeId,
+ RmUID: neInfo.RmUID,
+ NeName: neInfo.NeName,
+ PvFlag: neInfo.PvFlag,
+ Province: neInfo.Province,
+ VendorName: neInfo.VendorName,
+ Dn: neInfo.Dn,
+ }
+ body, _ = json.Marshal(omcNeConfig)
+ response, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}).
+ SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ SetBody(body).
+ Put(requestURI2NF)
+ if err != nil {
+ log.Error("Failed to Put:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Info("StatusCode: ", response.StatusCode())
+
+ respMsg := make(map[string]interface{})
+ switch response.StatusCode() {
+ case http.StatusOK, http.StatusCreated, http.StatusNoContent, http.StatusAccepted:
+ neInfo.Status = NEStatusActive
+ affected, err := dborm.XormUpdateNeInfo(neInfo)
+ if err != nil {
+ log.Error("Failed to dborm.XormUpdateNeInfo:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else if affected <= 0 {
+ log.Infof("Not record affected to insert ne_info")
+ }
+
+ // 刷新缓存,不存在结构体网元Id空字符串
+ neService.NewNeInfoImpl.RefreshByNeTypeAndNeID(neInfo.NeType, neInfo.NeId)
+
+ services.ResponseStatusOK204NoContent(w)
+ return
+ default:
+ log.Info("response body:", string(response.Body()))
+ body := new(map[string]interface{})
+ _ = json.Unmarshal(response.Body(), &body)
+ respMsg["error"] = body
+ }
+
+ services.ResponseWithJson(w, response.StatusCode(), respMsg)
+ return
+ }
+}
+
+func DeleteNeInfo(w http.ResponseWriter, r *http.Request) {
+ log.Debug("DeleteNeInfo processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+ if neType == "" {
+ log.Error("elementTypeValue is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen))
+ if err != nil {
+ log.Error("io.ReadAll is failed:", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ neInfo := new(dborm.NeInfo)
+ _ = json.Unmarshal(body, neInfo)
+ neInfo.NeType = strings.ToUpper(neType)
+ neInfo.NeId = services.GetUriParamString(r, "ne_id", ",", false, false)
+ neInfo, err = dborm.XormGetNeInfo(neInfo.NeType, neInfo.NeId)
+ if err != nil || neInfo == nil {
+ log.Error("Failed to delete Ne info:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Debug("NE info:", neInfo)
+
+ // if NE in active status, can't delete NE
+ if !IsActiveNF(neInfo) {
+ affected, err := dborm.XormDeleteNeInfo(neInfo)
+ if err != nil {
+ log.Error("Failed to delete Ne info:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ // 刷新缓存,不存在结构体网元Id空字符串
+ neService.NewNeInfoImpl.RefreshByNeTypeAndNeID(neInfo.NeType, neInfo.NeId)
+
+ mapRow := make(map[string]interface{})
+ row := map[string]interface{}{"affectedRows": affected}
+ mapRow["data"] = row
+ services.ResponseWithJson(w, http.StatusOK, mapRow)
+ return
+ }
+ err = global.ErrCMCannotDeleteActiveNE
+ log.Error(err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+}
+
+func IsActiveNF(neInfo *dborm.NeInfo) bool {
+ log.Debug("IsActiveNF processing... ")
+
+ hostUri := fmt.Sprintf("http://%s:%v", neInfo.Ip, neInfo.Port)
+ requestURI := fmt.Sprintf(config.UriPrefix+"/systemManagement/v1/elementType/%s/objectType/systemState",
+ strings.ToLower(neInfo.NeType))
+
+ response, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ Get(hostUri + requestURI)
+ if err != nil {
+ log.Error("Failed to Get:", err)
+ return false
+ }
+
+ switch response.StatusCode() {
+ case http.StatusOK, http.StatusCreated, http.StatusNoContent, http.StatusAccepted:
+ return true
+ }
+ return false
+}
+
+func ExportCmFromNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("ExportCmFromNF processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+ if neType == "" {
+ log.Error("elementTypeValue is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ neTypeUpper := strings.ToUpper(neType)
+ neTypeLower := strings.ToLower(neType)
+
+ neId := services.GetUriParamString(r, "ne_id", ",", false, false)
+
+ // neInfo := new(dborm.NeInfo)
+ neInfo, err := dborm.XormGetNeInfo(neType, neId)
+ if err != nil {
+ log.Errorf("Failed to get ne_info:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Debug("neInfo:", neInfo)
+ nePath := fmt.Sprintf("%s/etc/%s", config.GetYamlConfig().OMC.Backup, neTypeLower)
+ isExist, err := global.PathExists(nePath)
+ if err != nil {
+ log.Errorf("Failed to stat:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ if isExist {
+ err = os.RemoveAll(nePath)
+ if err != nil {
+ log.Errorf("Failed to RemoveAll:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ }
+ err = os.MkdirAll(nePath, os.ModePerm)
+ if err != nil {
+ log.Errorf("Failed to MkdirAll:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ var scpCmd string
+ ipType := global.ParseIPAddr(neInfo.Ip)
+ 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/etc/*.yaml %s/etc/%s", config.GetYamlConfig().NE.User,
+ neInfo.Ip, config.GetYamlConfig().NE.OmcDir, config.GetYamlConfig().OMC.Backup, neTypeLower)
+ } else {
+ 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)
+ }
+
+ 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)
+ }
+
+ case "mme":
+ if ipType == global.IsIPv4 {
+ scpCmd = fmt.Sprintf("scp -r %s@%s:%s/%s/*.conf %s/etc/%s", config.GetYamlConfig().NE.User,
+ neInfo.Ip, config.GetYamlConfig().NE.EtcDir,
+ neTypeLower, config.GetYamlConfig().OMC.Backup, neTypeLower)
+ } else {
+ scpCmd = fmt.Sprintf("scp -r %s@[%s]:%s/%s/*.conf %s/etc/%s", config.GetYamlConfig().NE.User,
+ neInfo.Ip, config.GetYamlConfig().NE.EtcDir,
+ neTypeLower, config.GetYamlConfig().OMC.Backup, neTypeLower)
+ }
+
+ default:
+ 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,
+ neTypeLower, config.GetYamlConfig().OMC.Backup, neTypeLower)
+ } else {
+ scpCmd = fmt.Sprintf("scp -r %s@[%s]:%s/%s/*.yaml %s/etc/%s", config.GetYamlConfig().NE.User,
+ neInfo.Ip, config.GetYamlConfig().NE.EtcDir,
+ neTypeLower, config.GetYamlConfig().OMC.Backup, neTypeLower)
+ }
+
+ }
+
+ zipFile := fmt.Sprintf("%s-%s-etc-%s.zip", neTypeLower, strings.ToLower(neInfo.NeId), time.Now().Format(global.DateData))
+ zipFilePath := config.GetYamlConfig().OMC.Backup + "/" + zipFile
+ zipCmd := fmt.Sprintf("cd %s/etc && zip -r %s %s/*", config.GetYamlConfig().OMC.Backup, zipFilePath, neTypeLower)
+
+ command := fmt.Sprintf("%s&&%s", scpCmd, zipCmd)
+
+ log.Debug("command:", command)
+ err = ExecCmd(command)
+ if err != nil {
+ log.Error("Faile to exec command:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ md5Sum, err := global.GetFileMD5Sum(zipFilePath)
+ if err != nil {
+ log.Error("Faile to md5sum:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ //log.Debug("md5Str:", md5Sum)
+ path := config.GetYamlConfig().OMC.Backup
+ neBackup := dborm.NeBackup{NeType: neTypeUpper, NeId: neId, FileName: zipFile, Path: path, Md5Sum: md5Sum}
+ _, err = dborm.XormInsertTableOne("ne_backup", neBackup)
+ if err != nil {
+ log.Error("Faile to XormInsertTableOne:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ //services.ResponseFileWithNameAndMD5(w, http.StatusOK, zipFile, path, md5Sum)
+ services.ResponseStatusOK204NoContent(w)
+}
+
+type ImportCMJson struct {
+ FileName string `json:"fileName"`
+}
+
+func ImportCmToNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("ImportCmToNF processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+ if neType == "" {
+ log.Error("elementTypeValue is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ neTypeUpper := strings.ToUpper(neType)
+ neTypeLower := strings.ToLower(neType)
+
+ neId := services.GetUriParamString(r, "ne_id", ",", false, false)
+
+ var fileName, path string
+ if services.IsJsonContentType(r) {
+ body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen))
+ if err != nil {
+ log.Error("io.ReadAll is failed:", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ log.Debug("Body:", string(body))
+
+ importCMJson := new(ImportCMJson)
+ _ = json.Unmarshal(body, importCMJson)
+ fileName = importCMJson.FileName
+ path = config.GetYamlConfig().OMC.Backup
+ } else {
+ path = config.GetYamlConfig().OMC.Upload
+ fileName, err = services.HandleUploadFile(r, path, "")
+ if err != nil {
+ log.Error("Faile to HandleUploadFile:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ }
+ filePath := fmt.Sprintf("%s/%s", path, fileName)
+
+ // neInfo := new(dborm.NeInfo)
+ neInfo, err := dborm.XormGetNeInfo(neType, neId)
+ if err != nil {
+ log.Errorf("Failed to get ne_info:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Debug("neInfo:", neInfo)
+
+ md5Sum, err := global.GetFileMD5Sum(filePath)
+ if err != nil {
+ log.Error("Faile to GetFileMD5Sum:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ //neBackup := dborm.NeBackup{NeType: neType, NeId: neId, Md5Sum: md5Sum}
+ //log.Debug("neBackup:", neBackup)
+ where := fmt.Sprintf("ne_type='%s' and ne_id='%s' and md5_sum='%s'", neTypeUpper, neId, md5Sum)
+ has, err := dborm.XormExistTableOne("ne_backup", where)
+ if err != nil {
+ log.Error("Faile to XormInsertTableOne:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else if !has {
+ err = global.ErrCMInvalidBackupFile
+ log.Error(err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ // nePath := fmt.Sprintf("%s/etc/%s", config.GetYamlConfig().OMC.Upload, neTypeLower)
+ // isExist, err := global.PathExists(nePath)
+ // if err != nil {
+ // log.Errorf("Failed to stat:", err)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // }
+ // if isExist {
+ // err = os.RemoveAll(nePath)
+ // if err != nil {
+ // log.Errorf("Failed to remove:", err)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // }
+ // }
+ // unzipCmd := fmt.Sprintf("unzip -o %s -d %s", filePath, config.GetYamlConfig().OMC.Upload)
+
+ // var scpCmd string
+ // ipType := global.ParseIPAddr(neInfo.Ip)
+ // if ipType == global.IsIPv4 {
+ // scpCmd = fmt.Sprintf("scp -r %s/etc/%s %s@%s:%s", config.GetYamlConfig().OMC.Upload,
+ // neTypeLower, config.GetYamlConfig().NE.User, neInfo.Ip, config.GetYamlConfig().NE.EtcDir)
+ // } else {
+ // scpCmd = fmt.Sprintf("scp -r %s/etc/%s %s@[%s]:%s", config.GetYamlConfig().OMC.Upload,
+ // neTypeLower, config.GetYamlConfig().NE.User, neInfo.Ip, config.GetYamlConfig().NE.EtcDir)
+ // }
+
+ // err = ExecCmd(fmt.Sprintf("%s && %s", unzipCmd, scpCmd))
+ // if err != nil {
+ // log.Errorf("Faile to scp NF: neType=%s, neId=%s, ip=%s", neType, neId, neInfo.Ip)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // }
+
+ // nePath := fmt.Sprintf("%s/etc/%s", config.GetYamlConfig().OMC.Upload, neTypeLower)
+ // isExist, err := global.PathExists(nePath)
+ // if err != nil {
+ // log.Errorf("Failed to stat:", err)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // }
+ // if isExist {
+ // err = os.RemoveAll(nePath)
+ // if err != nil {
+ // log.Errorf("Failed to remove:", err)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // }
+ // }
+ var scpZipCmd string
+ ipType := global.ParseIPAddr(neInfo.Ip)
+ if ipType == global.IsIPv4 {
+ scpZipCmd = fmt.Sprintf("scp -r %s %s@%s:%s", filePath,
+ config.GetYamlConfig().NE.User, neInfo.Ip, config.GetYamlConfig().NE.ScpDir)
+ } else {
+ scpZipCmd = fmt.Sprintf("scp -r %s %s@[%s]:%s", filePath,
+ config.GetYamlConfig().NE.User, neInfo.Ip, config.GetYamlConfig().NE.ScpDir)
+ }
+ err = ExecCmd(scpZipCmd)
+ if err != nil {
+ log.Errorf("Faile to scp NF: neType=%s, neId=%s, ip=%s", neType, neId, neInfo.Ip)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ neFilePath := config.GetYamlConfig().NE.ScpDir + "/" + fileName
+ var unzipCmd string
+ if neTypeLower != "omc" {
+ unzipCmd = fmt.Sprintf("sudo unzip -o %s -d %s", neFilePath, config.GetYamlConfig().NE.EtcDir)
+ } else {
+ unzipCmd = fmt.Sprintf("sudo unzip -oj %s -d %s/etc", neFilePath, config.GetYamlConfig().NE.OmcDir)
+ }
+ sshHost := fmt.Sprintf("%s@%s", config.GetYamlConfig().NE.User, neInfo.Ip)
+ cmd := exec.Command("ssh", sshHost, unzipCmd)
+ out, err := cmd.CombinedOutput()
+ log.Tracef("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("Faile to execute command:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ services.ResponseStatusOK204NoContent(w)
+}
+
+func DownloadNeBackupFile(w http.ResponseWriter, r *http.Request) {
+ log.Debug("DownloadNeBackupFile processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["neType"]
+ if neType == "" {
+ log.Error("neType is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ neTypeUpper := strings.ToUpper(neType)
+ //neTypeLower := strings.ToLower(neType)
+
+ fileName := vars["fileName"]
+ if fileName == "" {
+ log.Error("fileName is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ sql := fmt.Sprintf("select * from ne_backup where ne_type='%s' and file_name='%s'", neTypeUpper, fileName)
+ neBackup, err := dborm.XormGetDataBySQL(sql)
+ if err != nil {
+ log.Error("Faile to XormGetDataBySQL:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else if len(*neBackup) == 0 {
+ err := global.ErrCMNotFoundTargetBackupFile
+ log.Error(err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ path := (*neBackup)[0]["path"]
+ md5Sum := (*neBackup)[0]["md5_sum"]
+
+ services.ResponseFileWithNameAndMD5(w, http.StatusOK, fileName, path, md5Sum)
+}
+
+func DeleteNeBackupFile(w http.ResponseWriter, r *http.Request) {
+ log.Debug("DeleteNeBackupFile processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["neType"]
+ if neType == "" {
+ log.Error("neType is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ neTypeUpper := strings.ToUpper(neType)
+ //neTypeLower := strings.ToLower(neType)
+
+ fileName := vars["fileName"]
+ if fileName == "" {
+ log.Error("fileName is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ sql := fmt.Sprintf("select * from ne_backup where ne_type='%s' and file_name='%s'", neTypeUpper, fileName)
+ neBackup, err := dborm.XormGetDataBySQL(sql)
+ if err != nil {
+ log.Error("Faile to XormGetDataBySQL:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else if len(*neBackup) == 0 {
+ err := global.ErrCMNotFoundTargetBackupFile
+ log.Error(err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ where := fmt.Sprintf("ne_type='%s' and file_name='%s'", neTypeUpper, fileName)
+ affected, err := dborm.XormDeleteDataByWhere(where, "ne_backup")
+ if err != nil || affected == 0 {
+ log.Error("Faile to XormGetDataBySQL:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ path := (*neBackup)[0]["path"]
+ filePath := fmt.Sprintf("%s/%s", path, fileName)
+ err = os.Remove(filePath)
+ if err != nil {
+ log.Error("Faile to Remove:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ services.ResponseStatusOK204NoContent(w)
+}
+
+func PostNeServiceAction(w http.ResponseWriter, r *http.Request) {
+ log.Debug("PostNeServiceAction processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+ if neType == "" {
+ log.Error("elementTypeValue is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ //neTypeUpper := strings.ToUpper(neType)
+ neTypeLower := strings.ToLower(neType)
+ action := vars["action"]
+
+ neId := services.GetUriParamString(r, "neId", ",", false, false)
+
+ // neInfo := new(dborm.NeInfo)
+ neInfo, err := dborm.XormGetNeInfo(neType, neId)
+ if err != nil {
+ log.Error("Failed to get ne_info:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Debug("neInfo:", neInfo)
+
+ sshHost := fmt.Sprintf("%s@%s", config.GetYamlConfig().NE.User, neInfo.Ip)
+ switch neTypeLower {
+ case "omc":
+ actionCmd := fmt.Sprintf("sudo %s/bin/omcsvc.sh %s", config.GetYamlConfig().NE.OmcDir, action)
+ cmd := exec.Command("ssh", sshHost, actionCmd)
+ out, err := cmd.CombinedOutput()
+ log.Debugf("Exec output: %v", string(out))
+ if err != nil {
+ log.Errorf("Faile to execute ssh %s omc:%v", action, err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ case "ims":
+ switch action {
+ case "start", "stop":
+ actionCmd := fmt.Sprintf("sudo ims-%s", action)
+ cmd := exec.Command("ssh", sshHost, actionCmd)
+ out, err := cmd.CombinedOutput()
+ log.Debugf("Exec output: %v", string(out))
+ if err != nil {
+ log.Errorf("Faile to execute %s command:%v", actionCmd, err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ case "restart":
+ actionCmd := "sudo ims-stop && sudo ims-start"
+ cmd := exec.Command("ssh", sshHost, actionCmd)
+ out, err := cmd.CombinedOutput()
+ log.Debugf("Exec output: %v", string(out))
+ if err != nil {
+ log.Errorf("Faile to execute %s command:%v", actionCmd, err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ default:
+ err = global.ErrCMUnknownServiceAction
+ log.Errorf("%v, action:%s", err, action)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ default:
+ actionCmd := fmt.Sprintf("sudo systemctl %s %s.service", action, neTypeLower)
+ cmd := exec.Command("ssh", sshHost, actionCmd)
+ out, err := cmd.CombinedOutput()
+ log.Debugf("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("Faile to execute command:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ }
+
+ services.ResponseStatusOK204NoContent(w)
+}
+
+func PostNeInstanceAction(w http.ResponseWriter, r *http.Request) {
+ log.Debug("PostNeInstanceAction processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+ if neType == "" {
+ log.Error("elementTypeValue is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ //neTypeUpper := strings.ToUpper(neType)
+ //neTypeLower := strings.ToLower(neType)
+ action := vars["action"]
+
+ neId := services.GetUriParamString(r, "neId", ",", false, false)
+
+ // neInfo := new(dborm.NeInfo)
+ neInfo, err := dborm.XormGetNeInfo(neType, neId)
+ if err != nil {
+ log.Errorf("Failed to get ne_info:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Debug("neInfo:", neInfo)
+
+ sshHost := fmt.Sprintf("%s@%s", config.GetYamlConfig().NE.User, neInfo.Ip)
+ switch action {
+ case "poweron":
+ actionCmd := "sudo poweron"
+ cmd := exec.Command("ssh", sshHost, actionCmd)
+ out, err := cmd.CombinedOutput()
+ log.Debugf("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("Faile to execute ssh %s omc:", action, err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ case "poweroff":
+ actionCmd := "sudo poweroff"
+ cmd := exec.Command("ssh", sshHost, actionCmd)
+ out, err := cmd.CombinedOutput()
+ log.Debugf("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("Faile to execute ssh sudo poweroff:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ case "reboot":
+ actionCmd := "sudo reboot"
+ cmd := exec.Command("ssh", sshHost, actionCmd)
+ out, err := cmd.CombinedOutput()
+ log.Debugf("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("Faile to execute ssh sudo reboot:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ default:
+ err = global.ErrCMUnknownInstanceAction
+ log.Errorf("%v, action:%s", err, action)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ services.ResponseStatusOK204NoContent(w)
+}
diff --git a/features/cm/param.go b/features/cm/param.go
new file mode 100644
index 0000000..e0e1cc4
--- /dev/null
+++ b/features/cm/param.go
@@ -0,0 +1,278 @@
+package cm
+
+import (
+ "strings"
+
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/global"
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+
+ tokenConst "nms_nbi/src/framework/constants/token"
+
+ "github.com/go-resty/resty/v2"
+ "github.com/gorilla/mux"
+)
+
+var (
+ // parameter config management
+ ParamConfigUri = config.DefaultUriPrefix + "/systemManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/config/{paraName}"
+
+ CustomParamConfigUri = config.UriPrefix + "/systemManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/config/{paraName}"
+)
+
+func GetParamConfigFromNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("GetParamConfigFromNF processing... ")
+
+ // data := make([]map[string]interface{}, 1)
+ var response services.DataResponse
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+ if neType == "" {
+ log.Error("elementTypeValue is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ token, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ restHostPort := fmt.Sprintf("http://127.0.0.1:%d", config.GetYamlConfig().Rest[0].Port)
+ getNeInfoPattern := fmt.Sprintf(config.DefaultUriPrefix+"/databaseManagement/v1/%s/ne_info", config.GetYamlConfig().Database.Name)
+ getNeInfoURI := restHostPort + getNeInfoPattern
+ neId := services.GetUriParamString(r, "ne_id", ",", true, true)
+ if neId == "" {
+ getNeInfoURI = getNeInfoURI + fmt.Sprintf("?WHERE=status+in+('0','3')+and+ne_type='%s'", neType)
+ } else {
+ getNeInfoURI = getNeInfoURI + fmt.Sprintf("?WHERE=status+in+('0','3')+and+ne_type='%v'+and+ne_id+in+%v", neType, neId)
+ }
+ log.Debug("getNeInfoURI:", getNeInfoURI)
+
+ client := resty.New()
+ resp, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}).
+ SetHeaders(map[string]string{"accessToken": token}).
+ SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ Get(getNeInfoURI)
+ if err != nil {
+ log.Error("Failed to Get:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Trace("NE info:", string(resp.Body()))
+
+ // var neList []dborm.NeInfo
+ neList, _ := dborm.XormParseResult(resp.Body())
+
+ if len(neList) >= 1 {
+ s := neList[0]
+ requestURI2NF := fmt.Sprintf("http://%s:%v%s", s.Ip, s.Port, r.RequestURI)
+ 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("Failed to Get from NF:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else {
+ _ = json.Unmarshal(resp.Body(), &response)
+ }
+ log.Debug("response:", response)
+ }
+
+ services.ResponseWithJson(w, http.StatusOK, response)
+}
+
+func PostParamConfigToNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("PostParamConfigToNF processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+ if neType == "" {
+ log.Error("elementTypeValue is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ neId := services.GetUriParamString(r, "ne_id", ",", false, false)
+
+ // no, _ := strconv.ParseInt(neId, 10, 64)
+ neInfo, err := dborm.XormGetNeInfo(neType, neId)
+ if err != nil {
+ log.Error("dborm.XormGetNeInfo is failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+
+ requestURI2NF := fmt.Sprintf("http://%s:%v%s", neInfo.Ip, neInfo.Port, r.RequestURI)
+ log.Debug("requestURI2NF: POST ", requestURI2NF)
+
+ body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen)) //io.LimitReader限制大小
+ if err != nil {
+ log.Error("io.ReadAll is failed:", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ client := resty.New()
+ response, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ SetBody(body).
+ Post(requestURI2NF)
+ if err != nil {
+ log.Error("Failed to POST to NF:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ services.ResponseWithJson(w, http.StatusNoContent, response)
+}
+
+func PutParamConfigToNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("PutParamConfigToNF processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+ if neType == "" {
+ log.Error("elementTypeValue is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ // OMC配置接口 /api/rest/systemManagement/v1/elementType/omc/objectType/config/omcNeConfig
+ if v, ok := vars["paraName"]; ok && v == "omcNeConfig" && strings.ToLower(neType) == "omc" {
+ PutOMCNeConfig(w, r)
+ return
+ }
+
+ neId := services.GetUriParamString(r, "ne_id", ",", false, false)
+ // no, _ := strconv.ParseInt(neId, 10, 64)
+ neInfo, err := dborm.XormGetNeInfo(neType, neId)
+ if err != nil {
+ log.Error("dborm.XormGetNeInfo is failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+
+ requestURI2NF := fmt.Sprintf("http://%s:%v%s", neInfo.Ip, neInfo.Port, r.RequestURI)
+ log.Debug("requestURI2NF: PUT ", requestURI2NF)
+
+ body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen)) //io.LimitReader限制大小
+ if err != nil {
+ log.Error("io.ReadAll is failed:", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ client := resty.New()
+ response, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ SetBody(body).
+ Put(requestURI2NF)
+ if err != nil {
+ log.Error("Failed to Put to NF:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ services.ResponseWithJson(w, http.StatusNoContent, response)
+}
+
+// PutOMCNeConfig 网元OMC配置
+//
+// 目前没配置,返回204
+func PutOMCNeConfig(w http.ResponseWriter, r *http.Request) {
+ // vars := mux.Vars(r)
+ // neType := vars["elementTypeValue"]
+ // if neType == "" {
+ // log.Error("elementTypeValue is empty")
+ // services.ResponseNotFound404UriNotExist(w, r)
+ // return
+ // }
+ // body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen))
+ // if err != nil {
+ // log.Error("Failed to io.ReadAll:", err)
+ // services.ResponseNotFound404UriNotExist(w, r)
+ // return
+ // }
+
+ // neInfo := new(dborm.NeInfo)
+ // _ = json.Unmarshal(body, neInfo)
+ // neInfo.NeType = strings.ToUpper(neType)
+
+ services.ResponseStatusOK204NoContent(w)
+}
+
+func DeleteParamConfigToNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("DeleteParamConfigToNF processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+ if neType == "" {
+ log.Error("elementTypeValue is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ neId := services.GetUriParamString(r, "ne_id", ",", false, false)
+
+ // no, _ := strconv.ParseInt(neId, 10, 64)
+ neInfo, err := dborm.XormGetNeInfo(neType, neId)
+ if err != nil {
+ log.Error("dborm.XormGetNeInfo is failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+
+ requestURI2NF := fmt.Sprintf("http://%s:%v%s", neInfo.Ip, neInfo.Port, r.RequestURI)
+ log.Debug("requestURI2NF: DELETE ", requestURI2NF)
+
+ client := resty.New()
+ response, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ Delete(requestURI2NF)
+ if err != nil {
+ log.Error("Failed to delete parameter:", err)
+ services.ResponseInternalServerError500NFConnectRefused(w)
+ return
+ }
+
+ services.ResponseWithJson(w, http.StatusNoContent, response)
+}
diff --git a/features/cm/software.go b/features/cm/software.go
new file mode 100644
index 0000000..dd46fa1
--- /dev/null
+++ b/features/cm/software.go
@@ -0,0 +1,1025 @@
+package cm
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "os/exec"
+ "strconv"
+ "strings"
+
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/global"
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+
+ "github.com/gorilla/mux"
+)
+
+const (
+ SoftwareStatusUploaded = "Uploaded"
+ SoftwareStatusInactive = "Inactive"
+ SoftwareStatusActive = "Active"
+ DigestsSignOkString = "digests signatures OK"
+ SoftwareVerifiedOk = "Verified OK"
+)
+
+var (
+ UriSoftware = config.DefaultUriPrefix + "/systemManagement/{apiVersion}/{neType}/software/{version}"
+ UriSoftwareNE = config.DefaultUriPrefix + "/systemManagement/{apiVersion}/{neType}/software/{version}/{neId}"
+
+ CustomUriSoftware = config.UriPrefix + "/systemManagement/{apiVersion}/{neType}/software/{version}"
+ CustomUriSoftwareNE = config.UriPrefix + "/systemManagement/{apiVersion}/{neType}/software/{version}/{neId}"
+)
+
+// 验证签名
+func verify_signature(public_key_name string, source_cms_file string, source_file string) bytes.Buffer {
+ cmd := exec.Command("/usr/local/omc/run/iv", "verify_signature", public_key_name, source_cms_file, source_file)
+ var out bytes.Buffer
+ cmd.Stdout = &out
+ cmd.Env = append(os.Environ(),
+ "FOO=duplicate_value", // 重复被忽略
+ "FOO=actual_value", // 实际被使用
+ )
+ err := cmd.Run()
+ if err != nil {
+ log.Error(err)
+ }
+
+ return out
+}
+
+func UploadSoftwareFile(w http.ResponseWriter, r *http.Request) {
+ log.Debug("UploadSoftwareFile processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Http request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["neType"]
+ if neType == "" {
+ log.Error("neType is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ neTypeUpper := strings.ToUpper(neType)
+ neTypeLower := strings.ToLower(neType)
+
+ version := vars["version"]
+ if version == "" {
+ log.Error("version is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ md5Param := services.GetUriParamString(r, "md5Sum", ",", false, false)
+
+ // body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen))
+ // if err != nil {
+ // log.Error("io.ReadAll is failed:", err)
+ // services.ResponseNotFound404UriNotExist(w, r)
+ // return
+ // }
+ // neSWBody := new(dborm.NeSoftware)
+ // _ = json.Unmarshal(body, neSWBody)
+ // log.Trace("neSoftware:", neSWBody)
+
+ softwarePath := fmt.Sprintf("%s/%s", config.GetYamlConfig().OMC.Software, neTypeLower)
+ fileName, err := services.HandleUploadFile(r, softwarePath, "")
+ if err != nil {
+ log.Error("Faile to HandleUploadFile:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ filePrefix := fileName[:strings.Index(fileName, ".zip")]
+ filePath := fmt.Sprintf("%s/%s", softwarePath, fileName)
+ log.Debugf("filePath:%s filePrefix:%s softwarePath:%s", filePath, filePrefix, softwarePath)
+ cmd := exec.Command("unzip", "-o", filePath)
+ cmd.Dir = softwarePath
+ out, err := cmd.CombinedOutput()
+ log.Debugf("Exec outpout:%s", string(out))
+ if err != nil {
+ log.Error("Failed to unzip:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ md5File, err := global.GetFileMD5Sum(filePath)
+ if err != nil {
+ log.Error("Faile to GetFileMD5Sum:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ if md5File != md5Param {
+ err = global.ErrCMNotMatchMD5File
+ log.Error(err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ rpmFileName := filePrefix + ".rpm"
+ if config.GetYamlConfig().OMC.CheckSign {
+ rpmFilePath := softwarePath + "/" + rpmFileName
+ cmsFileName := rpmFileName + ".cms"
+ cmsFilePath := softwarePath + "/" + cmsFileName
+ log.Debugf("cmsFilePath:%s rpmFilePath:%s publicKey:%s", rpmFilePath, cmsFilePath, config.GetYamlConfig().Auth.PublicKey)
+ result := verify_signature(config.GetYamlConfig().Auth.PublicKey, cmsFilePath, rpmFilePath)
+ log.Debug("result:", result.String())
+ if !strings.Contains(result.String(), SoftwareVerifiedOk) {
+ err = global.ErrCMNotMatchSignFile
+ log.Error(err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ // cmd := exec.Command("rpm", "-K", filePath)
+ // out, err := cmd.CombinedOutput()
+ // log.Debugf("Exec outpout:%s", string(out))
+ // if err != nil {
+ // log.Error("Failed to execute rpm:", err)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // }
+ // if !strings.Contains(string(out), DigestsSignOkString) {
+ // err = global.ErrCMNotMatchSignFile
+ // log.Error(err)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // }
+ }
+
+ //neBackup := dborm.NeBackup{NeType: neType, NeId: neId, Md5Sum: md5Sum}
+ //log.Debug("neBackup:", neBackup)
+ where := fmt.Sprintf("ne_type='%s' and version='%s'", neTypeUpper, version)
+ has, err := dborm.XormExistTableOne("ne_software", where)
+ if err != nil {
+ log.Error("Faile to XormInsertTableOne:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else if has {
+ err = global.ErrCMExistSoftwareFile
+ log.Error(err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ neSoftware := dborm.NeSoftware{
+ NeType: neTypeUpper,
+ FileName: rpmFileName,
+ Path: softwarePath,
+ Version: version,
+ Md5Sum: md5Param,
+ Comment: neType + " 5GC " + version,
+ //Comment: neSWBody.Comment,
+ }
+
+ _, err = dborm.XormInsertTableOne("ne_software", neSoftware)
+ if err != nil {
+ log.Error("Faile to XormInsertTableOne:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ services.ResponseStatusOK204NoContent(w)
+}
+
+func UploadSoftwareMultiFile(w http.ResponseWriter, r *http.Request) {
+ log.Info("UploadSoftwareMultiFile processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Http request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["neType"]
+ if neType == "" {
+ log.Error("neType is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ neTypeUpper := strings.ToUpper(neType)
+ neTypeLower := strings.ToLower(neType)
+
+ version := vars["version"]
+ if version == "" {
+ log.Error("version is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ md5Param := services.GetUriParamString(r, "md5Sum", ",", false, false)
+
+ softwarePath := fmt.Sprintf("%s/%s", config.GetYamlConfig().OMC.Software, neTypeLower)
+ err = os.MkdirAll(softwarePath, os.ModePerm)
+ if err != nil {
+ log.Error("Failed to Mkdir:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ //fileName, err := services.HandleUploadFile(r, softwarePath, "")
+
+ // 解析multipart/form-data请求
+ err = r.ParseMultipartForm(1000 << 20) // 1000MB
+ if err != nil {
+ log.Error("Faile to ParseMultipartForm:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ // 获取文件和数据
+ swFile := r.MultipartForm.File["file"]
+ cmsFile := r.MultipartForm.File["cms"]
+ data := r.MultipartForm.Value["comment"]
+
+ var softwareFileName, cmsFileName, comment string
+
+ // 处理软件rpm/deb文件
+ if len(swFile) > 0 {
+ file := swFile[0]
+ // 打开文件
+ f, err := file.Open()
+ if err != nil {
+ log.Error("Faile to Open:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ defer f.Close()
+
+ // 创建本地文件
+ dst, err := os.Create(softwarePath + "/" + file.Filename)
+ if err != nil {
+ log.Error("Faile to Create:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ defer dst.Close()
+
+ softwareFileName = file.Filename
+ // 将文件内容拷贝到本地文件
+ _, err = io.Copy(dst, f)
+ if err != nil {
+ log.Error("Faile to Copy:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ }
+
+ // 处理cms文件
+ if len(cmsFile) > 0 {
+ file := cmsFile[0]
+ // 打开文件
+ f, err := file.Open()
+ if err != nil {
+ log.Error("Faile to Open:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ defer f.Close()
+
+ // 创建本地文件
+ dst, err := os.Create(softwarePath + "/" + file.Filename)
+ if err != nil {
+ log.Error("Faile to Create:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ defer dst.Close()
+
+ cmsFileName = file.Filename
+ // 将文件内容拷贝到本地文件
+ _, err = io.Copy(dst, f)
+ if err != nil {
+ log.Error("Faile to Copy:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ }
+
+ // 处理数据
+ if len(data) > 0 {
+ comment = data[0]
+ }
+
+ if config.GetYamlConfig().OMC.CheckSign && cmsFileName != "" {
+ rpmFilePath := softwarePath + "/" + softwareFileName
+ cmsFileName := cmsFileName
+ cmsFilePath := softwarePath + "/" + cmsFileName
+ log.Debugf("cmsFilePath:%s rpmFilePath:%s publicKey:%s", rpmFilePath, cmsFilePath, config.GetYamlConfig().Auth.PublicKey)
+ result := verify_signature(config.GetYamlConfig().Auth.PublicKey, cmsFilePath, rpmFilePath)
+ log.Debug("result:", result.String())
+ if !strings.Contains(result.String(), SoftwareVerifiedOk) {
+ err = global.ErrCMNotMatchSignFile
+ log.Error(err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ // cmd := exec.Command("rpm", "-K", filePath)
+ // out, err := cmd.CombinedOutput()
+ // log.Debugf("Exec outpout:%s", string(out))
+ // if err != nil {
+ // log.Error("Failed to execute rpm:", err)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // }
+ // if !strings.Contains(string(out), DigestsSignOkString) {
+ // err = global.ErrCMNotMatchSignFile
+ // log.Error(err)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // }
+ }
+
+ // //neBackup := dborm.NeBackup{NeType: neType, NeId: neId, Md5Sum: md5Sum}
+ // //log.Debug("neBackup:", neBackup)
+ // where := fmt.Sprintf("ne_type='%s' and version='%s'", neTypeUpper, version)
+ // has, err := dborm.XormExistTableOne("ne_software", where)
+ // if err != nil {
+ // log.Error("Faile to XormInsertTableOne:", err)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // } else if has == true {
+ // err = global.ErrCMExistSoftwareFile
+ // log.Error(err)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // }
+
+ neSoftware := dborm.NeSoftware{
+ NeType: neTypeUpper,
+ FileName: softwareFileName,
+ Path: softwarePath,
+ Version: version,
+ Md5Sum: md5Param,
+ Comment: comment,
+ }
+
+ _, err = dborm.XormInsertTableOne("ne_software", neSoftware)
+ if err != nil {
+ log.Error("Faile to XormInsertTableOne:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ services.ResponseStatusOK204NoContent(w)
+}
+
+func DownloadSoftwareFile(w http.ResponseWriter, r *http.Request) {
+ log.Debug("DownloadSoftwareFile processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["neType"]
+ if neType == "" {
+ log.Error("neType is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ neTypeUpper := strings.ToUpper(neType)
+ //neTypeLower := strings.ToLower(neType)
+
+ version := vars["version"]
+ if version == "" {
+ log.Error("version is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ sql := fmt.Sprintf("select * from ne_software where ne_type='%s' and version='%s'", neTypeUpper, version)
+ neSoftware, err := dborm.XormGetDataBySQL(sql)
+ if err != nil {
+ log.Error("Faile to XormGetDataBySQL:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else if len(*neSoftware) == 0 {
+ err := global.ErrCMNotFoundTargetSoftware
+ log.Error(err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ fileName := (*neSoftware)[0]["file_name"]
+ path := (*neSoftware)[0]["path"]
+ md5Sum := (*neSoftware)[0]["md5_sum"]
+
+ services.ResponseFileWithNameAndMD5(w, http.StatusOK, fileName, path, md5Sum)
+}
+
+func DeleteSoftwareFile(w http.ResponseWriter, r *http.Request) {
+ log.Debug("DeleteSoftwareFile processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["neType"]
+ if neType == "" {
+ log.Error("neType is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ neTypeUpper := strings.ToUpper(neType)
+ //neTypeLower := strings.ToLower(neType)
+
+ version := vars["version"]
+ if version == "" {
+ log.Error("version is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ sql := fmt.Sprintf("select * from ne_software where ne_type='%s' and version='%s'", neTypeUpper, version)
+ neSoftware, err := dborm.XormGetDataBySQL(sql)
+ if err != nil {
+ log.Error("Faile to XormGetDataBySQL:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else if len(*neSoftware) == 0 {
+ err := global.ErrCMNotFoundTargetSoftware
+ log.Error(err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ where := fmt.Sprintf("ne_type='%s' and version='%s'", neTypeUpper, version)
+ affected, err := dborm.XormDeleteDataByWhere(where, "ne_software")
+ if err != nil || affected == 0 {
+ log.Error("Faile to XormGetDataBySQL:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ fileName := (*neSoftware)[0]["file_name"]
+ path := (*neSoftware)[0]["path"]
+ filePath := fmt.Sprintf("%s/%s", path, fileName)
+ err = os.Remove(filePath)
+ if err != nil {
+ log.Error("Faile to Remove:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ services.ResponseStatusOK204NoContent(w)
+}
+
+func DistributeSoftwareToNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("DistributeSoftwareFile processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Http request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["neType"]
+ if neType == "" {
+ log.Error("neType is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ neTypeUpper := strings.ToUpper(neType)
+ neTypeLower := strings.ToLower(neType)
+
+ version := vars["version"]
+ if version == "" {
+ log.Error("version is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ neId := vars["neId"]
+ if version == "" {
+ log.Error("neId is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ neInfo, err := dborm.XormGetNeInfo(neTypeUpper, neId)
+ if err != nil {
+ log.Error("dborm.XormGetNeInfo is failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+
+ sql := fmt.Sprintf("select * from ne_software where ne_type='%s' and version='%s'", neTypeUpper, version)
+ neSoftware, err := dborm.XormGetDataBySQL(sql)
+ if err != nil {
+ log.Error("Faile to XormGetDataBySQL:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else if len(*neSoftware) == 0 {
+ err := global.ErrCMNotFoundTargetSoftware
+ log.Error(err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Trace("neSoftware:", neSoftware)
+
+ sql = fmt.Sprintf("select * from ne_version where ne_type='%s' and ne_id='%s'", neTypeUpper, neId)
+ neVersion, err := dborm.XormGetDataBySQL(sql)
+ if err != nil {
+ log.Error("Faile to XormGetDataBySQL:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Trace("neVersion:", neVersion)
+
+ sshHost := fmt.Sprintf("%s@%s", config.GetYamlConfig().NE.User, neInfo.Ip)
+ mkdirCmd := fmt.Sprintf("sudo mkdir -p %s/software/%s", config.GetYamlConfig().NE.OmcDir, neTypeLower)
+ cmd := exec.Command("ssh", sshHost, mkdirCmd)
+ out, err := cmd.CombinedOutput()
+ log.Tracef("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("Faile to mkdir:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ fileName := (*neSoftware)[0]["file_name"]
+ path := (*neSoftware)[0]["path"]
+ srcFile := fmt.Sprintf("%s/%s", path, fileName)
+
+ scpDir := fmt.Sprintf("%s@%s:%s", config.GetYamlConfig().NE.User,
+ neInfo.Ip, config.GetYamlConfig().NE.ScpDir)
+ cmd = exec.Command("scp", "-r", srcFile, scpDir)
+ out, err = cmd.CombinedOutput()
+ log.Tracef("Exec output: %v", string(out))
+ if err != nil {
+ log.Errorf("Faile to scp NF: neType=%s, neId=%s, ip=%s", neType, neId, neInfo.Ip)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ neFilePath := config.GetYamlConfig().NE.ScpDir + "/" + fileName
+ cpCmd := fmt.Sprintf("sudo mv -f %s %s/software/%s", neFilePath,
+ config.GetYamlConfig().NE.OmcDir, neTypeLower)
+ cmd = exec.Command("ssh", sshHost, cpCmd)
+ out, err = cmd.CombinedOutput()
+ log.Tracef("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("Faile to execute cp command:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ if len(*neVersion) == 0 {
+ neVersionData := dborm.NeVersion{
+ NeType: neTypeUpper,
+ NeId: neInfo.NeId,
+ Version: (*neSoftware)[0]["version"],
+ FilePath: fmt.Sprintf("%s/software/%s/%s", config.GetYamlConfig().NE.OmcDir, neTypeLower, fileName),
+ PreVersion: "",
+ PreFile: "",
+ Status: SoftwareStatusInactive,
+ }
+
+ _, err = dborm.XormInsertTableOne("ne_version", neVersionData)
+ if err != nil {
+ log.Error("Faile to XormInsertTableOne:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ } else {
+ idNeVersion, _ := strconv.Atoi((*neVersion)[0]["id"])
+ neVersionData := dborm.NeVersion{
+ NeType: neTypeUpper,
+ NeId: neInfo.NeId,
+ Version: (*neSoftware)[0]["version"],
+ FilePath: fmt.Sprintf("%s/software/%s/%s", config.GetYamlConfig().NE.OmcDir, neTypeLower, fileName),
+ PreVersion: (*neVersion)[0]["version"],
+ PreFile: (*neVersion)[0]["file_path"],
+ Status: SoftwareStatusInactive,
+ }
+
+ _, err = dborm.XormUpdateTableById(idNeVersion, "ne_version", neVersionData)
+ if err != nil {
+ log.Error("Faile to UpdateTableById:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ }
+
+ services.ResponseStatusOK204NoContent(w)
+}
+
+func ActiveSoftwareToNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("ActiveSoftwareToNF processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Http request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["neType"]
+ if neType == "" {
+ log.Error("neType is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ neTypeUpper := strings.ToUpper(neType)
+ neTypeLower := strings.ToLower(neType)
+
+ version := vars["version"]
+ if version == "" {
+ log.Error("version is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ neId := vars["neId"]
+ if version == "" {
+ log.Error("neId is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ neInfo, err := dborm.XormGetNeInfo(neTypeUpper, neId)
+ if err != nil {
+ log.Error("dborm.XormGetNeInfo is failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+
+ sql := fmt.Sprintf("select * from ne_software where ne_type='%s' and version='%s'", neTypeUpper, version)
+ neSoftware, err := dborm.XormGetDataBySQL(sql)
+ if err != nil {
+ log.Error("Faile to XormGetDataBySQL:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else if len(*neSoftware) == 0 {
+ err := global.ErrCMNotFoundTargetSoftware
+ log.Error(err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Trace("neSoftware:", neSoftware)
+
+ sql = fmt.Sprintf("select * from ne_version where ne_type='%s' and ne_id='%s' and version='%s'", neTypeUpper, neId, version)
+ neVersion, err := dborm.XormGetDataBySQL(sql)
+ if err != nil {
+ log.Error("Faile to XormGetDataBySQL:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else if len(*neVersion) == 0 {
+ err := global.ErrCMNotFoundTargetNeVersion
+ log.Error(err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Trace("neVersion:", neVersion)
+
+ if !config.GetYamlConfig().OMC.TestMode {
+ filePath := (*neVersion)[0]["file_path"]
+ sshHost := fmt.Sprintf("%s@%s", config.GetYamlConfig().NE.User, neInfo.Ip)
+ srcFile := fmt.Sprintf("%s/actpkg.sh", config.GetYamlConfig().OMC.BinDir)
+
+ scpDir := fmt.Sprintf("%s@%s:%s", config.GetYamlConfig().NE.User,
+ neInfo.Ip, config.GetYamlConfig().NE.ScpDir)
+ cmd := exec.Command("scp", "-r", srcFile, scpDir)
+ _, err := cmd.CombinedOutput()
+ if err != nil {
+ log.Errorf("Faile to scp NF: neType=%s, neId=%s, ip=%s", neType, neId, neInfo.Ip)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ runCmd := fmt.Sprintf("sudo %s/actpkg.sh '%s' %s",
+ config.GetYamlConfig().NE.ScpDir, filePath, neTypeUpper)
+ if neTypeLower == "omc" {
+ idNeVersion, _ := strconv.Atoi((*neVersion)[0]["id"])
+ neVersionData := dborm.NeVersion{
+ Status: SoftwareStatusActive,
+ }
+
+ _, err = dborm.XormUpdateTableById(idNeVersion, "ne_version", neVersionData)
+ if err != nil {
+ log.Error("Faile to UpdateTableById:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ services.ResponseStatusOK204NoContent(w)
+ go RunSSHCmd(sshHost, runCmd)
+ return
+ }
+ err = RunSSHCmd(sshHost, runCmd)
+ if err != nil {
+ log.Errorf("Faile to execute command: %s, error: %v", runCmd, err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ // fileType := global.IsRpmOrDebPackage(filePath)
+ // if fileType == 1 {
+ // srcFile := fmt.Sprintf("%s/spawnrpm.sh", config.GetYamlConfig().OMC.BinDir)
+
+ // scpDir := fmt.Sprintf("%s@%s:%s", config.GetYamlConfig().NE.User,
+ // neInfo.Ip, config.GetYamlConfig().NE.ScpDir)
+ // cmd := exec.Command("scp", "-r", srcFile, scpDir)
+ // _, err := cmd.CombinedOutput()
+ // if err != nil {
+ // log.Errorf("Faile to scp NF: neType=%s, neId=%s, ip=%s", neType, neId, neInfo.Ip)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // }
+
+ // dpkgCmd := fmt.Sprintf("sudo %s/spawnrpm.sh '%s'",
+ // config.GetYamlConfig().NE.ScpDir, filePath)
+ // err = RunSSHCmd(sshHost, dpkgCmd)
+ // if err != nil {
+ // log.Errorf("Faile to execute dpkg command: %s, error: %v", dpkgCmd, err)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // }
+ // } else if fileType == 2 {
+ // srcFile := fmt.Sprintf("%s/spawndpkg.sh", config.GetYamlConfig().OMC.BinDir)
+
+ // scpDir := fmt.Sprintf("%s@%s:%s", config.GetYamlConfig().NE.User,
+ // neInfo.Ip, config.GetYamlConfig().NE.ScpDir)
+ // cmd := exec.Command("scp", "-r", srcFile, scpDir)
+ // _, err := cmd.CombinedOutput()
+ // if err != nil {
+ // log.Errorf("Faile to scp NF: neType=%s, neId=%s, ip=%s", neType, neId, neInfo.Ip)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // }
+
+ // dpkgCmd := fmt.Sprintf("sudo %s/spawndpkg.sh '%s'",
+ // config.GetYamlConfig().NE.ScpDir, filePath)
+ // err = RunSSHCmd(sshHost, dpkgCmd)
+ // if err != nil {
+ // log.Errorf("Faile to execute dpkg command: %s, error: %v", dpkgCmd, err)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // }
+ // } else {
+ // err := global.ErrCMUnknownSoftwareFormat
+ // log.Error(err)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // }
+ switch neTypeLower {
+ case "omc":
+ restartCmd := fmt.Sprintf("sudo %s/bin/omcsvc.sh restart", config.GetYamlConfig().NE.OmcDir)
+ cmd := exec.Command("ssh", sshHost, restartCmd)
+ out, err := cmd.CombinedOutput()
+ log.Tracef("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("Faile to execute ssh restart omc:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ case "ims":
+ restartCmd := "sudo ims-stop && sudo ims-start"
+ cmd := exec.Command("ssh", sshHost, restartCmd)
+ out, err := cmd.CombinedOutput()
+ log.Tracef("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("Faile to execute ssh command: %s, error: %v", restartCmd, err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ default:
+ restartCmd := fmt.Sprintf("sudo systemctl restart %s.service", neTypeLower)
+ cmd := exec.Command("ssh", sshHost, restartCmd)
+ out, err := cmd.CombinedOutput()
+ log.Tracef("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("Faile to execute ssh sudo systemctl command:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ }
+ }
+
+ idNeVersion, _ := strconv.Atoi((*neVersion)[0]["id"])
+ neVersionData := dborm.NeVersion{
+ Status: SoftwareStatusActive,
+ }
+
+ _, err = dborm.XormUpdateTableById(idNeVersion, "ne_version", neVersionData)
+ if err != nil {
+ log.Error("Faile to UpdateTableById:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ services.ResponseStatusOK204NoContent(w)
+}
+
+func RollBackSoftwareToNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("ActiveSoftwareToNF processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Http request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["neType"]
+ if neType == "" {
+ log.Error("neType is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ neTypeUpper := strings.ToUpper(neType)
+ neTypeLower := strings.ToLower(neType)
+
+ version := vars["version"]
+ if version == "" {
+ log.Error("version is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ neId := vars["neId"]
+ if version == "" {
+ log.Error("neId is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ neInfo, err := dborm.XormGetNeInfo(neTypeUpper, neId)
+ if err != nil {
+ log.Error("dborm.XormGetNeInfo is failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+
+ sql := fmt.Sprintf("select * from ne_version where ne_type='%s' and ne_id='%s'", neTypeUpper, neId)
+ neVersion, err := dborm.XormGetDataBySQL(sql)
+ if err != nil {
+ log.Error("Faile to XormGetDataBySQL:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else if len(*neVersion) == 0 {
+ err := global.ErrCMNotFoundTargetNeVersion
+ log.Error(err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Debug("neVersion:", neVersion)
+
+ filePath := (*neVersion)[0]["pre_file"]
+ if filePath == "" {
+ err := global.ErrCMNotFoundRollbackNeVersion
+ log.Error(err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ if !config.GetYamlConfig().OMC.TestMode {
+ sshHost := fmt.Sprintf("%s@%s", config.GetYamlConfig().NE.User, neInfo.Ip)
+ srcFile := fmt.Sprintf("%s/rbkpkg.sh", config.GetYamlConfig().OMC.BinDir)
+
+ scpDir := fmt.Sprintf("%s@%s:%s", config.GetYamlConfig().NE.User,
+ neInfo.Ip, config.GetYamlConfig().NE.ScpDir)
+ cmd := exec.Command("scp", "-r", srcFile, scpDir)
+ _, err := cmd.CombinedOutput()
+ if err != nil {
+ log.Errorf("Faile to scp NF: neType=%s, neId=%s, ip=%s", neType, neId, neInfo.Ip)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ runCmd := fmt.Sprintf("sudo %s/rbkpkg.sh '%s' %s",
+ config.GetYamlConfig().NE.ScpDir, filePath, neTypeUpper)
+ if neTypeLower == "omc" {
+ idNeVersion, _ := strconv.Atoi((*neVersion)[0]["id"])
+ neVersionData := dborm.NeVersion{
+ Version: (*neVersion)[0]["pre_version"],
+ FilePath: (*neVersion)[0]["pre_file"],
+ PreVersion: "-",
+ PreFile: "-",
+ NewVersion: (*neVersion)[0]["version"],
+ NewFile: (*neVersion)[0]["file_path"],
+ Status: SoftwareStatusActive,
+ }
+
+ _, err = dborm.XormUpdateTableById(idNeVersion, "ne_version", neVersionData)
+ if err != nil {
+ log.Error("Faile to UpdateTableById:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ services.ResponseStatusOK204NoContent(w)
+ RunSSHCmd(sshHost, runCmd)
+ return
+ }
+ err = RunSSHCmd(sshHost, runCmd)
+ if err != nil {
+ log.Errorf("Faile to execute command: %s, error: %v", runCmd, err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ // fileType := global.IsRpmOrDebPackage(filePath)
+ // if fileType == 1 {
+ // rpmCmd := fmt.Sprintf("sudo rpm -Uvh --oldpackage '%s'", filePath)
+ // cmd := exec.Command("ssh", sshHost, rpmCmd)
+ // _, err := cmd.CombinedOutput()
+ // if err != nil {
+ // log.Error("Faile to execute rpm command:", err)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // }
+ // } else if fileType == 2 {
+ // srcFile := fmt.Sprintf("%s/spawndpkg.sh", config.GetYamlConfig().OMC.BinDir)
+
+ // scpDir := fmt.Sprintf("%s@%s:%s", config.GetYamlConfig().NE.User,
+ // neInfo.Ip, config.GetYamlConfig().NE.ScpDir)
+ // cmd := exec.Command("scp", "-r", srcFile, scpDir)
+ // _, err := cmd.CombinedOutput()
+ // if err != nil {
+ // log.Errorf("Faile to scp NF: neType=%s, neId=%s, ip=%s", neType, neId, neInfo.Ip)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // }
+ // var inputStr string = "n"
+ // if config.GetYamlConfig().NE.DpkgOverwrite {
+ // inputStr = "y"
+ // }
+ // dpkgCmd := fmt.Sprintf("sudo %s/spawndpkg.sh %s '%s'",
+ // config.GetYamlConfig().NE.ScpDir, inputStr, filePath)
+ // err = RunSSHCmd(sshHost, dpkgCmd)
+ // if err != nil {
+ // log.Errorf("Faile to execute dpkg command: %s, error: %v", dpkgCmd, err)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // }
+ // } else {
+ // err := global.ErrCMUnknownSoftwareFormat
+ // log.Error(err)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // }
+ switch neTypeLower {
+ case "omc":
+ restartCmd := fmt.Sprintf("sudo %s/bin/omcsvc.sh restart", config.GetYamlConfig().NE.OmcDir)
+ cmd := exec.Command("ssh", sshHost, restartCmd)
+ out, err := cmd.CombinedOutput()
+ log.Tracef("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("Faile to execute ssh restart omc:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ case "ims":
+ restartCmd := "sudo ims-stop && sudo ims-start"
+ cmd := exec.Command("ssh", sshHost, restartCmd)
+ out, err := cmd.CombinedOutput()
+ log.Tracef("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("Faile to execute ssh command: %s, error: %v", restartCmd, err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ default:
+ restartCmd := fmt.Sprintf("sudo systemctl restart %s.service", neTypeLower)
+ cmd := exec.Command("ssh", sshHost, restartCmd)
+ out, err := cmd.CombinedOutput()
+ log.Tracef("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("Faile to execute ssh sudo systemctl command:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ }
+ }
+
+ idNeVersion, _ := strconv.Atoi((*neVersion)[0]["id"])
+ neVersionData := dborm.NeVersion{
+ Version: (*neVersion)[0]["pre_version"],
+ FilePath: (*neVersion)[0]["pre_file"],
+ PreVersion: "-",
+ PreFile: "-",
+ NewVersion: (*neVersion)[0]["version"],
+ NewFile: (*neVersion)[0]["file_path"],
+ Status: SoftwareStatusActive,
+ }
+
+ _, err = dborm.XormUpdateTableById(idNeVersion, "ne_version", neVersionData)
+ if err != nil {
+ log.Error("Faile to UpdateTableById:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ services.ResponseStatusOK204NoContent(w)
+}
diff --git a/features/dbrest/backup.go b/features/dbrest/backup.go
new file mode 100644
index 0000000..f5ca992
--- /dev/null
+++ b/features/dbrest/backup.go
@@ -0,0 +1,135 @@
+package dbrest
+
+import (
+ "archive/tar"
+ "compress/gzip"
+ "io"
+ "net/http"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "time"
+
+ "nms_nbi/lib/core/conf"
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+
+ _ "github.com/go-sql-driver/mysql"
+)
+
+var (
+ // 系统备份-数据库备份
+ UriDbBackup = config.DefaultUriPrefix + "/dataManagement/{apiVersion}/dbBackup"
+ CustomUriDbBackup = config.UriPrefix + "/dataManagement/{apiVersion}/dbBackup" // for external
+
+ // 系统备份-文件备份
+ UriConfBackup = config.DefaultUriPrefix + "/dataManagement/{apiVersion}/confBackup"
+ CustomUriConfBackup = config.UriPrefix + "/dataManagement/{apiVersion}/confBackup" // for external
+
+)
+
+// DbBackup 系统备份-数据库备份1
+func DbBackup(w http.ResponseWriter, r *http.Request) {
+ dbConfigHost := conf.Get("database.host").(string)
+ dbConfigUser := conf.Get("database.user").(string)
+ dbConfigPassword := conf.Get("database.password").(string)
+ dbConfigBackup := conf.Get("database.backup").(string)
+
+ // 备份SQL文件路径
+ fileName := "database_backup_" + time.Now().Format("20060102150405") + ".sql"
+ backupFile := dbConfigBackup + "/" + fileName
+
+ // 执行mysqldump命令进行备份
+ cmd := exec.Command("mysqldump", "-u", dbConfigUser, "-p"+dbConfigPassword, "-h", dbConfigHost, "--all-databases", ">", backupFile)
+ err := cmd.Start()
+ if err != nil {
+ services.ResponseErrorWithJson(w, 400, err.Error())
+ return
+ }
+
+ // 备份结果
+ res, err := dborm.DbClient.XEngine.Exec("INSERT INTO `sys_backup`(`id`, `created_at`, `updated_at`, `backup_type`, `backup_way`, `name`, `path`) VALUES (null, ?, ?, '1', '0', ?, ?);", time.Now(), time.Now(), fileName, backupFile)
+ if err != nil {
+ services.ResponseErrorWithJson(w, 500, err.Error())
+ return
+ }
+ services.ResponseWithJson(w, 200, res)
+ log.Info("Backup completed successfully.")
+}
+
+// 系统备份-文件备份0
+func ConfBackup(w http.ResponseWriter, r *http.Request) {
+ dbConfigBackup := conf.Get("database.backup").(string)
+ etcDir := conf.Get("ne.omcdir").(string)
+
+ // 文件名
+ fileName := "conf_backup_" + time.Now().Format("20060102150405") + ".tar.gz"
+ backupFile := dbConfigBackup + "/" + fileName
+
+ err := createTarGz(etcDir, backupFile)
+ if err != nil {
+ services.ResponseErrorWithJson(w, 500, err.Error())
+ return
+ }
+
+ // 备份结果
+ res, err := dborm.DbClient.XEngine.Exec("INSERT INTO `sys_backup`(`id`, `created_at`, `updated_at`, `backup_type`, `backup_way`, `name`, `path`) VALUES (null, ?, ?, '1', '0', ?, ?);", time.Now(), time.Now(), fileName, backupFile)
+ if err != nil {
+ services.ResponseErrorWithJson(w, 500, err.Error())
+ return
+ }
+ services.ResponseWithJson(w, 200, res)
+ log.Info("Backup completed successfully.")
+}
+
+// 打压缩
+func createTarGz(source, target string) error {
+ tarFile, err := os.Create(target)
+ if err != nil {
+ return err
+ }
+ defer tarFile.Close()
+
+ gw := gzip.NewWriter(tarFile)
+ defer gw.Close()
+
+ tw := tar.NewWriter(gw)
+ defer tw.Close()
+
+ return filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ header, err := tar.FileInfoHeader(info, "")
+ if err != nil {
+ return err
+ }
+
+ relPath, err := filepath.Rel(source, path)
+ if err != nil {
+ return err
+ }
+
+ header.Name = relPath
+
+ if err := tw.WriteHeader(header); err != nil {
+ return err
+ }
+
+ if !info.Mode().IsRegular() {
+ return nil
+ }
+
+ file, err := os.Open(path)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ _, err = io.Copy(tw, file)
+ return err
+ })
+}
diff --git a/features/dbrest/dbrest.go b/features/dbrest/dbrest.go
new file mode 100644
index 0000000..ce5153e
--- /dev/null
+++ b/features/dbrest/dbrest.go
@@ -0,0 +1,916 @@
+package dbrest
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "regexp"
+ "strings"
+ "time"
+
+ "nms_nbi/lib/core/utils/ctx"
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/global"
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+
+ "github.com/gorilla/mux"
+ "xorm.io/xorm"
+)
+
+type XormResponse struct {
+ Data interface{} `json:"data"`
+}
+
+type XormInsertResponse struct {
+ Data interface{} `json:"data"`
+}
+
+var (
+ // database management rest pattern, discard
+ XormGetDataUri = config.DefaultUriPrefix + "/databaseManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/{objectTypeValue}"
+ XormSelectDataUri = config.DefaultUriPrefix + "/databaseManagement/{apiVersion}/select/{elementTypeValue}/{objectTypeValue}"
+ XormInsertDataUri = config.DefaultUriPrefix + "/databaseManagement/{apiVersion}/insert/{elementTypeValue}/{objectTypeValue}"
+ XormUpdateDataUri = config.DefaultUriPrefix + "/databaseManagement/{apiVersion}/update/{elementTypeValue}/{objectTypeValue}"
+ XormDeleteDataUri = config.DefaultUriPrefix + "/databaseManagement/{apiVersion}/delete/{elementTypeValue}/{objectTypeValue}"
+
+ CustomXormGetDataUri = config.UriPrefix + "/databaseManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/{objectTypeValue}"
+ CustomXormSelectDataUri = config.UriPrefix + "/databaseManagement/{apiVersion}/select/{elementTypeValue}/{objectTypeValue}"
+ CustomXormInsertDataUri = config.UriPrefix + "/databaseManagement/{apiVersion}/insert/{elementTypeValue}/{objectTypeValue}"
+ CustomXormUpdateDataUri = config.UriPrefix + "/databaseManagement/{apiVersion}/update/{elementTypeValue}/{objectTypeValue}"
+ CustomXormDeleteDataUri = config.UriPrefix + "/databaseManagement/{apiVersion}/delete/{elementTypeValue}/{objectTypeValue}"
+
+ XormCommonUri = config.DefaultUriPrefix + "/databaseManagement/{apiVersion}/{elementTypeValue}/{objectTypeValue}" // for internal
+ XormDatabaseUri = config.DefaultUriPrefix + "/database/{apiVersion}/{elementTypeValue}/{objectTypeValue}" // for crontask
+ XormDataRestUri = config.DefaultUriPrefix + "/dataManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/{objectTypeValue}"
+ XormExtDataUri = config.DefaultUriPrefix + "/dataManagement/{apiVersion}/{elementTypeValue}/{objectTypeValue}" // for external
+ XormDataSQLUri = config.DefaultUriPrefix + "/dataManagement/{apiVersion}/{elementTypeValue}/{objectTypeValue}" // for external
+
+ CustomXormCommonUri = config.UriPrefix + "/databaseManagement/{apiVersion}/{elementTypeValue}/{objectTypeValue}" // for internal
+ CustomXormExtDataUri = config.UriPrefix + "/dataManagement/{apiVersion}/{elementTypeValue}/{objectTypeValue}" // for external
+ CustomXormDataSQLUri = config.UriPrefix + "/dataManagement/{apiVersion}/{elementTypeValue}/{objectTypeValue}" // for external
+
+ // 查询数据库连接情况
+ UriDbConnection = config.DefaultUriPrefix + "/dataManagement/{apiVersion}/dbConnection"
+ CustomUriDbConnection = config.UriPrefix + "/dataManagement/{apiVersion}/dbConnection" // for external
+
+ // 终结非法的数据库连接
+ UriDbStop = config.DefaultUriPrefix + "/dataManagement/{apiVersion}/dbStop"
+ CustomUriDbStop = config.UriPrefix + "/dataManagement/{apiVersion}/dbStop" // for external
+
+)
+
+var XEngine *xorm.Engine
+
+type DatabaseClient struct {
+ dbType string
+ dbUrl string
+ dbConnMaxLifetime time.Duration
+ dbMaxIdleConns int
+ dbMaxOpenConns int
+ IsShowSQL bool
+
+ XEngine *xorm.Engine
+}
+
+var DbClient DatabaseClient
+
+func InitDbClient(dbType, dbUser, dbPassword, dbHost, dbPort, dbName string) error {
+ DbClient.dbUrl = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=true&loc=Local",
+ dbUser, dbPassword, dbHost, dbPort, dbName)
+ DbClient.dbType = dbType
+ DbClient.dbConnMaxLifetime = 0
+ DbClient.dbMaxIdleConns = 0
+ DbClient.dbMaxOpenConns = 0
+ if log.GetLevel() == log.LOG_TRACE {
+ DbClient.IsShowSQL = true
+ }
+ log.Debugf("dbType:%s dbUrl:%s:******@tcp(%s:%s)/%s??charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=true&loc=Local",
+ dbType, dbUser, dbHost, dbPort, dbName)
+
+ var err error
+ DbClient.XEngine, err = xorm.NewEngine(DbClient.dbType, DbClient.dbUrl)
+ if err != nil {
+ log.Error("Failed to connet database:", err)
+ return err
+ }
+ DbClient.XEngine.SetConnMaxLifetime(DbClient.dbConnMaxLifetime)
+ DbClient.XEngine.SetMaxIdleConns(DbClient.dbMaxIdleConns)
+ DbClient.XEngine.SetMaxOpenConns(DbClient.dbMaxOpenConns)
+ if DbClient.IsShowSQL {
+ //DbClient.XEngine.SetLogger(&log.Elogger)
+ DbClient.XEngine.ShowSQL(true)
+ }
+ XEngine = DbClient.XEngine
+
+ return nil
+}
+
+func GetUriSQLArray(r *http.Request) []string {
+ var sa []string
+ vars := r.URL.Query()
+
+ // 默认SQL
+ if s, ok := vars["SQL"]; ok {
+ for _, r := range s {
+ if r != "" {
+ sa = append(sa, r)
+ }
+ }
+ }
+
+ // 查询总数
+ if totalSQL, ok := vars["totalSQL"]; ok {
+ if totalSQL[0] != "" {
+ sa = append(sa, totalSQL[0])
+ }
+ }
+ // 查询列表
+ if rowsSQL, ok := vars["rowsSQL"]; ok {
+ if rowsSQL[0] != "" {
+ sa = append(sa, rowsSQL[0])
+ }
+ }
+
+ if len(sa) == 0 {
+ log.Info("SQL is not exist")
+ return nil
+ }
+
+ log.Debug("SQL array:", sa)
+ return sa
+}
+
+// Get table name from SQL
+func GetTableNameFromSQL(s string) string {
+ ls := strings.ToLower(s)
+ i1 := strings.Index(ls, "from")
+ i2 := strings.Index(ls, "where")
+
+ var ts string
+ if i1 > 0 {
+ if i2 > 0 && i2 > i1 {
+ ts = ls[i1+4 : i2]
+ }
+ if i2 < 0 {
+ ts = ls[i1+4:]
+ }
+ }
+
+ tn := strings.Trim(ts, " ")
+ log.Debug("i1:", i1, "i2:", i2, "tn:", tn)
+ return tn
+}
+
+func GetTableName(sql string) string {
+ ls := strings.ToLower(sql)
+
+ re := regexp.MustCompile(`from\s+(\S+)`)
+ matches := re.FindStringSubmatch(ls)
+ if len(matches) < 2 {
+ return ""
+ }
+ return matches[1]
+}
+
+func IsQuerySQL(s string) bool {
+ ts := strings.Trim(strings.ToLower(s), " ")
+ if strings.Index(ts, "select") != 0 {
+ return false
+ }
+ return true
+}
+
+// xorm Get data from database
+func ExtDatabaseExecSQL(w http.ResponseWriter, r *http.Request) {
+ log.Debug("ExtDatabaseExecSQL processing... ")
+
+ var sql []string
+ var err error
+
+ _, err = services.CheckExtValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ //vars := mux.Vars(r)
+ //tblName := vars["objectTypeValue"]
+ sql = GetUriSQLArray(r)
+ // select as must, todo ...
+
+ ls := services.ExtGetUriPageLimitString(r)
+
+ // data := make([]map[string]interface{}, 0)
+ // xormResponse := make([]map[string]interface{}, len(sql))
+ var xormResponse XormResponse
+ data := make([]map[string]interface{}, 0)
+ for i, s := range sql {
+ log.Tracef("SQL[%d]: %s", i, sql[i])
+
+ //rows := make([]map[string]interface{}, 0)
+ mapRows := make(map[string]interface{})
+
+ if s != "" {
+ // err = XEngine.SQL(s).Find(&rows)
+ // if IsQuerySQL(s) == false {
+ // services.ResponseNotAcceptable406QuerySQLError(w)
+ // return
+ // }
+
+ querySQL := s
+ if i == (len(sql) - 1) {
+ querySQL = querySQL + " " + ls
+ }
+ log.Debug("querySQL:", querySQL)
+ rows, err := DbClient.XEngine.Exec(querySQL)
+ if err != nil {
+ log.Error("SQL failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+ tableName := GetTableName(s)
+ log.Debugf("s:%s tableName:%s", s, tableName)
+ mapRows[tableName] = rows
+ data = append(data, mapRows)
+ log.Trace("data:", data)
+ }
+ i++
+ }
+ xormResponse.Data = data
+
+ services.ResponseWithJson(w, http.StatusOK, xormResponse)
+}
+
+// xorm Get data from database
+func ExtDatabaseGetData(w http.ResponseWriter, r *http.Request) {
+ log.Debug("ExtDatabaseGetData processing... ")
+
+ var sql []string
+ token, err := services.CheckExtValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ pack := "dbrest"
+ vars := mux.Vars(r)
+ module := ""
+ dbname := vars["elementTypeValue"]
+ tbname := vars["objectTypeValue"]
+
+ log.Debugf("token:%s, method:%s, module:%s, dbname:%s, tbname:%s, pack:%s", token, r.Method, module, dbname, tbname, pack)
+
+ // exist, err := services.CheckUserPermission(token, strings.ToLower(r.Method), module, dbname, tbname, pack)
+ // if err != nil {
+ // log.Error("Failed to get permission:", err)
+ // services.ResponseForbidden403NotPermission(w)
+ // return
+ // }
+ // if !exist {
+ // log.Error("Not permission!")
+ // services.ResponseForbidden403NotPermission(w)
+ // return
+ // }
+
+ sql = GetUriSQLArray(r)
+ // select as must, todo ...
+ if sql == nil {
+ wc := services.GetUriLocString(r)
+ if wc == "" {
+ sql = append(sql, fmt.Sprintf("select * from %s", tbname))
+ } else {
+ sql = append(sql, fmt.Sprintf("select * from %s where %s", tbname, wc))
+ }
+ }
+
+ ls := services.ExtGetUriPageLimitString(r)
+
+ // data := make([]map[string]interface{}, 0)
+ // xormResponse := make([]map[string]interface{}, len(sql))
+ var xormResponse XormResponse
+ data := make([]map[string]interface{}, 0)
+ for i, s := range sql {
+ log.Tracef("SQL[%d]: %s", i, sql[i])
+
+ rows := make([]map[string]interface{}, 0)
+ mapRows := make(map[string]interface{})
+
+ if s != "" {
+ // err = XEngine.SQL(s).Find(&rows)
+ if IsQuerySQL(s) == false {
+ services.ResponseNotAcceptable406QuerySQLError(w)
+ return
+ }
+
+ querySQL := s
+ if i == (len(sql) - 1) {
+ querySQL = querySQL + " " + ls
+ }
+ log.Debug("querySQL:", querySQL)
+ rows, err = DbClient.XEngine.QueryInterface(querySQL)
+ if err != nil {
+ log.Error("SQL failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+ tableName := GetTableName(s)
+ log.Debugf("s:%s tableName:%s", s, tableName)
+ mapRows[tableName] = rows
+ data = append(data, mapRows)
+ log.Trace("data:", data)
+ }
+ i++
+ }
+ xormResponse.Data = data
+
+ services.ResponseWithJson(w, http.StatusOK, xormResponse)
+}
+
+func ExtDatabaseInsertData(w http.ResponseWriter, r *http.Request) {
+ log.Debug("ExtDatabaseInsertData processing... ")
+
+ token, err := services.CheckExtValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen)) //io.LimitReader限制大小
+ if err != nil {
+ log.Error("io.ReadAll failed:", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ vars := mux.Vars(r)
+ module := ""
+ dbname := vars["elementTypeValue"]
+ tbname := vars["objectTypeValue"]
+ pack := "dbrest"
+
+ log.Debugf("token:%s, method:%s, module:%s, dbname:%s, tbname:%s, pack:%s", token, r.Method, module, dbname, tbname, pack)
+
+ // exist, err := services.CheckUserPermission(token, strings.ToLower(r.Method), module, dbname, tbname, pack)
+ // if err != nil {
+ // log.Error("Failed to get permission:", err)
+ // services.ResponseForbidden403NotPermission(w)
+ // return
+ // }
+ // if !exist {
+ // log.Error("permission deny!")
+ // services.ResponseForbidden403NotPermission(w)
+ // return
+ // }
+
+ log.Debug("Request body:", string(body), "dataObject:", tbname)
+ insertData := make(map[string]interface{})
+ _ = json.Unmarshal(body, &insertData)
+
+ tn, sql := dborm.ConstructInsertSQL(tbname, insertData)
+ log.Tracef("tn: %s sql :%s", tn, sql)
+ xSession := DbClient.XEngine.NewSession()
+ defer xSession.Close()
+ var affected int64
+ for _, s := range sql {
+ res, err := xSession.Exec(s)
+ if err != nil {
+ log.Error("Insert failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+ n, _ := res.RowsAffected()
+ affected = affected + n
+ }
+ xSession.Commit()
+ // affected, err := InsertDataWithJson(insertData)
+ mapRow := make(map[string]interface{})
+ row := map[string]interface{}{"affectedRows": affected}
+ mapRow[tn] = row
+ // xormResponse.Data = mapRow
+ services.ResponseWithJson(w, http.StatusOK, mapRow)
+}
+
+func ExtDatabaseUpdateData(w http.ResponseWriter, r *http.Request) {
+ log.Debug("ExtDatabaseUpdateData processing... ")
+
+ token, err := services.CheckExtValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ module := ""
+ dbname := vars["elementTypeValue"]
+ tbname := vars["objectTypeValue"]
+ pack := "dbrest"
+
+ log.Debugf("token:%s, method:%s, module:%s, dbname:%s, tbname:%s, pack:%s", token, r.Method, module, dbname, tbname, pack)
+
+ // exist, err := services.CheckUserPermission(token, strings.ToLower(r.Method), module, dbname, tbname, pack)
+ // if err != nil {
+ // log.Error("Failed to get permission:", err)
+ // services.ResponseForbidden403NotPermission(w)
+ // return
+ // }
+ // if !exist {
+ // log.Error("Not permission!")
+ // services.ResponseForbidden403NotPermission(w)
+ // return
+ // }
+
+ body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen))
+ if err != nil {
+ log.Error("io.ReadAll is failed:", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ wc := services.GetUriLocString(r)
+
+ log.Debug("Request body:", string(body), "Tablename:", tbname, "wc:", wc)
+ updateData := make(map[string]interface{})
+ _ = json.Unmarshal(body, &updateData)
+
+ tn, sql := dborm.ConstructUpdateSQL(tbname, updateData, wc)
+ log.Tracef("tn: %s sql :%s", tn, sql)
+ xSession := DbClient.XEngine.NewSession()
+ defer xSession.Close()
+ var affected int64
+ for _, s := range sql {
+ res, err := xSession.Exec(s)
+ if err != nil {
+ log.Error("Update failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+ n, _ := res.RowsAffected()
+ affected = affected + n
+ }
+ xSession.Commit()
+ mapRow := make(map[string]interface{})
+ row := map[string]interface{}{"affectedRows": affected}
+ mapRow[tn] = row
+ services.ResponseWithJson(w, http.StatusOK, mapRow)
+}
+
+func ExtDatabaseDeleteData(w http.ResponseWriter, r *http.Request) {
+ log.Debug("ExtDatabaseDeleteData processing... ")
+
+ token, err := services.CheckExtValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ module := ""
+ dbname := vars["elementTypeValue"]
+ tbname := vars["objectTypeValue"]
+ pack := "dbreset"
+
+ log.Debugf("token:%s, method:%s, module:%, dbname:%s, tbname:%s pack:%s", token, r.Method, module, dbname, tbname, pack)
+
+ // exist, err := services.CheckUserPermission(token, strings.ToLower(r.Method), module, dbname, tbname, pack)
+ // if err != nil {
+ // log.Error("Failed to get permission:", err)
+ // services.ResponseForbidden403NotPermission(w)
+ // return
+ // }
+ // if !exist {
+ // log.Error("Not permission!")
+ // services.ResponseForbidden403NotPermission(w)
+ // return
+ // }
+
+ wc := services.GetUriLocString(r)
+
+ log.Debug("Table name:", tbname, "wc:", wc)
+
+ sql := dborm.ConstructDeleteSQL(tbname, wc)
+ xSession := DbClient.XEngine.NewSession()
+ defer xSession.Close()
+ res, err := xSession.Exec(sql)
+ if err != nil {
+ log.Error("Update failed, err:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+ affected, _ := res.RowsAffected()
+ xSession.Commit()
+ mapRow := make(map[string]interface{})
+ row := map[string]interface{}{"affectedRows": affected}
+ mapRow["data"] = row
+ services.ResponseWithJson(w, http.StatusOK, mapRow)
+}
+
+// xorm Get data from database
+func DatabaseGetData(w http.ResponseWriter, r *http.Request) {
+ log.Debug("DatabaseGetData processing... ")
+
+ var sql []string
+ var err error
+
+ _, err = services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ tblName := vars["objectTypeValue"]
+ sql = GetUriSQLArray(r)
+ // select as must, todo ...
+
+ if sql == nil {
+ wc := services.GetUriWhereString(r)
+ if wc == "" {
+ sql = append(sql, fmt.Sprintf("select * from %s", tblName))
+ } else {
+ sql = append(sql, fmt.Sprintf("select * from %s where %s", tblName, wc))
+ }
+ }
+
+ ls := services.GetUriPageLimitString(r)
+
+ // data := make([]map[string]interface{}, 0)
+ // xormResponse := make([]map[string]interface{}, len(sql))
+ var xormResponse XormResponse
+ data := make([]map[string]interface{}, 0)
+ for i, s := range sql {
+ log.Tracef("SQL[%d]: %s", i, sql[i])
+
+ rows := make([]map[string]interface{}, 0)
+ mapRows := make(map[string]interface{})
+
+ if s != "" {
+ // err = XEngine.SQL(s).Find(&rows)
+ if IsQuerySQL(s) == false {
+ services.ResponseNotAcceptable406QuerySQLError(w)
+ return
+ }
+
+ querySQL := s
+ if i == (len(sql) - 1) {
+ querySQL = querySQL + " " + ls
+ }
+ log.Debug("querySQL:", querySQL)
+ rows, err = DbClient.XEngine.QueryInterface(querySQL)
+ if err != nil {
+ log.Error("SQL failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+ tableName := GetTableName(s)
+ log.Debugf("s:%s tableName:%s", s, tableName)
+ mapRows[tableName] = rows
+ data = append(data, mapRows)
+ log.Trace("data:", data)
+ }
+ i++
+ }
+ xormResponse.Data = data
+
+ services.ResponseWithJson(w, http.StatusOK, xormResponse)
+}
+
+func DatabaseInsertData(w http.ResponseWriter, r *http.Request) {
+ log.Debug("DatabaseInsertData processing... ")
+
+ body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen)) //io.LimitReader限制大小
+ if err != nil {
+ log.Error("io.ReadAll failed:", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ vars := mux.Vars(r)
+ tableName := vars["objectTypeValue"]
+ log.Debug("Request body:", string(body), "tableName:", tableName)
+ insertData := make(map[string]interface{})
+ _ = json.Unmarshal(body, &insertData)
+
+ // 操作日志的IP
+ if tableName == "operation_log" || tableName == "security_log" {
+ ipAddr := strings.Split(r.RemoteAddr, ":")[0]
+ s := insertData["data"].([]any)
+ a := s[0].(map[string]any)
+ a["op_ip"] = ipAddr
+ } else if tableName == "mml_log" {
+ ipAddr := strings.Split(r.RemoteAddr, ":")[0]
+ s := insertData["data"].([]any)
+ a := s[0].(map[string]any)
+ a["ip"] = ipAddr
+ }
+
+ tn, sql := dborm.ConstructInsertSQL(tableName, insertData)
+ log.Tracef("tn: %s sql :%s", tn, sql)
+ xSession := DbClient.XEngine.NewSession()
+ defer xSession.Close()
+ var affected int64
+ for _, s := range sql {
+ res, err := xSession.Exec(s)
+ if err != nil {
+ log.Error("Insert failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+ n, _ := res.RowsAffected()
+ affected = affected + n
+ }
+ xSession.Commit()
+ // affected, err := InsertDataWithJson(insertData)
+ mapRow := make(map[string]interface{})
+ row := map[string]interface{}{"affectedRows": affected}
+ mapRow[tn] = row
+ // xormResponse.Data = mapRow
+ services.ResponseWithJson(w, http.StatusOK, mapRow)
+}
+
+func DatabaseUpdateData(w http.ResponseWriter, r *http.Request) {
+ log.Debug("DatabaseUpdateData processing... ")
+
+ body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen))
+ if err != nil {
+ log.Error("io.ReadAll is failed:", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ vars := mux.Vars(r)
+ tblName := vars["objectTypeValue"]
+ wc := services.GetUriWhereString(r)
+
+ log.Debug("Request body:", string(body), "Table name:", tblName, "wc:", wc)
+ updateData := make(map[string]interface{})
+ _ = json.Unmarshal(body, &updateData)
+
+ tn, sql := dborm.ConstructUpdateSQL(tblName, updateData, wc)
+ log.Tracef("tn: %s sql :%s", tn, sql)
+ xSession := DbClient.XEngine.NewSession()
+ defer xSession.Close()
+ var affected int64
+ for _, s := range sql {
+ res, err := xSession.Exec(s)
+ if err != nil {
+ log.Error("Update failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+ n, _ := res.RowsAffected()
+ affected = affected + n
+ }
+ xSession.Commit()
+ mapRow := make(map[string]interface{})
+ row := map[string]interface{}{"affectedRows": affected}
+ mapRow[tn] = row
+ services.ResponseWithJson(w, http.StatusOK, mapRow)
+}
+
+func DatabaseDeleteData(w http.ResponseWriter, r *http.Request) {
+ log.Debug("DatabaseDeleteData processing... ")
+
+ vars := mux.Vars(r)
+ tblName := vars["objectTypeValue"]
+ wc := services.GetUriWhereString(r)
+
+ log.Debug("Table name:", tblName, "wc:", wc)
+
+ sql := dborm.ConstructDeleteSQL(tblName, wc)
+ xSession := DbClient.XEngine.NewSession()
+ defer xSession.Close()
+ res, err := xSession.Exec(sql)
+ if err != nil {
+ log.Error("Update failed, err:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+ affected, _ := res.RowsAffected()
+ xSession.Commit()
+ mapRow := make(map[string]interface{})
+ row := map[string]interface{}{"affectedRows": affected}
+ mapRow["data"] = row
+ services.ResponseWithJson(w, http.StatusOK, mapRow)
+}
+
+// 连接用户实例
+func DbConnection(w http.ResponseWriter, r *http.Request) {
+ if dborm.DbClient.XEngine == nil {
+ services.ResponseErrorWithJson(w, 400, "无连接")
+ }
+ // 查询实例
+ result, err := dborm.DbClient.XEngine.QueryString("SHOW PROCESSLIST;")
+ if err != nil {
+ services.ResponseErrorWithJson(w, 500, err.Error())
+ }
+ filterData := []map[string]string{}
+ for _, r := range result {
+ if r["User"] != "system user" {
+ filterData = append(filterData, r)
+ }
+ }
+ // Sleep:连接处于空闲状态,没有执行任何操作。
+ // Query:连接正在执行一个查询语句。
+ // Execute:连接正在执行一个准备好的 SQL 语句。
+ // Connect:连接正在建立但尚未完成。
+ services.ResponseWithJson(w, 200, filterData)
+}
+
+// 关闭数据库连接
+func DbStop(w http.ResponseWriter, r *http.Request) {
+ if dborm.DbClient.XEngine == nil {
+ services.ResponseErrorWithJson(w, 400, "无连接")
+ }
+
+ // json 請求參數獲取
+ var bodyArgs struct {
+ ID string `json:"ID" validate:"required"`
+ }
+ err := ctx.ShouldBindJSON(r, &bodyArgs)
+ if err != nil {
+ log.Error("io.ReadAll is failed:", err)
+ services.ResponseErrorWithJson(w, 400, err.Error())
+ return
+ }
+
+ // 关闭
+ rse, err := dborm.DbClient.XEngine.Exec("KILL ?;", bodyArgs.ID)
+ if err != nil {
+ services.ResponseErrorWithJson(w, 500, err.Error())
+ return
+ }
+ services.ResponseWithJson(w, 200, rse)
+}
+
+// xorm Get data from database
+func TaskDatabaseGetData(w http.ResponseWriter, r *http.Request) {
+ log.Debug("DatabaseGetData processing... ")
+
+ var sql []string
+ var err error
+
+ vars := mux.Vars(r)
+ tblName := vars["objectTypeValue"]
+ sql = GetUriSQLArray(r)
+ // select as must, todo ...
+
+ if sql == nil {
+ wc := services.GetUriWhereString(r)
+ if wc == "" {
+ sql = append(sql, fmt.Sprintf("select * from %s", tblName))
+ } else {
+ sql = append(sql, fmt.Sprintf("select * from %s where %s", tblName, wc))
+ }
+ }
+
+ ls := services.GetUriPageLimitString(r)
+
+ // data := make([]map[string]interface{}, 0)
+ // xormResponse := make([]map[string]interface{}, len(sql))
+ var xormResponse XormResponse
+ data := make([]map[string]interface{}, 0)
+ for i, s := range sql {
+ log.Tracef("SQL[%d]: %s", i, sql[i])
+
+ rows := make([]map[string]interface{}, 0)
+ mapRows := make(map[string]interface{})
+
+ if s != "" {
+ // err = XEngine.SQL(s).Find(&rows)
+ if IsQuerySQL(s) == false {
+ services.ResponseNotAcceptable406QuerySQLError(w)
+ return
+ }
+
+ querySQL := s
+ if i == (len(sql) - 1) {
+ querySQL = querySQL + " " + ls
+ }
+ log.Debug("querySQL:", querySQL)
+ rows, err = DbClient.XEngine.QueryInterface(querySQL)
+ if err != nil {
+ log.Error("SQL failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+ tableName := GetTableName(s)
+ log.Debugf("s:%s tableName:%s", s, tableName)
+ mapRows[tableName] = rows
+ data = append(data, mapRows)
+ log.Trace("data:", data)
+ }
+ i++
+ }
+ xormResponse.Data = data
+
+ services.ResponseWithJson(w, http.StatusOK, xormResponse)
+}
+
+func TaskDatabaseInsertData(w http.ResponseWriter, r *http.Request) {
+ log.Debug("DatabaseInsertData processing... ")
+
+ body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen)) //io.LimitReader限制大小
+ if err != nil {
+ log.Error("io.ReadAll failed:", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ vars := mux.Vars(r)
+ tableName := vars["objectTypeValue"]
+ log.Debug("Request body:", string(body), "tableName:", tableName)
+ insertData := make(map[string]interface{})
+ _ = json.Unmarshal(body, &insertData)
+
+ tn, sql := dborm.ConstructInsertSQL(tableName, insertData)
+ log.Tracef("tn: %s sql :%s", tn, sql)
+ xSession := DbClient.XEngine.NewSession()
+ defer xSession.Close()
+ var affected int64
+ for _, s := range sql {
+ res, err := xSession.Exec(s)
+ if err != nil {
+ log.Error("Insert failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+ n, _ := res.RowsAffected()
+ affected = affected + n
+ }
+ xSession.Commit()
+ // affected, err := InsertDataWithJson(insertData)
+ mapRow := make(map[string]interface{})
+ row := map[string]interface{}{"affectedRows": affected}
+ mapRow[tn] = row
+ // xormResponse.Data = mapRow
+ services.ResponseWithJson(w, http.StatusOK, mapRow)
+}
+
+func TaskDatabaseUpdateData(w http.ResponseWriter, r *http.Request) {
+ log.Debug("DatabaseUpdateData processing... ")
+
+ body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen))
+ if err != nil {
+ log.Error("io.ReadAll is failed:", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ vars := mux.Vars(r)
+ tblName := vars["objectTypeValue"]
+ wc := services.GetUriWhereString(r)
+
+ log.Debug("Request body:", string(body), "Table name:", tblName, "wc:", wc)
+ updateData := make(map[string]interface{})
+ _ = json.Unmarshal(body, &updateData)
+
+ tn, sql := dborm.ConstructUpdateSQL(tblName, updateData, wc)
+ log.Tracef("tn: %s sql :%s", tn, sql)
+ xSession := DbClient.XEngine.NewSession()
+ defer xSession.Close()
+ var affected int64
+ for _, s := range sql {
+ res, err := xSession.Exec(s)
+ if err != nil {
+ log.Error("Update failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+ n, _ := res.RowsAffected()
+ affected = affected + n
+ }
+ xSession.Commit()
+ mapRow := make(map[string]interface{})
+ row := map[string]interface{}{"affectedRows": affected}
+ mapRow[tn] = row
+ services.ResponseWithJson(w, http.StatusOK, mapRow)
+}
+
+func TaskDatabaseDeleteData(w http.ResponseWriter, r *http.Request) {
+ log.Debug("DatabaseDeleteData processing... ")
+
+ vars := mux.Vars(r)
+ tblName := vars["objectTypeValue"]
+ wc := services.GetUriWhereString(r)
+
+ log.Debug("Table name:", tblName, "wc:", wc)
+
+ sql := dborm.ConstructDeleteSQL(tblName, wc)
+ xSession := DbClient.XEngine.NewSession()
+ defer xSession.Close()
+ res, err := xSession.Exec(sql)
+ if err != nil {
+ log.Error("Update failed, err:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+ affected, _ := res.RowsAffected()
+ xSession.Commit()
+ mapRow := make(map[string]interface{})
+ row := map[string]interface{}{"affectedRows": affected}
+ mapRow["data"] = row
+ services.ResponseWithJson(w, http.StatusOK, mapRow)
+}
diff --git a/features/event/event.go b/features/event/event.go
new file mode 100644
index 0000000..0715489
--- /dev/null
+++ b/features/event/event.go
@@ -0,0 +1,70 @@
+package event
+
+import (
+ "encoding/json"
+ "io"
+ "time"
+
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/global"
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/services"
+ wsService "nms_nbi/src/modules/ws/service"
+
+ "github.com/gin-gonic/gin"
+)
+
+var (
+ UriUEEvent = "/upload-ue/v1/:eventType"
+)
+
+type UEEvent struct {
+ NeType string `json:"neType" xorm:"ne_type"`
+ NeName string `json:"neName" xorm:"ne_name"`
+ RmUID string `json:"rmUID" xorm:"rm_uid"`
+ Timestamp int `json:"timestamp" xorm:"timestamp"`
+ EventType string `json:"eventType" xorm:"event_type"`
+ EventJson map[string]any `json:"eventJSON" xorm:"event_json"`
+}
+
+func PostUEEventFromAMF(c *gin.Context) {
+ log.Info("PostUEEventFromAMF processing... ")
+
+ body, err := io.ReadAll(io.LimitReader(c.Request.Body, global.RequestBodyMaxLen))
+ if err != nil {
+ log.Error("Failed to io.ReadAll: ", err)
+ services.ResponseNotFound404UriNotExist(c.Writer, c.Request)
+ return
+ }
+
+ //vars := mux.Vars(c.Request)
+ eventType, ok := c.Params.Get("eventType")
+ if !ok || eventType == "" {
+ log.Error("eventType is empty")
+ services.ResponseNotFound404UriNotExist(c.Writer, c.Request)
+ return
+ }
+ ueEvent := new(UEEvent)
+ err = json.Unmarshal(body, &ueEvent.EventJson)
+ if err != nil {
+ log.Error("Failed to Unmarshal ueEvent:", err)
+ services.ResponseInternalServerError500ProcessError(c.Writer, err)
+ return
+ }
+ ueEvent.NeType = "AMF"
+ ueEvent.Timestamp = int(time.Now().Unix())
+ ueEvent.EventType = eventType
+ log.Trace("ueEvent:", ueEvent)
+
+ affected, err := dborm.XormInsertTableOne("ue_event", ueEvent)
+ if err != nil && affected <= 0 {
+ log.Error("Failed to insert ue_event:", err)
+ services.ResponseInternalServerError500ProcessError(c.Writer, err)
+ return
+ }
+
+ // 推送到ws订阅组
+ wsService.NewWSSendImpl.ByGroupID(wsService.GROUP_AMF_UE, ueEvent)
+
+ services.ResponseStatusOK204NoContent(c.Writer)
+}
diff --git a/features/file/file.go b/features/file/file.go
new file mode 100644
index 0000000..de0e43b
--- /dev/null
+++ b/features/file/file.go
@@ -0,0 +1,195 @@
+package file
+
+import (
+ "fmt"
+ "net/http"
+ "path/filepath"
+
+ "nms_nbi/lib/core/utils/ctx"
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/file"
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+
+ "github.com/gorilla/mux"
+ "github.com/shirou/gopsutil/disk"
+)
+
+var (
+ // parameter config management
+ UriFile = config.DefaultUriPrefix + "/fileManagement/{apiVersion}/{location}/file"
+
+ CustomUriFile = config.UriPrefix + "/fileManagement/{apiVersion}/{location}/file"
+
+ // 获取磁盘列表
+ UriDiskList = config.DefaultUriPrefix + "/fileManagement/{apiVersion}/files/diskList"
+
+ // 获取文件列表
+ UriListFiles = config.DefaultUriPrefix + "/fileManagement/{apiVersion}/files/listFiles"
+)
+
+// func init() {
+// routes.Register("POST", UriFile, UploadFile, nil)
+// routes.Register("GET", UriFile, DownloadFile, nil)
+// routes.Register("DELETE", UriFile, DeleteFile, nil)
+// }
+
+func UploadFile(w http.ResponseWriter, r *http.Request) {
+ log.Debug("UploadFile processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Http request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ location := vars["location"]
+ if location == "" {
+ log.Error("location is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ params := r.URL.Query()
+ rename, ok := params["rename"]
+ if !ok {
+ log.Info("rename parameter is not exist")
+ }
+ log.Debug("rename:", rename)
+
+ var path, fileName string
+ if len(rename) > 0 {
+ fileName = rename[0]
+ }
+ if location == "upload" {
+ path = config.GetYamlConfig().OMC.Upload
+ fileName, err = services.HandleUploadFile(r, path, fileName)
+ if err != nil {
+ log.Error("Faile to HandleUploadFile:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ } else {
+ path = config.GetYamlConfig().OMC.FrontUpload
+ fileName, err = services.HandleUploadFile(r, path, fileName)
+ if err != nil {
+ log.Error("Faile to HandleUploadFile:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ }
+ log.Debugf("upload file=%s to path=%s", fileName, path)
+
+ services.ResponseStatusOK204NoContent(w)
+ return
+}
+
+func DownloadFile(w http.ResponseWriter, r *http.Request) {
+ log.Debug("DownloadFile processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ location := vars["location"]
+ if location == "" {
+ log.Error("location is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ // 全路径文件下载
+ filePath := r.URL.Query().Get("path")
+ if location == "path" && filePath != "" {
+ // 截取文件路径
+ dir := filepath.Dir(filePath)
+ // 截取文件名
+ fileName := filepath.Base(filePath)
+ services.ResponseFileWithNameAndMD5(w, http.StatusOK, fileName, dir, "")
+ return
+ }
+
+ params := r.URL.Query()
+ fileNames, ok := params["loc"]
+ if !ok {
+ log.Info("loc parameter is not exist")
+ }
+ var path string
+ if location == "upload" {
+ path = config.GetYamlConfig().OMC.Upload
+ } else {
+ path = config.GetYamlConfig().OMC.FrontUpload
+ }
+ for _, fileName := range fileNames {
+ services.ResponseFileWithNameAndMD5(w, http.StatusOK, fileName, path, "")
+ }
+ return
+}
+
+func DeleteFile(w http.ResponseWriter, r *http.Request) {
+ log.Debug("DeleteFile processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["neType"]
+ if neType == "" {
+ log.Error("neType is empty")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ services.ResponseStatusOK204NoContent(w)
+ return
+}
+
+// 磁盘列表
+func DiskList(w http.ResponseWriter, r *http.Request) {
+ disks := make([]map[string]string, 0)
+
+ partitions, err := disk.Partitions(false)
+ if err != nil {
+ services.ResponseWithJson(w, 200, disks)
+ }
+
+ for _, partition := range partitions {
+ usage, err := disk.Usage(partition.Mountpoint)
+ if err != nil {
+ continue
+ }
+ disks = append(disks, map[string]string{
+ "size": file.FormatFileSize(float64(usage.Total)),
+ "used": file.FormatFileSize(float64(usage.Used)),
+ "avail": file.FormatFileSize(float64(usage.Free)),
+ "pcent": fmt.Sprintf("%.1f%%", usage.UsedPercent),
+ "target": partition.Device,
+ })
+ }
+ services.ResponseWithJson(w, 200, disks)
+}
+
+// 获取文件列表 /files/search
+func ListFiles(w http.ResponseWriter, r *http.Request) {
+ // json 請求參數獲取
+ var bodyArgs FileOption
+ err := ctx.ShouldBindJSON(r, &bodyArgs)
+ if err != nil || dborm.DbClient.XEngine == nil {
+ services.ResponseErrorWithJson(w, 400, err.Error())
+ return
+ }
+
+ files, err := GetFileList(bodyArgs)
+ if err != nil {
+ services.ResponseErrorWithJson(w, 400, err.Error())
+ return
+ }
+ services.ResponseWithJson(w, 200, files)
+}
diff --git a/features/file/model.go b/features/file/model.go
new file mode 100644
index 0000000..1de351f
--- /dev/null
+++ b/features/file/model.go
@@ -0,0 +1,85 @@
+package file
+
+import (
+ "bufio"
+ "fmt"
+ "io/fs"
+ "os"
+ "os/exec"
+ "time"
+
+ "github.com/spf13/afero"
+)
+
+type FileOption struct {
+ Path string `json:"path"`
+ Search string `json:"search"`
+ ContainSub bool `json:"containSub"`
+ Expand bool `json:"expand"`
+ Dir bool `json:"dir"`
+ ShowHidden bool `json:"showHidden"`
+ Page int `json:"page"`
+ PageSize int `json:"pageSize"`
+}
+
+type FileInfo struct {
+ Fs afero.Fs `json:"-"`
+ Path string `json:"path"`
+ Name string `json:"name"`
+ Extension string `json:"extension"`
+ Content string `json:"content"`
+ Size int64 `json:"size"`
+ IsDir bool `json:"isDir"`
+ IsSymlink bool `json:"isSymlink"`
+ IsHidden bool `json:"isHidden"`
+ LinkPath string `json:"linkPath"`
+ Type string `json:"type"`
+ Mode string `json:"mode"`
+ MimeType string `json:"mimeType"`
+ UpdateTime time.Time `json:"updateTime"`
+ ModTime time.Time `json:"modTime"`
+ FileMode os.FileMode `json:"-"`
+ Items []*FileInfo `json:"items"`
+ ItemTotal int `json:"itemTotal"`
+}
+
+func (f *FileInfo) search(search string, count int) (files []FileSearchInfo, total int, err error) {
+ cmd := exec.Command("find", f.Path, "-name", fmt.Sprintf("*%s*", search))
+ output, err := cmd.StdoutPipe()
+ if err != nil {
+ return
+ }
+ if err = cmd.Start(); err != nil {
+ return
+ }
+ defer func() {
+ _ = cmd.Wait()
+ _ = cmd.Process.Kill()
+ }()
+
+ scanner := bufio.NewScanner(output)
+ for scanner.Scan() {
+ line := scanner.Text()
+ info, err := os.Stat(line)
+ if err != nil {
+ continue
+ }
+ total++
+ if total > count {
+ continue
+ }
+ files = append(files, FileSearchInfo{
+ Path: line,
+ FileInfo: info,
+ })
+ }
+ if err = scanner.Err(); err != nil {
+ return
+ }
+ return
+}
+
+type FileSearchInfo struct {
+ Path string `json:"path"`
+ fs.FileInfo
+}
diff --git a/features/file/service.go b/features/file/service.go
new file mode 100644
index 0000000..c1605be
--- /dev/null
+++ b/features/file/service.go
@@ -0,0 +1,207 @@
+package file
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+
+ "nms_nbi/lib/file"
+
+ "github.com/spf13/afero"
+)
+
+// 获取文件列表
+func GetFileList(op FileOption) (FileInfo, error) {
+ var fileInfo FileInfo
+ if _, err := os.Stat(op.Path); err != nil && os.IsNotExist(err) {
+ return fileInfo, nil
+ }
+ info, err := NewFileInfo(op)
+ if err != nil {
+ return fileInfo, err
+ }
+ fileInfo = *info
+ return fileInfo, nil
+}
+
+func NewFileInfo(op FileOption) (*FileInfo, error) {
+ var appFs = afero.NewOsFs()
+
+ info, err := appFs.Stat(op.Path)
+ if err != nil {
+ return nil, err
+ }
+
+ fileInfo := &FileInfo{
+ Fs: appFs,
+ Path: op.Path,
+ Name: info.Name(),
+ IsDir: info.IsDir(),
+ FileMode: info.Mode(),
+ ModTime: info.ModTime(),
+ Size: info.Size(),
+ IsSymlink: file.IsSymlink(info.Mode()),
+ Extension: filepath.Ext(info.Name()),
+ IsHidden: file.IsHidden(op.Path),
+ Mode: fmt.Sprintf("%04o", info.Mode().Perm()),
+ MimeType: file.GetMimeType(op.Path),
+ }
+ if fileInfo.IsSymlink {
+ fileInfo.LinkPath = file.GetSymlink(op.Path)
+ }
+ if op.Expand {
+ if fileInfo.IsDir {
+ if err := listChildren(fileInfo, op.Dir, op.ShowHidden, op.ContainSub, op.Search, op.Page, op.PageSize); err != nil {
+ return nil, err
+ }
+ return fileInfo, nil
+ } else {
+ if err := getContent(fileInfo); err != nil {
+ return nil, err
+ }
+ }
+ }
+ return fileInfo, nil
+}
+
+func listChildren(f *FileInfo, dir, showHidden, containSub bool, search string, page, pageSize int) error {
+ afs := &afero.Afero{Fs: f.Fs}
+ var (
+ files []FileSearchInfo
+ err error
+ total int
+ )
+
+ if search != "" && containSub {
+ files, total, err = f.search(search, page*pageSize)
+ if err != nil {
+ return err
+ }
+ } else {
+ dirFiles, err := afs.ReadDir(f.Path)
+ if err != nil {
+ return err
+ }
+ for _, file := range dirFiles {
+ files = append(files, FileSearchInfo{
+ Path: f.Path,
+ FileInfo: file,
+ })
+ }
+ }
+
+ var items []*FileInfo
+ for _, df := range files {
+ if dir && !df.IsDir() {
+ continue
+ }
+ name := df.Name()
+ fPath := path.Join(df.Path, df.Name())
+ if search != "" {
+ if containSub {
+ fPath = df.Path
+ name = strings.TrimPrefix(strings.TrimPrefix(fPath, f.Path), "/")
+ } else {
+ lowerName := strings.ToLower(name)
+ lowerSearch := strings.ToLower(search)
+ if !strings.Contains(lowerName, lowerSearch) {
+ continue
+ }
+ }
+ }
+ if !showHidden && file.IsHidden(name) {
+ continue
+ }
+ f.ItemTotal++
+ isSymlink, isInvalidLink := false, false
+ if file.IsSymlink(df.Mode()) {
+ isSymlink = true
+ info, err := f.Fs.Stat(fPath)
+ if err == nil {
+ df.FileInfo = info
+ } else {
+ isInvalidLink = true
+ }
+ }
+
+ fileInfo := &FileInfo{
+ Fs: f.Fs,
+ Name: name,
+ Size: df.Size(),
+ ModTime: df.ModTime(),
+ FileMode: df.Mode(),
+ IsDir: df.IsDir(),
+ IsSymlink: isSymlink,
+ IsHidden: file.IsHidden(fPath),
+ Extension: filepath.Ext(name),
+ Path: fPath,
+ Mode: fmt.Sprintf("%04o", df.Mode().Perm()),
+ }
+
+ if isSymlink {
+ fileInfo.LinkPath = file.GetSymlink(fPath)
+ }
+ if df.Size() > 0 {
+ fileInfo.MimeType = file.GetMimeType(fPath)
+ }
+ if isInvalidLink {
+ fileInfo.Type = "invalid_link"
+ }
+ items = append(items, fileInfo)
+ }
+ if containSub {
+ f.ItemTotal = total
+ }
+ start := (page - 1) * pageSize
+ end := pageSize + start
+ var result []*FileInfo
+ if start < 0 || start > f.ItemTotal || end < 0 || start > end {
+ result = items
+ } else {
+ if end > f.ItemTotal {
+ result = items[start:]
+ } else {
+ result = items[start:end]
+ }
+ }
+
+ f.Items = result
+ return nil
+}
+
+func getContent(f *FileInfo) error {
+ if f.Size <= 10*1024*1024 {
+ afs := &afero.Afero{Fs: f.Fs}
+ cByte, err := afs.ReadFile(f.Path)
+ if err != nil {
+ return nil
+ }
+ if len(cByte) > 0 && detectBinary(cByte) {
+ return errors.New("ErrFileCanNotRead")
+ }
+ f.Content = string(cByte)
+ return nil
+ } else {
+ return errors.New("ErrFileCanNotRead")
+ }
+}
+
+func detectBinary(buf []byte) bool {
+ whiteByte := 0
+ n := 1024
+ if len(buf) < 1024 {
+ n = len(buf)
+ }
+ for i := 0; i < n; i++ {
+ if (buf[i] >= 0x20) || buf[i] == 9 || buf[i] == 10 || buf[i] == 13 {
+ whiteByte++
+ } else if buf[i] <= 6 || (buf[i] >= 14 && buf[i] <= 31) {
+ return true
+ }
+ }
+
+ return whiteByte < 1
+}
diff --git a/features/fm/alarm.go b/features/fm/alarm.go
new file mode 100644
index 0000000..8887bf1
--- /dev/null
+++ b/features/fm/alarm.go
@@ -0,0 +1,748 @@
+package fm
+
+import (
+ "database/sql"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "strconv"
+ "strings"
+ "time"
+
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/global"
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+
+ "xorm.io/xorm"
+
+ "github.com/go-resty/resty/v2"
+ _ "github.com/go-sql-driver/mysql"
+ "github.com/gorilla/mux"
+)
+
+const (
+ AlarmStatusClear = 0
+ AlarmStatusActive = 1
+ AlarmStatusClearString = "0"
+ AlarmStatusActiveString = "1"
+)
+
+const (
+ ClearTypeUnclear = 0
+ ClearTypeAutoClear = 1
+ ClearTypeManualClear = 2
+)
+
+const (
+ AckStateUnacked = 0
+ AckStateAcked = 1
+)
+
+const (
+ AlarmTypeCommunicationAlarm = 1
+ AlarmTypeEquipmentAlarm = 2
+ AlarmTypeProcessingFailure = 3
+ AlarmTypeEnvironmentalAlarm = 4
+ AlarmTypeQualityOfServiceAlarm = 5
+)
+
+const (
+ AlarmPerceivedSeverityCritical = 1
+ AlarmPerceivedSeverityMajor = 2
+ AlarmPerceivedSeverityMinor = 3
+ AlarmPerceivedSeverityWarning = 4
+ AlarmPerceivedSeverityEvent = 5
+)
+
+const (
+ AlarmSeqBeginNumber = 1
+)
+
+type Response struct {
+ Data interface{} `json:"data"`
+}
+
+type Alarm struct {
+ AlarmSeq int `json:"alarmSeq"`
+ AlarmId string `json:"alarmId" xorm:"alarm_id"`
+ NeId string `json:"neId"`
+ AlarmCode int `json:"alarmCode"`
+ AlarmTitle string `json:"alarmTitle"`
+ EventTime string `json:"eventTime"`
+ AlarmType string `json:"alarmType"`
+ OrigSeverity string `json:"origSeverity"`
+ PerceivedSeverity string `json:"perceivedSeverity"`
+ PVFlag string `json:"pvFlag" xorm:"pv_flag"`
+ NeName string `json:"neName"`
+ NeType string `json:"neType"`
+ ObjectUid string `json:"objectUid" xorm:"object_uid"`
+ ObjectName string `json:"objectName" xorm:"object_name"`
+ ObjectType string `json:"objectType" xorm:"object_type"`
+ LocationInfo string `json:"locationInfo"`
+ Province string `json:"province"`
+ AlarmStatus int `json:"alarmStatus"`
+ SpecificProblem string `json:"specificProblem"`
+ SpecificProblemID string `json:"specificProblemID" xorm:"specific_problem_id"`
+ AddInfo string `json:"addInfo"`
+
+ AckState int `json:"ackState"`
+ AckTime sql.NullTime `json:"ackTime"`
+ AckUser string `json:"ackUser"`
+ ClearType int `json:"clearType"` // 0: Unclear, 1: Auto clear, 2: Manual clear
+ ClearTime sql.NullTime `json:"clearTime"`
+}
+
+type AlarmLog struct {
+ NeType string `json:"neType" xorm:"ne_type"`
+ NeId string `json:"neId" xorm:"ne_id"`
+ AlarmSeq int `json:"alarmSeq" xorm:"alarm_seq"`
+ AlarmId string `json:"alarmId" xorm:"alarm_id"`
+ AlarmCode int `json:"alarmCode" xorm:"alarm_code"`
+ AlarmStatus int `json:"alarmStatus" xorm:"alarm_status"`
+ EventTime string `json:"eventTime" xorm:"event_time"`
+ // ClearTime sql.NullTime `json:"clearTime" xorm:"clear_time"`
+ LogTime string `json:"logTime" xorm:"-"`
+}
+
+var (
+ // alarm management
+ UriAlarms = config.DefaultUriPrefix + "/faultManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/alarms"
+ UriAlarmsFmt = config.DefaultUriPrefix + "/faultManagement/v1/elementType/%s/objectType/alarms"
+
+ CustomUriAlarms = config.UriPrefix + "/faultManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/alarms"
+ CustomUriAlarmsFmt = config.UriPrefix + "/faultManagement/v1/elementType/%s/objectType/alarms"
+)
+
+var client = resty.New()
+
+func init() {
+ /*
+ client.
+ SetTimeout(10 * time.Second).
+ SetRetryCount(1).
+ SetRetryWaitTime(1 * time.Second).
+ SetRetryMaxWaitTime(2 * time.Second).
+ SetRetryAfter(func(client *resty.Client, resp *resty.Response) (time.Duration, error) {
+ return 0, errors.New("quota exceeded")
+ })
+ */
+ client.
+ SetTimeout(time.Duration(500 * time.Millisecond))
+ // SetRetryCount(1).
+ // SetRetryWaitTime(time.Duration(1 * time.Second)).
+ // SetRetryMaxWaitTime(time.Duration(2 * time.Second))
+ //client.SetTimeout(2 * time.Second)
+}
+
+var xEngine *xorm.Engine
+
+type DatabaseClient struct {
+ dbType string
+ dbUrl string
+ dbConnMaxLifetime time.Duration
+ dbMaxIdleConns int
+ dbMaxOpenConns int
+ IsShowSQL bool
+
+ XEngine *xorm.Engine
+}
+
+var DbClient DatabaseClient
+
+func InitDbClient(dbType, dbUser, dbPassword, dbHost, dbPort, dbName string) error {
+ DbClient.dbUrl = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=true&loc=Local",
+ dbUser, dbPassword, dbHost, dbPort, dbName)
+ DbClient.dbType = dbType
+ DbClient.dbConnMaxLifetime = 0
+ DbClient.dbMaxIdleConns = 0
+ DbClient.dbMaxOpenConns = 0
+ if log.GetLevel() == log.LOG_TRACE {
+ DbClient.IsShowSQL = true
+ }
+ log.Debugf("dbType:%s dbUrl:%s:******@tcp(%s:%s)/%s??charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=true&loc=Local",
+ dbType, dbUser, dbHost, dbPort, dbName)
+
+ var err error
+ DbClient.XEngine, err = xorm.NewEngine(DbClient.dbType, DbClient.dbUrl)
+ if err != nil {
+ log.Error("Failed to connet database:", err)
+ return err
+ }
+ DbClient.XEngine.SetConnMaxLifetime(DbClient.dbConnMaxLifetime)
+ DbClient.XEngine.SetMaxIdleConns(DbClient.dbMaxIdleConns)
+ DbClient.XEngine.SetMaxOpenConns(DbClient.dbMaxOpenConns)
+ if DbClient.IsShowSQL {
+ DbClient.XEngine.ShowSQL(true)
+ }
+ xEngine = DbClient.XEngine
+
+ return nil
+}
+
+func XormConnectDatabase(dbType, dbUser, dbPassword, dbHost, dbPort, dbName string) (*xorm.Engine, error) {
+ sqlStr := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=true&loc=Local",
+ dbUser, dbPassword, dbHost, dbPort, dbName)
+ log.Debugf("dbType:%s Connect to:%s:******@tcp(%s:%s)/%s?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=true&loc=Local",
+ dbType, dbUser, dbHost, dbPort, dbName)
+ var err error
+ xEngine, err = xorm.NewEngine(dbType, sqlStr) //1、Create xorm engine
+ if err != nil {
+ log.Error("Failed to connect database:", err)
+ return nil, err
+ }
+ if log.GetLevel() == log.LOG_TRACE {
+ xEngine.ShowSQL(true)
+ }
+ return xEngine, nil
+}
+
+func IsNeedToAckAlarm(valueJson *dborm.ValueJson, alarm *Alarm) bool {
+ log.Info("IsNeedToAckAlarm processing... ")
+ if valueJson != nil {
+ status, _ := strconv.Atoi(valueJson.AlarmStatus)
+ log.Tracef("alarm.AlarmStatus=%d, alarm.AlarmType=%s, alarm.OrigSeverity=%s", alarm.AlarmStatus, alarm.AlarmType, alarm.OrigSeverity)
+ log.Tracef("status=%d, valueJson.AlarmType=%s, valueJson.OrigSeverity=%s", status, valueJson.AlarmType, valueJson.OrigSeverity)
+
+ if alarm.AlarmStatus == status &&
+ alarm.AlarmType == valueJson.AlarmType &&
+ alarm.OrigSeverity == valueJson.OrigSeverity {
+ return true
+ }
+ }
+
+ return false
+}
+
+func SetAlarmAckInfo(valueJson *dborm.ValueJson, alarm *Alarm) {
+ log.Info("SetAlarmAckInfo processing... ")
+ alarm.AckState = AckStateAcked
+ alarm.AckTime.Valid = true
+ alarm.AckTime.Time = time.Now()
+ alarm.AckUser = valueJson.AckUser
+}
+
+// process alarm post message from NFs
+func PostAlarmFromNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("PostAlarmFromNF processing... ")
+
+ vars := mux.Vars(r)
+ apiVer := vars["apiVersion"]
+ if apiVer != global.ApiVersionV1 {
+ log.Error("Uri api version is invalid. apiVersion:", apiVer)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen))
+ if err != nil {
+ log.Error("io.ReadAll is failed:", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ log.Debug("Request body:", string(body))
+ alarmArray := new([]Alarm)
+
+ err = json.Unmarshal(body, &alarmArray)
+ if err != nil {
+ log.Error("Failed to Unmarshal:", err)
+ services.ResponseBadRequest400InvalidJson(w)
+ return
+ }
+
+ valueJson, err := dborm.XormGetAAConfig()
+ if err != nil {
+ log.Error("Failed to XormGetAAConfig:", err)
+ //services.ResponseInternalServerError500ProcessError(w, err)
+ //return
+ }
+ log.Trace("valueJson:", valueJson)
+ // session := xEngine.NewSession()
+ // defer session.Close()
+ var activeAlarmNum int = 0
+ for _, alarmData := range *alarmArray {
+ log.Debug("alarmData:", alarmData)
+
+ session := xEngine.NewSession()
+ defer session.Close()
+ if alarmData.AlarmStatus == AlarmStatusClear {
+ alarmData.ClearType = ClearTypeAutoClear
+ alarmData.ClearTime.Valid = true
+ tm, _ := time.Parse(time.RFC3339, alarmData.EventTime)
+ log.Debugf("EventTime:%s tm:%d tm-datetime:%s", alarmData.EventTime, tm, tm.Local().Format(time.DateTime))
+ alarmData.ClearTime.Time = tm
+ if IsNeedToAckAlarm(valueJson, &alarmData) {
+ SetAlarmAckInfo(valueJson, &alarmData)
+ affected, err := session.Where("ne_type=? and ne_id=? and alarm_id=? and alarm_status=1", alarmData.NeType, alarmData.NeId, alarmData.AlarmId).
+ Cols("alarm_status", "clear_type", "clear_time", "ack_state", "ack_time", "ack_user").
+ Update(alarmData)
+ if err != nil && affected <= 0 {
+ log.Error("Failed to update alarm data:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ continue
+ }
+ } else {
+ affected, err := session.Where("ne_type=? and ne_id=? and alarm_id=? and alarm_status=1", alarmData.NeType, alarmData.NeId, alarmData.AlarmId).
+ Cols("alarm_status", "clear_type", "clear_time").
+ Update(alarmData)
+ if err != nil && affected <= 0 {
+ log.Error("Failed to update alarm data:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ continue
+ }
+ }
+ log.Trace("alarmData:", alarmData)
+ var currentSeq string
+ var seq int
+ has, err := xEngine.Table("alarm").
+ Where("ne_type=? and ne_id=?", alarmData.NeType, alarmData.NeId).
+ Desc("alarm_seq").
+ Cols("alarm_seq").
+ Limit(1).
+ Get(¤tSeq)
+ if err != nil {
+ log.Error("Failed to get alarm:", err)
+ continue
+ }
+ if has {
+ seq, _ = strconv.Atoi(currentSeq)
+ }
+
+ eventTime := global.GetFmtTimeString(time.RFC3339, alarmData.EventTime, time.DateTime)
+ alarmLog := new(AlarmLog)
+ alarmLog.NeType = alarmData.NeType
+ alarmLog.NeId = alarmData.NeId
+ alarmLog.AlarmSeq = seq
+ alarmLog.AlarmId = alarmData.AlarmId
+ alarmLog.AlarmCode = alarmData.AlarmCode
+ alarmLog.AlarmStatus = alarmData.AlarmStatus
+ alarmLog.EventTime = eventTime
+ log.Debug("alarmLog:", alarmLog)
+
+ affected, err := session.Insert(alarmLog)
+ if err != nil && affected <= 0 {
+ log.Error("Failed to insert alarm_log:", err)
+ }
+
+ // todo: PerceivedSeverity set color
+ var severity string
+ has, err = xEngine.Table("alarm").
+ Where("ne_type=? and ne_id=? and event_time=? and alarm_status=1", alarmData.NeType, alarmData.NeId, alarmData.EventTime).
+ //OrderBy("FIELD(orig_severity, 'Critical', 'Major', 'Minor', 'Warning', 'Event') ASC").
+ Asc("orig_severity").
+ Cols("orig_severity").
+ Limit(1).
+ Get(&severity)
+ if err != nil {
+ log.Error("Failed to get alarm:", err)
+ continue
+ }
+ log.Debugf("neType=%s, neId=%s, eventTime=%s, severity=%s", alarmData.NeType, alarmData.NeId, alarmData.EventTime, severity)
+
+ if has && severity > alarmData.OrigSeverity {
+ // update exist record
+ _, err := session.Table("alarm").
+ Where("ne_type=? and ne_id=? and event_time=? and alarm_status=1", alarmData.NeType, alarmData.NeId, alarmData.EventTime).
+ Update(&Alarm{PerceivedSeverity: severity})
+ if err != nil {
+ log.Error("Failed to update alarm:", err)
+ continue
+ }
+ }
+ session.Commit()
+ // for alarm forward time format
+ alarmData.EventTime = eventTime
+ } else {
+ activeAlarmNum++
+ has, err := xEngine.Table("alarm").
+ Where("alarm_id=? and ne_type=? and ne_id=? and alarm_status=1",
+ alarmData.AlarmId, alarmData.NeType, alarmData.NeId).
+ Exist()
+ if err == nil && has {
+ log.Warn("Exist the same alarm")
+ continue
+ }
+
+ var currentSeq string
+ has, err = xEngine.Table("alarm").
+ Where("ne_type=? and ne_id=?", alarmData.NeType, alarmData.NeId).
+ Desc("alarm_seq").
+ //Desc("event_time").
+ Cols("alarm_seq").
+ Limit(1).
+ Get(¤tSeq)
+ if err != nil {
+ log.Error("Failed to get alarm:", err)
+ continue
+ }
+ log.Debugf("neType=%s, neId=%s, currentSeq=%s activeAlarmNum=%d",
+ alarmData.NeType, alarmData.NeId, currentSeq, activeAlarmNum)
+
+ if has {
+ seq, _ := strconv.Atoi(currentSeq)
+ alarmData.AlarmSeq = seq + 1
+ if alarmData.AlarmSeq > global.MaxInt32Number {
+ alarmData.AlarmSeq = AlarmSeqBeginNumber
+ }
+ } else {
+ alarmData.AlarmSeq = AlarmSeqBeginNumber
+ }
+
+ // todo: PerceivedSeverity set color
+ var severity string
+ has, err = xEngine.Table("alarm").
+ Where("ne_type=? and ne_id=? and event_time=? and alarm_status=1", alarmData.NeType, alarmData.NeId, alarmData.EventTime).
+ //OrderBy("FIELD(orig_severity, 'Critical', 'Major', 'Minor', 'Warning', 'Event') ASC").
+ Asc("orig_severity").
+ Cols("orig_severity").
+ Limit(1).
+ Get(&severity)
+ if err != nil {
+ log.Error("Failed to get alarm:", err)
+ continue
+ }
+ log.Debugf("neType=%s, neId=%s, eventTime=%s, severity=%s", alarmData.NeType, alarmData.NeId, alarmData.EventTime, severity)
+
+ if !has || severity == alarmData.OrigSeverity {
+ alarmData.PerceivedSeverity = alarmData.OrigSeverity
+ } else if severity > alarmData.OrigSeverity {
+ alarmData.PerceivedSeverity = alarmData.OrigSeverity
+ } else {
+ alarmData.PerceivedSeverity = severity
+ // update exist record
+ _, err := session.Table("alarm").
+ Where("ne_type=? and ne_id=? and event_time=? and alarm_status=1", alarmData.NeType, alarmData.NeId, alarmData.EventTime).
+ Update(&Alarm{PerceivedSeverity: alarmData.PerceivedSeverity})
+ if err != nil {
+ log.Error("Failed to update alarm:", err)
+ continue
+ }
+ }
+ eventTime := global.GetFmtTimeString(time.RFC3339, alarmData.EventTime, time.DateTime)
+ alarmData.ObjectUid = alarmData.NeId
+ alarmData.ObjectType = "VNFM"
+ alarmData.EventTime = eventTime
+ if alarmData.LocationInfo == "" {
+ alarmData.LocationInfo = fmt.Sprintf("Host:%s", r.RemoteAddr)
+ }
+ if alarmData.AddInfo == "" {
+ alarmData.AddInfo = fmt.Sprintf("subNeInfo:%s", alarmData.NeType)
+ }
+ if IsNeedToAckAlarm(valueJson, &alarmData) {
+ SetAlarmAckInfo(valueJson, &alarmData)
+ }
+ log.Debug("alarmData:", alarmData)
+ affected, err := session.Insert(alarmData)
+ if err != nil && affected <= 0 {
+ log.Error("Failed to insert alarm data:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ continue
+ }
+ alarmLog := new(AlarmLog)
+ alarmLog.NeType = alarmData.NeType
+ alarmLog.NeId = alarmData.NeId
+ alarmLog.AlarmSeq = alarmData.AlarmSeq
+ alarmLog.AlarmId = alarmData.AlarmId
+ alarmLog.AlarmCode = alarmData.AlarmCode
+ alarmLog.AlarmStatus = alarmData.AlarmStatus
+ alarmLog.EventTime = eventTime
+ log.Trace("alarmLog:", alarmLog)
+
+ affected, err = session.Insert(alarmLog)
+ if err != nil && affected <= 0 {
+ log.Error("Failed to insert alarm_log:", err)
+ }
+ session.Commit()
+ }
+ if config.GetYamlConfig().Alarm.ForwardAlarm {
+ if err = AlarmEmailForward(&alarmData); err != nil {
+ log.Error("Failed to AlarmEmailForward:", err)
+ }
+ if err = AlarmForwardBySMSC(&alarmData); err != nil {
+ log.Error("Failed to AlarmForwardBySMSC:", err)
+ }
+ }
+ }
+
+ services.ResponseStatusOK200Null(w)
+}
+
+// process alarm get from NFs
+func GetAlarmFromNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("GetAlarmFromNF processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+ neTypeLower := strings.ToLower(neType)
+
+ // Get alarms from OMC return 204
+ if neTypeLower == strings.ToLower(config.GetYamlConfig().OMC.NeType) {
+ log.Infof("Return no content alarms from %s", neType)
+ services.ResponseStatusOK204NoContent(w)
+ return
+ }
+
+ //var neInfo *dborm.NeInfo
+ var nes []dborm.NeInfo
+ _, err = dborm.XormGetAllNeInfo(&nes)
+ if err != nil {
+ log.Error("Failed to get all ne info:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ for _, ne := range nes {
+ hostUri := fmt.Sprintf("http://%s:%v", ne.Ip, ne.Port)
+ apiUri := fmt.Sprintf(UriAlarmsFmt, strings.ToLower(ne.NeType))
+ requestURI2NF := fmt.Sprintf("%s%s", hostUri, apiUri)
+ log.Debug("requestURI2NF: Get ", requestURI2NF)
+ // client := resty.New()
+ response, 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("Failed to Get:", err)
+ //services.ResponseInternalServerError500ProcessError(w, err)
+ continue
+ }
+
+ alarmArray := new([]Alarm)
+ switch response.StatusCode() {
+ case http.StatusOK, http.StatusCreated, http.StatusNoContent, http.StatusAccepted:
+ body := response.Body()
+ if len(body) == 0 || body == nil {
+ log.Infof("Empty alarm body from neType=%s, neId=%s", ne.NeType, ne.NeId)
+ //services.ResponseInternalServerError500ProcessError(w, err)
+ continue
+ }
+ //log.Debug("Request body:", string(body))
+
+ err = json.Unmarshal(body, &alarmArray)
+ if err != nil {
+ log.Error("Failed to Unmarshal:", err)
+ //services.ResponseInternalServerError500ProcessError(w, err)
+ continue
+ }
+ default:
+ log.Error("Failed to get alarms:", response.Status)
+ continue
+ }
+
+ valueJson, err := dborm.XormGetAAConfig()
+ if err != nil {
+ log.Error("Failed to XormGetAAConfig:", err)
+ //services.ResponseInternalServerError500ProcessError(w, err)
+ continue
+ }
+ if alarmArray == nil {
+ log.Infof("Not found sync alarms, neType=%s, neId=%s", ne.NeType, ne.NeId)
+ //services.ResponseInternalServerError500ProcessError(w, err)
+ continue
+ }
+ // session := xEngine.NewSession()
+ // defer session.Close()
+ var activeAlarmNum int = 0
+ for _, alarmData := range *alarmArray {
+ log.Debug("alarmData:", alarmData)
+
+ session := xEngine.NewSession()
+ defer session.Close()
+ // todo: clear alarm ....
+ if alarmData.AlarmStatus == AlarmStatusClear {
+ exist, err := session.Table("alarm").
+ Where("ne_type=? and ne_id=? and alarm_id=? and alarm_status=1", alarmData.NeType, alarmData.NeId, alarmData.AlarmId).
+ Exist()
+ if err == nil || !exist {
+ log.Infof("Not found active alarm: ne_id=%s, alarm_id=%s", alarmData.NeId, alarmData.AlarmId)
+ continue
+ }
+ alarmData.ClearType = ClearTypeAutoClear
+ alarmData.ClearTime.Valid = true
+ tm, _ := time.Parse(time.RFC3339, alarmData.EventTime)
+ log.Debugf("EventTime:%s tm:%d tm-datetime:%s", alarmData.EventTime, tm, tm.Local().Format(time.DateTime))
+ alarmData.ClearTime.Time = tm
+ if IsNeedToAckAlarm(valueJson, &alarmData) {
+ SetAlarmAckInfo(valueJson, &alarmData)
+ log.Debug("alarmData:", alarmData)
+ affected, err := session.
+ Where("ne_type=? and ne_id=? and alarm_id=? and alarm_status=1", alarmData.NeType, alarmData.NeId, alarmData.AlarmId).
+ Cols("alarm_status", "clear_type", "clear_time", "ack_state", "ack_time", "ack_user").
+ Update(alarmData)
+ if err != nil && affected <= 0 {
+ log.Error("Failed to update alarm data:", err)
+ //services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ continue
+ }
+ } else {
+ affected, err := session.
+ Where("ne_type=? and ne_id=? and alarm_id=? and alarm_status=1", alarmData.NeType, alarmData.NeId, alarmData.AlarmId).
+ Cols("alarm_status", "clear_type", "clear_time").
+ Update(alarmData)
+ if err != nil && affected <= 0 {
+ log.Error("Failed to update alarm data:", err)
+ //services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ continue
+ }
+ }
+
+ eventTime := global.GetFmtTimeString(time.RFC3339, alarmData.EventTime, time.DateTime)
+ alarmLog := new(AlarmLog)
+ alarmLog.NeType = alarmData.NeType
+ alarmLog.NeId = alarmData.NeId
+ alarmLog.AlarmSeq = alarmData.AlarmSeq
+ alarmLog.AlarmId = alarmData.AlarmId
+ alarmLog.AlarmCode = alarmData.AlarmCode
+ alarmLog.AlarmStatus = alarmData.AlarmStatus
+ alarmLog.EventTime = eventTime
+ log.Debug("alarmLog:", alarmLog)
+
+ affected, err := session.Insert(alarmLog)
+ if err != nil && affected <= 0 {
+ log.Error("Failed to insert alarm_log:", err)
+ }
+
+ // todo: PerceivedSeverity set color
+ var severity string
+ has, err := xEngine.Table("alarm").
+ Where("ne_type=? and ne_id=? and event_time=? and alarm_status=1", alarmData.NeType, alarmData.NeId, alarmData.EventTime).
+ //OrderBy("FIELD(orig_severity, 'Critical', 'Major', 'Minor', 'Warning', 'Event') ASC").
+ Asc("orig_severity").
+ Cols("orig_severity").
+ Limit(1).
+ Get(&severity)
+ if err != nil {
+ log.Error("Failed to get alarm:", err)
+ continue
+ }
+ log.Debugf("neType=%s, neId=%s, eventTime=%s, severity=%s", alarmData.NeType, alarmData.NeId, alarmData.EventTime, severity)
+
+ if has && severity > alarmData.OrigSeverity {
+ // update exist record
+ _, err := session.Table("alarm").
+ Where("ne_type=? and ne_id=? and event_time=?", alarmData.NeType, alarmData.NeId, alarmData.EventTime).
+ Update(&Alarm{PerceivedSeverity: severity})
+ if err != nil {
+ log.Error("Failed to update alarm:", err)
+ continue
+ }
+ }
+ session.Commit()
+ // for alarm forward time format
+ alarmData.EventTime = eventTime
+ } else {
+ activeAlarmNum++
+ has, err := xEngine.Table("alarm").
+ Where("alarm_id=? and ne_type=? and ne_id=? and alarm_status=1",
+ alarmData.AlarmId, alarmData.NeType, alarmData.NeId).
+ Exist()
+ if err == nil && has {
+ log.Warn("Exist the same alarm")
+ continue
+ }
+
+ var currentSeq string
+ has, err = xEngine.Table("alarm").
+ Where("ne_type=? and ne_id=?", alarmData.NeType, alarmData.NeId).
+ Desc("alarm_seq").
+ //Desc("event_time").
+ Cols("alarm_seq").
+ Limit(1).
+ Get(¤tSeq)
+ if err != nil {
+ log.Error("Failed to get alarm:", err)
+ continue
+ }
+ log.Debugf("neType=%s, neId=%s, currentSeq=%s, activeAlarmNum=%d",
+ alarmData.NeType, alarmData.NeId, currentSeq, activeAlarmNum)
+
+ if has {
+ seq, _ := strconv.Atoi(currentSeq)
+ alarmData.AlarmSeq = seq + 1
+ if alarmData.AlarmSeq > global.MaxInt32Number {
+ alarmData.AlarmSeq = AlarmSeqBeginNumber
+ }
+ } else {
+ alarmData.AlarmSeq = AlarmSeqBeginNumber
+ }
+
+ // todo: PerceivedSeverity set color
+ var severity string
+ has, err = xEngine.Table("alarm").
+ Where("ne_type=? and ne_id=? and event_time=? and alarm_status=1", alarmData.NeType, alarmData.NeId, alarmData.EventTime).
+ //OrderBy("FIELD(orig_severity, 'Critical', 'Major', 'Minor', 'Warning', 'Event') ASC").
+ Asc("orig_severity").
+ Cols("orig_severity").
+ Limit(1).
+ Get(&severity)
+ if err != nil {
+ log.Error("Failed to get alarm:", err)
+ continue
+ }
+ log.Debugf("neType=%s, neId=%s, eventTime=%s, severity=%s", alarmData.NeType, alarmData.NeId, alarmData.EventTime, severity)
+
+ if !has || severity == alarmData.OrigSeverity {
+ alarmData.PerceivedSeverity = alarmData.OrigSeverity
+ } else if severity > alarmData.OrigSeverity {
+ alarmData.PerceivedSeverity = alarmData.OrigSeverity
+ } else {
+ alarmData.PerceivedSeverity = severity
+ // update exist record
+ _, err := session.Table("alarm").
+ Where("ne_type=? and ne_id=? and event_time=?", alarmData.NeType, alarmData.NeId, alarmData.EventTime).
+ Update(&Alarm{PerceivedSeverity: alarmData.PerceivedSeverity})
+ if err != nil {
+ log.Error("Failed to update alarm:", err)
+ continue
+ }
+ }
+
+ alarmData.ObjectUid = alarmData.NeId
+ alarmData.ObjectType = "VNFM"
+ alarmData.EventTime = global.GetFmtTimeString(time.RFC3339, alarmData.EventTime, time.DateTime)
+ if IsNeedToAckAlarm(valueJson, &alarmData) {
+ SetAlarmAckInfo(valueJson, &alarmData)
+ }
+ log.Trace("alarmData:", alarmData)
+ affected, err := session.Insert(alarmData)
+ if err == nil && affected > 0 {
+ alarmLog := new(AlarmLog)
+ alarmLog.NeType = alarmData.NeType
+ alarmLog.NeId = alarmData.NeId
+ alarmLog.AlarmSeq = alarmData.AlarmSeq
+ alarmLog.AlarmId = alarmData.AlarmId
+ alarmLog.AlarmCode = alarmData.AlarmCode
+ alarmLog.AlarmStatus = alarmData.AlarmStatus
+ alarmLog.EventTime = global.GetFmtTimeString(time.RFC3339, alarmData.EventTime, time.DateTime)
+ log.Debug("alarmLog:", alarmLog)
+ affected, err = session.Insert(alarmLog)
+ if err != nil && affected <= 0 {
+ log.Error("Failed to insert data:", err)
+ //services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ continue
+ }
+ session.Commit()
+ if config.GetYamlConfig().Alarm.ForwardAlarm {
+ if err = AlarmEmailForward(&alarmData); err != nil {
+ log.Error("Failed to AlarmEmailForward:", err)
+ }
+ if err = AlarmForwardBySMSC(&alarmData); err != nil {
+ log.Error("Failed to AlarmForwardBySMSC:", err)
+ }
+ }
+ }
+ log.Warn("Failed to insert alarm data:", err)
+ }
+ }
+ }
+ services.ResponseStatusOK204NoContent(w)
+}
diff --git a/features/fm/email.go b/features/fm/email.go
new file mode 100644
index 0000000..dd680d8
--- /dev/null
+++ b/features/fm/email.go
@@ -0,0 +1,129 @@
+package fm
+
+import (
+ "crypto/tls"
+ "errors"
+ "fmt"
+ "strings"
+
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/log"
+ "nms_nbi/restagent/config"
+
+ "gopkg.in/gomail.v2"
+)
+
+func AlarmEmailForward(alarmData *Alarm) error {
+ log.Info("AlarmEmailForward processing... ")
+
+ message := fmt.Sprintf(`
+
Alarm information
+ Sequence: %d
+ NE name: %s
+ Title: %s
+ Severity: %s
+ Event Time: %s
+ Alarm Status: %d
+ Automatic sent by OMC, please do not reply!
+ `, alarmData.AlarmSeq, alarmData.NeName, alarmData.AlarmTitle, alarmData.OrigSeverity, alarmData.EventTime, alarmData.AlarmStatus)
+
+ // message := fmt.Sprintf(`
+ // Alarm information
+
+ // Sequence: %d
+ // NE name: %s
+ // Title: %s
+ // Severity: %s
+ // Event Time: %s
+
+ // Automatic send by OMC, don't reply!
+ // `, alarmData.AlarmSeq, alarmData.NeName, alarmData.AlarmTitle, alarmData.OrigSeverity, alarmData.EventTime)
+
+ // QQ 邮箱:
+ // SMTP 服务器地址:smtp.qq.com(SSL协议端口:465/994 | 非SSL协议端口:25)
+ // 163 邮箱:
+ // SMTP 服务器地址:smtp.163.com(端口:25)
+ // host := "mail.agrandtech.com"
+ // port := 25
+ // userName := "smtpext@agrandtech.com"
+ // password := "1000smtp@omc!"
+
+ host := config.GetYamlConfig().Alarm.Email.Smtp
+ port := int(config.GetYamlConfig().Alarm.Email.Port)
+ userName := config.GetYamlConfig().Alarm.Email.User
+ password := config.GetYamlConfig().Alarm.Email.Password
+
+ m := gomail.NewMessage()
+ m.SetHeader("From", userName) // 发件人
+ //m.SetHeader("From", "alias"+"<"+"aliastest"+">") // 增加发件人别名
+
+ emails, err := dborm.XormGetAlarmForward("Email")
+ if err != nil {
+ log.Error("Failed to XormGetAlarmForward:", err)
+ return err
+ } else if emails == nil || len(*emails) == 0 {
+ err := errors.New("not found forward email list")
+ log.Error(err)
+ return err
+ }
+
+ forwardLog := &dborm.AlarmForwardLog{
+ NeType: alarmData.NeType,
+ NeID: alarmData.NeId,
+ AlarmID: alarmData.AlarmId,
+ AlarmTitle: alarmData.AlarmTitle,
+ AlarmSeq: alarmData.AlarmSeq,
+ EventTime: alarmData.EventTime,
+ ToUser: strings.Join(*emails, ","),
+ }
+
+ m.SetHeader("To", *emails...) // 收件人,可以多个收件人,但必须使用相同的 SMTP 连接
+ //m.SetHeader("To", strings.Join(*emails, " ")) // 收件人,可以多个收件人,但必须使用相同的 SMTP 连接
+ //m.SetHeader("To", "zhangshuzhong@agrandtech.com", "simonzhangsz@outlook.com") // 收件人,可以多个收件人,但必须使用相同的 SMTP 连接
+ //m.SetHeader("Cc", "******@qq.com") // 抄送,可以多个
+ //m.SetHeader("Bcc", "******@qq.com") // 暗送,可以多个
+ m.SetHeader("Subject", "Alarm from OMC!") // 邮件主题
+
+ // text/html 的意思是将文件的 content-type 设置为 text/html 的形式,浏览器在获取到这种文件时会自动调用html的解析器对文件进行相应的处理。
+ // 可以通过 text/html 处理文本格式进行特殊处理,如换行、缩进、加粗等等
+ //m.SetBody("text/html", fmt.Sprintf(message, *alarm))
+ m.SetBody("text/html", message)
+
+ // text/plain的意思是将文件设置为纯文本的形式,浏览器在获取到这种文件时并不会对其进行处理
+ //m.SetBody("text/plain", message)
+ // m.Attach("test.sh") // 附件文件,可以是文件,照片,视频等等
+ // m.Attach("lolcatVideo.mp4") // 视频
+ // m.Attach("lolcat.jpg") // 照片
+
+ d := gomail.NewDialer(
+ host,
+ port,
+ userName,
+ password,
+ )
+ // 关闭SSL协议认证
+ d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
+ if !config.GetYamlConfig().Alarm.Email.TlsSkipVerify {
+ // 打开SSL协议认证
+ d.TLSConfig = &tls.Config{InsecureSkipVerify: false}
+ }
+
+ if err := d.DialAndSend(m); err != nil {
+ operResult := fmt.Sprintf("Failed to DialAndSend:%v", err)
+ log.Error(operResult)
+ forwardLog.OperResult = operResult
+ affected, err := dborm.XormInsertAlarmForwardLog(forwardLog)
+ if err != nil && affected <= 0 {
+ log.Error("Failed to insert data:", err)
+ }
+ return err
+ }
+
+ forwardLog.OperResult = "Email sent successfully!"
+ affected, err := dborm.XormInsertAlarmForwardLog(forwardLog)
+ if err != nil && affected <= 0 {
+ log.Error("Failed to insert data:", err)
+ return err
+ }
+ return nil
+}
diff --git a/features/fm/smsforward.go b/features/fm/smsforward.go
new file mode 100644
index 0000000..e192fff
--- /dev/null
+++ b/features/fm/smsforward.go
@@ -0,0 +1,249 @@
+package fm
+
+import (
+ "errors"
+ "fmt"
+ "net/http"
+ "net/url"
+ "time"
+
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/log"
+ "nms_nbi/restagent/config"
+
+ "github.com/linxGnu/gosmpp"
+ "github.com/linxGnu/gosmpp/data"
+ "github.com/linxGnu/gosmpp/pdu"
+)
+
+func AlarmForwardBySMS(alarmData *Alarm) error {
+ log.Info("AlarmForwardBySMS processing... ")
+
+ SMSFforwardconfig := config.GetYamlConfig().Alarm.SMS
+ // 阿里云短信API的请求地址
+ apiURL := SMSFforwardconfig.ApiURL
+
+ // 阿里云短信API的AccessKey ID和AccessKey Secret
+ //accessKeyID := SMSFforwardconfig.AccessKeyID
+ accessKeySecret := SMSFforwardconfig.AccessKeySecret
+
+ toUsers, err := dborm.XormGetAlarmForward("SMS")
+ if err != nil {
+ log.Error("Failed to XormGetAlarmForward:", err)
+ return err
+ } else if toUsers == nil {
+ err := errors.New("not found forward phone number")
+ log.Error(err)
+ return err
+ }
+
+ for _, toUser := range *toUsers {
+ // 短信相关参数
+ params := url.Values{}
+ params.Set("PhoneNumbers", toUser)
+ params.Set("SignName", SMSFforwardconfig.SignName)
+ params.Set("TemplateCode", SMSFforwardconfig.TemplateCode)
+ params.Set("TemplateParam", `{"message":"alarm"}`)
+
+ // 构建请求URL
+ reqURL := apiURL + "?Action=SendSms&" + params.Encode()
+
+ // 创建HTTP请求
+ req, err := http.NewRequest("GET", reqURL, nil)
+ if err != nil {
+ log.Error("Failed to create request:", err)
+ return err
+ }
+
+ // 添加请求头部
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+ req.Header.Set("Authorization", "APPCODE "+accessKeySecret)
+
+ forwardLog := &dborm.AlarmForwardLog{
+ NeType: alarmData.NeType,
+ NeID: alarmData.NeId,
+ AlarmID: alarmData.AlarmId,
+ AlarmTitle: alarmData.AlarmTitle,
+ AlarmSeq: alarmData.AlarmSeq,
+ EventTime: alarmData.EventTime,
+ ToUser: toUser,
+ }
+ // 发送请求
+ client := &http.Client{}
+ resp, err := client.Do(req)
+ if err != nil {
+ operResult := fmt.Sprintf("Failed to send request:%v", err)
+ log.Error(operResult)
+ forwardLog.OperResult = operResult
+ affected, err := dborm.XormInsertAlarmForwardLog(forwardLog)
+ if err != nil && affected <= 0 {
+ log.Error("Failed to insert data:", err)
+ }
+ continue
+ }
+ defer resp.Body.Close()
+
+ // 解析响应
+ if resp.StatusCode == http.StatusOK {
+ operResult := "SMS sent successfully!"
+ log.Info(operResult)
+ forwardLog.OperResult = operResult
+ affected, err := dborm.XormInsertAlarmForwardLog(forwardLog)
+ if err != nil && affected <= 0 {
+ log.Error("Failed to insert data:", err)
+ continue
+ }
+ } else {
+ operResult := fmt.Sprintf("Failed to send SMS, StatusCode=%d", resp.StatusCode)
+ log.Error(operResult)
+ forwardLog.OperResult = operResult
+ affected, err := dborm.XormInsertAlarmForwardLog(forwardLog)
+ if err != nil && affected <= 0 {
+ log.Error("Failed to insert data:", err)
+ continue
+ }
+ }
+ }
+ return nil
+}
+
+func AlarmForwardBySMPP(alarmData *Alarm) error {
+ log.Info("AlarmForwardBySMPP processing... ")
+
+ auth := gosmpp.Auth{
+ SMSC: config.GetYamlConfig().Alarm.SMSC.Addr,
+ SystemID: config.GetYamlConfig().Alarm.SMSC.UserName,
+ Password: config.GetYamlConfig().Alarm.SMSC.Password,
+ SystemType: "",
+ }
+
+ // conn, err := gosmpp.NonTLSDialer(auth.SMSC)
+ // connection := gosmpp.NewConnection(conn)
+
+ trans, err := gosmpp.NewSession(
+ gosmpp.TXConnector(gosmpp.NonTLSDialer, auth),
+ gosmpp.Settings{
+ ReadTimeout: 2 * time.Second,
+
+ OnPDU: func(p pdu.PDU, _ bool) {
+ log.Debug("%+v", p)
+ },
+
+ OnSubmitError: func(_ pdu.PDU, err error) {
+ log.Error(err)
+ },
+
+ OnRebindingError: func(err error) {
+ log.Error(err)
+ },
+
+ OnClosed: func(state gosmpp.State) {
+ log.Error(state)
+ },
+ }, -1)
+ if err != nil {
+ log.Error(err)
+ return err
+ }
+ defer func() {
+ _ = trans.Close()
+ }()
+
+ toUsers, err := dborm.XormGetAlarmForward("SMS")
+ if err != nil {
+ log.Error("Failed to XormGetAlarmForward:", err)
+ return err
+ } else if toUsers == nil {
+ err := errors.New("not found forward phone number")
+ log.Error(err)
+ return err
+ }
+ // sending SMS(s)
+ for _, toUser := range *toUsers {
+ forwardLog := &dborm.AlarmForwardLog{
+ NeType: alarmData.NeType,
+ NeID: alarmData.NeId,
+ AlarmID: alarmData.AlarmId,
+ AlarmTitle: alarmData.AlarmTitle,
+ AlarmSeq: alarmData.AlarmSeq,
+ EventTime: alarmData.EventTime,
+ ToUser: toUser,
+ }
+ message := alarmData.AlarmTitle + "from" + alarmData.NeType + alarmData.NeId + "at" + alarmData.EventTime
+ if err = trans.Transceiver().Submit(newSubmitSM(toUser, message)); err != nil {
+ operResult := fmt.Sprintf("Failed to submit short message:%v", err)
+ log.Error(operResult)
+ forwardLog.OperResult = operResult
+ } else {
+ operResult := "SMS sent successfully!"
+ log.Trace(operResult)
+ forwardLog.OperResult = operResult
+ }
+
+ affected, err := dborm.XormInsertAlarmForwardLog(forwardLog)
+ if err != nil && affected <= 0 {
+ log.Error("Failed to insert data:", err)
+ continue
+ }
+ }
+ return nil
+}
+
+func handlePDU() func(pdu.PDU) (pdu.PDU, bool) {
+ return func(p pdu.PDU) (pdu.PDU, bool) {
+ switch pd := p.(type) {
+ case *pdu.Unbind:
+ log.Trace("Unbind Received")
+ return pd.GetResponse(), true
+
+ case *pdu.UnbindResp:
+ log.Trace("UnbindResp Received")
+
+ case *pdu.SubmitSMResp:
+ log.Trace("SubmitSMResp Received")
+
+ case *pdu.GenericNack:
+ log.Trace("GenericNack Received")
+
+ case *pdu.EnquireLinkResp:
+ fmt.Println("EnquireLinkResp Received")
+
+ case *pdu.EnquireLink:
+ log.Trace("EnquireLink Received")
+ return pd.GetResponse(), false
+
+ case *pdu.DataSM:
+ log.Trace("DataSM receiver")
+ return pd.GetResponse(), false
+
+ case *pdu.DeliverSM:
+ log.Trace("DeliverSM receiver")
+ return pd.GetResponse(), false
+ }
+ return nil, false
+ }
+}
+
+func newSubmitSM(phoneNumber string, message string) *pdu.SubmitSM {
+ // build up submitSM
+ srcAddr := pdu.NewAddress()
+ srcAddr.SetTon(5)
+ srcAddr.SetNpi(0)
+ _ = srcAddr.SetAddress("alarm notification:")
+
+ destAddr := pdu.NewAddress()
+ destAddr.SetTon(1)
+ destAddr.SetNpi(1)
+ _ = destAddr.SetAddress(phoneNumber)
+
+ submitSM := pdu.NewSubmitSM().(*pdu.SubmitSM)
+ submitSM.SourceAddr = srcAddr
+ submitSM.DestAddr = destAddr
+ _ = submitSM.Message.SetMessageWithEncoding(message, data.UCS2)
+ submitSM.ProtocolID = 0
+ submitSM.RegisteredDelivery = 1
+ submitSM.ReplaceIfPresentFlag = 0
+ submitSM.EsmClass = 0
+
+ return submitSM
+}
diff --git a/features/fm/ucpcli.go b/features/fm/ucpcli.go
new file mode 100644
index 0000000..2a4c349
--- /dev/null
+++ b/features/fm/ucpcli.go
@@ -0,0 +1,61 @@
+package fm
+
+import (
+ "fmt"
+ "strings"
+
+ "nms_nbi/lib/log"
+ "nms_nbi/restagent/config"
+
+ "github.com/chzyer/readline"
+ "github.com/go-gsm/ucp"
+)
+
+func AlarmForwardBySMSC(alarmData *Alarm) error {
+ opt := &ucp.Options{
+ Addr: config.GetYamlConfig().Alarm.SMSC.Addr,
+ User: config.GetYamlConfig().Alarm.SMSC.UserName,
+ Password: config.GetYamlConfig().Alarm.SMSC.Password,
+ AccessCode: "",
+ }
+
+ client := ucp.New(opt)
+ if err := client.Connect(); err != nil {
+ log.Error("Failed to connect:", err)
+ return err
+ }
+ defer client.Close()
+
+ reader, _ := readline.New(">>> ")
+ defer reader.Close()
+
+ for {
+ fmt.Print(">>> ")
+ lines, _ := reader.Readline()
+ fields := strings.Fields(lines)
+
+ if len(fields) == 1 {
+ // exit CLI
+ if fields[0] == "exit" {
+ return nil
+ }
+ // display help message
+ if fields[0] == "help" {
+ log.Trace("\n\tSend a 'message' to 'receiver' with a 'sender' mask\n\t>>> sender receiver message\n\n\tExit the cli\n\t>>> exit\n")
+ }
+ }
+
+ // sender receiver message...
+ if len(fields) >= 3 {
+ sender := fields[0]
+ receiver := fields[1]
+ message := strings.Join(fields[2:], " ")
+ ids, err := client.Send(sender, receiver, message)
+ if err != nil {
+ log.Error(err)
+ } else {
+ log.Debug("%v", ids)
+ }
+ }
+ }
+}
diff --git a/features/handle/handle.go b/features/handle/handle.go
new file mode 100644
index 0000000..5fc9445
--- /dev/null
+++ b/features/handle/handle.go
@@ -0,0 +1,586 @@
+package handle
+
+import (
+ "bytes"
+ "encoding/json"
+ "io"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "nms_nbi/lib/dborm"
+
+ "github.com/gorilla/mux"
+ g "github.com/gosnmp/gosnmp"
+
+ "nms_nbi/lib/global"
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/oauth"
+ "nms_nbi/lib/services"
+ "nms_nbi/lib/session"
+ "nms_nbi/restagent/config"
+)
+
+var TodoList []stTodo
+
+type stTodo struct {
+ No string
+ Item string
+ Value string
+}
+
+type ErrorOAuthResponse struct {
+ Error map[string]interface{}
+}
+
+type FailOAuthResponse struct {
+ Error struct {
+ ErrorCode string
+ ErrorInfo string
+ }
+}
+
+type ApiResponse struct {
+ ResultCode string
+ ResultMessage interface{}
+}
+
+var globalSession = session.NewSessManager("restagent")
+
+func init() {
+ conf := config.GetYamlConfig()
+ // Default is a pointer to a GoSNMP struct that contains sensible defaults
+ // eg port 161, community public, etc
+ g.Default.Target = conf.NE.Addr
+ g.Default.Port = conf.NE.Port
+ err := g.Default.Connect()
+ if err != nil {
+ log.Fatalf("Connect() err: %v", err)
+ }
+ //defer g.Default.Conn.Close()
+}
+
+/*
+func IsValidOAuthInfo(oAuthBody OAuthBody) bool {
+ log.Debug("IsValidOAuthInfo processing... ")
+
+ conf := config.GetYamlConfig()
+ for _, o := range conf.Auth {
+ if oAuthBody.GrantType == o.Type && oAuthBody.UserName == o.User && oAuthBody.Value == o.Password {
+ return true
+ }
+ }
+
+ return false
+}
+
+func IsWrongOAuthInfo(oAuthBody OAuthBody) bool {
+ log.Debug("IsWrongOAuthInfo processing... ")
+
+ if oAuthBody.GrantType == "" || strings.ToLower(oAuthBody.GrantType) != "password" || oAuthBody.UserName == "" || oAuthBody.Value == "" {
+ return true
+ }
+
+ return false
+}
+*/
+
+func IsValidOAuthUri(r *http.Request) bool {
+ vars := mux.Vars(r)
+ Uri := vars["apiCategory"] + "/" + vars["apiVersion"] // 获取Uri
+ if Uri != "securityManagement/v1" {
+ return false
+ }
+
+ return true
+}
+
+func IsVallidContentType(r *http.Request) bool {
+ log.Debug("IsVallidContentType processing ...")
+
+ ctype := r.Header["Content-Type"]
+ if len(ctype) != 0 && !strings.Contains(ctype[0], "application/json") {
+ return false
+ }
+
+ return true
+}
+
+func LoginFromOMC(w http.ResponseWriter, r *http.Request) {
+ log.Debug("LoginFromOMC processing... ")
+
+ body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen)) //io.LimitReader限制大小
+ if err != nil {
+ log.Debug(err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ // check media type(content type) only support "application/json"
+ /* if !IsVallidContentType(r) {
+ log.Debug("Invalid Content-Type")
+ services.ResponseUnsupportedMediaType415(w)
+ return
+ }
+
+ // check extend uri, response 404
+ if !IsValidOAuthUri(r) {
+ log.Debug("Uri is invalid")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ */
+ // Error process ....
+ // response 400-7
+ if !json.Valid([]byte(body)) {
+ log.Debug("Invalid Json Format")
+ services.ResponseBadRequest400InvalidJson(w)
+ return
+ }
+
+ var oAuthBody oauth.OAuthBody
+ _ = json.Unmarshal(body, &oAuthBody) //转为json
+ log.Debug("body:", string(body), "oAuthBody:", oAuthBody)
+
+ defer r.Body.Close()
+ // response 400-5
+ if oauth.IsWrongOAuthInfo(oAuthBody) {
+ log.Debug("Wrong parameter value")
+ services.ResponseBadRequest400WrongParamValue(w)
+ return
+ }
+ /*
+ if oauth.IsValidOAuthInfo(oAuthBody) {
+ plist := config.GetPermissionFromConfig(oAuthBody.UserName, oAuthBody.GrantType)
+ log.Debug("Permission list:", plist)
+
+ token := globalSession.NewSession(w, r, plist)
+ services.ResponseStatusOK200Login(w, token)
+ } else {
+ // response 400-4
+ log.Debug("Authentication failed, mismatch user or password")
+
+ services.ResponseBadRequest400IncorrectLogin(w)
+ }
+ */
+ validUser, changePassword, _ := dborm.XormCheckLoginUser(oAuthBody.UserName,
+ oAuthBody.Value, config.GetYamlConfig().Auth.Crypt)
+ if validUser {
+ // plist := config.GetPermissionFromConfig(oAuthBody.UserName, oAuthBody.GrantType)
+ plist := []bool{true, true, true, true}
+ log.Debug("Permission list:", plist)
+
+ token := globalSession.NewSession(w, r, plist)
+ services.ResponseStatusOK200Login(w, token, changePassword)
+ } else {
+ // response 400-4
+ log.Error("Authentication failed, mismatch user or password")
+
+ services.ResponseBadRequest400IncorrectLogin(w)
+ }
+ return
+}
+
+func LogoutFromOMC(w http.ResponseWriter, r *http.Request) {
+ log.Debug("LogoutFromOMC processing... ")
+
+ // check media type(content type) only support "application/json"
+ if !IsVallidContentType(r) {
+ log.Debug("Invalid Content-Type")
+ services.ResponseUnsupportedMediaType415(w)
+ return
+ }
+
+ // check extend uri, response 404
+ if !IsValidOAuthUri(r) {
+ log.Debug("Uri is invalid")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ // error processing ...
+ // 401-1 response
+ token, ret := globalSession.IsCarriedToken(r)
+ if ret == false {
+ log.Debug("AccessToken is not carried")
+ services.ResponseUnauthorized401AccessTokenNotCarried(w)
+ return
+ }
+
+ // 401-2 response
+ if globalSession.IsValidToken(token) == false {
+ log.Debug("AccessToken fails or does not exist")
+ services.ResponseUnauthorized401AccessTokenNotExist(w)
+ return
+ }
+
+ globalSession.EndSession(w, r)
+ services.ResponseStatusOK200Null(w)
+ return
+}
+
+func HandshakeFromOMC(w http.ResponseWriter, r *http.Request) {
+ log.Debug("HandshakeFromOMC processing... ")
+
+ // check media type(content type) only support "application/json"
+ if !IsVallidContentType(r) {
+ log.Debug("Invalid Content-Type")
+ services.ResponseUnsupportedMediaType415(w)
+ return
+ }
+
+ // check extend uri, response 404
+ if !IsValidOAuthUri(r) {
+ log.Debug("Uri is invalid")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ // error processing ...
+ // 401-1 response
+ token, ret := globalSession.IsCarriedToken(r)
+ if ret == false {
+ log.Debug("AccessToken is not carried")
+ services.ResponseUnauthorized401AccessTokenNotCarried(w)
+ return
+ }
+
+ if !globalSession.ShakeSession(token) {
+ // 401-2 response
+ log.Debug("AccessToken fails or does not exist")
+ services.ResponseUnauthorized401AccessTokenNotExist(w)
+ return
+ }
+
+ // 200 response
+ services.ResponseStatusOK200Null(w)
+ return
+}
+
+var (
+ MAX_RMUID_NUM = config.GetRmUIDMaxNumFromConfig()
+ MAX_ALARMID_NUM = config.GetAlarmIDMaxNumFromConfig()
+ MAX_PMUID_NUM = config.GetPmIDMaxNumFromConfig()
+ MAX_SUBID_NUM = config.GetSubIDMaxNumFromConfig()
+ MAX_URI_LEN = config.GetUriMaxLenFromConfig()
+ RMUID_REGEXP = config.GetRmUIDRegexpFromConfig()
+)
+
+func CheckParameterName(r *http.Request) []string {
+ var errorParams []string
+ vars := r.URL.Query()
+ for k, v := range vars {
+ log.Debug("vars:", k, v)
+ if k != "rmUIDs" && k != "fields" {
+ errorParams = append(errorParams, k)
+ }
+ }
+
+ return errorParams
+}
+
+func GetRmUIDArr(r *http.Request) []string {
+ vars := r.URL.Query()
+ rmUIDs, ok := vars["rmUIDs"]
+ if !ok {
+ log.Debug("rmUIDs is not exist")
+ return nil
+ }
+
+ var rmUIDValues []string
+ for _, r := range rmUIDs {
+ if r != "" {
+ rmUIDValues = global.MergeStringArr(rmUIDValues, strings.Split(r, `,`))
+ }
+ }
+
+ return rmUIDValues
+}
+
+func GetAttrNameArr(r *http.Request) []string {
+ vars := r.URL.Query()
+ fields, ok := vars["fields"]
+ if !ok {
+ log.Debug("attributeNames does not exist")
+ return nil
+ }
+ var attrNames []string
+ for _, a := range fields {
+ if a != "" {
+ attrNames = global.MergeStringArr(attrNames, strings.Split(a, `,`))
+ }
+ }
+
+ return attrNames
+}
+
+func CheckValidRmUID(rmUIDs []string) []string {
+ log.Debug("GetLocalRmUID processing... ")
+
+ var invalidRmUIDs []string
+ for _, r := range rmUIDs {
+ if !global.MatchRmUID(RMUID_REGEXP, r) {
+ invalidRmUIDs = append(invalidRmUIDs, r)
+ }
+ }
+
+ return invalidRmUIDs
+}
+
+func CheckLocalRmUID(rmUIDs []string) string {
+ log.Debug("GetLocalRmUID processing... ")
+
+ rmUID := config.GetRmUIDFromConfig()
+ for _, r := range rmUIDs {
+ if r == rmUID {
+ return rmUID
+ }
+ }
+
+ return ""
+}
+
+func GetNRMByUri(w http.ResponseWriter, r *http.Request) {
+ log.Debug("GetNRMByUri processing... ")
+
+ // response 414-4 uri too long ? (optional)
+ // todo ... ?
+ if bytes.Count([]byte(r.RequestURI), nil) > MAX_URI_LEN {
+ log.Debug("Request Uri too long:", bytes.Count([]byte(r.RequestURI), nil))
+ services.ResponseRequestURITooLong414UriTooLong(w)
+ return
+ }
+
+ // check media type(content type) only support "application/json"
+ // response 415-1
+ if !IsVallidContentType(r) {
+ log.Debug("Invalid Content-Type")
+ services.ResponseUnsupportedMediaType415(w)
+ return
+ }
+
+ // error processing ...
+ // 401-1 response
+ token, ret := globalSession.IsCarriedToken(r)
+ if ret == false {
+ log.Debug("AccessToken is not carried")
+ services.ResponseUnauthorized401AccessTokenNotCarried(w)
+ return
+ }
+
+ // 401-2 response
+ if globalSession.IsValidToken(token) == false {
+ log.Debug("AccessToken fails or does not exist")
+ services.ResponseUnauthorized401AccessTokenNotExist(w)
+ return
+ }
+ // response 403 Forbidden, permissions deny
+ // todo...
+ plist := globalSession.GetPermissionFromSession(token)
+ log.Debug("permission list:", plist)
+ if len(plist) == 0 || plist[0] == false {
+ log.Debug("User permission deny")
+ services.ResponseForbidden403NotPermission(w)
+ return
+ }
+
+ vars := mux.Vars(r)
+ qeuryUri := vars["apiCategory"] + "/" + vars["elementTypeValue"] + "/" + vars["objectTypeValue"]
+ log.Debug("Get by Uri: ", qeuryUri)
+
+ apiVer := vars["apiVersion"]
+ if apiVer != "v1" {
+ log.Debug("Uri is invalid")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ // response 406-1
+ rmUIDValues := GetRmUIDArr(r)
+ if rmUIDValues == nil {
+ log.Debug("missing parameter: rmUIDs")
+ services.ResponseNotAcceptable406MissingParam(w)
+ return
+ }
+
+ // response 406-2
+ errorParams := CheckParameterName(r)
+ if errorParams != nil {
+ log.Debug("parameter name error: ", errorParams)
+ services.ResponseNotAcceptable406ParamError(w, errorParams)
+ return
+ }
+
+ // response 400-5
+ if len(rmUIDValues) == 0 {
+ log.Debug("rmUIDs is wrong or NULL")
+ services.ResponseBadRequest400WrongParamValue(w)
+ return
+ }
+
+ // response 414-1
+ if len(rmUIDValues) > MAX_RMUID_NUM {
+ log.Debug("rmUID greater than", MAX_RMUID_NUM)
+ services.ResponseRequestURITooLong414NRMNumExceed(w, MAX_RMUID_NUM)
+ return
+ }
+
+ // response 400-1
+ // check rmUID is valid
+ // todo ...
+ invalidRmUIDs := CheckValidRmUID(rmUIDValues)
+ if len(invalidRmUIDs) != 0 {
+ log.Debug("rmUID is invalid")
+ services.ResponseBadRequest400RmUIDsIsInvalid(w, invalidRmUIDs)
+ return
+ }
+
+ // response 404-2
+ rmUID := CheckLocalRmUID(rmUIDValues)
+ if rmUID == "" {
+ log.Debug("rmUID does not exist")
+ services.ResponseNotFound404NRMNotExist(w, rmUIDValues)
+ return
+ }
+
+ // response 404-1, uri is not exist in map
+ attrNames := GetAttrNameArr(r)
+ var Oids []string
+ Oids = *config.GetOidByFileds(qeuryUri, attrNames, &Oids)
+ if len(Oids) == 0 {
+ log.Debug("Nothing of config map")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ // response 404-1, uri is not exist in map
+ var nameOids []config.NameOid
+ nameOids = *config.GetDataOidByFields(qeuryUri, attrNames, &nameOids)
+ if len(nameOids) == 0 {
+ log.Debug("Nothing of config map")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ result, err2 := g.Default.Get(Oids) // Get() accepts up to g.MAX_OIDS
+ if err2 != nil {
+ log.Fatalf("Get() err: %v", err2)
+
+ }
+
+ // var nameValues []config.NameValue
+ var nameValue config.NameValue
+
+ nameValues := make(map[string]interface{})
+ nameValues["rmUID"] = rmUID
+ for i, variable := range result.Variables {
+ nameValue.Name = nameOids[i].Name
+ log.Debugf("%d: oid: %s name: %s\n", i, variable.Name, nameValue.Name)
+ // if nameOids[i].Oid == variable.Name && global.IsContain(attributeNames, nameValue.Name) {
+ if nameOids[i].Oid == variable.Name {
+ // the Value of each variable returned by Get() implements
+ // interface{}. You could do a type switch...
+ switch variable.Type {
+ case g.OctetString:
+ bytes := variable.Value.([]byte)
+ log.Debugf("string: %s\n", string(bytes))
+ nameValue.Value = string(bytes)
+ nameValues[nameValue.Name] = nameValue.Value
+ case g.Integer:
+ value := variable.Value.(int)
+ log.Debugf("integer: %d\n", value)
+ nameValue.Value = strconv.Itoa(value)
+ nameValues[nameValue.Name] = nameValue.Value
+ case g.IPAddress:
+ value := variable.Value.(string)
+ log.Debugf("IPAddress: %s\n", variable.Value)
+ nameValue.Value = value
+ nameValues[nameValue.Name] = nameValue.Value
+ default:
+ // ... or often you're just interested in numeric values.
+ // ToBigInt() will return the Value as a BigInt, for plugging
+ // into your calculations.
+ log.Debugf("number: %d\n", g.ToBigInt(variable.Value))
+ }
+ }
+ }
+
+ getResponse := services.DataResponse{nameValues}
+ services.ResponseWithJson(w, http.StatusOK, getResponse)
+}
+
+func RestfulPut(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ queryNo := vars["no"]
+ var targetTodo stTodo
+ for _, Todo := range TodoList {
+ if Todo.No == queryNo {
+ targetTodo = Todo
+ }
+ }
+ response := ApiResponse{"200", targetTodo}
+ services.ResponseWithJson(w, http.StatusOK, response)
+}
+
+func RestfulHead(w http.ResponseWriter, r *http.Request) {
+ var targetTodo stTodo
+
+ response := ApiResponse{"200", targetTodo}
+ services.ResponseWithJson(w, http.StatusOK, response)
+
+}
+
+func RemoveElement(TodoList []stTodo, No string) stTodo {
+ // var targetTodo stTodo
+ j := 0
+ if len(TodoList) == 0 {
+ return TodoList[j]
+ }
+
+ for i := 0; i < len(TodoList); i++ {
+ if TodoList[i].No != No {
+ if i != j {
+ TodoList[i], TodoList[j] = TodoList[j], TodoList[i]
+ }
+ j++
+ }
+ }
+ log.Debug(TodoList[j].No, TodoList[j].Item)
+ return TodoList[j]
+}
+
+func RestfulDelete(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ queryNo := vars["no"] //获取No
+
+ log.Debug("RestfulDelete processing... ")
+
+ targetTodo := RemoveElement(TodoList, queryNo)
+ response := ApiResponse{"200", targetTodo}
+ services.ResponseWithJson(w, http.StatusOK, response)
+}
+
+func RestfulOptions(w http.ResponseWriter, r *http.Request) {
+ var targetTodo stTodo
+
+ response := ApiResponse{"200", targetTodo}
+ services.ResponseWithJson(w, http.StatusOK, response)
+
+}
+
+func RestfulTrace(w http.ResponseWriter, r *http.Request) {
+ var targetTodo stTodo
+
+ response := ApiResponse{"200", targetTodo}
+ services.ResponseWithJson(w, http.StatusOK, response)
+
+}
+
+func RestfulPatch(w http.ResponseWriter, r *http.Request) {
+ var targetTodo stTodo
+
+ response := ApiResponse{"200", targetTodo}
+ services.ResponseWithJson(w, http.StatusOK, response)
+
+}
diff --git a/features/lm/logbak.go b/features/lm/logbak.go
new file mode 100644
index 0000000..624b109
--- /dev/null
+++ b/features/lm/logbak.go
@@ -0,0 +1,149 @@
+package lm
+
+import (
+ "fmt"
+ "net/http"
+ "os/exec"
+ "time"
+
+ "nms_nbi/lib/global"
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+
+ "github.com/gorilla/mux"
+ "xorm.io/xorm"
+)
+
+type XormResponse struct {
+ Data interface{} `json:"data"`
+}
+
+type XormInsertResponse struct {
+ Data interface{} `json:"data"`
+}
+
+var (
+ ExtBackupDataUri = config.DefaultUriPrefix + "/dataManagement/{apiVersion}/{dataStorage}/{dataObject}/backup" // for external
+
+ CustomExtBackupDataUri = config.UriPrefix + "/dataManagement/{apiVersion}/{dataStorage}/{dataObject}/backup" // for external
+)
+
+var XEngine *xorm.Engine
+
+type DatabaseClient struct {
+ dbType string
+ dbUrl string
+ dbConnMaxLifetime time.Duration
+ dbMaxIdleConns int
+ dbMaxOpenConns int
+ IsShowSQL bool
+
+ XEngine *xorm.Engine
+}
+
+var DbClient DatabaseClient
+
+// func init() {
+// conf := config.GetYamlConfig()
+// InitDbClient(conf.Database.Type, conf.Database.User, conf.Database.Password,
+// conf.Database.Host, conf.Database.Port, conf.Database.Name)
+// }
+
+func InitDbClient(dbType, dbUser, dbPassword, dbHost, dbPort, dbName string) error {
+ DbClient.dbUrl = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=true&loc=Local",
+ dbUser, dbPassword, dbHost, dbPort, dbName)
+ DbClient.dbType = dbType
+ DbClient.dbConnMaxLifetime = 0
+ DbClient.dbMaxIdleConns = 0
+ DbClient.dbMaxOpenConns = 0
+ if log.GetLevel() == log.LOG_TRACE {
+ DbClient.IsShowSQL = true
+ }
+ log.Debugf("dbType:%s dbUrl:%s:******@tcp(%s:%s)/%s??charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=true&loc=Local",
+ dbType, dbUser, dbHost, dbPort, dbName)
+
+ var err error
+ DbClient.XEngine, err = xorm.NewEngine(DbClient.dbType, DbClient.dbUrl)
+ if err != nil {
+ log.Error("Failed to connet database:", err)
+ return err
+ }
+ DbClient.XEngine.SetConnMaxLifetime(DbClient.dbConnMaxLifetime)
+ DbClient.XEngine.SetMaxIdleConns(DbClient.dbMaxIdleConns)
+ DbClient.XEngine.SetMaxOpenConns(DbClient.dbMaxOpenConns)
+ if DbClient.IsShowSQL {
+ DbClient.XEngine.ShowSQL(true)
+ }
+ XEngine = DbClient.XEngine
+
+ return nil
+}
+
+func ExtDatabaseBackupData(w http.ResponseWriter, r *http.Request) {
+ log.Debug("ExtDatabaseBackupData processing... ")
+
+ token, err := services.CheckExtValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ module := vars["managementModule"]
+ dbname := vars["dataStorage"]
+ tbname := vars["dataObject"]
+ pack := "lm"
+ log.Debugf("token:%s, method:%s, module:%s dbname:%s, tbname:%s pack:%s", token, r.Method, module, dbname, tbname, pack)
+ // exist, err := services.CheckUserPermission(token, strings.ToLower(r.Method), module, dbname, tbname, pack)
+ // if err != nil {
+ // log.Error("Failed to get permission:", err)
+ // services.ResponseForbidden403NotPermission(w)
+ // return
+ // }
+ // if !exist {
+ // log.Error("permission deny!")
+ // services.ResponseForbidden403NotPermission(w)
+ // return
+ // }
+
+ var sql string
+ var filePath string
+ switch tbname {
+ case "operation_log":
+ filePath = fmt.Sprintf("/tmp/%s-%s.csv", tbname, time.Now().Local().Format(global.DateData))
+ sql = fmt.Sprintf("select * into outfile '%s' fields terminated by ',' escaped by '' optionally enclosed by '' lines terminated by '\n' from (select 'op_id','account_name','op_ip','subsys_tag','op_type','op_content','op_result','begin_time','end_time','vnf_flag','log_time' union select op_id,account_name,op_ip,subsys_tag,op_type,op_content,op_result,begin_time,end_time,vnf_flag,log_time from operation_log) b", filePath)
+ case "security_log":
+ filePath = fmt.Sprintf("/tmp/%s-%s.csv", tbname, time.Now().Local().Format(global.DateData))
+ sql = fmt.Sprintf("select * into outfile '%s' fields terminated by ',' escaped by '' optionally enclosed by '' lines terminated by '\n' from (select 'id','account_name','account_type','op_ip','op_type','op_content','op_result','op_time' union select id,account_name,account_type,op_ip,op_type,op_content,op_result,op_time from security_log) b", filePath)
+ case "alarm_log":
+ filePath = fmt.Sprintf("/tmp/%s-%s.csv", tbname, time.Now().Local().Format(global.DateData))
+ sql = fmt.Sprintf("select * into outfile '%s' fields terminated by ',' escaped by '' optionally enclosed by '' lines terminated by '\n' from (select 'id','ne_type','ne_id','alarm_seq','alarm_id','alarm_code','alarm_status','event_time','log_time' union select id,ne_type,ne_id,alarm_seq,alarm_id,alarm_code,alarm_status,event_time,log_time from alarm_log) b", filePath)
+ default:
+ log.Error("error target table")
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+
+ res, err := DbClient.XEngine.Exec(sql)
+ if err != nil {
+ log.Error("Failed to exec SQL:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+ affected, _ := res.RowsAffected()
+
+ log.Debugf("filePath:%s backup dir:%s", filePath, config.GetYamlConfig().Database.Backup)
+ cmd := exec.Command("cp", "-rf", filePath, config.GetYamlConfig().Database.Backup)
+ out, err := cmd.CombinedOutput()
+ log.Debugf("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("Faile to exec:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ mapRow := make(map[string]interface{})
+ row := map[string]interface{}{"affectedRows": affected}
+ mapRow[tbname] = row
+ services.ResponseWithJson(w, http.StatusOK, mapRow)
+}
diff --git a/features/maintenance/maintenance.go b/features/maintenance/maintenance.go
new file mode 100644
index 0000000..1b43b6f
--- /dev/null
+++ b/features/maintenance/maintenance.go
@@ -0,0 +1,314 @@
+package maintenance
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "os"
+ "os/exec"
+ "path"
+ "runtime"
+ "time"
+
+ "nms_nbi/lib/core/utils/ctx"
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+
+ "github.com/shirou/gopsutil/cpu"
+ "github.com/shirou/gopsutil/disk"
+ "github.com/shirou/gopsutil/mem"
+)
+
+// (1) OMC能够对相关的文件系统资源、内存、CPU资源、数据存储空间、数据库空间等系统指标进行监控管理;
+// 对于虚拟化部署OMC系统,能够对虚机内存、虚机CPU、虚拟存储空间、文件系统资源、数据库空间等指标进行监控,提供界面截图 ;
+
+// (2) 系统监控指标的采样时间和阈值可由用户设定,超过阈值将产生不同级别的告警,提供界面截图 ;
+
+// (3) OMC能够方便的查询数据库连接情况;并可手工干预数据库的连接,能方便的终结非法的数据库连接 ;
+
+// (4) 用户能方便的查询系统进程、应用进程等的进程名、进程类型、开始时间、运行主机等信息,提供界面截图
+// (5) 用户能方便的对系统进程、应用进程等做中断或者启动操作,提供界面截图
+
+// (6) 对于文件系统资源、内存、CPU资源、数据存储空间、数据库空间等系统指标数据,要求OMC能够保存至少3个月,提供界面截图 ;
+// (7) 用户可以按照需求自定义报表模板并生成OMC系统维护数据报表,提供界面截图 ;
+
+// (8) OMC具备自身告警管理功能,对于传统OMC系统,如:服务器单电源告警,存储硬盘告警、OMC系统软件故障等;
+// 对于虚拟化OMC系统,如虚机告警、虚拟硬盘告警等,提供界面截图 。
+
+var (
+ // parameter config management
+ Uri = config.UriPrefix + "/maintenance/{apiVersion}/zz"
+
+ // (1) OMC能够对相关的文件系统资源、内存、CPU资源、数据存储空间、数据库空间等系统指标进行监控管理;
+ UriPref = config.UriPrefix + "/maintenance/{apiVersion}/pref"
+
+ // (6) 对于文件系统资源、内存、CPU资源、数据存储空间、数据库空间等系统指标数据,要求OMC能够保存至少3个月,提供界面截图 ;
+ UriPrefLog = config.UriPrefix + "/maintenance/{apiVersion}/prefLog"
+
+ // (2) 系统监控指标的采样时间和阈值可由用户设定,超过阈值将产生不同级别的告警,提供界面截图 ;
+ UriConfig = config.UriPrefix + "/maintenance/{apiVersion}/config"
+
+ // (3) OMC能够方便的查询数据库连接情况;并可手工干预数据库的连接,能方便的终结非法的数据库连接
+ UriSqlClient = config.UriPrefix + "/maintenance/{apiVersion}/sqlClient"
+
+ // (4) 用户能方便的查询系统进程、应用进程等的进程名、进程类型、开始时间、运行主机等信息,提供界面截图
+ // (5) 用户能方便的对系统进程、应用进程等做中断或者启动操作,提供界面截图
+ UriTop = config.UriPrefix + "/maintenance/{apiVersion}/top"
+)
+
+func init() {
+ // 定時收集 TODO
+ prefLogSave("")
+}
+
+func List(w http.ResponseWriter, r *http.Request) {
+ fmt.Println("zz List")
+ services.ResponseStatusOK200Null(w)
+}
+
+// 性能指標
+func prefInfo(dirName string) map[string]any {
+ data := make(map[string]any)
+
+ // 显示文件資源目录
+ dirPath := "D://"
+ if runtime.GOOS == "linux" {
+ dirPath = "/home"
+ }
+ // 訪問下級
+ if dirName != "" {
+ dirPath = path.Join(dirPath, dirName)
+ }
+ dir_list, e := os.ReadDir(dirPath)
+ if e != nil {
+ log.Error(e)
+ }
+ list := make([]map[string]any, 0)
+ for _, v := range dir_list {
+ o, err := v.Info()
+ if err != nil {
+ continue
+ }
+ list = append(list, map[string]any{
+ "name": o.Name(),
+ "size": o.Size(),
+ "mode": o.Mode().String(),
+ "modTime": o.ModTime().Format("2006-01-02 15:04:05"),
+ "isDir": o.IsDir(),
+ })
+ }
+ data["dirList"] = list
+
+ // 文件資源使用率
+ u, _ := disk.Usage(dirPath)
+ usedGB := int(u.Used) / (1024 * 1024 * 1024 * 1)
+ data["dirUse"] = fmt.Sprintf("%d", usedGB)
+
+ // CPU使用率
+ percent, err := cpu.Percent(time.Second, false)
+ if err != nil {
+ log.Error(err)
+ }
+ data["cpuUse"] = fmt.Sprintf("%.2f", percent[0])
+
+ // 内存使用率
+ memInfo, err := mem.VirtualMemory()
+ if err != nil {
+ log.Error(err)
+ }
+ data["memUse"] = memInfo.UsedPercent
+
+ // 獲取數據庫占用空間
+ if dborm.DbClient.XEngine != nil {
+ conf := config.GetYamlConfig()
+ result, err := dborm.DbClient.XEngine.QueryString(`SELECT
+ CONCAT(TRUNCATE(SUM(data_length)/1024/1024,2),'MB') AS data_size,
+ CONCAT(TRUNCATE(SUM(max_data_length)/1024/1024,2),'MB') AS max_data_size,
+ CONCAT(TRUNCATE(SUM(data_free)/1024/1024,2),'MB') AS data_free,
+ CONCAT(TRUNCATE(SUM(index_length)/1024/1024,2),'MB') AS index_size
+ FROM information_schema.tables WHERE TABLE_SCHEMA = ?;
+ `, conf.Database.Name)
+ if err == nil {
+ data["dbInfo"] = result[0]
+ } else {
+ data["dbInfo"] = map[string]string{}
+ }
+ }
+ return data
+}
+
+// 性能指標存入數據庫
+func prefLogSave(dirName string) {
+ if dborm.DbClient.XEngine != nil {
+ data := prefInfo(dirName)
+
+ dirListByte, err := json.Marshal(data["dirList"])
+ if err != nil {
+ log.Error(err)
+ }
+ dbInfoByte, err := json.Marshal(data["dbInfo"])
+ if err != nil {
+ log.Error(err)
+ }
+ rse, err := dborm.DbClient.XEngine.Exec(`INSERT INTO sys_perf_data
+ (id, create_time, dir_used, dir_list, db_info, mem_used, cpu_used)
+ VALUES(NULL, NOW(), ?, ?, ?, ?, ?);
+ `, data["dirUse"], string(dirListByte), string(dbInfoByte), data["memUse"], data["cpuUse"])
+ if err != nil {
+ log.Error(err)
+ }
+ fmt.Println(rse.LastInsertId())
+ }
+}
+
+// GET http://192.168.21.183:3040/api/rest/maintenance/v1/pref?dir=true
+func Pref(w http.ResponseWriter, r *http.Request) {
+ // 知道下級文件資源目录
+ dirName := r.URL.Query().Get("dirName")
+ data := prefInfo(dirName)
+ services.ResponseWithJson(w, http.StatusOK, data)
+}
+
+// POST http://192.168.21.183:3040/api/rest/maintenance/v1/config
+func Config(w http.ResponseWriter, r *http.Request) {
+ // json 請求參數獲取
+ var bodyArgs struct {
+ Key string `json:"key"`
+ Value string `json:"value"`
+ }
+ err := ctx.ShouldBindJSON(r, &bodyArgs)
+ if err != nil {
+ log.Error("io.ReadAll is failed:", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ // 進行值更新
+ if dborm.DbClient.XEngine != nil {
+ result, err := dborm.DbClient.XEngine.QueryString("SELECT * FROM information_schema.processlist")
+ if err != nil {
+ fmt.Println(err)
+ }
+ fmt.Println(result)
+ rse, err := dborm.DbClient.XEngine.Exec("UPDATE sys_config SET value = ? where id = ?", "true", 100)
+ if err != nil {
+ fmt.Println(err)
+ }
+ fmt.Println(rse)
+ }
+
+ services.ResponseStatusOK200Null(w)
+}
+
+// http://192.168.21.183:3040/api/rest/maintenance/v1/sqlClient?type=close
+// http://192.168.21.183:3040/api/rest/maintenance/v1/sqlClient?type=connet
+// http://192.168.21.183:3040/api/rest/maintenance/v1/sqlClient?type=user
+func SqlClient(w http.ResponseWriter, r *http.Request) {
+ // 关闭
+ isClose := r.URL.Query().Get("type")
+ if isClose == "close" && dborm.DbClient.XEngine != nil {
+ dborm.DbClient.XEngine.Close()
+ }
+
+ // 重连
+ isConnet := r.URL.Query().Get("type")
+ if isConnet == "connet" && dborm.DbClient.XEngine == nil {
+ conf := config.GetYamlConfig()
+ err := dborm.InitDbClient(conf.Database.Type, conf.Database.User, conf.Database.Password,
+ conf.Database.Host, conf.Database.Port, conf.Database.Name)
+ if err != nil {
+ fmt.Println("dborm.initDbClient err:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+ }
+
+ // 查询实例
+ isUser := r.URL.Query().Get("type")
+ if isUser == "user" && dborm.DbClient.XEngine != nil {
+ result, err := dborm.DbClient.XEngine.QueryString("SELECT * FROM information_schema.processlist")
+ if err != nil {
+ fmt.Println(err)
+ }
+ fmt.Println(result)
+ rse, err := dborm.DbClient.XEngine.Exec("KILL CONNECTION CONNECTION_ID()")
+ if err != nil {
+ fmt.Println(err)
+ }
+ fmt.Println(rse)
+ }
+
+ // 进行连接测试
+ err := dborm.DbClient.XEngine.Ping()
+ if err != nil {
+ fmt.Println(err)
+ }
+ services.ResponseStatusOK200Null(w)
+}
+
+// GET http://192.168.21.183:3040/api/rest/maintenance/v1/top?grep=
+func Top(w http.ResponseWriter, r *http.Request) {
+ // 過濾命令
+ grep := r.URL.Query().Get("grep")
+ // 命令拼接
+ var cmd *exec.Cmd
+ switch runtime.GOOS {
+ case "linux":
+ command := "ps -ef "
+ if grep != "" {
+ command += grep
+ }
+ cmd = exec.Command(command)
+ case "windows":
+ command := "wmic process list brief "
+ if grep != "" {
+ command += grep
+ }
+ cmd = exec.Command("cmd", "/C", command)
+ }
+
+ out, err := cmd.CombinedOutput()
+ fmt.Println(string(out))
+ if err != nil {
+ fmt.Println(err)
+ }
+ services.ResponseWithJson(w, http.StatusOK, string(out))
+}
+
+// PATCH http://192.168.21.183:3040/api/rest/maintenance/v1/top?ops=&name=
+func TopOps(w http.ResponseWriter, r *http.Request) {
+ // json 請求參數獲取
+ var bodyArgs struct {
+ Ops string `json:"ops"`
+ Pid string `json:"pid"`
+ }
+ err := ctx.ShouldBindJSON(r, &bodyArgs)
+ if err != nil {
+ log.Error("io.ReadAll is failed:", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ // 命令拼接
+ var cmd *exec.Cmd
+ switch runtime.GOOS {
+ case "linux":
+ switch bodyArgs.Ops {
+ case "kill":
+ cmd = exec.Command("kill", "-9", bodyArgs.Pid)
+ }
+ case "windows":
+ switch bodyArgs.Ops {
+ case "kill":
+ cmd = exec.Command("cmd", "/C", "taskkill", "-PID", bodyArgs.Pid, "-F")
+ }
+ }
+
+ out, err := cmd.CombinedOutput()
+ fmt.Println(string(out))
+ if err != nil {
+ fmt.Println(err)
+ }
+ services.ResponseWithJson(w, http.StatusOK, string(out))
+}
diff --git a/features/mml/mml.go b/features/mml/mml.go
new file mode 100644
index 0000000..9f67dc4
--- /dev/null
+++ b/features/mml/mml.go
@@ -0,0 +1,702 @@
+package mml
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net"
+ "net/http"
+ "regexp"
+ "strings"
+ "time"
+
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/global"
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/mmlp"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+
+ tokenConst "nms_nbi/src/framework/constants/token"
+
+ "github.com/gorilla/mux"
+)
+
+// const (
+// //经过测试,linux下,延时需要大于100ms
+// TIME_DELAY_AFTER_WRITE = 200
+// TIME_DEAD_LINE = 10
+// )
+
+type Response struct {
+ Data []string `json:"data"`
+}
+
+type MMLRequest struct {
+ MML []string `json:"mml"`
+}
+
+var (
+ // MML interface
+ UriMML = config.DefaultUriPrefix + "/operationManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/mml"
+ UriMML2 = config.DefaultUriPrefix + "/operationManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/mml2"
+ UriMMLDiscard = config.DefaultUriPrefix + "/opeartionManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/mml"
+ UriNeOmMml = config.DefaultUriPrefix + "/omManagement/{apiVersion}/mml/{netype}/{neid}"
+ UriOmMmlExt = config.DefaultUriPrefix + "/{managedType}/{apiVersion}/elementType/OMC/objectType/mml"
+ UriOmMmlInt = config.DefaultUriPrefix + "/omManagement/{apiVersion}/mml/{neType}/{neId}"
+
+ CustomUriMML = config.UriPrefix + "/operationManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/mml"
+ CustomUriMML2 = config.UriPrefix + "/operationManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/mml2"
+ CustomUriNeOmMml = config.UriPrefix + "/omManagement/{apiVersion}/mml/{netype}/{neid}"
+ CustomUriOmMmlExt = config.UriPrefix + "/opeartionManagement/{apiVersion}/elementType/OMC/objectType/mml"
+ CustomUriOmMmlInt = config.UriPrefix + "/omManagement/{apiVersion}/mml/{neType}/{neId}"
+)
+
+var TIME_DELAY_AFTER_WRITE time.Duration = 200
+var TIME_DEAD_LINE time.Duration = 10
+
+func init() {
+ if config.GetYamlConfig().MML.Sleep != 0 {
+ TIME_DELAY_AFTER_WRITE = time.Duration(config.GetYamlConfig().MML.Sleep)
+ }
+ if config.GetYamlConfig().MML.DeadLine != 0 {
+ TIME_DEAD_LINE = time.Duration(config.GetYamlConfig().MML.DeadLine)
+ }
+}
+
+func PostMML2ToNF(w http.ResponseWriter, r *http.Request) {
+ log.Info("PostMML2ToNF processing... ")
+
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+ neInfo := new(dborm.NeInfo)
+ params := r.URL.Query()
+ neId := params["ne_id"]
+ neInfo, err := dborm.XormGetNeInfo(neType, neId[0])
+ if err != nil {
+ log.Error("Failed to dborm.XormGetNeInfo:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ var buf [20 * 1024]byte
+ //buf := make([]byte, 0)
+ var n int
+ var mmlResult []string
+ port2 := 5002
+ if config.GetYamlConfig().MML.Port2 != 0 {
+ port2 = config.GetYamlConfig().MML.Port2
+ }
+
+ if neInfo != nil {
+ hostMML := fmt.Sprintf("%s:%d", neInfo.Ip, port2)
+ conn, err := net.Dial("tcp", hostMML)
+ if err != nil {
+ errMsg := fmt.Sprintf("Failed to dial %s: %v", hostMML, err)
+ log.Error(errMsg)
+ mmlResult = append(mmlResult, errMsg)
+ response := Response{mmlResult}
+ services.ResponseWithJson(w, http.StatusOK, response)
+ return
+ }
+ defer conn.Close()
+
+ // localAddr := conn.LocalAddr()
+ // remoteAddr := conn.RemoteAddr()
+ // if localAddr == nil || remoteAddr == nil {
+ // errMsg := fmt.Sprintf("connect invalid: localAddr=%v, remoteAddr=%v", localAddr, remoteAddr)
+ // log.Error(errMsg)
+ // mmlResult = append(mmlResult, errMsg)
+ // response := Response{mmlResult}
+ // services.ResponseWithJson(w, http.StatusOK, response)
+ // return
+ // }
+
+ conn.SetDeadline(time.Now().Add(TIME_DEAD_LINE * time.Second))
+ loginStr := fmt.Sprintf("%s\n%s\n", config.GetYamlConfig().MML.User, config.GetYamlConfig().MML.Password)
+ _, err = conn.Write([]byte(loginStr))
+ if err != nil {
+ log.Error("Failed to write:", err)
+ mmlResult = append(mmlResult, err.Error())
+ response := Response{mmlResult}
+ services.ResponseWithJson(w, http.StatusOK, response)
+ return
+ }
+
+ time.Sleep(time.Millisecond * TIME_DELAY_AFTER_WRITE)
+
+ n, err = conn.Read(buf[0:])
+ if err != nil {
+ log.Error("Failed to read:", err)
+ mmlResult = append(mmlResult, err.Error())
+ response := Response{mmlResult}
+ services.ResponseWithJson(w, http.StatusOK, response)
+ return
+ }
+ log.Trace(string(buf[0:n]))
+
+ body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen))
+ if err != nil {
+ log.Error("Failed to ReadAll:", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ log.Trace("Body:", string(body))
+
+ mmlRequest := new(MMLRequest)
+ _ = json.Unmarshal(body, mmlRequest)
+
+ for _, mml := range mmlRequest.MML {
+ mmlCommand := fmt.Sprintf("%s\n", mml)
+ _, err = conn.Write([]byte(mmlCommand))
+ if err != nil {
+ log.Error("Failed to write:", err)
+ mmlResult = append(mmlResult, err.Error())
+ // response := Response{mmlResult}
+ // services.ResponseWithJson(w, http.StatusOK, response)
+ // return
+ continue
+ }
+ time.Sleep(time.Millisecond * TIME_DELAY_AFTER_WRITE)
+
+ n, err = conn.Read(buf[0:])
+ if err != nil {
+ log.Error("Failed to read:", err)
+ mmlResult = append(mmlResult, err.Error())
+ // response := Response{mmlResult}
+ // services.ResponseWithJson(w, http.StatusOK, response)
+ // return
+ continue
+ }
+ log.Trace(string(buf[0 : n-len(neType)-2]))
+ re1 := regexp.MustCompile(`\x1B\[[0-9;]*[a-zA-Z]`) // 匹配包含␛的控制字符
+ //re2 := regexp.MustCompile(`\x00`) // 匹配空字符
+ re2 := regexp.MustCompile(`[\x00-\x08\x0B\x0C\x0E-\x1F\x7F\x1B]`) // 匹配空字符和包含␛的控制字符
+ //re := regexp.MustCompile(`[\x00-\x1F\x7F]`)
+ result := re1.ReplaceAllString(string(buf[0:n-len(neType)-2]), "")
+ result = re2.ReplaceAllString(result, "")
+ mmlResult = append(mmlResult, result)
+ }
+
+ }
+
+ response := Response{mmlResult}
+ services.ResponseWithJson(w, http.StatusOK, response)
+}
+
+func PostMMLToNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("PostMMLToNF processing... ")
+
+ token, err := services.CheckExtValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+ // 经过测试,linux下,延时需要大于100ms
+ // var TIME_DELAY_AFTER_WRITE time.Duration = 200
+ // var TIME_DEAD_LINE time.Duration = 10
+ // if config.GetYamlConfig().MML.Sleep != 0 {
+ // TIME_DELAY_AFTER_WRITE = time.Duration(config.GetYamlConfig().MML.Sleep)
+ // }
+ // if config.GetYamlConfig().MML.DeadLine != 0 {
+ // TIME_DEAD_LINE = time.Duration(config.GetYamlConfig().MML.DeadLine)
+ // }
+
+ pack := "mml"
+ vars := mux.Vars(r)
+ module := vars["managedType"]
+ neType := vars["elementTypeValue"]
+ params := r.URL.Query()
+ neId := params["ne_id"]
+ if len(neId) == 0 {
+ log.Error("NOT FOUND ne_id")
+ services.ResponseBadRequest400WrongParamValue(w)
+ return
+ }
+ log.Debug("neType:", neType, "neId", neId)
+
+ log.Debugf("token:%s, method:%s, managementType:%s dbname:%s, tbname:%s pack:%s",
+ token, r.Method, module, neType, neId[0], pack)
+
+ var buf [20 * 1024]byte
+ //buf := make([]byte, 0)
+ var n int
+ var mmlResult []string
+
+ // exist, err := services.CheckUserPermission(token, strings.ToLower(r.Method), module, neType, neId[0], pack)
+ // if err != nil {
+ // log.Error("Failed to get permission:", err)
+ // errMsg := fmt.Sprintf("RetCode = -1 operation failed: do not have the operation permissions")
+ // log.Error(errMsg)
+ // mmlResult = append(mmlResult, errMsg)
+ // response := Response{mmlResult}
+ // services.ResponseWithJson(w, http.StatusOK, response)
+ // //services.ResponseForbidden403NotPermission(w)
+ // return
+ // }
+ // if !exist {
+ // log.Error("Not permission!")
+ // errMsg := fmt.Sprintf("RetCode = -1 operation failed: do not have the operation permissions")
+ // log.Error(errMsg)
+ // mmlResult = append(mmlResult, errMsg)
+ // response := Response{mmlResult}
+ // services.ResponseWithJson(w, http.StatusOK, response)
+ // //services.ResponseForbidden403NotPermission(w)
+ // return
+ // }
+
+ if strings.ToLower(neType) == "omc" {
+ PostMMLToOMC(w, r)
+ return
+ }
+
+ neInfo := new(dborm.NeInfo)
+ neInfo, err = dborm.XormGetNeInfo(neType, neId[0])
+ if err != nil {
+ log.Error("dborm.XormGetNeInfo is failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+
+ if neInfo != nil {
+ switch strings.ToLower(neType) {
+ case "xxx":
+ body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen))
+ if err != nil {
+ log.Error("io.ReadAll is failed:", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ log.Trace("Body:", string(body))
+
+ mmlRequest := new(MMLRequest)
+ _ = json.Unmarshal(body, mmlRequest)
+
+ // n, err = conn.Read(buf[0:])
+ // if err != nil {
+ // log.Error("Failed to read:", err)
+ // mmlResult = append(mmlResult, err.Error())
+ // response := Response{mmlResult}
+ // services.ResponseWithJson(w, http.StatusOK, response)
+ // return
+ // }
+ // log.Debug(string(buf[0:n]))
+
+ // hostMML := fmt.Sprintf("%s:%d", neInfo.Ip, config.GetYamlConfig().MML.Port)
+ // conn, err := net.Dial("tcp", hostMML)
+ // if err != nil {
+ // errMsg := fmt.Sprintf("Failed to dial %s: %v", hostMML, err)
+ // log.Error(errMsg)
+ // mmlResult = append(mmlResult, errMsg)
+ // response := Response{mmlResult}
+ // services.ResponseWithJson(w, http.StatusOK, response)
+ // return
+ // }
+ // defer conn.Close()
+
+ // conn.SetDeadline(time.Now().Add(10 * time.Second))
+ // loginStr := fmt.Sprintf("%s\n%s\n", config.GetYamlConfig().MML.User, config.GetYamlConfig().MML.Password)
+ // _, err = conn.Write([]byte(loginStr))
+ // if err != nil {
+ // log.Error("Failed to write:", err)
+ // mmlResult = append(mmlResult, err.Error())
+ // response := Response{mmlResult}
+ // services.ResponseWithJson(w, http.StatusOK, response)
+ // return
+ // }
+ // time.Sleep(time.Millisecond * TIME_DELAY_AFTER_WRITE)
+
+ // n, err = conn.Write([]byte(config.GetYamlConfig().MML.User + "\n"))
+ // if err != nil {
+ // log.Error("Failed to write:", err)
+ // mmlResult = append(mmlResult, err.Error())
+ // response := Response{mmlResult}
+ // services.ResponseWithJson(w, http.StatusOK, response)
+ // return
+ // }
+ // log.Debug(string(buf[0:n]))
+ // time.Sleep(time.Millisecond * TIME_DELAY_AFTER_WRITE)
+
+ // n, err = conn.Read(buf[0:])
+ // if err != nil {
+ // log.Error("Failed to read:", err)
+ // mmlResult = append(mmlResult, err.Error())
+ // response := Response{mmlResult}
+ // services.ResponseWithJson(w, http.StatusOK, response)
+ // return
+ // }
+ // log.Debug(string(buf[0:n]))
+
+ // n, err = conn.Write([]byte(config.GetYamlConfig().MML.Password + "\n"))
+ // if err != nil {
+ // log.Error("Failed to write:", err)
+ // mmlResult = append(mmlResult, err.Error())
+ // response := Response{mmlResult}
+ // services.ResponseWithJson(w, http.StatusOK, response)
+ // return
+ // }
+ // log.Debug(string(buf[0:n]))
+
+ // time.Sleep(time.Millisecond * TIME_DELAY_AFTER_WRITE)
+
+ // n, err = conn.Read(buf[0:])
+ // if err != nil {
+ // log.Error("Failed to read:", err)
+ // mmlResult = append(mmlResult, err.Error())
+ // response := Response{mmlResult}
+ // services.ResponseWithJson(w, http.StatusOK, response)
+ // return
+ // }
+ // log.Debug(string(buf[0:n]))
+
+ for _, mml := range mmlRequest.MML {
+ hostMML := fmt.Sprintf("%s:%d", neInfo.Ip, config.GetYamlConfig().MML.Port)
+ conn, err := net.Dial("tcp", hostMML)
+ if err != nil {
+ errMsg := fmt.Sprintf("Failed to dial %s: %v", hostMML, err)
+ log.Error(errMsg)
+ mmlResult = append(mmlResult, errMsg)
+ response := Response{mmlResult}
+ services.ResponseWithJson(w, http.StatusOK, response)
+ return
+ }
+ defer conn.Close()
+
+ conn.SetDeadline(time.Now().Add(TIME_DEAD_LINE * time.Second))
+
+ loginStr := fmt.Sprintf("%s\n%s\n", config.GetYamlConfig().MML.User, config.GetYamlConfig().MML.Password)
+ _, err = conn.Write([]byte(loginStr))
+ if err != nil {
+ log.Error("Failed to write:", err)
+ mmlResult = append(mmlResult, err.Error())
+ //response := Response{mmlResult}
+ //services.ResponseWithJson(w, http.StatusOK, response)
+ //return
+ continue
+ }
+ time.Sleep(time.Millisecond * TIME_DELAY_AFTER_WRITE)
+ mmlCommand := fmt.Sprintf("%s\n", mml)
+ log.Debug("mml command:", mmlCommand)
+ _, err = conn.Write([]byte(mmlCommand))
+ if err != nil {
+ log.Error("Failed to write:", err)
+ mmlResult = append(mmlResult, err.Error())
+ //response := Response{mmlResult}
+ //services.ResponseWithJson(w, http.StatusOK, response)
+ //return
+ continue
+ }
+ time.Sleep(time.Millisecond * TIME_DELAY_AFTER_WRITE)
+
+ n, err = conn.Read(buf[0:])
+ if err != nil {
+ log.Error("Failed to read:", err)
+ mmlResult = append(mmlResult, err.Error())
+ //response := Response{mmlResult}
+ //services.ResponseWithJson(w, http.StatusOK, response)
+ //return
+ continue
+ }
+ log.Trace(string(buf[0 : n-len(neType)-2]))
+
+ re1 := regexp.MustCompile(`\x1B\[[0-9;]*[a-zA-Z]`) // 匹配包含␛的控制字符
+ //re2 := regexp.MustCompile(`\x00`) // 匹配空字符
+ re2 := regexp.MustCompile(`[\x00-\x08\x0B\x0C\x0E-\x1F\x7F\x1B]`) // 匹配空字符和包含␛的控制字符
+ //re := regexp.MustCompile(`[\x00-\x1F\x7F]`)
+ // upf telnet buffer只能读取一次,需要去掉前面的多余字符
+ //result := re1.ReplaceAllString(string(buf[config.GetYamlConfig().MML.UpfHeaderLength:n-len(neType)-2]), "")
+ result := re1.ReplaceAllString(string(buf[0:n-len(neType)-2]), "")
+ result = re2.ReplaceAllString(result, "")
+ mmlResult = append(mmlResult, result)
+ conn.Close()
+ //mmlResult = append(mmlResult, string(buf[0:n-len(neType)-2]))
+
+ // can't read buffer from upf telnet server, so return ok always
+ // mmlResult = append(mmlResult, "COMMAND OK\n")
+ }
+ case "ims":
+ hostMML := fmt.Sprintf("%s:%d", neInfo.Ip, config.GetYamlConfig().MML.Port)
+ conn, err := net.Dial("tcp", hostMML)
+ if err != nil {
+ errMsg := fmt.Sprintf("Failed to dial %s: %v", hostMML, err)
+ log.Error(errMsg)
+ mmlResult = append(mmlResult, errMsg)
+ response := Response{mmlResult}
+ services.ResponseWithJson(w, http.StatusOK, response)
+ return
+ }
+ defer conn.Close()
+
+ // localAddr := conn.LocalAddr()
+ // remoteAddr := conn.RemoteAddr()
+ // if localAddr == nil || remoteAddr == nil {
+ // errMsg := fmt.Sprintf("connect invalid: localAddr=%v, remoteAddr=%v", localAddr, remoteAddr)
+ // log.Error(errMsg)
+ // mmlResult = append(mmlResult, errMsg)
+ // response := Response{mmlResult}
+ // services.ResponseWithJson(w, http.StatusOK, response)
+ // return
+ // }
+ conn.SetDeadline(time.Now().Add(TIME_DEAD_LINE * time.Second))
+
+ _, err = conn.Write([]byte(config.GetYamlConfig().MML.User + "\r\n"))
+ if err != nil {
+ log.Error("Failed to write:", err)
+ mmlResult = append(mmlResult, err.Error())
+ response := Response{mmlResult}
+ services.ResponseWithJson(w, http.StatusOK, response)
+ return
+ }
+ time.Sleep(time.Millisecond * TIME_DELAY_AFTER_WRITE)
+
+ n, err = conn.Read(buf[0:])
+ if err != nil {
+ log.Error("Failed to read:", err)
+ mmlResult = append(mmlResult, err.Error())
+ response := Response{mmlResult}
+ services.ResponseWithJson(w, http.StatusOK, response)
+ return
+ }
+ log.Trace(string(buf[0:n]))
+
+ _, err = conn.Write([]byte(config.GetYamlConfig().MML.Password + "\r\n"))
+ if err != nil {
+ log.Error("Failed to write:", err)
+ mmlResult = append(mmlResult, err.Error())
+ response := Response{mmlResult}
+ services.ResponseWithJson(w, http.StatusOK, response)
+ return
+ }
+ time.Sleep(time.Millisecond * TIME_DELAY_AFTER_WRITE)
+
+ // loginStr := fmt.Sprintf("%s\r\n%s\r\n", config.GetYamlConfig().MML.User, config.GetYamlConfig().MML.Password)
+ // _, err = conn.Write([]byte(loginStr))
+ // if err != nil {
+ // log.Error("Failed to write:", err)
+ // mmlResult = append(mmlResult, err.Error())
+ // response := Response{mmlResult}
+ // services.ResponseWithJson(w, http.StatusOK, response)
+ // return
+ // }
+ // time.Sleep(time.Millisecond * TIME_DELAY_AFTER_WRITE)
+
+ n, err = conn.Read(buf[0:])
+ if err != nil {
+ log.Error("Failed to read:", err)
+ mmlResult = append(mmlResult, err.Error())
+ response := Response{mmlResult}
+ services.ResponseWithJson(w, http.StatusOK, response)
+ return
+ }
+ log.Trace(string(buf[0 : n-len(neType)-2]))
+ body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen))
+ if err != nil {
+ log.Error("io.ReadAll is failed:", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ log.Trace("Body:", string(body))
+
+ mmlRequest := new(MMLRequest)
+ _ = json.Unmarshal(body, mmlRequest)
+
+ for _, mml := range mmlRequest.MML {
+ mmlCommand := fmt.Sprintf("%s\r\n", mml)
+ log.Debug("mml command:", mmlCommand)
+ _, err = conn.Write([]byte(mmlCommand))
+ if err != nil {
+ log.Error("Failed to write:", err)
+ mmlResult = append(mmlResult, err.Error())
+ // response := Response{mmlResult}
+ // services.ResponseWithJson(w, http.StatusOK, response)
+ // return
+ continue
+ }
+ time.Sleep(time.Millisecond * TIME_DELAY_AFTER_WRITE)
+
+ n, err = conn.Read(buf[0:])
+ if err != nil {
+ log.Error("Failed to read:", err)
+ mmlResult = append(mmlResult, err.Error())
+ // response := Response{mmlResult}
+ // services.ResponseWithJson(w, http.StatusOK, response)
+ // return
+ continue
+ }
+ log.Trace(string(buf[0 : n-len(neType)-2]))
+
+ re1 := regexp.MustCompile(`\x1B\[[0-9;]*[a-zA-Z]`) // 匹配包含␛的控制字符
+ //re2 := regexp.MustCompile(`\x00`) // 匹配空字符
+ re2 := regexp.MustCompile(`[\x00-\x08\x0B\x0C\x0E-\x1F\x7F\x1B]`) // 匹配空字符和包含␛的控制字符
+ //re := regexp.MustCompile(`[\x00-\x1F\x7F]`)
+ result := re1.ReplaceAllString(string(buf[0:n-len(neType)-2]), "")
+ result = re2.ReplaceAllString(result, "")
+ mmlResult = append(mmlResult, result)
+ }
+ default:
+ hostMML := fmt.Sprintf("%s:%d", neInfo.Ip, config.GetYamlConfig().MML.Port)
+ conn, err := net.Dial("tcp", hostMML)
+ if err != nil {
+ errMsg := fmt.Sprintf("Failed to dial %s: %v", hostMML, err)
+ log.Error(errMsg)
+ mmlResult = append(mmlResult, errMsg)
+ response := Response{mmlResult}
+ services.ResponseWithJson(w, http.StatusOK, response)
+ return
+ }
+ defer conn.Close()
+
+ // localAddr := conn.LocalAddr()
+ // remoteAddr := conn.RemoteAddr()
+ // if localAddr == nil || remoteAddr == nil {
+ // errMsg := fmt.Sprintf("connect invalid: localAddr=%v, remoteAddr=%v", localAddr, remoteAddr)
+ // log.Error(errMsg)
+ // mmlResult = append(mmlResult, errMsg)
+ // response := Response{mmlResult}
+ // services.ResponseWithJson(w, http.StatusOK, response)
+ // return
+ // }
+
+ conn.SetDeadline(time.Now().Add(TIME_DEAD_LINE * time.Second))
+ loginStr := fmt.Sprintf("%s\n%s\n", config.GetYamlConfig().MML.User, config.GetYamlConfig().MML.Password)
+ _, err = conn.Write([]byte(loginStr))
+ if err != nil {
+ log.Error("Failed to write:", err)
+ mmlResult = append(mmlResult, err.Error())
+ response := Response{mmlResult}
+ services.ResponseWithJson(w, http.StatusOK, response)
+ return
+ }
+
+ time.Sleep(time.Millisecond * TIME_DELAY_AFTER_WRITE)
+
+ n, err = conn.Read(buf[0:])
+ if err != nil {
+ log.Error("Failed to read:", err)
+ mmlResult = append(mmlResult, err.Error())
+ response := Response{mmlResult}
+ services.ResponseWithJson(w, http.StatusOK, response)
+ return
+ }
+ log.Trace(string(buf[0:n]))
+
+ body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen))
+ if err != nil {
+ log.Error("Failed to ReadAll:", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ log.Trace("Body:", string(body))
+
+ mmlRequest := new(MMLRequest)
+ _ = json.Unmarshal(body, mmlRequest)
+
+ for _, mml := range mmlRequest.MML {
+ mmlCommand := fmt.Sprintf("%s\n", mml)
+ _, err = conn.Write([]byte(mmlCommand))
+ if err != nil {
+ log.Error("Failed to write:", err)
+ mmlResult = append(mmlResult, err.Error())
+ // response := Response{mmlResult}
+ // services.ResponseWithJson(w, http.StatusOK, response)
+ // return
+ continue
+ }
+ time.Sleep(time.Millisecond * TIME_DELAY_AFTER_WRITE)
+
+ n, err = conn.Read(buf[0:])
+ if err != nil {
+ log.Error("Failed to read:", err)
+ mmlResult = append(mmlResult, err.Error())
+ // response := Response{mmlResult}
+ // services.ResponseWithJson(w, http.StatusOK, response)
+ // return
+ continue
+ }
+ log.Trace(string(buf[0 : n-len(neType)-2]))
+ re1 := regexp.MustCompile(`\x1B\[[0-9;]*[a-zA-Z]`) // 匹配包含␛的控制字符
+ //re2 := regexp.MustCompile(`\x00`) // 匹配空字符
+ re2 := regexp.MustCompile(`[\x00-\x08\x0B\x0C\x0E-\x1F\x7F\x1B]`) // 匹配空字符和包含␛的控制字符
+ //re := regexp.MustCompile(`[\x00-\x1F\x7F]`)
+ result := re1.ReplaceAllString(string(buf[0:n-len(neType)-2]), "")
+ result = re2.ReplaceAllString(result, "")
+ mmlResult = append(mmlResult, result)
+ }
+ }
+ }
+
+ response := Response{mmlResult}
+ services.ResponseWithJson(w, http.StatusOK, response)
+}
+
+func PostMMLToOMC(w http.ResponseWriter, r *http.Request) {
+ log.Debug("PostMMLToOMC processing... ")
+
+ token, err := services.CheckExtValidRequest(w, r)
+ if err != nil {
+ log.Error("Failed to CheckMmlValidRequest:", err)
+ return
+ }
+
+ params := r.URL.Query()
+ neId := params["ne_id"]
+ if len(neId) == 0 {
+ log.Error("NOT FOUND ne_id ")
+ services.ResponseBadRequest400WrongParamValue(w)
+ return
+ }
+
+ neInfo := new(dborm.NeInfo)
+ neInfo, err = dborm.XormGetNeInfo("OMC", neId[0])
+ if err != nil {
+ log.Error("dborm.XormGetNeInfo is failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+ log.Trace("neInfo:", neInfo)
+
+ body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen))
+ if err != nil {
+ log.Error("io.ReadAll is failed:", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ log.Trace("Body:", string(body))
+
+ hostUri := fmt.Sprintf("http://%s:%s", neInfo.Ip, neInfo.Port)
+
+ omcMmlVar := &mmlp.MmlVar{
+ Version: global.Version,
+ Output: mmlp.DefaultFormatType,
+ MmlHome: config.GetYamlConfig().MML.MmlHome,
+ Limit: 50,
+ User: "",
+ SessionToken: token, // 旧token
+ Authorization: r.Header.Get(tokenConst.HEADER_KEY), // 请求Token
+ HttpUri: hostUri,
+ UserAgent: config.GetDefaultUserAgent(),
+ }
+ mmlRequest := new(MMLRequest)
+ _ = json.Unmarshal(body, mmlRequest)
+
+ var mmlResult []string
+ mmlLine := strings.Join(mmlRequest.MML, ";")
+
+ var mmlCmds []mmlp.MmlCommand
+ if err = mmlp.ParseMMLCommand(mmlLine, &mmlCmds); err != nil {
+ response := fmt.Sprintf("parse command error: %v\n", err)
+ mmlResult = append(mmlResult, response)
+ }
+
+ for _, mmlCmd := range mmlCmds {
+ output, err := mmlp.TransMml2HttpReq(omcMmlVar, &mmlCmd)
+ if err != nil {
+ response := fmt.Sprintf("translate MML command error: %v]\n", err)
+ mmlResult = append(mmlResult, response)
+ }
+ mmlResult = append(mmlResult, string(*output))
+ }
+
+ response := Response{mmlResult}
+ services.ResponseWithJson(w, http.StatusOK, response)
+}
diff --git a/features/monitor/monitor/model.go b/features/monitor/monitor/model.go
new file mode 100644
index 0000000..8997a22
--- /dev/null
+++ b/features/monitor/monitor/model.go
@@ -0,0 +1,55 @@
+package monitor
+
+import "time"
+
+type MonitorBase struct {
+ ID uint `xorm:"id" json:"id"`
+ CreatedAt time.Time `xorm:"created_at" json:"createdAt"`
+ UpdatedAt time.Time `xorm:"updated_at" json:"updatedAt"`
+
+ Cpu float64 `xorm:"cpu" json:"cpu"`
+
+ LoadUsage float64 `xorm:"load_usage" json:"loadUsage"`
+ CpuLoad1 float64 `xorm:"cpu_load1" json:"cpuLoad1"`
+ CpuLoad5 float64 `xorm:"cpu_load5" json:"cpuLoad5"`
+ CpuLoad15 float64 `xorm:"cpu_load15" json:"cpuLoad15"`
+
+ Memory float64 `xorm:"memory" json:"memory"`
+
+ DbSize uint `xorm:"db_size" json:"dbSize"`
+}
+
+type MonitorIO struct {
+ ID uint `xorm:"id" json:"id"`
+ CreatedAt time.Time `xorm:"created_at" json:"createdAt"`
+ UpdatedAt time.Time `xorm:"updated_at" json:"updatedAt"`
+
+ Name string `xorm:"name" json:"name"`
+ Read uint64 `xorm:"read" json:"read"`
+ Write uint64 `xorm:"write" json:"write"`
+ Count uint64 `xorm:"count" json:"count"`
+ Time uint64 `xorm:"time" json:"time"`
+}
+
+type MonitorNetwork struct {
+ ID uint `xorm:"id" json:"id"`
+ CreatedAt time.Time `xorm:"created_at" json:"createdAt"`
+ UpdatedAt time.Time `xorm:"updated_at" json:"updatedAt"`
+
+ Name string `xorm:"name" json:"name"`
+ Up float64 `xorm:"up" json:"up"`
+ Down float64 `xorm:"down" json:"down"`
+}
+
+type MonitorSearch struct {
+ Param string `json:"param" validate:"required,oneof=all cpu memory load io network"`
+ Info string `json:"info"`
+ StartTime time.Time `json:"startTime"`
+ EndTime time.Time `json:"endTime"`
+}
+
+type MonitorData struct {
+ Param string `json:"param" validate:"required,oneof=cpu memory load io network"`
+ Date []time.Time `json:"date"`
+ Value []interface{} `json:"value"`
+}
diff --git a/features/monitor/monitor/monitor.go b/features/monitor/monitor/monitor.go
new file mode 100644
index 0000000..2a789ba
--- /dev/null
+++ b/features/monitor/monitor/monitor.go
@@ -0,0 +1,163 @@
+package monitor
+
+import (
+ "net/http"
+ "sort"
+ "strings"
+ "time"
+
+ "nms_nbi/lib/core/utils/ctx"
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+
+ "github.com/shirou/gopsutil/disk"
+ "github.com/shirou/gopsutil/net"
+)
+
+// 点击【主机 - 监控】菜单,进入监控报表,直观的了解服务器的运行状态,包含【平均负载】、【CPU性能监控】、【内存使用监控】、【磁盘IO监控】、【网络IO监控】
+
+// 可以查看昨天,今天,最近7天,最近30天,自定义时间的监控指标情况。
+// 默认监控是开启的,可以在【面板设置 - 监控】页面中根据需求对监控进行开启和关闭。
+// 监控数据默认保存30天,可以自行修改,也可手动清理该日志。
+
+var (
+ // IP地址
+ UriIPAddr = config.DefaultUriPrefix + "/monitor/{apiVersion}/monitor/ipaddr"
+ // 可选网络
+ UriNetOpt = config.DefaultUriPrefix + "/monitor/{apiVersion}/monitor/netoptions"
+ // 可选磁盘
+ UriIoOpt = config.DefaultUriPrefix + "/monitor/{apiVersion}/monitor/iooptions"
+ // 加载
+ UriLoad = config.DefaultUriPrefix + "/monitor/{apiVersion}/monitor/load"
+
+ // IP地址
+ UriIPAddrOAM = config.UriPrefix + "/monitor/{apiVersion}/monitor/ipaddr"
+ // 可选网络
+ UriNetOptOAM = config.UriPrefix + "/monitor/{apiVersion}/monitor/netoptions"
+ // 可选磁盘
+ UriIoOptOAM = config.UriPrefix + "/monitor/{apiVersion}/monitor/iooptions"
+ // 加载
+ UriLoadOAM = config.UriPrefix + "/monitor/{apiVersion}/monitor/load"
+)
+
+// IPAddr IP地址
+func IPAddr(w http.ResponseWriter, r *http.Request) {
+ ipAddrs := []map[string]string{}
+ interfaces, err := net.Interfaces()
+ if err == nil {
+ for _, iface := range interfaces {
+ addrs := map[string]string{}
+ for _, v := range iface.Addrs {
+ prefix := strings.Split(v.Addr, "/")[0]
+ if strings.Contains(prefix, "::") {
+ addrs["IPv6"] = prefix
+ }
+ if strings.Contains(prefix, ".") {
+ addrs["IPv4"] = prefix
+ }
+ }
+ ipAddrs = append(ipAddrs, addrs)
+ }
+ }
+ services.ResponseWithJson(w, 200, ipAddrs)
+}
+
+// Netoptions 可选网络
+func Netoptions(w http.ResponseWriter, r *http.Request) {
+ netStat, _ := net.IOCounters(true)
+ var options []string
+ options = append(options, "all")
+ for _, net := range netStat {
+ options = append(options, net.Name)
+ }
+ sort.Strings(options)
+ services.ResponseWithJson(w, 200, options)
+}
+
+// Iooptions 可选磁盘
+func Iooptions(w http.ResponseWriter, r *http.Request) {
+ diskStat, _ := disk.IOCounters()
+ var options []string
+ options = append(options, "all")
+ for _, net := range diskStat {
+ options = append(options, net.Name)
+ }
+ sort.Strings(options)
+ services.ResponseWithJson(w, 200, options)
+}
+
+// LoadMonitor 载入监控
+func LoadMonitor(w http.ResponseWriter, r *http.Request) {
+ // json 請求參數獲取
+ var bodyArgs MonitorSearch
+ err := ctx.ShouldBindJSON(r, &bodyArgs)
+ if err != nil || dborm.DbClient.XEngine == nil {
+ services.ResponseErrorWithJson(w, 400, err.Error())
+ return
+ }
+
+ loc := time.Now().Location()
+ bodyArgs.StartTime = bodyArgs.StartTime.In(loc)
+ bodyArgs.EndTime = bodyArgs.EndTime.In(loc)
+
+ var backdatas []MonitorData
+ if bodyArgs.Param == "all" || bodyArgs.Param == "cpu" || bodyArgs.Param == "memory" || bodyArgs.Param == "load" {
+ var bases []MonitorBase
+ err := dborm.DbClient.XEngine.Table("monitor_base").
+ Where("created_at > ? AND created_at < ?", bodyArgs.StartTime, bodyArgs.EndTime).
+ Desc("created_at").
+ Find(&bases)
+ if err != nil {
+ services.ResponseErrorWithJson(w, 400, err.Error())
+ return
+ }
+
+ var itemData MonitorData
+ itemData.Param = "base"
+ for _, base := range bases {
+ itemData.Date = append(itemData.Date, base.CreatedAt)
+ itemData.Value = append(itemData.Value, base)
+ }
+ backdatas = append(backdatas, itemData)
+ }
+ if bodyArgs.Param == "all" || bodyArgs.Param == "io" {
+ var bases []MonitorIO
+ err := dborm.DbClient.XEngine.Table("monitor_io").
+ Where("created_at > ? AND created_at < ?", bodyArgs.StartTime, bodyArgs.EndTime).
+ Desc("created_at").
+ Find(&bases)
+ if err != nil {
+ services.ResponseErrorWithJson(w, 400, err.Error())
+ return
+ }
+
+ var itemData MonitorData
+ itemData.Param = "io"
+ for _, base := range bases {
+ itemData.Date = append(itemData.Date, base.CreatedAt)
+ itemData.Value = append(itemData.Value, base)
+ }
+ backdatas = append(backdatas, itemData)
+ }
+ if bodyArgs.Param == "all" || bodyArgs.Param == "network" {
+ var bases []MonitorNetwork
+ err := dborm.DbClient.XEngine.Table("monitor_network").
+ Where("name = ? AND created_at > ? AND created_at < ?", bodyArgs.Info, bodyArgs.StartTime, bodyArgs.EndTime).
+ Desc("created_at").
+ Find(&bases)
+ if err != nil {
+ services.ResponseErrorWithJson(w, 400, err.Error())
+ return
+ }
+
+ var itemData MonitorData
+ itemData.Param = "network"
+ for _, base := range bases {
+ itemData.Date = append(itemData.Date, base.CreatedAt)
+ itemData.Value = append(itemData.Value, base)
+ }
+ backdatas = append(backdatas, itemData)
+ }
+ services.ResponseWithJson(w, 200, backdatas)
+}
diff --git a/features/monitor/monitor/task.go b/features/monitor/monitor/task.go
new file mode 100644
index 0000000..74d4977
--- /dev/null
+++ b/features/monitor/monitor/task.go
@@ -0,0 +1,234 @@
+package monitor
+
+import (
+ "encoding/json"
+ "fmt"
+ "strconv"
+ "time"
+
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/log"
+ "nms_nbi/restagent/config"
+
+ "github.com/robfig/cron/v3"
+ "github.com/shirou/gopsutil/v3/cpu"
+ "github.com/shirou/gopsutil/v3/disk"
+ "github.com/shirou/gopsutil/v3/load"
+ "github.com/shirou/gopsutil/v3/mem"
+ "github.com/shirou/gopsutil/v3/net"
+)
+
+type MonitorService struct{}
+
+type IMonitorService interface {
+ Run()
+}
+
+func NewIMonitorService() IMonitorService {
+ return &MonitorService{}
+}
+
+func (m *MonitorService) Run() {
+ // monitorStatus, _ := dborm.XormGetConfig("SystemMonitor", "MonitorStatus")
+ // if monitorStatus["value"] == "disable" {
+ // return
+ // }
+ var itemModel MonitorBase
+ itemModel.CreatedAt = time.Now()
+ itemModel.UpdatedAt = time.Now()
+
+ totalPercent, _ := cpu.Percent(3*time.Second, false)
+ if len(totalPercent) == 1 {
+ itemModel.Cpu = totalPercent[0]
+ }
+ cpuCount, _ := cpu.Counts(false)
+
+ loadInfo, _ := load.Avg()
+ itemModel.CpuLoad1 = loadInfo.Load1
+ itemModel.CpuLoad5 = loadInfo.Load5
+ itemModel.CpuLoad15 = loadInfo.Load15
+ itemModel.LoadUsage = loadInfo.Load1 / (float64(cpuCount*2) * 0.75) * 100
+
+ memoryInfo, _ := mem.VirtualMemory()
+ itemModel.Memory = memoryInfo.UsedPercent
+
+ var dataSize int
+ conf := config.GetYamlConfig()
+ result, err := dborm.DbClient.XEngine.QueryString("SELECT SUM(data_length) AS data_size FROM information_schema.tables WHERE TABLE_SCHEMA = ?;", conf.Database.Name)
+ if err != nil {
+ dataSize = 0
+ } else {
+ v, _ := strconv.Atoi(result[0]["data_size"])
+ dataSize = v
+ }
+ itemModel.DbSize = uint(dataSize)
+
+ _, errx := dborm.DbClient.XEngine.Table("monitor_base").Insert(itemModel)
+ if errx != nil {
+ log.Errorf("Insert basic monitoring data failed, err: %v", err)
+ }
+
+ go loadDiskIO()
+ go loadNetIO()
+
+ // 删除保留的记录
+ // monitorStoreDays, _ := dborm.XormGetConfig("SystemMonitor", "MonitorStoreDays")
+ // if monitorStoreDays["value"] != "" {
+ // return
+ // }
+ // storeDays, err := strconv.Atoi(MonitorStoreDays.Value)
+ // if err != nil {
+ // timeForDelete := time.Now().AddDate(0, 0, -storeDays)
+ // DelMonitorBase(timeForDelete)
+ // DelMonitorIO(timeForDelete)
+ // DelMonitorNet(timeForDelete)
+ // }
+
+}
+
+func loadDiskIO() {
+ ioStat, _ := disk.IOCounters()
+
+ time.Sleep(60 * time.Second)
+
+ ioStat2, _ := disk.IOCounters()
+ var ioList []MonitorIO
+ for _, io2 := range ioStat2 {
+ for _, io1 := range ioStat {
+ if io2.Name == io1.Name {
+ var itemIO MonitorIO
+ itemIO.CreatedAt = time.Now()
+ itemIO.UpdatedAt = time.Now()
+
+ itemIO.Name = io1.Name
+ if io2.ReadBytes != 0 && io1.ReadBytes != 0 && io2.ReadBytes > io1.ReadBytes {
+ itemIO.Read = uint64(float64(io2.ReadBytes-io1.ReadBytes) / 60)
+ }
+ if io2.WriteBytes != 0 && io1.WriteBytes != 0 && io2.WriteBytes > io1.WriteBytes {
+ itemIO.Write = uint64(float64(io2.WriteBytes-io1.WriteBytes) / 60)
+ }
+
+ if io2.ReadCount != 0 && io1.ReadCount != 0 && io2.ReadCount > io1.ReadCount {
+ itemIO.Count = uint64(float64(io2.ReadCount-io1.ReadCount) / 60)
+ }
+ writeCount := uint64(0)
+ if io2.WriteCount != 0 && io1.WriteCount != 0 && io2.WriteCount > io1.WriteCount {
+ writeCount = uint64(float64(io2.WriteCount-io1.WriteCount) / 60)
+ }
+ if writeCount > itemIO.Count {
+ itemIO.Count = writeCount
+ }
+
+ if io2.ReadTime != 0 && io1.ReadTime != 0 && io2.ReadTime > io1.ReadTime {
+ itemIO.Time = uint64(float64(io2.ReadTime-io1.ReadTime) / 60)
+ }
+ writeTime := uint64(0)
+ if io2.WriteTime != 0 && io1.WriteTime != 0 && io2.WriteTime > io1.WriteTime {
+ writeTime = uint64(float64(io2.WriteTime-io1.WriteTime) / 60)
+ }
+ if writeTime > itemIO.Time {
+ itemIO.Time = writeTime
+ }
+ ioList = append(ioList, itemIO)
+ break
+ }
+ }
+ }
+ _, err := dborm.DbClient.XEngine.Table("monitor_io").Insert(ioList)
+ if err != nil {
+ log.Errorf("Insert io monitoring data failed, err: %v", err)
+ }
+}
+
+func loadNetIO() {
+ netStat, _ := net.IOCounters(true)
+ netStatAll, _ := net.IOCounters(false)
+
+ time.Sleep(60 * time.Second)
+
+ netStat2, _ := net.IOCounters(true)
+ var netList []MonitorNetwork
+ for _, net2 := range netStat2 {
+ for _, net1 := range netStat {
+ if net2.Name == net1.Name {
+ var itemNet MonitorNetwork
+ itemNet.CreatedAt = time.Now()
+ itemNet.UpdatedAt = time.Now()
+
+ itemNet.Name = net1.Name
+
+ if net2.BytesSent != 0 && net1.BytesSent != 0 && net2.BytesSent > net1.BytesSent {
+ itemNet.Up = float64(net2.BytesSent-net1.BytesSent) / 1024 / 60
+ }
+ if net2.BytesRecv != 0 && net1.BytesRecv != 0 && net2.BytesRecv > net1.BytesRecv {
+ itemNet.Down = float64(net2.BytesRecv-net1.BytesRecv) / 1024 / 60
+ }
+ netList = append(netList, itemNet)
+ break
+ }
+ }
+ }
+ netStatAll2, _ := net.IOCounters(false)
+ for _, net2 := range netStatAll2 {
+ for _, net1 := range netStatAll {
+ if net2.Name == net1.Name {
+ var itemNet MonitorNetwork
+ itemNet.Name = net1.Name
+ if net2.BytesSent != 0 && net1.BytesSent != 0 && net2.BytesSent > net1.BytesSent {
+ itemNet.Up = float64(net2.BytesSent-net1.BytesSent) / 1024 / 60
+ }
+
+ if net2.BytesRecv != 0 && net1.BytesRecv != 0 && net2.BytesRecv > net1.BytesRecv {
+ itemNet.Down = float64(net2.BytesRecv-net1.BytesRecv) / 1024 / 60
+ }
+ netList = append(netList, itemNet)
+ break
+ }
+ }
+ }
+
+ _, err := dborm.DbClient.XEngine.Table("monitor_network").Insert(netList)
+ if err != nil {
+ log.Errorf("Insert network monitoring data failed, err: %v", err)
+ }
+}
+
+var c *cron.Cron
+var monitorCronID int
+
+func init() {
+ c = cron.New()
+ c.Start()
+ monitorCronID = 0
+}
+
+// StartMonitor 开始监控任务 removeBefore删除上次任务,间隔interval分钟
+func StartMonitor(removeBefore bool, interval string) error {
+ if removeBefore {
+ c.Remove(cron.EntryID(monitorCronID))
+ }
+
+ // 读取配置
+ if interval == "" {
+ v, err := dborm.XormGetConfig("SystemMonitor", "sampleTime")
+ if err != nil {
+ return err
+ }
+ data := make(map[string]any)
+ err = json.Unmarshal([]byte(v["value_json"].(string)), &data)
+ if err != nil {
+ log.Error("json StartMonitor:%s", err.Error())
+ return err
+ }
+ interval = data["sampleTime"].(string)
+ }
+
+ imservice := NewIMonitorService()
+ monitorID, err := c.AddJob(fmt.Sprintf("@every %sm", interval), imservice)
+ if err != nil {
+ return err
+ }
+ imservice.Run()
+ monitorCronID = int(monitorID)
+ return nil
+}
diff --git a/features/monitor/psnet/psnet.go b/features/monitor/psnet/psnet.go
new file mode 100644
index 0000000..8a12b4a
--- /dev/null
+++ b/features/monitor/psnet/psnet.go
@@ -0,0 +1,94 @@
+package psnet
+
+import (
+ "fmt"
+ "net"
+ "net/http"
+ "time"
+
+ "nms_nbi/lib/core/utils/ctx"
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/services"
+ "nms_nbi/lib/wsinfo"
+ "nms_nbi/restagent/config"
+
+ "github.com/gorilla/websocket"
+ "github.com/shirou/gopsutil/process"
+)
+
+var (
+ // websockte通信
+ UriWs = config.DefaultUriPrefix + "/monitor/{apiVersion}/psnet/ws"
+ // 停止进程
+ UriStop = config.DefaultUriPrefix + "/monitor/{apiVersion}/psnet/stop"
+
+ // 检查ip端口请求
+ UriPing = config.DefaultUriPrefix + "/monitor/{apiVersion}/psnet/ping"
+)
+
+// 进程管理
+var wsUpgrade = websocket.Upgrader{
+ CheckOrigin: func(r *http.Request) bool {
+ return true
+ },
+}
+
+// ProcessWs
+func ProcessWs(w http.ResponseWriter, r *http.Request) {
+ ws, err := wsUpgrade.Upgrade(w, r, nil)
+ if err != nil {
+ return
+ }
+ wsClient := wsinfo.NewWsClient("processClient", ws)
+ go wsClient.Read()
+ go wsClient.Write()
+}
+
+// 停止进程 {"PID":30040}
+func StopProcess(w http.ResponseWriter, r *http.Request) {
+ // json 請求參數獲取
+ var bodyArgs struct {
+ PID int32 `json:"PID" validate:"required"`
+ }
+ err := ctx.ShouldBindJSON(r, &bodyArgs)
+ if err != nil {
+ log.Error("io.ReadAll is failed:", err)
+ services.ResponseErrorWithJson(w, 400, err.Error())
+ return
+ }
+
+ proc, err := process.NewProcess(bodyArgs.PID)
+ if err != nil {
+ services.ResponseErrorWithJson(w, 400, err.Error())
+ return
+ }
+ if err := proc.Kill(); err != nil {
+ services.ResponseErrorWithJson(w, 400, err.Error())
+ return
+ }
+ services.ResponseStatusOK200Null(w)
+}
+
+// 检查ip端口请求
+func Ping(w http.ResponseWriter, r *http.Request) {
+ // json 請求參數獲取
+ var bodyArgs struct {
+ Host string `json:"host" validate:"required"`
+ Port string `json:"port" validate:"required"`
+ }
+ err := ctx.ShouldBindJSON(r, &bodyArgs)
+ if err != nil {
+ log.Error("io.ReadAll is failed:", err)
+ services.ResponseErrorWithJson(w, 400, err.Error())
+ return
+ }
+
+ conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%s", bodyArgs.Host, bodyArgs.Port), 3*time.Second)
+ if err != nil {
+ services.ResponseErrorWithJson(w, 400, err.Error())
+ return
+ }
+ defer conn.Close()
+ services.ResponseStatusOK200Null(w)
+
+}
diff --git a/features/nbi/nbi.go b/features/nbi/nbi.go
new file mode 100644
index 0000000..df5e8cb
--- /dev/null
+++ b/features/nbi/nbi.go
@@ -0,0 +1,260 @@
+package nbi
+
+import (
+ "bytes"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "nms_nbi/lib/dborm"
+
+ "github.com/go-resty/resty/v2"
+
+ "github.com/gorilla/mux"
+
+ "nms_nbi/lib/global"
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/oauth"
+ "nms_nbi/lib/services"
+ "nms_nbi/lib/session"
+ "nms_nbi/restagent/config"
+)
+
+type ErrorOAuthResponse struct {
+ Error map[string]interface{}
+}
+
+type FailOAuthResponse struct {
+ Error struct {
+ ErrorCode string
+ ErrorInfo string
+ }
+}
+
+type ApiResponse struct {
+ ResultCode string
+ ResultMessage interface{}
+}
+
+var globalSession = session.NewSessManager("restagent")
+
+var (
+ MAX_RMUID_NUM int
+ MAX_ALARMID_NUM int
+ MAX_PMUID_NUM int
+ MAX_SUBID_NUM int
+ MAX_URI_LEN int
+ RMUID_REGEXP string
+)
+
+var (
+ // Northbound interface
+ GetNRMUri = config.DefaultUriPrefix + "/resourceManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/{objectTypeValue}"
+ NorthboundGetAlarmUri = config.DefaultUriPrefix + "/faultManagement/{apiVersion}/alarms" // ?alarmIds={alarmIdValues}
+
+ CustomGetNRMUri = config.UriPrefix + "/resourceManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/{objectTypeValue}"
+)
+
+func CheckParameterName(r *http.Request) []string {
+ var errorParams []string
+ vars := r.URL.Query()
+ for k, v := range vars {
+ log.Debug("vars:", k, v)
+ if k != "rmUIDs" && k != "fields" {
+ errorParams = append(errorParams, k)
+ }
+ }
+
+ return errorParams
+}
+
+func GetRmUIDArr(r *http.Request) []string {
+ vars := r.URL.Query()
+ rmUIDs, ok := vars["rmUIDs"]
+ if !ok {
+ log.Debug("rmUIDs is not exist")
+ return nil
+ }
+
+ var rmUIDValues []string
+ for _, r := range rmUIDs {
+ if r != "" {
+ rmUIDValues = global.MergeStringArr(rmUIDValues, strings.Split(r, `,`))
+ }
+ }
+
+ return rmUIDValues
+}
+
+func GetAttrNameArr(r *http.Request) []string {
+ vars := r.URL.Query()
+ fields, ok := vars["fields"]
+ if !ok {
+ log.Debug("attributeNames does not exist")
+ return nil
+ }
+ var attrNames []string
+ for _, a := range fields {
+ if a != "" {
+ attrNames = global.MergeStringArr(attrNames, strings.Split(a, `,`))
+ }
+ }
+
+ return attrNames
+}
+
+func CheckValidRmUID(rmUIDs []string) []string {
+ log.Debug("CheckValidRmUID processing... ")
+
+ var invalidRmUIDs []string
+ for _, r := range rmUIDs {
+ if !global.MatchRmUID(RMUID_REGEXP, r) {
+ invalidRmUIDs = append(invalidRmUIDs, r)
+ }
+ }
+
+ return invalidRmUIDs
+}
+
+func CheckLocalRmUID(rmUIDs []string) string {
+ log.Debug("GetLocalRmUID processing... ")
+
+ rmUID := config.GetRmUIDFromConfig()
+ for _, r := range rmUIDs {
+ if r == rmUID {
+ return rmUID
+ }
+ }
+
+ return ""
+}
+
+func NBIGetNRMFromNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("NBIGetNRMFromNF processing... ")
+
+ // response 414-4 uri too long ? (optional)
+ // todo ... ?
+ if bytes.Count([]byte(r.RequestURI), nil) > config.GetUriMaxLenFromConfig() {
+ log.Error("Request Uri too long:", bytes.Count([]byte(r.RequestURI), nil), config.GetUriMaxLenFromConfig())
+ services.ResponseRequestURITooLong414UriTooLong(w)
+ return
+ }
+
+ // check media type(content type) only support "application/json"
+ // response 415-1
+ if !services.IsVallidContentType(r, config.GetYamlConfig().OMC.CheckContentType) {
+ log.Debug("Invalid Content-Type")
+ services.ResponseUnsupportedMediaType415(w)
+ return
+ }
+
+ // error processing ...
+ // 401-1 response
+ token, ret := oauth.IsCarriedToken(r)
+ if ret == false {
+ log.Error("AccessToken is not carried")
+ services.ResponseUnauthorized401AccessTokenNotCarried(w)
+ return
+ }
+
+ // 401-2 response
+ if dborm.XormExistValidToken(token, config.GetExpiresFromConfig()) == false {
+ log.Error("AccessToken fails or does not exist")
+ services.ResponseUnauthorized401AccessTokenNotExist(w)
+ return
+ }
+
+ _, err := dborm.XormUpdateSessionShakeTime(token)
+ if err != nil {
+ log.Error("Failed to update session table:", err)
+ services.ResponseUnauthorized401AccessTokenNotExist(w)
+ return
+ }
+
+ /*
+ // response 403 Forbidden, permissions deny
+ // todo...
+ plist := globalSession.GetPermissionFromSession(token)
+ log.Debug("permission list:", plist)
+ if len(plist) == 0 || plist[0] == false {
+ log.Debug("User permission deny")
+ services.ResponseForbidden403NotPermission(w)
+ return
+ }
+ */
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+
+ apiVer := vars["apiVersion"]
+ if apiVer != "v1" {
+ log.Error("Uri is invalid")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ // response 406-1
+ rmUIDValues := GetRmUIDArr(r)
+ if rmUIDValues == nil {
+ log.Error("missing parameter: rmUIDs")
+ services.ResponseNotAcceptable406MissingParam(w)
+ return
+ }
+
+ // response 406-2
+ errorParams := CheckParameterName(r)
+ if errorParams != nil {
+ log.Error("parameter name error: ", errorParams)
+ services.ResponseNotAcceptable406ParamError(w, errorParams)
+ return
+ }
+
+ // response 400-5
+ if len(rmUIDValues) == 0 {
+ log.Error("rmUIDs is wrong or NULL")
+ services.ResponseBadRequest400WrongParamValue(w)
+ return
+ }
+
+ // response 414-1
+ if len(rmUIDValues) > config.GetYamlConfig().Params.RmUIDMaxNum {
+ log.Error("rmUID greater than", config.GetYamlConfig().Params.RmUIDMaxNum)
+ services.ResponseRequestURITooLong414NRMNumExceed(w, config.GetYamlConfig().Params.RmUIDMaxNum)
+ return
+ }
+
+ var response *resty.Response
+ respMsg := make(map[string]interface{})
+ for _, rmUID := range rmUIDValues {
+ neInfo, err := dborm.XormGetNeInfoByRmUID(neType, rmUID)
+ if err != nil {
+ log.Error("dborm.XormGetNeInfo is failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+
+ requestURI2NF := fmt.Sprintf("http://%s:%v%s", neInfo.Ip, neInfo.Port, r.RequestURI)
+ log.Debug("requestURI2NF: GET ", requestURI2NF)
+
+ client := resty.New()
+ response, 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("Failed to Get from NF:", err)
+ services.ResponseInternalServerError500NFConnectRefused(w)
+ return
+ }
+ switch response.StatusCode() {
+ case http.StatusOK, http.StatusAccepted, http.StatusNoContent, http.StatusCreated:
+ respMsg["data"] = response
+ default:
+ if response != nil {
+ services.TransportResponse(w, response.StatusCode(), response.Body())
+ }
+ }
+ }
+
+ services.TransportResponse(w, response.StatusCode(), response.Body())
+}
diff --git a/features/nbi/snmp.go b/features/nbi/snmp.go
new file mode 100644
index 0000000..f288e90
--- /dev/null
+++ b/features/nbi/snmp.go
@@ -0,0 +1,203 @@
+package nbi
+
+import (
+ "bytes"
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/gorilla/mux"
+ g "github.com/gosnmp/gosnmp"
+
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+)
+
+func init() {
+ conf := config.GetYamlConfig()
+ // Default is a pointer to a GoSNMP struct that contains sensible defaults
+ // eg port 161, community public, etc
+ g.Default.Target = conf.NE.Addr
+ g.Default.Port = conf.NE.Port
+ err := g.Default.Connect()
+ if err != nil {
+ fmt.Printf("Connect() err: %v", err)
+ }
+ //defer g.Default.Conn.Close()
+ MAX_RMUID_NUM = config.GetRmUIDMaxNumFromConfig()
+ MAX_ALARMID_NUM = config.GetAlarmIDMaxNumFromConfig()
+ MAX_PMUID_NUM = config.GetPmIDMaxNumFromConfig()
+ MAX_SUBID_NUM = config.GetSubIDMaxNumFromConfig()
+ MAX_URI_LEN = config.GetUriMaxLenFromConfig()
+ RMUID_REGEXP = config.GetRmUIDRegexpFromConfig()
+}
+
+func GetNRMByUri(w http.ResponseWriter, r *http.Request) {
+ log.Debug("GetNRMByUri processing... ")
+
+ // response 414-4 uri too long ? (optional)
+ // todo ... ?
+ if bytes.Count([]byte(r.RequestURI), nil) > MAX_URI_LEN {
+ log.Error("Request Uri too long:", bytes.Count([]byte(r.RequestURI), nil))
+ services.ResponseRequestURITooLong414UriTooLong(w)
+ return
+ }
+
+ // check media type(content type) only support "application/json"
+ // response 415-1
+ if !services.IsVallidContentType(r, config.GetYamlConfig().OMC.CheckContentType) {
+ log.Debug("Invalid Content-Type")
+ services.ResponseUnsupportedMediaType415(w)
+ return
+ }
+
+ // error processing ...
+ // 401-1 response
+ token, ret := globalSession.IsCarriedToken(r)
+ if ret == false {
+ log.Error("AccessToken is not carried")
+ services.ResponseUnauthorized401AccessTokenNotCarried(w)
+ return
+ }
+
+ // 401-2 response
+ if globalSession.IsValidToken(token) == false {
+ log.Error("AccessToken fails or does not exist")
+ services.ResponseUnauthorized401AccessTokenNotExist(w)
+ return
+ }
+ // response 403 Forbidden, permissions deny
+ // todo...
+ plist := globalSession.GetPermissionFromSession(token)
+ log.Debug("permission list:", plist)
+ if len(plist) == 0 || plist[0] == false {
+ log.Error("User permission deny")
+ services.ResponseForbidden403NotPermission(w)
+ return
+ }
+
+ vars := mux.Vars(r)
+ qeuryUri := vars["apiCategory"] + "/" + vars["elementTypeValue"] + "/" + vars["objectTypeValue"]
+ log.Debug("Get by Uri: ", qeuryUri)
+
+ apiVer := vars["apiVersion"]
+ if apiVer != "v1" {
+ log.Error("Uri is invalid")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ // response 406-1
+ rmUIDValues := GetRmUIDArr(r)
+ if rmUIDValues == nil {
+ log.Error("missing parameter: rmUIDs")
+ services.ResponseNotAcceptable406MissingParam(w)
+ return
+ }
+
+ // response 406-2
+ errorParams := CheckParameterName(r)
+ if errorParams != nil {
+ log.Error("parameter name error: ", errorParams)
+ services.ResponseNotAcceptable406ParamError(w, errorParams)
+ return
+ }
+
+ // response 400-5
+ if len(rmUIDValues) == 0 {
+ log.Error("rmUIDs is wrong or NULL")
+ services.ResponseBadRequest400WrongParamValue(w)
+ return
+ }
+
+ // response 414-1
+ if len(rmUIDValues) > MAX_RMUID_NUM {
+ log.Error("rmUID greater than", MAX_RMUID_NUM)
+ services.ResponseRequestURITooLong414NRMNumExceed(w, MAX_RMUID_NUM)
+ return
+ }
+
+ // response 400-1
+ // check rmUID is valid
+ // todo ...
+ invalidRmUIDs := CheckValidRmUID(rmUIDValues)
+ if len(invalidRmUIDs) != 0 {
+ log.Error("rmUID is invalid")
+ services.ResponseBadRequest400RmUIDsIsInvalid(w, invalidRmUIDs)
+ return
+ }
+
+ // response 404-2
+ rmUID := CheckLocalRmUID(rmUIDValues)
+ if rmUID == "" {
+ log.Error("rmUID does not exist")
+ services.ResponseNotFound404NRMNotExist(w, rmUIDValues)
+ return
+ }
+
+ // response 404-1, uri is not exist in map
+ attrNames := GetAttrNameArr(r)
+ var Oids []string
+ Oids = *config.GetOidByFileds(qeuryUri, attrNames, &Oids)
+ if len(Oids) == 0 {
+ log.Error("Nothing of config map")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ // response 404-1, uri is not exist in map
+ var nameOids []config.NameOid
+ nameOids = *config.GetDataOidByFields(qeuryUri, attrNames, &nameOids)
+ if len(nameOids) == 0 {
+ log.Error("Nothing of config map")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ result, err2 := g.Default.Get(Oids) // Get() accepts up to g.MAX_OIDS
+ if err2 != nil {
+ log.Fatalf("Get() err: %v", err2)
+
+ }
+
+ // var nameValues []config.NameValue
+ var nameValue config.NameValue
+
+ nameValues := make(map[string]interface{})
+ nameValues["rmUID"] = rmUID
+ for i, variable := range result.Variables {
+ nameValue.Name = nameOids[i].Name
+ log.Debugf("%d: oid: %s name: %s\n", i, variable.Name, nameValue.Name)
+ // if nameOids[i].Oid == variable.Name && global.IsContain(attributeNames, nameValue.Name) {
+ if nameOids[i].Oid == variable.Name {
+ // the Value of each variable returned by Get() implements
+ // interface{}. You could do a type switch...
+ switch variable.Type {
+ case g.OctetString:
+ bytes := variable.Value.([]byte)
+ log.Debugf("string: %s\n", string(bytes))
+ nameValue.Value = string(bytes)
+ nameValues[nameValue.Name] = nameValue.Value
+ case g.Integer:
+ value := variable.Value.(int)
+ log.Debugf("integer: %d\n", value)
+ nameValue.Value = strconv.Itoa(value)
+ nameValues[nameValue.Name] = nameValue.Value
+ case g.IPAddress:
+ value := variable.Value.(string)
+ log.Debugf("IPAddress: %s\n", variable.Value)
+ nameValue.Value = value
+ nameValues[nameValue.Name] = nameValue.Value
+ default:
+ // ... or often you're just interested in numeric values.
+ // ToBigInt() will return the Value as a BigInt, for plugging
+ // into your calculations.
+ log.Debugf("number: %d\n", g.ToBigInt(variable.Value))
+ }
+ }
+ }
+
+ getResponse := services.DataResponse{nameValues}
+ services.ResponseWithJson(w, http.StatusOK, getResponse)
+}
diff --git a/features/pm/performance.go b/features/pm/performance.go
new file mode 100644
index 0000000..628152e
--- /dev/null
+++ b/features/pm/performance.go
@@ -0,0 +1,1000 @@
+package pm
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "math"
+ "net/http"
+ "strconv"
+ "time"
+
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/global"
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+
+ "xorm.io/xorm"
+
+ wsService "nms_nbi/src/modules/ws/service"
+
+ "github.com/go-resty/resty/v2"
+ _ "github.com/go-sql-driver/mysql"
+ "github.com/gorilla/mux"
+)
+
+type Response struct {
+ Data interface{} `json:"data"`
+}
+
+type KpiReport struct {
+ Timestamp string `json:"TimeStamp"`
+ Task struct {
+ Period struct {
+ StartTime string `json:"StartTime"`
+ EndTime string `json:"EndTime"`
+ } `json:"Period"`
+ NE struct {
+ NEName string `json:"NEName"`
+ RmUID string `json:"rmUID"`
+ NeType string `json:"NeType"`
+ KPIs []struct {
+ KPIID string `json:"KPIID"`
+ Value int `json:"Value"`
+ Err string `json:"Err"`
+ } `json:"KPIs"`
+ } `json:"NE"`
+ } `json:"Task"`
+}
+
+type GoldKpi struct {
+ // Id int `json:"-" xorm:"pk 'id' autoincr"`
+ Date string `json:"date" xorm:"date"`
+ Index int `json:"index"`
+ Granularity int8 `json:"granularity"`
+ StartTime string `json:"startTime"`
+ EndTime string `json:"endTime"`
+ NEName string `json:"neName" xorm:"ne_name"`
+ RmUid string `json:"rmUid" xorm:"rm_uid"`
+ NEType string `json:"neType" xorm:"ne_type"`
+ KpiId string `json:"kpiId" xorm:"kpi_id"`
+ Value int `json:"value"`
+ Error string `json:"error"`
+ Timestamp string `json:"timestamp"`
+}
+
+var (
+ // performance management
+ PerformanceUri = config.DefaultUriPrefix + "/performanceManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/kpiReport/{index}"
+ MeasureTaskUri = config.DefaultUriPrefix + "/performanceManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/measureTask"
+ MeasureReportUri = config.DefaultUriPrefix + "/performanceManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/measureReport"
+ MeasureReportFmt = config.DefaultUriPrefix + "/performanceManagement/v1/elementType/%s/objectType/measureReport"
+ MeasurementUri = config.DefaultUriPrefix + "/performanceManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/measurement/{index}"
+ UriMeasureTask = config.DefaultUriPrefix + "/performanceManagement/{apiVersion}/measureTask/{netype}"
+
+ // performance management
+ CustomPerformanceUri = config.UriPrefix + "/performanceManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/kpiReport/{index}"
+ CustomMeasureTaskUri = config.UriPrefix + "/performanceManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/measureTask"
+ CustomMeasureReportUri = config.UriPrefix + "/performanceManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/measureReport"
+ CustomMeasureReportFmt = config.UriPrefix + "/performanceManagement/v1/elementType/%s/objectType/measureReport"
+ CustomMeasurementUri = config.UriPrefix + "/performanceManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/measurement/{index}"
+ CustomUriMeasureTask = config.UriPrefix + "/performanceManagement/{apiVersion}/measureTask/{netype}"
+)
+
+var xEngine *xorm.Engine
+
+type DatabaseClient struct {
+ dbType string
+ dbUrl string
+ dbConnMaxLifetime time.Duration
+ dbMaxIdleConns int
+ dbMaxOpenConns int
+ IsShowSQL bool
+
+ XEngine *xorm.Engine
+}
+
+var DbClient DatabaseClient
+
+func InitDbClient(dbType, dbUser, dbPassword, dbHost, dbPort, dbName string) error {
+ DbClient.dbUrl = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=true&loc=Local",
+ dbUser, dbPassword, dbHost, dbPort, dbName)
+ DbClient.dbType = dbType
+ DbClient.dbConnMaxLifetime = 0
+ DbClient.dbMaxIdleConns = 0
+ DbClient.dbMaxOpenConns = 0
+ if log.GetLevel() == log.LOG_TRACE {
+ DbClient.IsShowSQL = true
+ }
+ log.Debugf("dbType:%s dbUrl:%s:******@tcp(%s:%s)/%s??charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=true&loc=Local",
+ dbType, dbUser, dbHost, dbPort, dbName)
+
+ var err error
+ DbClient.XEngine, err = xorm.NewEngine(DbClient.dbType, DbClient.dbUrl)
+ if err != nil {
+ log.Error("Failed to connet database:", err)
+ return err
+ }
+ DbClient.XEngine.SetConnMaxLifetime(DbClient.dbConnMaxLifetime)
+ DbClient.XEngine.SetMaxIdleConns(DbClient.dbMaxIdleConns)
+ DbClient.XEngine.SetMaxOpenConns(DbClient.dbMaxOpenConns)
+ if DbClient.IsShowSQL {
+ DbClient.XEngine.ShowSQL(true)
+ }
+ xEngine = DbClient.XEngine
+
+ return nil
+}
+
+func XormConnectDatabase(dbType, dbUser, dbPassword, dbHost, dbPort, dbName string) (*xorm.Engine, error) {
+ sqlStr := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=true&loc=Local",
+ dbUser, dbPassword, dbHost, dbPort, dbName)
+ log.Debugf("dbType:%s Connect to:%s:******@tcp(%s:%s)/%s?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=true&loc=Local",
+ dbType, dbUser, dbHost, dbPort, dbName)
+ var err error
+ xEngine, err = xorm.NewEngine(dbType, sqlStr) //1、Create xorm engine
+ if err != nil {
+ log.Error("Failed to connect database:", err)
+ return nil, err
+ }
+ if log.GetLevel() == log.LOG_TRACE {
+ xEngine.ShowSQL(true)
+ }
+ return xEngine, nil
+}
+
+func GetDateFromTimeString(fmtString string, timeString string) string {
+ t, _ := time.ParseInLocation(fmtString, timeString, time.Local)
+ return t.Format("2006-01-02")
+}
+
+func GetDateTimeFromTimeString(fmtString string, timeString string) string {
+ t, _ := time.ParseInLocation(fmtString, timeString, time.Local)
+ return t.Format(global.DateTime)
+}
+
+// process alarm post message from NFs
+func PostKPIReportFromNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("PostKPIReportFromNF processing... ")
+
+ vars := mux.Vars(r)
+ apiVer := vars["apiVersion"]
+ if apiVer != global.ApiVersionV1 {
+ log.Error("Uri api version is invalid. apiVersion:", apiVer)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ // body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen))
+ body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen))
+ if err != nil {
+ log.Error("Faile to io.ReadAll: ", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ log.Trace("Request body:", string(body))
+ kpiReport := new(KpiReport)
+ _ = json.Unmarshal(body, &kpiReport)
+ log.Trace("kpiReport:", kpiReport)
+
+ session := xEngine.NewSession()
+ defer session.Close()
+ goldKpi := new(GoldKpi)
+ layout := time.RFC3339Nano
+ goldKpi.Date = GetDateFromTimeString(layout, kpiReport.Task.Period.StartTime)
+ goldKpi.Index, _ = strconv.Atoi(vars["index"])
+ goldKpi.StartTime = global.GetFmtTimeString(layout, kpiReport.Task.Period.StartTime, time.DateTime)
+ goldKpi.EndTime = global.GetFmtTimeString(layout, kpiReport.Task.Period.EndTime, time.DateTime)
+ // get time granularity from startTime and endTime
+ seconds, _ := global.GetSecondDuration(goldKpi.StartTime, goldKpi.EndTime)
+ goldKpi.Granularity = 60
+ if seconds != 0 && seconds <= math.MaxInt8 && seconds >= math.MinInt8 {
+ goldKpi.Granularity = int8(seconds)
+ }
+ goldKpi.NEName = kpiReport.Task.NE.NEName
+ goldKpi.RmUid = kpiReport.Task.NE.RmUID
+ goldKpi.NEType = kpiReport.Task.NE.NeType
+ goldKpi.Timestamp = global.GetFmtTimeString(layout, kpiReport.Timestamp, time.DateTime)
+ // 黄金指标事件对象
+ kpiEvent := map[string]any{
+ // kip_id ...
+ "neType": goldKpi.NEType,
+ "neName": goldKpi.NEName,
+ "startIndex": goldKpi.Index,
+ "timeGroup": goldKpi.StartTime,
+ }
+ for _, k := range kpiReport.Task.NE.KPIs {
+ kpiEvent[k.KPIID] = k.Value // kip_id
+ goldKpi.KpiId = k.KPIID
+ goldKpi.Value = k.Value
+ goldKpi.Error = k.Err
+ log.Trace("goldKpi:", goldKpi)
+
+ // 启动事务
+ err := session.Begin()
+ if err != nil {
+ log.Error("Failed to Begin gold_kpi:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ gkpi := &GoldKpi{}
+ _, err = session.Where("id = ?", 1).ForUpdate().Get(gkpi)
+ if err != nil {
+ // 回滚事务
+ session.Rollback()
+ log.Error("Failed to ForUpdate gold_kpi:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ affected, err := session.Insert(goldKpi)
+ if err != nil && affected <= 0 {
+ session.Rollback()
+ log.Error("Failed to insert gold_kpi:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ // 提交事务
+ err = session.Commit()
+ if err != nil {
+ log.Error("Failed to Commit gold_kpi:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ }
+
+ // 推送到ws订阅组
+ wsService.NewWSSendImpl.ByGroupID(wsService.GROUP_KPI, kpiEvent)
+
+ services.ResponseStatusOK200Null(w)
+}
+
+type MeasureTask struct {
+ Tasks []Task `json:"Tasks"`
+ NotifyUrl string `json:"NotifyUrl"` /* "http://xEngine.xEngine.xEngine.x:xxxx/api/rest/performanceManagement/v1/elementType/smf/objectType/measureReport */
+}
+
+type Task struct {
+ Id int `json:"Id"`
+
+ StartTime string `json:"StartTime"`
+ EndTime string `json:"EndTime"`
+
+ Schedule struct {
+ Type string `json:"Type"` // 计划类型:Weekly/Monthly, 如果type为"", 则任务以StartTime和EndTime为条件进行统计, 否则以Shedule方式进行
+ Days []int `json:"Days"` // Weekly: [0,1,...,5,6] 星期日为0, Monthly: [1,2,3,...,30,31]
+ Periods []dborm.Period `json:"Periods"`
+ /*
+ Periods []struct {
+ Start string `json:"Start"` // 零点或者零点加测量粒度的整数倍
+ End string `json:"End"` //零点加测量粒度的整数倍
+ } `json:"Periods"`
+ */
+ } `json:"Schedule"`
+
+ GranulOption string `json:"GranulOption"` // 测量粒度选项:15M/30M/60M/24H
+ KPISet []dborm.KpiSetJ `json:"KPISet"`
+ /*
+ KPISet []struct {
+ Code string `json:"Code"` // 统计编码 如:SMFHA01
+ KPIs []string `json:"KPIs` // 指标项集合 ["SMF.AttCreatePduSession", "SMF.AttCreatePduSession._Dnn"]
+ } `json:"KPISet"`
+ */
+}
+
+type MeasureReport struct {
+ Id int `json:"Id"`
+ TimeStamp string `json:"TimeStamp"`
+ NeName string `json:"NeName"`
+ RmUID string `json:"rmUID"`
+ NeType string `json:"NeType"`
+
+ Report struct {
+ Period struct {
+ StartTime string `json:"StartTime"`
+ EndTime string `json:"EndTime"`
+ } `json:"Period"`
+
+ Datas []struct {
+ Code string `json:"Code"` // 统计编码 如:SMFHA01
+ KPIs []struct {
+ KPIID string `json:"KPIID"` // 指标项, 如: SMF.AttCreatePduSession._Dnn
+ KPIValues []struct {
+ Name string `json:"Name"` // 单个的写"Total", 或者指标项有多个测量项,如Dnn的名称写对应的Dnn"cmnet"/"ims"
+ Value int64 `json:"Value"`
+ } `json:"KPIValues"`
+ } `json:"KPIs"`
+ } `json:"Datas"`
+ } `json:"Report"`
+}
+
+type MeasureData struct {
+ // Id int `json:"id" xorm:"pk 'id' autoincr"`
+ Id int `json:"id" xorm:"-"`
+ Date string `json:"date" xorm:"date"`
+ TaskId int `json:"taskId"`
+ NeType string `json:"neType" xorm:"ne_type"`
+ NeName string `json:"neName" xorm:"ne_name"`
+ RmUid string `json:"rmUid" xorm:"rm_uid"`
+ GranulOption string `json:"granulOption" xorm:"granul_option"`
+ StartTime string `json:"startTime"`
+ EndTime string `json:"endTime"`
+ KpiCode string `json:"kpiCode" xorm:"kpi_code"`
+ KpiId string `json:"kpiId" xorm:"kpi_id"`
+ KpiExt string `json:"kpiExt" xorm:"kpi_ext"`
+ Value int64 `json:"value"`
+ Timestamp string `json:"timestamp"`
+}
+
+// process measure report from NFs
+func PostMeasureReportFromNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("PostMeasureReportFromNF processing... ")
+
+ // vars := mux.Vars(r)
+ // neType := vars["elementTypeValue"]
+ vars := mux.Vars(r)
+ apiVer := vars["apiVersion"]
+ if apiVer != global.ApiVersionV1 {
+ log.Error("Uri api version is invalid. apiVersion:", apiVer)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ // body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen))
+ body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen))
+ if err != nil {
+ log.Error("Faile to io.ReadAll: ", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ log.Debug("Request body:", string(body))
+ measureReport := new(MeasureReport)
+ _ = json.Unmarshal(body, &measureReport)
+ log.Debug("measureReport:", measureReport)
+
+ session := xEngine.NewSession()
+ defer session.Close()
+ measureData := new(MeasureData)
+ layout := global.DateTime
+ measureData.Date = GetDateFromTimeString(layout, measureReport.Report.Period.StartTime)
+ measureData.TaskId = measureReport.Id
+ measureData.StartTime = measureReport.Report.Period.StartTime
+ measureData.EndTime = measureReport.Report.Period.EndTime
+ measureData.NeType = measureReport.NeType
+ measureData.NeName = measureReport.NeName
+ measureData.RmUid = measureReport.RmUID
+ measureData.GranulOption, _ = dborm.XormGetSingleCol("measure_task", "granul_option", fmt.Sprintf("id=%d", measureReport.Id))
+ t, _ := strconv.ParseInt(measureReport.TimeStamp, 10, 64)
+ timestamp := time.Unix(t, 0)
+ log.Debug("timestamp:", timestamp.Format(layout))
+ measureData.Timestamp = timestamp.Format(layout)
+ log.Debug("Datas:", measureReport.Report.Datas)
+ for _, d := range measureReport.Report.Datas {
+ measureData.KpiCode = d.Code
+
+ log.Debug("KPIs:", d.KPIs)
+ for _, k := range d.KPIs {
+ measureData.KpiId = k.KPIID
+
+ log.Debug("KPIValues:", k.KPIValues)
+ if len(k.KPIValues) != 0 {
+ for _, v := range k.KPIValues {
+ measureData.KpiExt = v.Name
+ measureData.Value = v.Value
+ log.Debug("measureData:", measureData)
+
+ affected, err := session.Insert(measureData)
+ if err != nil && affected <= 0 {
+ log.Error("Failed to insert measure_data:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+ }
+ } else {
+ measureData.Value = 0
+ log.Debug("measureData:", measureData)
+
+ affected, err := session.Insert(measureData)
+ if err != nil && affected <= 0 {
+ log.Error("Failed to insert measure_data:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+ }
+ }
+ }
+
+ services.ResponseStatusOK204NoContent(w)
+}
+
+func PostMeasureTaskToNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("PostMeasureTaskToNF processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+ params := r.URL.Query()
+ taskIds := params["id"]
+ log.Debug("taskIds:", taskIds)
+
+ var response *resty.Response
+ client := resty.New()
+ measureTask := new(MeasureTask)
+ measureTask.Tasks = make([]Task, 1)
+ for _, taskId := range taskIds {
+ id, _ := strconv.Atoi(taskId)
+ task, err := dborm.GetMeasureTask(id)
+ if err != nil {
+ log.Error("Failed to connect database: ", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+ log.Debug("Table Task:", task)
+
+ measureTask.Tasks[0].Id = task.Id
+ measureTask.Tasks[0].StartTime = task.StartTime
+ measureTask.Tasks[0].EndTime = task.EndTime
+ // v := new(dborm.ScheduleJson)
+ // _ = json.Unmarshal(task.Schedule, v)
+ // measureTask.Task[0].Schedule.Type = v.Type
+ // measureTask.Task[0].Schedule.Days = v.Days
+ if len(task.Schedule) >= 1 {
+ measureTask.Tasks[0].Schedule.Type = task.Schedule[0].Type
+ measureTask.Tasks[0].Schedule.Days = task.Schedule[0].Days
+ }
+ //v := new(dborm.ScheduleJ)
+ //_ = json.Unmarshal(task.Schedule, v)
+ measureTask.Tasks[0].Schedule.Periods = task.Periods
+ measureTask.Tasks[0].GranulOption = task.GranulOption
+
+ measureTask.Tasks[0].KPISet = task.KpiSet
+ ips, err := global.GetIps()
+ if err != nil {
+ log.Error("Failed to get local IP:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Debug("ips:", ips)
+
+ measureTask.NotifyUrl = global.SetNotifyUrl(ips[0], config.GetYamlConfig().Rest[0].Port, fmt.Sprintf(MeasureReportFmt, neType))
+ log.Debug("Measure Task to NF:", measureTask)
+
+ if len(task.NeIds) == 0 {
+ var neInfos []dborm.NeInfo
+ err := dborm.XormGetNeInfoByNeType(neType, &neInfos)
+ if err != nil {
+ log.Error("Failed to dborm.XormGetNeInfoByNeType:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ for _, neInfo := range neInfos {
+ task.NeIds = append(task.NeIds, neInfo.NeId)
+ }
+ }
+
+ for _, neId := range task.NeIds {
+ var err error
+ neInfo, err := dborm.XormGetNeInfo(neType, neId)
+ if err != nil {
+ log.Error("Failed to dborm.XormGetNeInfo:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ if neInfo == nil {
+ err := errors.New(fmt.Sprintf("not found target NE neType=%s, neId=%s", neType, neId))
+ log.Error(err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ requestURI2NF := fmt.Sprintf("http://%s:%v%s", neInfo.Ip, neInfo.Port, r.RequestURI)
+ log.Debug("requestURI2NF: POST ", requestURI2NF)
+
+ switch task.Status {
+ case dborm.MeasureTaskStatusInactive:
+ body, _ := json.Marshal(measureTask)
+ log.Debug("body: ", string(body))
+
+ log.Debug("User-Agent: ", config.GetDefaultUserAgent())
+ response, err = client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ SetBody(body).
+ SetContentLength(true).
+ Post(requestURI2NF)
+
+ if err != nil {
+ log.Error("Post to NF failed:", err)
+ services.ResponseInternalServerError500NFConnectRefused(w)
+ return
+ }
+ log.Debug("response info: ")
+ log.Debug("Status Code:", response.StatusCode())
+ log.Debug("Status:", response.Status())
+ log.Debug("Proto:", response.Proto())
+ log.Debug("Time:", response.Time())
+ log.Debug("Received At:", response.ReceivedAt())
+ log.Debug("Size:", response.Size())
+
+ case dborm.MeasureTaskStatusSuspend:
+ body, _ := json.Marshal(measureTask)
+ log.Debug("body: ", string(body))
+ response, err = client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ SetBody(body).
+ SetContentLength(true).
+ Put(requestURI2NF)
+
+ if err != nil {
+ log.Error("Put to NF failed:", err)
+ services.ResponseInternalServerError500NFConnectRefused(w)
+ return
+ }
+ default:
+ err = errors.New(fmt.Sprintf("measure task status must be inactive id=%d", id))
+ log.Error("Unable to active measure task:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ log.Debug("StatusCode: ", response.StatusCode())
+ switch response.StatusCode() {
+ case http.StatusOK, http.StatusCreated, http.StatusNoContent, http.StatusAccepted:
+ taskInfo := new(dborm.MeasureTask)
+ taskInfo.Status = dborm.MeasureTaskStatusActive
+ taskInfo.CreateTime = time.Now().Format(time.DateTime)
+ affected, err := dborm.XormUpdateTableById(id, dborm.TableNameMeasureTask, taskInfo)
+ if err != nil {
+ log.Error("dborm.XormUpdateTableById is failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ } else if affected <= 0 {
+ log.Info("Not record affected in measure_task")
+ }
+ default:
+ log.Error("NF return failure to active measure task")
+ if response != nil {
+ log.Info("response body:", string(response.Body()))
+ services.TransportResponse(w, response.StatusCode(), response.Body())
+ return
+ } else {
+ err = errors.New(fmt.Sprintf("failed to active measure task, NF return error status=%v", response.Status()))
+ log.Error("Unable to active measure task:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ }
+ }
+ }
+
+ services.ResponseStatusOK204NoContent(w)
+}
+
+func PutMeasureTaskToNF(w http.ResponseWriter, r *http.Request) {
+
+ services.ResponseStatusOK200Null(w)
+}
+
+func DeleteMeasureTaskToNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("DeleteMeasureTaskToNF processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+ params := r.URL.Query()
+ taskIds := params["id"]
+ log.Debug("taskIds:", taskIds)
+
+ var response *resty.Response
+ respMsg := make(map[string]interface{})
+ for _, taskId := range taskIds {
+ id, _ := strconv.Atoi(taskId)
+ task, err := dborm.GetMeasureTask(id)
+ if err != nil {
+ log.Error("Failed to connect database: ", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+ log.Debug("Measure Task:", task)
+
+ if len(task.NeIds) == 0 {
+ var neInfos []dborm.NeInfo
+ err := dborm.XormGetNeInfoByNeType(neType, &neInfos)
+ if err != nil {
+ log.Error("Failed to dborm.XormGetNeInfoByNeType:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ for _, neInfo := range neInfos {
+ task.NeIds = append(task.NeIds, neInfo.NeId)
+ }
+ }
+ log.Debug("neIds:", task.NeIds)
+ if len(task.NeIds) == 0 {
+ log.Warn("Not found target NE in the measure task")
+ taskInfo := new(dborm.MeasureTask)
+ taskInfo.Status = dborm.MeasureTaskStatusDeleted
+ affected, err := dborm.XormUpdateTableById(id, dborm.TableNameMeasureTask, taskInfo)
+ if err != nil {
+ log.Error("dborm.XormUpdateTableById is failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ } else if affected <= 0 {
+ log.Info("Not record affected in measure_task")
+ }
+ services.ResponseStatusOK204NoContent(w)
+ return
+ }
+
+ for _, neId := range task.NeIds {
+ var err error
+ neInfo, err := dborm.XormGetNeInfo(neType, neId)
+ if err != nil {
+ log.Error("dborm.XormGetNeInfo is failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+ if neInfo != nil {
+ requestURI2NF := fmt.Sprintf("http://%s:%v%s", neInfo.Ip, neInfo.Port, r.RequestURI)
+ log.Debug("requestURI2NF: DELETE ", requestURI2NF)
+ client := resty.New()
+ response, err = client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ Delete(requestURI2NF)
+ if err != nil {
+ // to avoid can't delete the task for abnormal NF
+ log.Error("Failed to resty delete:", err)
+ taskInfo := new(dborm.MeasureTask)
+ taskInfo.Status = dborm.MeasureTaskStatusDeleted
+ affected, err := dborm.XormUpdateTableById(id, dborm.TableNameMeasureTask, taskInfo)
+ if err != nil {
+ log.Error("dborm.XormUpdateTableById is failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ } else if affected <= 0 {
+ log.Info("Not record affected in measure_task")
+ }
+ services.ResponseStatusOK204NoContent(w)
+ return
+ }
+
+ log.Info("StatusCode: ", response.StatusCode())
+ switch response.StatusCode() {
+ case http.StatusOK, http.StatusCreated, http.StatusNoContent, http.StatusAccepted:
+ taskInfo := new(dborm.MeasureTask)
+ taskInfo.Status = dborm.MeasureTaskStatusDeleted
+ affected, err := dborm.XormUpdateTableById(id, dborm.TableNameMeasureTask, taskInfo)
+ if err != nil {
+ log.Error("dborm.XormUpdateTableById is failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ } else if affected <= 0 {
+ log.Infof("Not record affected in measure_task")
+ }
+ services.ResponseStatusOK204NoContent(w)
+ return
+ default:
+ log.Info("response body:", string(response.Body()))
+ body := new(map[string]interface{})
+ _ = json.Unmarshal(response.Body(), &body)
+ respMsg["error"] = body
+ }
+ } else {
+ taskInfo := new(dborm.MeasureTask)
+ taskInfo.Status = dborm.MeasureTaskStatusDeleted
+ affected, err := dborm.XormUpdateTableById(id, dborm.TableNameMeasureTask, taskInfo)
+ if err != nil {
+ log.Error("dborm.XormUpdateTableById is failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ } else if affected <= 0 {
+ log.Info("Not record affected in measure_task")
+ }
+ services.ResponseStatusOK204NoContent(w)
+ return
+ }
+ }
+ }
+
+ services.ResponseWithJson(w, response.StatusCode(), respMsg)
+}
+
+func PatchMeasureTaskToNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("PatchMeasureTaskToNF processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+ params := r.URL.Query()
+ taskIds := params["id"]
+ log.Debug("taskIds:", taskIds)
+
+ var response *resty.Response
+ respMsg := make(map[string]interface{})
+ for _, taskId := range taskIds {
+ id, _ := strconv.Atoi(taskId)
+ task, err := dborm.GetMeasureTask(id)
+ if err != nil {
+ log.Error("Failed to connect database: ", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+ log.Debug("Measure Task:", task)
+
+ // for neType
+ if len(task.NeIds) == 0 {
+ var neInfos []dborm.NeInfo
+ err := dborm.XormGetNeInfoByNeType(neType, &neInfos)
+ if err != nil {
+ log.Error("Failed to dborm.XormGetNeInfoByNeType:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ for _, neInfo := range neInfos {
+ task.NeIds = append(task.NeIds, neInfo.NeId)
+ }
+ }
+
+ if len(task.NeIds) == 0 {
+ taskInfo := new(dborm.MeasureTask)
+ taskInfo.Status = dborm.MeasureTaskStatusInactive
+ affected, err := dborm.XormUpdateTableById(id, dborm.TableNameMeasureTask, taskInfo)
+ if err != nil {
+ log.Error("dborm.XormUpdateTableById is failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ } else if affected <= 0 {
+ log.Info("Not record affected in measure_task")
+ }
+ services.ResponseStatusOK204NoContent(w)
+ return
+ }
+
+ for _, neId := range task.NeIds {
+ var err error
+ neInfo, err := dborm.XormGetNeInfo(neType, neId)
+ if err != nil {
+ log.Error("dborm.XormGetNeInfo is failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+ if neInfo == nil {
+ em := errors.New("Not found NE info in database")
+ log.Error(em)
+ taskInfo := new(dborm.MeasureTask)
+ taskInfo.Status = dborm.MeasureTaskStatusInactive
+ affected, err := dborm.XormUpdateTableById(id, dborm.TableNameMeasureTask, taskInfo)
+ if err != nil {
+ log.Error("dborm.XormUpdateTableById is failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ } else if affected <= 0 {
+ log.Info("Not record affected in measure_task")
+ }
+ services.ResponseStatusOK204NoContent(w)
+ //services.ResponseInternalServerError500ProcessError(w, em)
+ return
+ }
+
+ requestURI2NF := fmt.Sprintf("http://%s:%v%s", neInfo.Ip, neInfo.Port, r.RequestURI)
+ log.Debug("requestURI2NF: PATCH ", requestURI2NF)
+ client := resty.New()
+ response, err = client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ Patch(requestURI2NF)
+ if err != nil {
+ log.Error("Patch to NF failed:", err)
+ services.ResponseInternalServerError500NFConnectRefused(w)
+ return
+ }
+
+ log.Debug("StatusCode: ", response.StatusCode())
+ switch response.StatusCode() {
+ case http.StatusOK, http.StatusCreated, http.StatusNoContent, http.StatusAccepted:
+ taskInfo := new(dborm.MeasureTask)
+ taskInfo.Status = dborm.MeasureTaskStatusInactive
+ affected, err := dborm.XormUpdateTableById(id, dborm.TableNameMeasureTask, taskInfo)
+ if err != nil {
+ log.Error("dborm.XormUpdateTableById is failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ } else if affected <= 0 {
+ log.Info("Not record affected in measure_task")
+ }
+ default:
+ log.Debug("response body:", string(response.Body()))
+ body := new(map[string]interface{})
+ _ = json.Unmarshal(response.Body(), &body)
+ respMsg["error"] = body
+ }
+ }
+ }
+
+ services.ResponseWithJson(w, response.StatusCode(), respMsg)
+ return
+}
+
+type Measurement struct {
+ Id int `json:"-" xorm:"pk 'id' autoincr"`
+ Date string `json:"-" xorm:"date"`
+ Index int `json:"Index"` // 1天中测量时间粒度(如15分钟)的切片索引: 0~95
+ Timestamp string `json:"TimeStamp" xorm:"-"`
+ NeName string `json:"NeName"` // UserLabel
+ RmUID string `json:"RmUID" xorm:"rm_uid"`
+ NeType string `json:"NeType"` // 网元类型
+ PmVersion string `json:"PmVersion"` // 性能数据版本号
+ Dn string `json:"Dn"` // (???)网元标识, 如:RJN-CMZJ-TZ,SubNetwork=5GC88,ManagedElement=SMF53456,SmfFunction=53456
+ Period string `json:"Period"` // 测量时间粒度选项:5/15/30/60
+ TimeZone string `json:"TimeZone"`
+ StartTime string `json:"StartTime"`
+
+ Datas []Data `json:"Datas"`
+}
+
+type KPIValue struct {
+ Name string `json:"Name"` // 单个的写"Total", 或者指标项有多个测量项,如Dnn的名称写对应的Dnn"cmnet"/"ims"
+ Value int64 `json:"Value"`
+}
+
+type KPI struct {
+ KPIID string `json:"KPIID"` // 指标项, 如: SMF.AttCreatePduSession._Dnn
+ KPIValues []KPIValue `json:"KPIValues"`
+}
+
+type Data struct {
+ ObjectType string `json:"ObjectType"` // 网络资源类别名称, Pm指标项列表中为空间粒度 如:SmfFunction
+ KPIs []KPI `json:"KPIs"` // 指标项, 如: SMF.AttCreatePduSession._Dnn
+}
+
+// process measurement post message from NFs
+func PostMeasurementFromNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("PostMeasurementFromNF processing... ")
+
+ vars := mux.Vars(r)
+ apiVer := vars["apiVersion"]
+ if apiVer != global.ApiVersionV1 {
+ log.Error("Uri api version is invalid. apiVersion:", apiVer)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen))
+ if err != nil {
+ log.Error("Faile to io.ReadAll: ", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ log.Debug("Request body:", string(body))
+ // measurement := new(dborm.NorthboundPm)
+ measurement := new(dborm.NorthboundPm)
+ _ = json.Unmarshal(body, &measurement)
+ log.Debug("measurement:", measurement)
+
+ session := dborm.DbClient.XEngine.NewSession()
+ defer session.Close()
+
+ // layout := global.DateTime
+ layout := time.RFC3339
+ measurement.Date = GetDateFromTimeString(layout, measurement.StartTime)
+ measurement.StartTime = GetDateTimeFromTimeString(layout, measurement.StartTime)
+ affected, err := session.Table("northbound_pm").Insert(measurement)
+ if err != nil && affected <= 0 {
+ log.Error("Failed to insert northbound_pm:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+
+ services.ResponseStatusOK204NoContent(w)
+}
+
+// get measurement message from NFs
+func GetMeasurementFromNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("GetMeasurementFromNF processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+
+ vars := mux.Vars(r)
+ apiVer := vars["apiVersion"]
+ if apiVer != global.ApiVersionV1 {
+ log.Error("Uri api version is invalid. apiVersion:", apiVer)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ neType := vars["elementTypeValue"]
+ if neType == "" {
+ log.Error("elementTypeValue is null.")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ params := r.URL.Query()
+ neIds := params["ne_id"]
+ if len(neIds) == 0 {
+ log.Error("ne_id NOT FOUND")
+ services.ResponseBadRequest400WrongParamValue(w)
+ return
+ }
+ log.Debugf("neType: %s neId:%s", neType, neIds)
+
+ //var neInfo *dborm.NeInfo
+ neInfo := new(dborm.NeInfo)
+
+ neInfo, err = dborm.XormGetNeInfo(neType, neIds[0])
+ if err != nil {
+ log.Error("dborm.XormGetNeInfo is failed:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+
+ requestURI2NF := fmt.Sprintf("http://%s:%v%s", neInfo.Ip, neInfo.Port, r.RequestURI)
+ log.Debug("requestURI2NF: GET ", requestURI2NF)
+
+ client := resty.New()
+ response, 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("Failed to Get from NF:", err)
+ services.ResponseInternalServerError500NFConnectRefused(w)
+ return
+ }
+
+ respMsg := make(map[string]interface{})
+ switch response.StatusCode() {
+ case http.StatusOK, http.StatusCreated, http.StatusNoContent, http.StatusAccepted:
+ log.Debug("response:", response)
+ // measurement := new(dborm.NorthboundPm)
+ measurement := new(dborm.NorthboundPm)
+ _ = json.Unmarshal(response.Body(), &measurement)
+ log.Debug("measurement:", measurement)
+
+ session := dborm.DbClient.XEngine.NewSession()
+ defer session.Close()
+
+ layout := time.RFC3339
+ measurement.Date = GetDateFromTimeString(layout, measurement.StartTime)
+ measurement.StartTime = GetDateTimeFromTimeString(layout, measurement.StartTime)
+ affected, err := session.Table("northbound_pm").Insert(measurement)
+ if err != nil && affected <= 0 {
+ log.Error("Failed to insert northbound_pm:", err)
+ services.ResponseInternalServerError500DatabaseOperationFailed(w)
+ return
+ }
+ default:
+ log.Debug("response body:", string(response.Body()))
+ body := new(map[string]interface{})
+ _ = json.Unmarshal(response.Body(), &body)
+ respMsg["error"] = body
+ }
+
+ services.ResponseWithJson(w, response.StatusCode(), respMsg)
+}
diff --git a/features/security/account.go b/features/security/account.go
new file mode 100644
index 0000000..faaf8f4
--- /dev/null
+++ b/features/security/account.go
@@ -0,0 +1,436 @@
+package security
+
+import (
+ "encoding/json"
+ "fmt"
+ "image/color"
+ "io"
+ "net/http"
+ "strconv"
+ "strings"
+ "time"
+
+ "nms_nbi/features/security/service"
+ sysConfigService "nms_nbi/features/sys_config/service"
+ "nms_nbi/lib/core/account"
+ "nms_nbi/lib/core/cache"
+ "nms_nbi/lib/core/constants/cachekey"
+ "nms_nbi/lib/core/utils/ctx"
+ "nms_nbi/lib/core/vo/result"
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/global"
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/oauth"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+ srcConfig "nms_nbi/src/framework/config"
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/redis"
+
+ "github.com/mojocn/base64Captcha"
+)
+
+var (
+ UriOauthToken2 = config.DefaultUriPrefix + "/securityManagement/{apiVersion}/{elementTypeValue}/token2"
+ UriOauthHandshake = config.DefaultUriPrefix + "/securityManagement/{apiVersion}/{elementTypeValue}/handshake"
+
+ CustomUriOauthToken = config.UriPrefix + "/securityManagement/{apiVersion}/{elementTypeValue}/token"
+ CustomUriOauthHandshake = config.UriPrefix + "/securityManagement/{apiVersion}/{elementTypeValue}/handshake"
+
+ // 系统登录
+ UriLogin = config.DefaultUriPrefix + "/securityManagement/{apiVersion}/login"
+ CustomUriLogin = config.UriPrefix + "/securityManagement/{apiVersion}/login"
+
+ // 获取验证码
+ UriCaptchaImage = config.DefaultUriPrefix + "/securityManagement/{apiVersion}/captchaImage"
+ CustomUriCaptchaImage = config.UriPrefix + "/securityManagement/{apiVersion}/captchaImage"
+
+ // 登录用户信息
+ UriUserInfo = config.DefaultUriPrefix + "/securityManagement/{apiVersion}/getUserInfo"
+ CustomUriUserInfo = config.UriPrefix + "/securityManagement/{apiVersion}/getUserInfo"
+
+ // 登录用户路由信息
+ UriRouters = config.DefaultUriPrefix + "/securityManagement/{apiVersion}/getRouters"
+ CustomUriRouters = config.UriPrefix + "/securityManagement/{apiVersion}/getRouters"
+)
+
+func LoginFromOMC(w http.ResponseWriter, r *http.Request) {
+ log.Info("LoginFromOMC processing... ")
+
+ body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen)) //io.LimitReader限制大小
+ if err != nil {
+ log.Error("Failed to ReadAll:", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ // check media type(content type) only support "application/json"
+ if !services.IsVallidContentType(r, config.GetYamlConfig().OMC.CheckContentType) {
+ log.Debug("Invalid Content-Type")
+ services.ResponseUnsupportedMediaType415(w)
+ return
+ }
+
+ // // check extend uri, response 404
+ // if !IsValidOAuthUri(r) {
+ // log.Debug("Uri is invalid")
+ // services.ResponseNotFound404UriNotExist(w, r)
+ // return
+ // }
+
+ // Error process ....
+ // response 400-7
+ if !json.Valid([]byte(body)) {
+ log.Error("Invalid Json Format")
+ services.ResponseBadRequest400InvalidJson(w)
+ return
+ }
+
+ var oAuthBody oauth.OAuthBody
+ _ = json.Unmarshal(body, &oAuthBody) //转为json
+ //log.Debug("body:", string(body), "oAuthBody:", oAuthBody)
+
+ defer r.Body.Close()
+ // response 400-5
+ if oauth.IsWrongOAuthInfo(oAuthBody) {
+ log.Error("Wrong parameter value")
+ services.ResponseBadRequest400WrongParamValue(w)
+ return
+ }
+ /*
+ if oauth.IsValidOAuthInfo(oAuthBody) {
+ plist := config.GetPermissionFromConfig(oAuthBody.UserName, oAuthBody.GrantType)
+ log.Debug("Permission list:", plist)
+
+ token := globalSession.NewSession(w, r, plist)
+ services.ResponseStatusOK200Login(w, token)
+ } else {
+ // response 400-4
+ log.Debug("Authentication failed, mismatch user or password")
+
+ services.ResponseBadRequest400IncorrectLogin(w)
+ }
+ */
+ validUser, user, err := dborm.XormCheckLoginUser(oAuthBody.UserName,
+ oAuthBody.Value, config.GetYamlConfig().Auth.Crypt)
+ if !validUser || err != nil {
+ // response 400-4
+ log.Error("Authentication failed, mismatch user or password")
+ services.ResponseErrorWithJson(w, 400, err.Error())
+ return
+ }
+
+ token := oauth.GenRandToken("omc") // Generate new token to session ID
+ sourceAddr := r.RemoteAddr[:strings.Index(r.RemoteAddr, ":")]
+ affected, err := dborm.XormInsertSession(oAuthBody.UserName, sourceAddr, token,
+ config.GetExpiresFromConfig(), config.GetYamlConfig().Auth.Session)
+ if err != nil {
+ log.Error("Failed to XormInsertSession:", err)
+ if affected == -1 {
+ services.ResponseForbidden403MultiLoginNotAllowed(w)
+ } else {
+ services.ResponseBadRequest400IncorrectLogin(w)
+ }
+
+ return
+ }
+
+ if user != nil {
+ // 缓存用户信息
+ account.CacheLoginUser(user)
+ redis.SetByExpire("", "session_token", token, time.Second*1800)
+ // 角色权限集合,管理员拥有所有权限
+ userId := fmt.Sprint(user.Id)
+ isAdmin := srcConfig.IsAdmin(userId)
+ roles, perms := service.NewServiceAccount.RoleAndMenuPerms(userId, isAdmin)
+ services.ResponseStatusOK200LoginWhitRP(w, token, user, roles, perms)
+ return
+ }
+ services.ResponseBadRequest400IncorrectLogin(w)
+}
+
+func LogoutFromOMC(w http.ResponseWriter, r *http.Request) {
+ log.Info("LogoutFromOMC processing... ")
+
+ token, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+ // // check media type(content type) only support "application/json"
+ // if services.IsVallidContentType(r, config.GetYamlConfig().OMC.CheckContentType) == false {
+ // log.Error("Invalid Content-Type")
+ // services.ResponseUnsupportedMediaType415(w)
+ // return
+ // }
+
+ // // check extend uri, response 404
+ // if !services.IsValidOAuthUri(r) {
+ // log.Error("Uri is invalid")
+ // services.ResponseNotFound404UriNotExist(w, r)
+ // return
+ // }
+
+ // // error processing ...
+ // // 401-1 response
+ // token, ret := oauth.IsCarriedToken(r)
+ // if ret == false {
+ // log.Error("AccessToken is not carried")
+ // services.ResponseUnauthorized401AccessTokenNotCarried(w)
+ // return
+ // }
+
+ se, err := dborm.XormLogoutUpdateSession(token)
+ if err != nil {
+ log.Error("Uri is invalid")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ // 清除缓存用户信息
+ account.ClearLoginUser(se.AccountId)
+ services.ResponseStatusOK200Null(w)
+}
+
+func HandshakeFromOMC(w http.ResponseWriter, r *http.Request) {
+ log.Info("HandshakeFromOMC processing... ")
+
+ // check media type(content type) only support "application/json"
+ if !services.IsVallidContentType(r, config.GetYamlConfig().OMC.CheckContentType) {
+ log.Debug("Invalid Content-Type")
+ services.ResponseUnsupportedMediaType415(w)
+ return
+ }
+
+ // check extend uri, response 404
+ if !services.IsValidOAuthUri(r) {
+ log.Error("Uri is invalid")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ // error processing ...
+ // 401-1 response
+ token, ret := oauth.IsCarriedToken(r)
+ if !ret {
+ log.Error("AccessToken is not carried")
+ services.ResponseUnauthorized401AccessTokenNotCarried(w)
+ return
+ }
+
+ _, err := dborm.XormUpdateSessionShakeTime(token)
+ if err != nil {
+ log.Error("Uri is invalid")
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ services.ResponseStatusOK200Null(w)
+}
+
+// 系统登录
+//
+// POST /login
+func LoginOMC(w http.ResponseWriter, r *http.Request) {
+ log.Info("LoginOMC processing... ")
+ var body struct {
+ Username string `json:"username" binding:"required"` // Username 用户名
+ Password string `json:"password" binding:"required"` // Password 用户密码
+ Code string `json:"code"` // Code 验证码
+ UUID string `json:"uuid"` // UUID 验证码唯一标识
+ }
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil {
+ log.Error("Invalid Json Format")
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+ // response 400-5
+ if body.Username == "" || body.Password == "" {
+ log.Error("Wrong parameter value")
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ // 校验验证码
+ // 从数据库配置获取验证码开关 true开启,false关闭
+ captchaEnabledStr := sysConfigService.NewServiceSysConfig.SelectConfigValueByKey("sys.account.captchaEnabled")
+ captchaEnabled, err := strconv.ParseBool(captchaEnabledStr)
+ if err != nil {
+ captchaEnabled = false
+ }
+ if captchaEnabled {
+ if body.Code == "" || body.UUID == "" {
+ log.Error("Authentication failed, mismatch captcha")
+ ctx.JSON(w, 400, result.CodeMsg(400, "Verification code information error"))
+ return
+ }
+ verifyKey := cachekey.CAPTCHA_CODE_KEY + body.UUID
+ captcha, ok := cache.GetLocalTTL(verifyKey)
+ if captcha == nil || !ok {
+ log.Error("Authentication failed, captcha emtry")
+ ctx.JSON(w, 400, result.CodeMsg(400, "The verification code has expired"))
+ return
+ }
+ cache.DeleteLocalTTL(verifyKey)
+ if captcha.(string) != body.Code {
+ log.Error("Authentication failed, not match captcha")
+ ctx.JSON(w, 400, result.CodeMsg(400, "Verification code error"))
+ return
+ }
+ }
+
+ validUser, user, err := dborm.XormCheckLoginUser(body.Username, body.Password, config.GetYamlConfig().Auth.Crypt)
+ if !validUser || err != nil {
+ // response 400-4
+ log.Error("Authentication failed, mismatch user or password")
+ ctx.JSON(w, 400, result.CodeMsg(400, err.Error()))
+ return
+ }
+
+ token := oauth.GenRandToken("omc") // Generate new token to session ID
+ sourceAddr := r.RemoteAddr[:strings.Index(r.RemoteAddr, ":")]
+ affected, err := dborm.XormInsertSession(body.Username, sourceAddr, token,
+ config.GetExpiresFromConfig(), config.GetYamlConfig().Auth.Session)
+ if err != nil {
+ log.Error("Failed to XormInsertSession:", err)
+ if affected == -1 {
+ services.ResponseForbidden403MultiLoginNotAllowed(w)
+ } else {
+ services.ResponseBadRequest400IncorrectLogin(w)
+ }
+
+ return
+ }
+
+ if user != nil {
+ // 缓存用户信息
+ account.CacheLoginUser(user)
+ redis.SetByExpire("", "session_token", token, time.Second*1800)
+ ctx.JSON(w, 200, result.OkData(map[string]any{
+ "accessToken": token,
+ }))
+ return
+ }
+ ctx.JSON(w, 200, result.Err(nil))
+}
+
+// 获取验证码
+//
+// GET /captchaImage
+func CaptchaImage(w http.ResponseWriter, r *http.Request) {
+ configService := sysConfigService.NewServiceSysConfig
+
+ // 从数据库配置获取验证码开关 true开启,false关闭
+ captchaEnabledStr := configService.SelectConfigValueByKey("sys.account.captchaEnabled")
+ captchaEnabled, err := strconv.ParseBool(captchaEnabledStr)
+ if err != nil {
+ captchaEnabled = false
+ }
+ if !captchaEnabled {
+ ctx.JSON(w, 200, result.Ok(map[string]any{
+ "captchaEnabled": captchaEnabled,
+ }))
+ return
+ }
+
+ // 生成唯一标识
+ verifyKey := ""
+ data := map[string]any{
+ "captchaEnabled": captchaEnabled,
+ "uuid": "",
+ "img": "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
+ }
+
+ // char 字符验证
+ driverCaptcha := &base64Captcha.DriverString{
+ //Height png height in pixel.
+ Height: 40,
+ // Width Captcha png width in pixel.
+ Width: 120,
+ //NoiseCount text noise count.
+ NoiseCount: 4,
+ //Length random string length.
+ Length: 4,
+ //Source is a unicode which is the rand string from.
+ Source: "023456789abcdefghjkmnprstuvwxyz",
+ //ShowLineOptions := OptionShowHollowLine | OptionShowSlimeLine | OptionShowSineLine .
+ ShowLineOptions: base64Captcha.OptionShowHollowLine,
+ //BgColor captcha image background color (optional)
+ BgColor: &color.RGBA{
+ R: 250,
+ G: 250,
+ B: 250,
+ A: 255, // 不透明
+ },
+ }
+ // 验证码生成
+ id, question, answer := driverCaptcha.GenerateIdQuestionAnswer()
+ // 验证码表达式解析输出
+ item, err := driverCaptcha.DrawCaptcha(question)
+ if err != nil {
+ log.Infof("Generate Id Question Answer %s : %v", question, err)
+ } else {
+ data["uuid"] = id
+ data["img"] = item.EncodeB64string()
+ verifyKey = cachekey.CAPTCHA_CODE_KEY + id
+ cache.SetLocalTTL(verifyKey, answer, 120*time.Second)
+ }
+
+ // 本地开发下返回验证码结果,方便接口调试
+ // text, ok := cache.GetLocalTTL(verifyKey)
+ // if ok {
+ // data["text"] = text.(string)
+ // }
+
+ ctx.JSON(w, 200, result.Ok(data))
+}
+
+// 登录用户信息
+func UserInfo(w http.ResponseWriter, r *http.Request) {
+ loginUser, err := ctx.LoginUser(r)
+ if err != nil {
+ ctx.JSON(w, 200, result.OkData(err.Error()))
+ }
+ // 角色权限集合,管理员拥有所有权限
+ userId := fmt.Sprint(loginUser.UserID)
+ isAdmin := srcConfig.IsAdmin(userId)
+ roles, perms := service.NewServiceAccount.RoleAndMenuPerms(userId, isAdmin)
+
+ ctx.JSON(w, 200, result.OkData(map[string]any{
+ "user": loginUser.User,
+ "roles": roles,
+ "permissions": perms,
+ }))
+}
+
+// 登录用户路由信息
+func Routers(w http.ResponseWriter, r *http.Request) {
+ userID := ctx.LoginUserToUserID(r)
+
+ // 前端路由,管理员拥有所有
+ isAdmin := srcConfig.IsAdmin(userID)
+ buildMenus := service.NewServiceAccount.RouteMenus(userID, isAdmin)
+ ctx.JSON(w, 200, result.OkData(buildMenus))
+}
+
+// 鉴权登录接口
+var UriOauthToken = "/api/rest/securityManagement/{apiVersion}/oauth/token"
+
+// 鉴权登录接口
+func OauthToken(w http.ResponseWriter, r *http.Request) {
+ language := ctx.AcceptLanguage(r)
+ var querys struct {
+ Group string `form:"group" binding:"required"`
+ Type string `form:"type" binding:"omitempty,oneof=node edge combo"`
+ }
+ if err := ctx.ShouldBindQuery(r, &querys); err != nil {
+ ctx.JSON(w, 400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ apiVersion := ctx.Param(r, "apiVersion")
+ if apiVersion == "v1" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+}
diff --git a/features/security/service/service_account.go b/features/security/service/service_account.go
new file mode 100644
index 0000000..ea722fa
--- /dev/null
+++ b/features/security/service/service_account.go
@@ -0,0 +1,56 @@
+package service
+
+import (
+ menuService "nms_nbi/features/sys_menu/service"
+ roleService "nms_nbi/features/sys_role/service"
+ userService "nms_nbi/features/sys_user/service"
+ "nms_nbi/lib/core/utils/parse"
+ "nms_nbi/lib/core/vo"
+)
+
+// 实例化服务层 ServiceAccount 结构体
+var NewServiceAccount = &ServiceAccount{
+ sysUserService: userService.NewServiceSysUser,
+ sysRoleService: roleService.NewServiceSysRole,
+ sysMenuService: menuService.NewServiceSysMenu,
+}
+
+// 账号身份操作服务 服务层处理
+type ServiceAccount struct {
+ // 用户信息服务
+ sysUserService *userService.ServiceSysUser
+ // 角色服务
+ sysRoleService *roleService.ServiceSysRole
+ // 菜单服务
+ sysMenuService *menuService.ServiceSysMenu
+}
+
+// RoleAndMenuPerms 角色和菜单数据权限
+func (s *ServiceAccount) RoleAndMenuPerms(userId string, isAdmin bool) ([]string, []string) {
+ if isAdmin {
+ return []string{"admin"}, []string{"*:*:*"}
+ } else {
+ // 角色key
+ roleGroup := []string{}
+ roles := s.sysRoleService.SelectRoleListByUserId(userId)
+ for _, role := range roles {
+ roleGroup = append(roleGroup, role.RoleKey)
+ }
+ // 菜单权限key
+ perms := s.sysMenuService.SelectMenuPermsByUserId(userId)
+ return parse.RemoveDuplicates(roleGroup), parse.RemoveDuplicates(perms)
+ }
+}
+
+// RouteMenus 前端路由所需要的菜单
+func (s *ServiceAccount) RouteMenus(userId string, isAdmin bool) []vo.Router {
+ var buildMenus []vo.Router
+ if isAdmin {
+ menus := s.sysMenuService.SelectMenuTreeByUserId("*")
+ buildMenus = s.sysMenuService.BuildRouteMenus(menus, "")
+ } else {
+ menus := s.sysMenuService.SelectMenuTreeByUserId(userId)
+ buildMenus = s.sysMenuService.BuildRouteMenus(menus, "")
+ }
+ return buildMenus
+}
diff --git a/features/sm/backup.go b/features/sm/backup.go
new file mode 100644
index 0000000..a8c0b0a
--- /dev/null
+++ b/features/sm/backup.go
@@ -0,0 +1,146 @@
+package sm
+
+import (
+ "database/sql"
+ "fmt"
+ "net/http"
+ "os"
+ "os/exec"
+ "time"
+
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+
+ _ "github.com/go-sql-driver/mysql"
+)
+
+var (
+ // Get OMC local time
+ UriOMCLocalTime = config.DefaultUriPrefix + "/systemManagement/{apiVersion}/elementType/OMC/objectType/time"
+
+ CustomUriOMCLocalTime = config.UriPrefix + "/systemManagement/{apiVersion}/elementType/OMC/objectType/time"
+)
+
+type OMCLocalTime struct {
+ Timestamp int64 `json:"timestamp"` // 时间戳 (单位:毫秒)
+ TimeZone int `json:"timeZone"` // 本地时区偏移(单位:秒)
+}
+
+func GetOMCLocalTime(w http.ResponseWriter, r *http.Request) {
+ log.Debug("GetOMCLocalTime processing... ")
+
+ _, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Http request error:", err)
+ return
+ }
+
+ t := time.Now()
+ _, offset := t.Zone()
+
+ localTime := OMCLocalTime{
+ Timestamp: t.UnixMilli(),
+ TimeZone: offset,
+ }
+ response := services.DataResponse{
+ Data: localTime,
+ }
+ services.ResponseWithJson(w, http.StatusOK, response)
+}
+
+var dbConfig = config.GetYamlConfig().Database
+
+func DatabaseWhoreBackup() {
+ // MySQL数据库连接信息
+ sqlStr := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=true&loc=Local",
+ dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Name)
+ db, err := sql.Open("mysql", sqlStr)
+ if err != nil {
+ log.Error("Failed to connect to database:", err)
+ return
+ }
+ defer db.Close()
+
+ // 备份SQL文件路径
+ backupFile := dbConfig.Backup + "/" + "whore_backup_" + dbConfig.Name + ".sql"
+
+ // 执行mysqldump命令进行备份
+ cmd := exec.Command("mysqldump", "-u", dbConfig.User, "-p"+dbConfig.Password, "-h", dbConfig.Host, dbConfig.Name)
+ output, err := cmd.Output()
+ if err != nil {
+ log.Error("Failed to execute mysqldump command:", err)
+ return
+ }
+
+ // 将备份结果写入SQL文件
+ file, err := os.Create(backupFile)
+ if err != nil {
+ log.Error("Failed to create backup file:", err)
+ return
+ }
+ defer file.Close()
+
+ _, err = file.Write(output)
+ if err != nil {
+ log.Error("Failed to write backup file:", err)
+ return
+ }
+
+ log.Info("Backup completed successfully.")
+}
+
+func DatabaseIncrementalBackup() {
+ // MySQL数据库连接信息
+ sqlStr := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=true&loc=Local",
+ dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Name)
+ db, err := sql.Open("mysql", sqlStr)
+ if err != nil {
+ log.Error("Failed to connect to database:", err)
+ return
+ }
+ defer db.Close()
+
+ // 备份SQL文件路径
+ backupFile := dbConfig.Backup + "/" + "incremental_backup_" + dbConfig.Name + ".sql"
+
+ // 上次备份的时间点
+ lastBackupTime := time.Date(2022, time.January, 1, 0, 0, 0, 0, time.Local)
+
+ // 构建增量备份SQL语句
+ query := fmt.Sprintf("SELECT * FROM table WHERE modified_at > '%s'", lastBackupTime.Format("2006-01-02 15:04:05"))
+
+ // 执行查询
+ rows, err := db.Query(query)
+ if err != nil {
+ log.Error("Failed to execute query:", err)
+ return
+ }
+ defer rows.Close()
+
+ // 创建增量备份SQL文件
+ file, err := os.Create(backupFile)
+ if err != nil {
+ log.Error("Failed to create backup file:", err)
+ return
+ }
+ defer file.Close()
+
+ // 将查询结果写入SQL文件
+ for rows.Next() {
+ var data string
+ err := rows.Scan(&data)
+ if err != nil {
+ log.Error("Failed to scan row:", err)
+ return
+ }
+
+ _, err = file.WriteString(data + "\n")
+ if err != nil {
+ log.Error("Failed to write backup file:", err)
+ return
+ }
+ }
+
+ log.Info("Incremental backup completed successfully.")
+}
diff --git a/features/state/getstate.go b/features/state/getstate.go
new file mode 100644
index 0000000..95c8b7f
--- /dev/null
+++ b/features/state/getstate.go
@@ -0,0 +1,1042 @@
+package state
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "os"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/shirou/gopsutil/v3/net"
+
+ "github.com/go-resty/resty/v2"
+ "github.com/gorilla/mux"
+
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/global"
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+ tokenConst "nms_nbi/src/framework/constants/token"
+)
+
+type CpuUsage struct {
+ NfCpuUsage uint16 `json:"nfCpuUsage"`
+ SysCpuUsage uint16 `json:"sysCpuUsage"`
+}
+
+type MemUsage struct {
+ TotalMem uint32 `json:"totalMem"`
+ NfUsedMem uint32 `json:"nfUsedMem"`
+ SysMemUsage uint16 `json:"sysMemUsage"`
+}
+
+type PartitionInfo struct {
+ Total uint32 `json:"total"` // MB
+ Used uint32 `json:"used"` // MB
+}
+
+type DiskSpace struct {
+ PartitionNum uint8 `json:"partitionNum"`
+ PartitionInfo []PartitionInfo `json:"partitionInfo"`
+}
+
+type HardwareInfo struct {
+ CPUs int `json:"cpus"`
+ Memory int `json:"memory"`
+}
+
+type SysState struct {
+ HostName string `json:"hostName"` // linux命令: hostname
+ OsInfo string `json:"osInfo"` // linux命令: uname -a
+ DbInfo string `json:"dbInfo"` // 网元如果有db, 显示数据库名和版本信息, OMC: mysql --version
+ Version string `json:"version"` // 软件版本信息: 16.1.1
+ IpAddr []string `json:"ipAddr"` // 网管的ipv4和ipv6列表
+ Port uint16 `json:"port"` // 用于网管的port
+ Capability uint32 `json:"capability"`
+ SerialNum string `json:"serialNum"`
+ ExpiryDate string `json:"expiryDate"`
+ HardwareInfo HardwareInfo `json:"hardwareInfo"`
+ CpuUsage CpuUsage `json:"cpuUsage"`
+ MemUsage MemUsage `json:"memUsage"`
+ DiskSpace DiskSpace `json:"diskSpace"`
+ //Timestamp string `json:"timestamp"`
+}
+
+type SystemState struct {
+ HostName string `json:"hostName"` // linux命令: hostname
+ OsInfo string `json:"osInfo"` // linux命令: uname -a
+ DbInfo string `json:"dbInfo"` // 网元如果有db, 显示数据库名和版本信息, OMC: mysql --version
+ Version string `json:"version"` // 软件版本信息: 16.1.1
+ IpAddr []string `json:"ipAddr"` // 网管的ipv4和ipv6列表
+ Port uint16 `json:"port"` // 用于网管的port
+ Capability uint32 `json:"capability"`
+ SerialNum string `json:"serialNum"`
+ ExpiryDate string `json:"expiryDate"`
+ HardwareInfo struct {
+ CPUs int `json:"cpus"` // 主机(裸机/虚拟机)的cpu个数
+ Memory int `json:"memory"` // 主机(裸机/虚拟机): 配置的内存
+ } `json:"hardwareInfo"`
+ CpuUsage struct {
+ NfCpuUsage uint16 `json:"nfCpuUsage"`
+ SysCpuUsage uint16 `json:"sysCpuUsage"`
+ } `json:"cpuUsage"`
+ MemUsage struct {
+ TotalMem uint32 `json:"totalMem"`
+ NfUsedMem uint32 `json:"nfUsedMem"`
+ SysMemUsage uint16 `json:"sysMemUsage"`
+ } `json:"memUsage"`
+ DiskSpace struct {
+ PartitionNum uint8 `json:"partitionNum"`
+ PartitionInfo []struct {
+ Total uint32 `json:"total"` // MB
+ Used uint32 `json:"used"` // MB
+ } `json:"partitionInfo"`
+ } `json:"diskSpace"`
+ //Timestamp string `json:"timestamp"`
+}
+
+type SystemInfo struct {
+ NeType string `json:"neType" map:"neType, omitempty"`
+ NeId string `json:"neId" map:"neId, omitempty"`
+ HostName string `json:"hostName" map:"hostName, omitempty"` // linux命令: hostname
+ OsInfo string `json:"osInfo" map:"osInfo, omitempty"` // linux命令: uname -a
+ DbInfo string `json:"dbInfo" map:"dbInfo, omitempty"` // 网元如果有db, 显示数据库名和版本信息, OMC: mysql --version
+ Version string `json:"version" map:"version, omitempty"` // 软件版本信息: 16.1.1
+ IpAddr string `json:"ipAddr" map:"ipAddr, omitempty"` // 网管的ipv4和ipv6列表
+ Port uint16 `json:"port" map:"port, omitempty"` // 用于网管的port
+ CPUs int `json:"cpus" map:"cpus, omitempty"`
+ TotalMem int `json:"totalMem" map:"totalMem, omitempty"`
+ PvFlag string `json:"pvFlag" map:"pvFlag, omitempty"`
+ Status string `json:"status" map:"status, omitempty"`
+}
+
+type LicenseInfo struct {
+ NeType string `json:"neType"`
+ NeId string `json:"neId"`
+ SerialNum string `json:"serialNum"`
+ Capability uint32 `json:"capability"`
+ CapUsed uint32 `json:"capUsed"`
+ FeatureEnabled []string `json:"featureEnabled"`
+ ExpiryDate string `json:"expiryDate"`
+}
+
+type Response struct {
+ Data interface{} `json:"data"`
+}
+
+var (
+ UriSysState = config.DefaultUriPrefix + "/systemManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/systemState"
+ UriSysState2 = config.DefaultUriPrefix + "/systemManagement/{apiVersion}/{elementTypeValue}/systemState"
+ UriSysInfoAll = config.DefaultUriPrefix + "/systemManagement/{apiVersion}/sysInfo"
+ UriSysInfoOne = config.DefaultUriPrefix + "/systemManagement/{apiVersion}/sysInfo/{neType}/{neId}"
+ UriLicenseInfoAll = config.DefaultUriPrefix + "/systemManagement/{apiVersion}/licenseInfo"
+ UriLicenseInfoOne = config.DefaultUriPrefix + "/systemManagement/{apiVersion}/licenseInfo/{neType}/{neId}"
+
+ CustomUriSysState = config.UriPrefix + "/systemManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/systemState"
+ CustomUriSysState2 = config.UriPrefix + "/systemManagement/{apiVersion}/{elementTypeValue}/systemState"
+ CustomUriSysInfoAll = config.UriPrefix + "/systemManagement/{apiVersion}/sysInfo"
+ CustomUriSysInfoOne = config.UriPrefix + "/systemManagement/{apiVersion}/sysInfo/{neType}/{neId}"
+ CustomUriLicenseInfoAll = config.UriPrefix + "/systemManagement/{apiVersion}/licenseInfo"
+ CustomUriLicenseInfoOne = config.UriPrefix + "/systemManagement/{apiVersion}/licenseInfo/{neType}/{neId}"
+)
+
+var client = resty.New()
+
+func init() {
+ /*
+ client.
+ SetTimeout(10 * time.Second).
+ SetRetryCount(1).
+ SetRetryWaitTime(1 * time.Second).
+ SetRetryMaxWaitTime(2 * time.Second).
+ SetRetryAfter(func(client *resty.Client, resp *resty.Response) (time.Duration, error) {
+ return 0, errors.New("quota exceeded")
+ })
+ */
+ client.
+ SetTimeout(time.Duration(400 * time.Millisecond))
+ // SetRetryCount(1).
+ // SetRetryWaitTime(time.Duration(1 * time.Second)).
+ // SetRetryMaxWaitTime(time.Duration(2 * time.Second))
+ //client.SetTimeout(2 * time.Second)
+}
+
+func NeStatusEnumToStr(intStatus int) string {
+ switch intStatus {
+ case 0:
+ return "active"
+ case 1:
+ return "offline"
+ case 2:
+ return "standby"
+ case 3:
+ return "maintain"
+ default:
+ return "unkown"
+ }
+}
+
+// Get system state from NF/NFs
+func GetOneLicenseInfoFromNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("GetOneLicenseInfoFromNF processing... ")
+
+ data := make([]map[string]interface{}, 0)
+
+ vars := mux.Vars(r)
+ neType := vars["neType"]
+ neId := vars["neId"]
+ if neType == "" || neId == "" {
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ token, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+ log.Debug("AccessToken:", token)
+
+ neInfo, err := dborm.XormGetNeInfo(neType, neId)
+ if err != nil {
+ log.Error("Failed to XormGetNeInfo:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else if neInfo == nil {
+ err := global.ErrCMNotFoundTargetNE
+ log.Error(global.ErrCMNotFoundTargetNE)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Trace("neInfo:", neInfo)
+
+ //systemState := make(map[string]interface{})
+ systemState := &SysState{}
+ result := make(map[string]interface{})
+ //sysInfo := &SystemInfo{}
+ omcNeTypeLower := "omc"
+ if config.GetYamlConfig().OMC.NeType != "" {
+ omcNeTypeLower = strings.ToLower(config.GetYamlConfig().OMC.NeType)
+ }
+ if neType != omcNeTypeLower {
+ log.Debugf("r.RemoteAddr: %s omcNeTypeLower: %s", r.RemoteAddr, omcNeTypeLower)
+ var requestURI2NF string
+ if config.GetYamlConfig().OMC.TestMode && strings.ToLower(neType) != "udm" {
+ var udmNEs []dborm.NeInfo
+ err := dborm.XormGetNeInfoByNeType("UDM", &udmNEs)
+ if err != nil {
+ log.Error("Get system state from NF is failed:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ if len(udmNEs) > 0 {
+ udmNe := udmNEs[0]
+ hostUri := fmt.Sprintf("http://%s:%v", udmNe.Ip, udmNe.Port)
+ requestURI2NF = fmt.Sprintf("%s/api/rest/systemManagement/v1/elementType/%s/objectType/systemState",
+ hostUri, strings.ToLower(udmNe.NeType))
+ }
+ } else {
+ hostUri := fmt.Sprintf("http://%s:%v", neInfo.Ip, neInfo.Port)
+ requestURI2NF = fmt.Sprintf("%s/api/rest/systemManagement/v1/elementType/%s/objectType/systemState",
+ hostUri, strings.ToLower(neInfo.NeType))
+ }
+ log.Debug("requestURI2NF:", requestURI2NF)
+
+ resp, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}).
+ SetHeaders(map[string]string{"accessToken": token}).
+ 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("Failed to get system state:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else {
+ log.Trace("resp.Body():", string(resp.Body()))
+ _ = json.Unmarshal(resp.Body(), &systemState)
+
+ log.Trace("systemState:", systemState)
+ capUsed := config.TDatas[neInfo.NeType].CapUsed
+ log.Tracef("neInfo.NeType:%s capUsed: %v", capUsed)
+ licenseInfo := &LicenseInfo{
+ NeType: neInfo.NeType,
+ NeId: neInfo.NeId,
+ SerialNum: systemState.SerialNum,
+ Capability: systemState.Capability,
+ CapUsed: capUsed,
+ FeatureEnabled: config.TDatas[neInfo.NeType].FeatureEnabled,
+ ExpiryDate: systemState.ExpiryDate,
+ }
+ //neItem := strings.ToUpper(neType) + "/" + neId
+ result, err = global.ToMap(*licenseInfo, "json")
+ if err != nil {
+ log.Warn("Failed to map:", err)
+ }
+ }
+ } else {
+ systemState := GetEMSState(neInfo.Ip)
+ licenseInfo := &LicenseInfo{
+ NeType: neInfo.NeType,
+ NeId: neInfo.NeId,
+ SerialNum: systemState.SerialNum,
+ Capability: systemState.Capability,
+ CapUsed: config.TDatas[neInfo.NeType].CapUsed,
+ FeatureEnabled: config.TDatas[neInfo.NeType].FeatureEnabled,
+ ExpiryDate: systemState.ExpiryDate,
+ }
+ result, err = global.ToMap(*licenseInfo, "json")
+ if err != nil {
+ log.Warn("Failed to map:", err)
+ }
+ // neItem := strings.ToUpper(neType) + "/" + neId
+ // result[neItem] = sysInfo
+ }
+
+ data = append(data, result)
+ log.Trace("data:", data)
+
+ var response Response
+ response.Data = data
+ services.ResponseWithJson(w, http.StatusOK, response)
+}
+
+// Get system state from NF/NFs
+func GetAllLicenseInfoFromNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("GetAllLicenseInfoFromNF processing... ")
+
+ data := make([]map[string]interface{}, 0)
+
+ token, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+ log.Debug("AccessToken:", token)
+
+ var neList []dborm.NeInfo
+ _, err = dborm.XormGetAllNeInfo(&neList)
+ if err != nil {
+ log.Error("Failed to XormGetAllNeInfo:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ omcNeTypeLower := "omc"
+ if config.GetYamlConfig().OMC.NeType != "" {
+ omcNeTypeLower = strings.ToLower(config.GetYamlConfig().OMC.NeType)
+ }
+
+ for _, ne := range neList {
+ result := make(map[string]interface{})
+ log.Debugf("r.RemoteAddr: %s omcNeTypeLower: %s", r.RemoteAddr, omcNeTypeLower)
+ log.Debug("ne: ", ne)
+ //if strings.ToLower(ne.NeType) != omcNeTypeLower || !strings.Contains(r.RemoteAddr, ne.Ip) {
+ if strings.ToLower(ne.NeType) != omcNeTypeLower {
+ // 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))
+ var requestURI2NF string
+ if config.GetYamlConfig().OMC.TestMode && strings.ToLower(ne.NeType) != "udm" {
+ var udmNEs []dborm.NeInfo
+ err := dborm.XormGetNeInfoByNeType("UDM", &udmNEs)
+ if err != nil {
+ log.Error("Get system state from NF is failed:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ if len(udmNEs) > 0 {
+ udmNe := udmNEs[0]
+ hostUri := fmt.Sprintf("http://%s:%v", udmNe.Ip, udmNe.Port)
+ requestURI2NF = fmt.Sprintf("%s/api/rest/systemManagement/v1/elementType/%s/objectType/systemState",
+ hostUri, strings.ToLower(udmNe.NeType))
+ }
+ } else {
+ 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.SetTimeout(time.Duration(1 * time.Second)).R().
+ EnableTrace().
+ SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}).
+ SetHeaders(map[string]string{"accessToken": token}).
+ 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)
+ // errorMessage := services.ErrorMessage{
+ // ErrorCode: "1", ErrorInfo: "Internal server error, NF connnect refused",
+ // }
+ // //result["error"] = errorMessage
+ continue
+ } else {
+ systemState := &SysState{}
+ _ = json.Unmarshal(resp.Body(), &systemState)
+ licenseInfo := &LicenseInfo{
+ NeType: ne.NeType,
+ NeId: ne.NeId,
+ SerialNum: systemState.SerialNum,
+ Capability: systemState.Capability,
+ CapUsed: config.TDatas[ne.NeType].CapUsed,
+ FeatureEnabled: config.TDatas[ne.NeType].FeatureEnabled,
+ ExpiryDate: systemState.ExpiryDate,
+ }
+ result, err = global.ToMap(*licenseInfo, "json")
+ if err != nil {
+ log.Warn("Failed to map:", err)
+ }
+ // neItem := strings.ToUpper(ne.NeType) + "/" + ne.NeId
+ // result[neItem] = sysInfo
+ }
+ } else {
+ systemState := GetEMSState(ne.Ip)
+ licenseInfo := &LicenseInfo{
+ NeType: ne.NeType,
+ NeId: ne.NeId,
+ SerialNum: systemState.SerialNum,
+ Capability: systemState.Capability,
+ CapUsed: config.TDatas[ne.NeType].CapUsed,
+ FeatureEnabled: config.TDatas[ne.NeType].FeatureEnabled,
+ ExpiryDate: systemState.ExpiryDate,
+ }
+ result, err = global.ToMap(*licenseInfo, "json")
+ if err != nil {
+ log.Warn("Failed to map:", err)
+ }
+ // neItem := strings.ToUpper(ne.NeType) + "/" + ne.NeId
+ // result[neItem] = sysInfo
+ }
+
+ data = append(data, result)
+ log.Trace("data:", data)
+ }
+
+ var response Response
+ response.Data = data
+ services.ResponseWithJson(w, http.StatusOK, response)
+}
+
+// Get system state from NF/NFs
+func GetOneSysinfoFromNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("GetOneSysinfoFromNF processing... ")
+
+ data := make([]map[string]interface{}, 0)
+
+ vars := mux.Vars(r)
+ neType := vars["neType"]
+ neId := vars["neId"]
+ if neType == "" || neId == "" {
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ token, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+ log.Debug("AccessToken:", token)
+
+ neInfo, err := dborm.XormGetNeInfo(neType, neId)
+ if err != nil {
+ log.Error("Failed to XormGetNeInfo:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else if neInfo == nil {
+ err := global.ErrCMNotFoundTargetNE
+ log.Error(global.ErrCMNotFoundTargetNE)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Trace("neInfo:", neInfo)
+
+ //systemState := make(map[string]interface{})
+ systemState := &SysState{}
+ result := make(map[string]interface{})
+ //sysInfo := &SystemInfo{}
+ omcNeTypeLower := "omc"
+ if config.GetYamlConfig().OMC.NeType != "" {
+ omcNeTypeLower = strings.ToLower(config.GetYamlConfig().OMC.NeType)
+ }
+ if neType != omcNeTypeLower {
+ log.Debugf("r.RemoteAddr: %s omcNeTypeLower: %s", r.RemoteAddr, omcNeTypeLower)
+ var requestURI2NF string
+ if config.GetYamlConfig().OMC.TestMode && strings.ToLower(neType) != "udm" {
+ var udmNEs []dborm.NeInfo
+ err := dborm.XormGetNeInfoByNeType("UDM", &udmNEs)
+ if err != nil {
+ log.Error("Get system state from NF is failed:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ if len(udmNEs) > 0 {
+ udmNe := udmNEs[0]
+ hostUri := fmt.Sprintf("http://%s:%v", udmNe.Ip, udmNe.Port)
+ requestURI2NF = fmt.Sprintf("%s/api/rest/systemManagement/v1/elementType/%s/objectType/systemState",
+ hostUri, strings.ToLower(udmNe.NeType))
+ }
+ } else {
+ hostUri := fmt.Sprintf("http://%s:%v", neInfo.Ip, neInfo.Port)
+ requestURI2NF = fmt.Sprintf("%s/api/rest/systemManagement/v1/elementType/%s/objectType/systemState",
+ hostUri, strings.ToLower(neInfo.NeType))
+ }
+ log.Debug("requestURI2NF:", requestURI2NF)
+
+ resp, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}).
+ SetHeaders(map[string]string{"accessToken": token}).
+ 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)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else {
+ log.Trace("resp.Body():", string(resp.Body()))
+ _ = json.Unmarshal(resp.Body(), &systemState)
+
+ log.Trace("systemState:", systemState)
+ hostName := "5gc"
+ if systemState.HostName != "" {
+ hostName = systemState.HostName
+ }
+ osInfo := "Linux 5gc 4.15.0-29-generic #31-Ubuntu SMP Tue Jul 17 15:39:52 UTC 2018 x86_64 GNU/Linux"
+ if systemState.OsInfo != "" {
+ osInfo = systemState.OsInfo
+ }
+ dbInfo := "adb v1.0.1"
+ if systemState.OsInfo != "" {
+ dbInfo = systemState.DbInfo
+ }
+ port, _ := strconv.Atoi(neInfo.Port)
+ cpus := 4
+ if systemState.HardwareInfo.CPUs != 0 {
+ cpus = systemState.HardwareInfo.CPUs
+ }
+ totalMem := 34029125632
+ if systemState.HardwareInfo.Memory != 0 {
+ totalMem = systemState.HardwareInfo.Memory
+ }
+ sysInfo := &SystemInfo{
+ NeType: neInfo.NeType,
+ NeId: neInfo.NeId,
+ HostName: hostName,
+ OsInfo: osInfo,
+ DbInfo: dbInfo,
+ Version: systemState.Version,
+ IpAddr: neInfo.Ip,
+ Port: uint16(port),
+ CPUs: cpus,
+ TotalMem: totalMem,
+ PvFlag: neInfo.PvFlag,
+ Status: NeStatusEnumToStr(neInfo.Status),
+ }
+ //neItem := strings.ToUpper(neType) + "/" + neId
+ result, err = global.ToMap(*sysInfo, "json")
+ if err != nil {
+ log.Warn("Failed to map:", err)
+ }
+ }
+ } else {
+ systemState := GetEMSState(neInfo.Ip)
+ sysInfo := &SystemInfo{
+ NeType: neInfo.NeType,
+ NeId: neInfo.NeId,
+ HostName: systemState.HostName,
+ OsInfo: systemState.OsInfo,
+ DbInfo: systemState.DbInfo,
+ Version: systemState.Version,
+ IpAddr: neInfo.Ip,
+ Port: systemState.Port,
+ CPUs: systemState.HardwareInfo.CPUs,
+ TotalMem: systemState.HardwareInfo.Memory,
+ PvFlag: neInfo.PvFlag,
+ Status: NeStatusEnumToStr(neInfo.Status),
+ }
+ result, err = global.ToMap(*sysInfo, "json")
+ if err != nil {
+ log.Warn("Failed to map:", err)
+ }
+ // neItem := strings.ToUpper(neType) + "/" + neId
+ // result[neItem] = sysInfo
+ }
+
+ data = append(data, result)
+ log.Trace("data:", data)
+
+ var response Response
+ response.Data = data
+ services.ResponseWithJson(w, http.StatusOK, response)
+}
+
+// Get system state from NF/NFs
+func GetAllSysinfoFromNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("GetAllSysinfoFromNF processing... ")
+
+ data := make([]map[string]interface{}, 0)
+
+ token, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+ log.Debug("AccessToken:", token)
+
+ var neList []dborm.NeInfo
+ _, err = dborm.XormGetAllNeInfo(&neList)
+ if err != nil {
+ log.Error("Failed to XormGetAllNeInfo:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ omcNeTypeLower := "omc"
+ if config.GetYamlConfig().OMC.NeType != "" {
+ omcNeTypeLower = strings.ToLower(config.GetYamlConfig().OMC.NeType)
+ }
+
+ for _, ne := range neList {
+ result := make(map[string]interface{})
+ log.Debugf("r.RemoteAddr: %s omcNeTypeLower: %s", r.RemoteAddr, omcNeTypeLower)
+ log.Debug("ne: ", ne)
+ //if strings.ToLower(ne.NeType) != omcNeTypeLower || !strings.Contains(r.RemoteAddr, ne.Ip) {
+ if strings.ToLower(ne.NeType) != omcNeTypeLower {
+ // 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))
+ var requestURI2NF string
+ if config.GetYamlConfig().OMC.TestMode && strings.ToLower(ne.NeType) != "udm" {
+ var udmNEs []dborm.NeInfo
+ err := dborm.XormGetNeInfoByNeType("UDM", &udmNEs)
+ if err != nil {
+ log.Error("Get system state from NF is failed:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ if len(udmNEs) > 0 {
+ udmNe := udmNEs[0]
+ hostUri := fmt.Sprintf("http://%s:%v", udmNe.Ip, udmNe.Port)
+ requestURI2NF = fmt.Sprintf("%s/api/rest/systemManagement/v1/elementType/%s/objectType/systemState",
+ hostUri, strings.ToLower(udmNe.NeType))
+ }
+ } else {
+ 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.SetTimeout(time.Duration(1 * time.Second)).R().
+ EnableTrace().
+ SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}).
+ SetHeaders(map[string]string{"accessToken": token}).
+ 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)
+ // errorMessage := services.ErrorMessage{
+ // ErrorCode: "1", ErrorInfo: "Internal server error, NF connnect refused",
+ // }
+ // //result["error"] = errorMessage
+ continue
+ } else {
+ systemState := &SysState{}
+ _ = json.Unmarshal(resp.Body(), &systemState)
+ hostName := "5gc"
+ if systemState.HostName != "" {
+ hostName = systemState.HostName
+ }
+ osInfo := "Linux 5gc 4.15.0-29-generic #31-Ubuntu SMP Tue Jul 17 15:39:52 UTC 2018 x86_64 GNU/Linux"
+ if systemState.OsInfo != "" {
+ osInfo = systemState.OsInfo
+ }
+ dbInfo := "adb v1.0.1"
+ if systemState.OsInfo != "" {
+ dbInfo = systemState.DbInfo
+ }
+ port, _ := strconv.Atoi(ne.Port)
+ cpus := 4
+ if systemState.HardwareInfo.CPUs != 0 {
+ cpus = systemState.HardwareInfo.CPUs
+ }
+ totalMem := 34029125632
+ if systemState.HardwareInfo.Memory != 0 {
+ totalMem = systemState.HardwareInfo.Memory
+ }
+ sysInfo := &SystemInfo{
+ NeType: ne.NeType,
+ NeId: ne.NeId,
+ HostName: hostName,
+ OsInfo: osInfo,
+ DbInfo: dbInfo,
+ Version: systemState.Version,
+ IpAddr: ne.Ip,
+ Port: uint16(port),
+ CPUs: cpus,
+ TotalMem: totalMem,
+ PvFlag: ne.PvFlag,
+ Status: NeStatusEnumToStr(ne.Status),
+ }
+ // neItem := strings.ToUpper(ne.NeType) + "/" + ne.NeId
+ // result[neItem] = sysInfo
+ result, err = global.ToMap(*sysInfo, "json")
+ if err != nil {
+ log.Warn("Failed to map:", err)
+ }
+ }
+ } else {
+ port, _ := strconv.Atoi(ne.Port)
+ systemState := GetEMSState(ne.Ip)
+ sysInfo := &SystemInfo{
+ NeType: ne.NeType,
+ NeId: ne.NeId,
+ HostName: systemState.HostName,
+ OsInfo: systemState.OsInfo,
+ DbInfo: systemState.DbInfo,
+ Version: systemState.Version,
+ IpAddr: ne.Ip,
+ Port: (uint16(port)),
+ CPUs: systemState.HardwareInfo.CPUs,
+ TotalMem: systemState.HardwareInfo.Memory,
+ PvFlag: ne.PvFlag,
+ Status: NeStatusEnumToStr(ne.Status),
+ }
+ // neItem := strings.ToUpper(ne.NeType) + "/" + ne.NeId
+ // result[neItem] = sysInfo
+ result, err = global.ToMap(*sysInfo, "json")
+ if err != nil {
+ log.Warn("Failed to map:", err)
+ }
+ }
+
+ data = append(data, result)
+ log.Trace("data:", data)
+ }
+
+ var response Response
+ response.Data = data
+ services.ResponseWithJson(w, http.StatusOK, response)
+}
+
+// Get system state from NF/NFs
+func GetStateFromNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("GetStateFromNF processing... ")
+
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+ neType = strings.ToLower(neType)
+
+ if neType == "" {
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ if neType == "all" {
+ var neList []dborm.NeInfo
+ _, err := dborm.XormGetAllNeInfo(&neList)
+ if err != nil {
+ log.Error("Failed to get all ne info:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ data := make([]map[string]interface{}, 0)
+ for _, ne := range neList {
+ result := make(map[string]interface{})
+ 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)
+
+ result["ipAddress"] = ne.Ip
+ resp, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}).
+ //SetHeaders(map[string]string{"accessToken": token}).
+ 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)
+ 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
+ } else {
+ systemState := make(map[string]interface{})
+ _ = json.Unmarshal(resp.Body(), &systemState)
+ result["systemState"] = systemState
+ }
+
+ neItem := strings.ToUpper(ne.NeType) + "/" + ne.NeId
+ mapState := make(map[string]interface{})
+ mapState[neItem] = result
+ data = append(data, mapState)
+ log.Trace("data:", data)
+ }
+ var response Response
+ response.Data = data
+ services.ResponseWithJson(w, http.StatusOK, response)
+ return
+ }
+
+ 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)
+
+ 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 旧函数
+// Get system state from NF/NFs
+func GetStateFromNFOld(w http.ResponseWriter, r *http.Request) {
+ log.Debug("GetStateFromNF processing... ")
+
+ data := make([]map[string]interface{}, 0)
+
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+
+ var neList []dborm.NeInfo
+ if neType == "" {
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ token, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+ log.Debug("AccessToken:", token)
+
+ switch strings.ToLower(neType) {
+ case "all":
+ // query all NFs
+ // create rest client
+ restHostPort := fmt.Sprintf("http://127.0.0.1:%d", config.GetYamlConfig().Rest[0].Port)
+ getNeInfoPattern := fmt.Sprintf(config.DefaultUriPrefix+"/databaseManagement/v1/elementType/%s/objectType/ne_info",
+ config.GetYamlConfig().Database.Name)
+ getNeInfoURI := restHostPort + getNeInfoPattern + "?WHERE=status+in+('0','3')"
+ log.Debug("getNeInfoPattern:", getNeInfoPattern)
+
+ resp, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}).
+ SetHeaders(map[string]string{"AccessToken": token}).
+ SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ Get(getNeInfoURI)
+ if err != nil {
+ log.Error("Get ne_info from DB is failed:", err)
+ services.ResponseInternalServerError500NFConnectRefused(w)
+ return
+ }
+
+ neList, _ = dborm.XormParseResult(resp.Body())
+ default:
+ restHostPort := fmt.Sprintf("http://127.0.0.1:%d", config.GetYamlConfig().Rest[0].Port)
+ getNeInfoPattern := fmt.Sprintf(config.DefaultUriPrefix+"/databaseManagement/v1/elementType/%s/objectType/ne_info",
+ config.GetYamlConfig().Database.Name)
+ getNeInfoURI := restHostPort + getNeInfoPattern
+ neId := services.GetUriParamString(r, "ne_id", ",", true, true)
+ if neId == "" {
+ getNeInfoURI = getNeInfoURI + fmt.Sprintf("?WHERE=status+in+('0','3')+and+ne_type='%s'", neType)
+ } else {
+ getNeInfoURI = getNeInfoURI + fmt.Sprintf("?WHERE=status+in+('0','3')+and+ne_type='%v'+and+ne_id+in+%v", neType, neId)
+ }
+ log.Debug("getNeInfoURI:", getNeInfoURI)
+
+ resp, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}).
+ SetHeaders(map[string]string{"accessToken": token}).
+ SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ Get(getNeInfoURI)
+ if err != nil {
+ log.Error("Get ne_info from DB is failed:", err)
+ services.ResponseInternalServerError500NFConnectRefused(w)
+ return
+ }
+
+ neList, _ = dborm.XormParseResult(resp.Body())
+ }
+ omcNeTypeLower := "omc"
+ if config.GetYamlConfig().OMC.NeType != "" {
+ omcNeTypeLower = strings.ToLower(config.GetYamlConfig().OMC.NeType)
+ }
+ for _, ne := range neList {
+ result := make(map[string]interface{})
+ log.Debugf("r.RemoteAddr: %s omcNeTypeLower: %s", r.RemoteAddr, omcNeTypeLower)
+ log.Trace("ne: ", ne)
+ //if strings.ToLower(ne.NeType) != omcNeTypeLower || !strings.Contains(r.RemoteAddr, ne.Ip) {
+ if strings.ToLower(ne.NeType) != omcNeTypeLower {
+ hostUri := fmt.Sprintf("http://%s:%v", ne.Ip, ne.Port)
+ requestURI2NF := fmt.Sprintf("%s/api/rest/systemManagement/v1/elementType/%s/objectType/systemState",
+ hostUri, ne.NeType)
+ log.Debug("requestURI2NF:", requestURI2NF)
+
+ result["ipAddress"] = ne.Ip
+ resp, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}).
+ SetHeaders(map[string]string{"accessToken": token}).
+ 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)
+ 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
+ } else {
+ systemState := make(map[string]interface{})
+ _ = json.Unmarshal(resp.Body(), &systemState)
+ result["systemState"] = systemState
+ }
+ } else {
+ result["ipAddress"] = ne.Ip
+ emsState := GetEMSState(ne.Ip)
+ result["systemState"] = emsState
+ }
+ neItem := strings.ToUpper(ne.NeType) + "/" + ne.NeId
+ mapState := make(map[string]interface{})
+ mapState[neItem] = result
+
+ data = append(data, mapState)
+ log.Trace("data:", data)
+ }
+
+ var response Response
+ response.Data = data
+ services.ResponseWithJson(w, http.StatusOK, response)
+}
+
+func GetEMSState(ip string) *SysState {
+ log.Debug("GetEMSState processing... ")
+
+ sysInfo := new(SysInfo)
+ err := GetSysInfo(sysInfo)
+ if err != nil {
+ log.Error("Failed to GetSysInfo:", err)
+ return nil
+ }
+
+ cpuUsage := &CpuUsage{
+ NfCpuUsage: sysInfo.MyCpuPercent,
+ SysCpuUsage: sysInfo.SysCpuPercent,
+ }
+
+ memUsage := &MemUsage{
+ TotalMem: sysInfo.SysTotalRam,
+ NfUsedMem: sysInfo.MyUsedRam,
+ SysMemUsage: sysInfo.SysRamUsedPercent,
+ }
+
+ diskSpace := &DiskSpace{
+ PartitionNum: sysInfo.PartitionNum,
+ PartitionInfo: sysInfo.PartitionInfo,
+ }
+
+ // 获取主机的 IP 地址列表
+ ipAddrs := []string{ip}
+ if ip == "" || ip == "127.0.0.1" {
+ ipAddrs = []string{}
+ interfaces, err := net.Interfaces()
+ if err == nil {
+ for _, iface := range interfaces {
+ for _, v := range iface.Addrs {
+ name := iface.Name
+ if name[len(name)-1] == '0' {
+ name = name[0 : len(name)-1]
+ name = strings.Trim(name, "")
+ }
+ // ignore localhost
+ if name == "lo" {
+ continue
+ }
+ prefix := strings.Split(v.Addr, "/")[0]
+ if strings.Contains(prefix, "::") {
+ ipAddrs = append(ipAddrs, prefix)
+ }
+ if strings.Contains(prefix, ".") {
+ ipAddrs = append(ipAddrs, prefix)
+ }
+ }
+ }
+ }
+ }
+
+ version := "16.1.1"
+ if global.Version != "" {
+ version = global.Version
+ }
+ hostName, _ := os.Hostname()
+ dbInfo, _ := dborm.XormGetMySQLVersion()
+ emsState := &SysState{
+ HostName: hostName,
+ OsInfo: getUnameStr(),
+ DbInfo: dbInfo,
+ IpAddr: ipAddrs,
+ Port: config.GetYamlConfig().Rest[0].Port,
+ Version: version,
+ Capability: 9999999,
+ SerialNum: config.GetYamlConfig().OMC.Sn,
+ ExpiryDate: "-",
+ HardwareInfo: HardwareInfo{CPUs: getCpuNumber(), Memory: getTotalMemory()},
+ CpuUsage: *cpuUsage,
+ MemUsage: *memUsage,
+ DiskSpace: *diskSpace,
+ }
+
+ //getSystemInfo()
+ return emsState
+}
diff --git a/features/state/state_linux.go b/features/state/state_linux.go
new file mode 100644
index 0000000..cc7f31d
--- /dev/null
+++ b/features/state/state_linux.go
@@ -0,0 +1,244 @@
+//go:build linux
+// +build linux
+
+package state
+
+import (
+ "encoding/binary"
+ "fmt"
+ "os"
+ "runtime"
+ "syscall"
+ "time"
+
+ "nms_nbi/lib/log"
+
+ "github.com/shirou/gopsutil/v3/cpu"
+ "github.com/shirou/gopsutil/v3/disk"
+ "github.com/shirou/gopsutil/v3/mem"
+ "github.com/shirou/gopsutil/v3/process"
+)
+
+type SysInfo struct {
+ SysCpuPercent uint16 // x%
+ MyCpuPercent uint16 // x%, CPU percent of current proccess
+
+ SysTotalRam uint32 // KB
+ MyUsedRam uint32 // RAM usage of current proccess, KB
+ SysRamUsedPercent uint16 // x%
+
+ PartitionNum byte
+ PartitionInfo []PartitionInfo // usage of each partition
+}
+
+const MAX_PARTITION_NUM byte = 32
+
+func GetSysStat() ([]byte, int) {
+ // Get sys info
+ var sysInfo SysInfo
+ err := GetSysInfo(&sysInfo)
+ if err != nil {
+ return nil, 0
+ }
+
+ //log.Tracef("current sys info: %v", sysInfo)
+
+ // build ems buffer
+ var data []byte = make([]byte, 1024)
+ var len int
+ var i byte
+ binary.BigEndian.PutUint16(data[0:], sysInfo.MyCpuPercent) //x% * 100
+ binary.BigEndian.PutUint16(data[2:], sysInfo.SysCpuPercent) //x% * 100
+ binary.BigEndian.PutUint32(data[4:], sysInfo.SysTotalRam) // KB
+ binary.BigEndian.PutUint32(data[8:], sysInfo.MyUsedRam) // KB
+ binary.BigEndian.PutUint16(data[12:], sysInfo.SysRamUsedPercent) //x% * 100
+ data[14] = sysInfo.PartitionNum
+ for i = 0; i < sysInfo.PartitionNum; i++ {
+ binary.BigEndian.PutUint32(data[15+8*(i):], sysInfo.PartitionInfo[i].Total) // MB
+ binary.BigEndian.PutUint32(data[15+8*(i)+4:], sysInfo.PartitionInfo[i].Used) // MB
+ }
+ len = int(15 + 8*sysInfo.PartitionNum)
+
+ //log.Tracef("current sys stat buf: %v, len: %d", data, len)
+
+ return data, len
+}
+
+var pProc *process.Process = nil
+
+func GetSYsCpuPercent() float64 {
+ totalPercent, err := cpu.Percent(0, false) //(2*time.Second, false)
+ if err != nil {
+ return 0.0
+ } else {
+ return totalPercent[0]
+ }
+}
+
+func GetSysInfo(sysInfo *SysInfo) error {
+ // sys cpu percent
+ totalPercent, err := cpu.Percent(0, false) //(2*time.Second, false)
+ if err != nil {
+ sysInfo.SysCpuPercent = 0
+ } else {
+ sysInfo.SysCpuPercent = uint16(totalPercent[0] * 100)
+ }
+
+ if pProc == nil {
+ checkPid := os.Getpid()
+ pProc, err = process.NewProcess(int32(checkPid))
+ if err != nil {
+ log.Tracef("get process info error %v", err)
+ return err
+ }
+ }
+
+ // self cpu percent
+ percent, err := pProc.Percent(0) //(2*time.Second)
+ if err != nil {
+ log.Tracef("get process cpu percent error %v", err)
+ sysInfo.MyCpuPercent = 0
+ } else {
+ sysInfo.MyCpuPercent = uint16(percent * 100)
+ }
+
+ // self RAM(KB)
+ myRam, err := pProc.MemoryInfo()
+ if err != nil {
+ log.Tracef("get self memory info error %v", err)
+ sysInfo.MyUsedRam = 0
+ } else {
+ sysInfo.MyUsedRam = uint32(myRam.RSS / 1024)
+ }
+
+ // system RAM(KB)
+ sysRam, err := mem.VirtualMemory()
+ if err != nil {
+ log.Tracef("gett sys memory info error %v", err)
+ sysInfo.SysTotalRam = 0
+ sysInfo.SysRamUsedPercent = 0
+ } else {
+ sysInfo.SysTotalRam = uint32(sysRam.Total / 1024)
+ sysInfo.SysRamUsedPercent = uint16(sysRam.UsedPercent * 100)
+ }
+
+ // partition usage
+ GetPartitions(sysInfo)
+ return nil
+}
+
+func getProcess() process.Process {
+ checkPid := os.Getpid()
+ ret, _ := process.NewProcess(int32(checkPid))
+ return *ret
+}
+
+func GetSystemCpuInfo() {
+ physicalCnt, _ := cpu.Counts(false)
+ logicalCnt, _ := cpu.Counts(true)
+ log.Tracef("physical count:%d logical count:%d", physicalCnt, logicalCnt)
+
+ totalPercent, _ := cpu.Percent(3*time.Second, false) // per cpu is false
+ perPercents, _ := cpu.Percent(3*time.Second, true) // per cpu is true
+ log.Tracef("total percent:%v per percents:%v", totalPercent, perPercents)
+}
+
+func GetProcessCpuPercent() {
+ p := getProcess()
+ percent, err := p.Percent(0)
+ if err != nil {
+ log.Tracef("error %v", err)
+ }
+
+ numcpu := runtime.NumCPU()
+ // if percent < 0.0 || percent > 100.0*float64(numcpu) { // TODO
+ if percent < 0.0 {
+ log.Tracef("Err CPU Percent of Process: %f, CPU num: %d", percent, numcpu)
+ } else {
+ log.Tracef("get process CPU percent: %f, CPU num: %d", percent, numcpu)
+ }
+}
+
+func GetProcessMemoryInfo() {
+ p := getProcess()
+
+ v, err := p.MemoryInfo()
+ if err != nil {
+ log.Tracef("getting memory info error %v", err)
+ }
+
+ log.Tracef("get process memory info %v", v)
+
+ info, _ := mem.VirtualMemory()
+ fmt.Println(info)
+}
+
+func GetPartitions(sysInfo *SysInfo) {
+ sysInfo.PartitionNum = 0
+ //sysInfo.PartitionInfo = make([]PartitionInfo, MAX_PARTITION_NUM, MAX_PARTITION_NUM)
+ infos, _ := disk.Partitions(true)
+ for _, info := range infos {
+ GetOnePartitionUsage(info.Mountpoint, sysInfo)
+ if sysInfo.PartitionNum >= MAX_PARTITION_NUM {
+ break
+ }
+ }
+}
+
+func GetOnePartitionUsage(path string, sysInfo *SysInfo) int {
+ info, err := disk.Usage(path)
+ if err != nil {
+ return -1
+ }
+
+ if info.Total <= 0 { // info.Used/(1024 * 1024)MB
+ return 0
+ }
+ var partition PartitionInfo
+ partition.Total = uint32(info.Total / 1024 / 1024)
+ partition.Used = uint32(info.Used / 1024 / 1024)
+ sysInfo.PartitionInfo = append(sysInfo.PartitionInfo, partition)
+ sysInfo.PartitionNum++
+
+ /*data, err := json.MarshalIndent(info, "", " ")
+ if err != nil {
+ return -1
+ }
+
+ fmt.Println(string(data))*/
+ return 1
+}
+
+func getOS() string {
+ var osname string
+ if runtime.GOOS == "linux" {
+ osname = "GNU/Linux"
+ }
+ return osname
+}
+
+func utsnameToString(unameArray [65]int8) string {
+ var byteString [65]byte
+ var indexLength int
+ for ; unameArray[indexLength] != 0 && indexLength < 65; indexLength++ {
+ byteString[indexLength] = uint8(unameArray[indexLength])
+ }
+ return string(byteString[:indexLength])
+}
+
+func getUnameStr() string {
+ var utsname = syscall.Utsname{}
+ err := syscall.Uname(&utsname)
+ if err == nil {
+ name := utsnameToString(utsname.Sysname)
+ node := utsnameToString(utsname.Nodename)
+ release := utsnameToString(utsname.Release)
+ version := utsnameToString(utsname.Version)
+ machine := utsnameToString(utsname.Machine)
+ //domain:= utsnameToString(utsname.Domainname)
+ osName := getOS()
+ return fmt.Sprintf("%s %s %s %s %s %s", name, node,
+ release, version, machine, osName)
+ }
+ return ""
+}
diff --git a/features/state/state_windows.go b/features/state/state_windows.go
new file mode 100644
index 0000000..ef37c5c
--- /dev/null
+++ b/features/state/state_windows.go
@@ -0,0 +1,231 @@
+//go:build windows
+// +build windows
+
+package state
+
+import (
+ "encoding/binary"
+ "fmt"
+ "os"
+ "runtime"
+ "syscall"
+ "time"
+
+ "github.com/shirou/gopsutil/v3/cpu"
+ "github.com/shirou/gopsutil/v3/disk"
+ "github.com/shirou/gopsutil/v3/mem"
+ "github.com/shirou/gopsutil/v3/process"
+)
+
+type SysInfo struct {
+ SysCpuPercent uint16 // x%
+ MyCpuPercent uint16 // x%, CPU percent of current proccess
+
+ SysTotalRam uint32 // KB
+ MyUsedRam uint32 // RAM usage of current proccess, KB
+ SysRamUsedPercent uint16 // x%
+
+ PartitionNum byte
+ PartitionInfo []PartitionInfo // usage of each partition
+}
+
+const MAX_PARTITION_NUM byte = 32
+
+func GetSysStat() ([]byte, int) {
+ // Get sys info
+ var sysInfo SysInfo
+ err := GetSysInfo(&sysInfo)
+ if err != nil {
+ return nil, 0
+ }
+
+ //fmt.Printf("current sys info: %v", sysInfo)
+
+ // build ems buffer
+ var data []byte = make([]byte, 1024)
+ var len int
+ var i byte
+ binary.BigEndian.PutUint16(data[0:], sysInfo.MyCpuPercent) //x% * 100
+ binary.BigEndian.PutUint16(data[2:], sysInfo.SysCpuPercent) //x% * 100
+ binary.BigEndian.PutUint32(data[4:], sysInfo.SysTotalRam) // KB
+ binary.BigEndian.PutUint32(data[8:], sysInfo.MyUsedRam) // KB
+ binary.BigEndian.PutUint16(data[12:], sysInfo.SysRamUsedPercent) //x% * 100
+ data[14] = sysInfo.PartitionNum
+ for i = 0; i < sysInfo.PartitionNum; i++ {
+ binary.BigEndian.PutUint32(data[15+8*(i):], sysInfo.PartitionInfo[i].Total) // MB
+ binary.BigEndian.PutUint32(data[15+8*(i)+4:], sysInfo.PartitionInfo[i].Used) // MB
+ }
+ len = int(15 + 8*sysInfo.PartitionNum)
+
+ //fmt.Printf("current sys stat buf: %v, len: %d", data, len)
+
+ return data, len
+}
+
+var pProc *process.Process = nil
+
+func GetSYsCpuPercent() float64 {
+ totalPercent, err := cpu.Percent(0, false) //(2*time.Second, false)
+ if err != nil {
+ return 0.0
+ } else {
+ return totalPercent[0]
+ }
+}
+
+func GetSysInfo(sysInfo *SysInfo) error {
+ // sys cpu percent
+ totalPercent, err := cpu.Percent(0, false) //(2*time.Second, false)
+ if err != nil {
+ sysInfo.SysCpuPercent = 0
+ } else {
+ sysInfo.SysCpuPercent = uint16(totalPercent[0] * 100)
+ }
+
+ if pProc == nil {
+ checkPid := os.Getpid()
+ pProc, err = process.NewProcess(int32(checkPid))
+ if err != nil {
+ fmt.Printf("get process info error %v", err)
+ return err
+ }
+ }
+
+ // self cpu percent
+ percent, err := pProc.Percent(0) //(2*time.Second)
+ if err != nil {
+ fmt.Printf("get process cpu percent error %v", err)
+ sysInfo.MyCpuPercent = 0
+ } else {
+ sysInfo.MyCpuPercent = uint16(percent * 100)
+ }
+
+ // self RAM(KB)
+ myRam, err := pProc.MemoryInfo()
+ if err != nil {
+ fmt.Printf("get self memory info error %v", err)
+ sysInfo.MyUsedRam = 0
+ } else {
+ sysInfo.MyUsedRam = uint32(myRam.RSS / 1024)
+ }
+
+ // system RAM(KB)
+ sysRam, err := mem.VirtualMemory()
+ if err != nil {
+ fmt.Printf("gett sys memory info error %v", err)
+ sysInfo.SysTotalRam = 0
+ sysInfo.SysRamUsedPercent = 0
+ } else {
+ sysInfo.SysTotalRam = uint32(sysRam.Total / 1024)
+ sysInfo.SysRamUsedPercent = uint16(sysRam.UsedPercent * 100)
+ }
+
+ // partition usage
+ GetPartitions(sysInfo)
+ return nil
+}
+
+func getProcess() process.Process {
+ checkPid := os.Getpid()
+ ret, _ := process.NewProcess(int32(checkPid))
+ return *ret
+}
+
+func GetSystemCpuInfo() {
+ physicalCnt, _ := cpu.Counts(false)
+ logicalCnt, _ := cpu.Counts(true)
+ fmt.Printf("physical count:%d logical count:%d\n", physicalCnt, logicalCnt)
+
+ totalPercent, _ := cpu.Percent(3*time.Second, false) // per cpu is false
+ perPercents, _ := cpu.Percent(3*time.Second, true) // per cpu is true
+ fmt.Printf("total percent:%v per percents:%v\n", totalPercent, perPercents)
+}
+
+func GetProcessCpuPercent() {
+ p := getProcess()
+ percent, err := p.Percent(0)
+ if err != nil {
+ fmt.Printf("error %v", err)
+ }
+
+ numcpu := runtime.NumCPU()
+ // if percent < 0.0 || percent > 100.0*float64(numcpu) { // TODO
+ if percent < 0.0 {
+ fmt.Printf("Err CPU Percent of Process: %f, CPU num: %d", percent, numcpu)
+ } else {
+ fmt.Printf("get process CPU percent: %f, CPU num: %d", percent, numcpu)
+ }
+}
+
+func GetProcessMemoryInfo() {
+ p := getProcess()
+
+ v, err := p.MemoryInfo()
+ if err != nil {
+ fmt.Printf("getting memory info error %v", err)
+ }
+
+ fmt.Printf("get process memory info %v\n", v)
+
+ info, _ := mem.VirtualMemory()
+ fmt.Println(info)
+}
+
+func GetPartitions(sysInfo *SysInfo) {
+ sysInfo.PartitionNum = 0
+ //sysInfo.PartitionInfo = make([]PartitionInfo, MAX_PARTITION_NUM, MAX_PARTITION_NUM)
+ infos, _ := disk.Partitions(true)
+ for _, info := range infos {
+ GetOnePartitionUsage(info.Mountpoint, sysInfo)
+ if sysInfo.PartitionNum >= MAX_PARTITION_NUM {
+ break
+ }
+ }
+}
+
+func GetOnePartitionUsage(path string, sysInfo *SysInfo) int {
+ info, err := disk.Usage(path)
+ if err != nil {
+ return -1
+ }
+
+ if info.Total <= 0 { // info.Used/(1024 * 1024)MB
+ return 0
+ }
+ var partition PartitionInfo
+ partition.Total = uint32(info.Total / 1024 / 1024)
+ partition.Used = uint32(info.Used / 1024 / 1024)
+ sysInfo.PartitionInfo = append(sysInfo.PartitionInfo, partition)
+ sysInfo.PartitionNum++
+
+ /*data, err := json.MarshalIndent(info, "", " ")
+ if err != nil {
+ return -1
+ }
+
+ fmt.Println(string(data))*/
+ return 1
+}
+
+func getOS() string {
+ var osname string
+ if runtime.GOOS == "linux" {
+ osname = "GNU/Linux"
+ }
+ return osname
+}
+
+func utsnameToString(unameArray [65]int8) string {
+ var byteString [65]byte
+ var indexLength int
+ for ; unameArray[indexLength] != 0 && indexLength < 65; indexLength++ {
+ byteString[indexLength] = uint8(unameArray[indexLength])
+ }
+ return string(byteString[:indexLength])
+}
+
+func getUnameStr() string {
+ osInfo, _ := syscall.GetVersion()
+
+ return fmt.Sprintf("Widnows %d", osInfo)
+}
diff --git a/features/state/sysinfo.go b/features/state/sysinfo.go
new file mode 100644
index 0000000..171d211
--- /dev/null
+++ b/features/state/sysinfo.go
@@ -0,0 +1,75 @@
+package state
+
+import (
+ "nms_nbi/lib/log"
+
+ "github.com/shirou/gopsutil/cpu"
+ "github.com/shirou/gopsutil/disk"
+ "github.com/shirou/gopsutil/host"
+ "github.com/shirou/gopsutil/mem"
+)
+
+func getSystemInfo() {
+ // 获取主机信息
+ hostInfo, err := host.Info()
+ if err != nil {
+ log.Errorf("Failed to get host info: %v", err)
+ return
+ }
+ log.Tracef("Host info: %+v", hostInfo)
+
+ // 获取CPU信息
+ cpuInfo, err := cpu.Info()
+ if err != nil {
+ log.Errorf("Failed to get CPU info: %v", err)
+ return
+ }
+ log.Tracef("CPU info: %+v", cpuInfo)
+
+ // 获取内存信息
+ memInfo, err := mem.VirtualMemory()
+ if err != nil {
+ log.Errorf("Failed to get memory info: %v", err)
+ return
+ }
+ log.Tracef("Memory info: %+v", memInfo)
+
+ // 获取磁盘分区信息
+ diskPartitions, err := disk.Partitions(true)
+ if err != nil {
+ log.Errorf("Failed to get disk partitions: %v", err)
+ return
+ }
+ log.Tracef("Disk partitions: %+v", diskPartitions)
+ for _, partition := range diskPartitions {
+ // 获取每个磁盘分区的使用情况
+ usage, err := disk.Usage(partition.Mountpoint)
+ if err != nil {
+ log.Errorf("Failed to get disk usage for %s: %v", partition.Mountpoint, err)
+ continue
+ }
+ log.Tracef("%s usage: %+v", partition.Mountpoint, usage)
+ }
+}
+
+func getCpuNumber() int {
+ // 获取CPU信息
+ cpuInfo, err := cpu.Info()
+ if err != nil {
+ log.Errorf("Failed to get CPU info: %v", err)
+ return 0
+ }
+ log.Tracef("CPU info: %+v", cpuInfo)
+ return len(cpuInfo)
+}
+
+func getTotalMemory() int {
+ // 获取内存信息
+ memInfo, err := mem.VirtualMemory()
+ if err != nil {
+ log.Errorf("Failed to get memory info: %v", err)
+ return 0
+ }
+ log.Tracef("Memory info: %+v", memInfo)
+ return int(memInfo.Total)
+}
diff --git a/features/sys_config/api_sys_config.go b/features/sys_config/api_sys_config.go
new file mode 100644
index 0000000..806ac83
--- /dev/null
+++ b/features/sys_config/api_sys_config.go
@@ -0,0 +1,230 @@
+package sysconfig
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+
+ "nms_nbi/features/sys_config/model"
+ "nms_nbi/features/sys_config/service"
+ "nms_nbi/lib/core/utils/ctx"
+ "nms_nbi/lib/core/utils/parse"
+ "nms_nbi/lib/core/vo/result"
+ "nms_nbi/lib/midware"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+)
+
+// 参数配置信息接口添加到路由
+func Routers() []services.RouterItem {
+ // 实例化控制层 SysConfigApi 结构体
+ var apis = &SysConfigApi{
+ sysConfigService: service.NewServiceSysConfig,
+ }
+
+ rs := [...]services.RouterItem{
+ {
+ Method: "GET",
+ Pattern: "/configs",
+ Handler: apis.List,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "GET",
+ Pattern: "/config/{configId}",
+ Handler: apis.Info,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "POST",
+ Pattern: "/config",
+ Handler: apis.Add,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "PUT",
+ Pattern: "/config",
+ Handler: apis.Edit,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "DELETE",
+ Pattern: "/config/{configIds}",
+ Handler: apis.Remove,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "PUT",
+ Pattern: "/config/refreshCache",
+ Handler: apis.RefreshCache,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "GET",
+ Pattern: "/config/configKey/{configKey}",
+ Handler: apis.ConfigKey,
+ Middleware: midware.Authorize(nil),
+ },
+ // 添加更多的 Router 对象...
+ }
+
+ // 生成两组前缀路由
+ rsPrefix := []services.RouterItem{}
+ for _, v := range rs {
+ path := "/configManage/{apiVersion}" + v.Pattern
+ // 固定前缀
+ v.Pattern = config.DefaultUriPrefix + path
+ rsPrefix = append(rsPrefix, v)
+ // 可配置
+ v.Pattern = config.UriPrefix + path
+ rsPrefix = append(rsPrefix, v)
+ }
+ return rsPrefix
+}
+
+// 参数配置信息
+//
+// PATH /configManage
+type SysConfigApi struct {
+ // 参数配置服务
+ sysConfigService *service.ServiceSysConfig
+}
+
+// 参数配置列表
+//
+// GET /list
+func (s *SysConfigApi) List(w http.ResponseWriter, r *http.Request) {
+ querys := ctx.QueryMap(r)
+ data := s.sysConfigService.SelectConfigPage(querys)
+ ctx.JSON(w, 200, result.Ok(data))
+}
+
+// 参数配置信息
+//
+// GET /:configId
+func (s *SysConfigApi) Info(w http.ResponseWriter, r *http.Request) {
+ configId := ctx.Param(r, "configId")
+ if configId == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+ data := s.sysConfigService.SelectConfigById(configId)
+ if data.ConfigID == configId {
+ ctx.JSON(w, 200, result.OkData(data))
+ return
+ }
+ ctx.JSON(w, 200, result.Err(nil))
+}
+
+// 参数配置新增
+//
+// POST /
+func (s *SysConfigApi) Add(w http.ResponseWriter, r *http.Request) {
+ var body model.SysConfig
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil || body.ConfigID != "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ // 检查属性值唯一
+ uniqueConfigKey := s.sysConfigService.CheckUniqueConfigKey(body.ConfigKey, "")
+ if !uniqueConfigKey {
+ msg := fmt.Sprintf("[%s] Parameter key name already exists", body.ConfigKey)
+ ctx.JSON(w, 200, result.ErrMsg(msg))
+ return
+ }
+
+ body.CreateBy = ctx.LoginUserToUserName(r)
+ insertId := s.sysConfigService.InsertConfig(body)
+ if insertId != "" {
+ ctx.JSON(w, 200, result.Ok(nil))
+ return
+ }
+ ctx.JSON(w, 200, result.Err(nil))
+}
+
+// 参数配置修改
+//
+// PUT /
+func (s *SysConfigApi) Edit(w http.ResponseWriter, r *http.Request) {
+ var body model.SysConfig
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil || body.ConfigID == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ // 检查属性值唯一
+ uniqueConfigKey := s.sysConfigService.CheckUniqueConfigKey(body.ConfigKey, body.ConfigID)
+ if !uniqueConfigKey {
+ msg := fmt.Sprintf("[%s] Parameter key name already exists", body.ConfigKey)
+ ctx.JSON(w, 200, result.ErrMsg(msg))
+ return
+ }
+
+ // 检查是否存在
+ config := s.sysConfigService.SelectConfigById(body.ConfigID)
+ if config.ConfigID != body.ConfigID {
+ ctx.JSON(w, 200, result.ErrMsg("No permission to access parameter configuration data!"))
+ return
+ }
+
+ body.UpdateBy = ctx.LoginUserToUserName(r)
+ rows := s.sysConfigService.UpdateConfig(body)
+ if rows > 0 {
+ ctx.JSON(w, 200, result.Ok(nil))
+ return
+ }
+ ctx.JSON(w, 200, result.Err(nil))
+}
+
+// 参数配置删除
+//
+// DELETE /:configIds
+func (s *SysConfigApi) Remove(w http.ResponseWriter, r *http.Request) {
+ configIds := ctx.Param(r, "configIds")
+ if configIds == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+ // 处理字符转id数组后去重
+ ids := strings.Split(configIds, ",")
+ uniqueIDs := parse.RemoveDuplicates(ids)
+ if len(uniqueIDs) <= 0 {
+ ctx.JSON(w, 200, result.Err(nil))
+ return
+ }
+ rows, err := s.sysConfigService.DeleteConfigByIds(uniqueIDs)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+ msg := fmt.Sprintf("删除成功:%d", rows)
+ ctx.JSON(w, 200, result.OkMsg(msg))
+}
+
+// 参数配置刷新缓存
+//
+// PUT /refreshCache
+func (s *SysConfigApi) RefreshCache(w http.ResponseWriter, r *http.Request) {
+ s.sysConfigService.ResetConfigCache()
+ ctx.JSON(w, 200, result.Ok(nil))
+}
+
+// 参数配置根据参数键名
+//
+// GET /configKey/:configKey
+func (s *SysConfigApi) ConfigKey(w http.ResponseWriter, r *http.Request) {
+ configKey := ctx.Param(r, "configKey")
+ if configKey == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+ key := s.sysConfigService.SelectConfigValueByKey(configKey)
+ if key != "" {
+ ctx.JSON(w, 200, result.OkData(key))
+ return
+ }
+ ctx.JSON(w, 200, result.Err(nil))
+}
diff --git a/features/sys_config/model/sys_config.go b/features/sys_config/model/sys_config.go
new file mode 100644
index 0000000..8949c67
--- /dev/null
+++ b/features/sys_config/model/sys_config.go
@@ -0,0 +1,25 @@
+package model
+
+// 参数配置对象 sys_config
+type SysConfig struct {
+ // 参数主键
+ ConfigID string `json:"configId"`
+ // 参数名称
+ ConfigName string `json:"configName" binding:"required"`
+ // 参数键名
+ ConfigKey string `json:"configKey" binding:"required"`
+ // 参数键值
+ ConfigValue string `json:"configValue" binding:"required"`
+ // 系统内置(Y是 N否)
+ ConfigType string `json:"configType"`
+ // 创建者
+ CreateBy string `json:"createBy"`
+ // 创建时间
+ CreateTime int64 `json:"createTime"`
+ // 更新者
+ UpdateBy string `json:"updateBy"`
+ // 更新时间
+ UpdateTime int64 `json:"updateTime"`
+ // 备注
+ Remark string `json:"remark"`
+}
diff --git a/features/sys_config/service/repo_sys_config.go b/features/sys_config/service/repo_sys_config.go
new file mode 100644
index 0000000..35ae982
--- /dev/null
+++ b/features/sys_config/service/repo_sys_config.go
@@ -0,0 +1,336 @@
+package service
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "nms_nbi/features/sys_config/model"
+ "nms_nbi/lib/core/datasource"
+ "nms_nbi/lib/core/utils/date"
+ "nms_nbi/lib/core/utils/parse"
+ "nms_nbi/lib/log"
+)
+
+// 实例化数据层 RepoSysConfig 结构体
+var NewRepoSysConfig = &RepoSysConfig{
+ selectSql: `select
+ config_id, config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark
+ from sys_config`,
+
+ resultMap: map[string]string{
+ "config_id": "ConfigID",
+ "config_name": "ConfigName",
+ "config_key": "ConfigKey",
+ "config_value": "ConfigValue",
+ "config_type": "ConfigType",
+ "remark": "Remark",
+ "create_by": "CreateBy",
+ "create_time": "CreateTime",
+ "update_by": "UpdateBy",
+ "update_time": "UpdateTime",
+ },
+}
+
+// RepoSysConfig 参数配置表 数据层处理
+type RepoSysConfig struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *RepoSysConfig) convertResultRows(rows []map[string]any) []model.SysConfig {
+ arr := make([]model.SysConfig, 0)
+ for _, row := range rows {
+ sysConfig := model.SysConfig{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ datasource.SetFieldValue(&sysConfig, keyMapper, value)
+ }
+ }
+ arr = append(arr, sysConfig)
+ }
+ return arr
+}
+
+// SelectDictDataPage 分页查询参数配置列表数据
+func (r *RepoSysConfig) SelectConfigPage(query map[string]any) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if v, ok := query["configName"]; ok && v != "" {
+ conditions = append(conditions, "config_name like concat(?, '%')")
+ params = append(params, v)
+ }
+ if v, ok := query["configType"]; ok && v != "" {
+ conditions = append(conditions, "config_type = ?")
+ params = append(params, v)
+ }
+ if v, ok := query["configKey"]; ok && v != "" {
+ conditions = append(conditions, "config_key like concat(?, '%')")
+ params = append(params, v)
+ }
+ beginTime, ok := query["beginTime"]
+ if !ok {
+ beginTime, ok = query["params[beginTime]"]
+ }
+ if ok && beginTime != "" {
+ conditions = append(conditions, "create_time >= ?")
+ beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD)
+ params = append(params, beginDate.UnixMilli())
+ }
+ endTime, ok := query["endTime"]
+ if !ok {
+ endTime, ok = query["params[endTime]"]
+ }
+ if ok && endTime != "" {
+ conditions = append(conditions, "create_time <= ?")
+ endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD)
+ params = append(params, endDate.UnixMilli())
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := "select count(1) as 'total' from sys_config"
+ totalRows, err := datasource.RawDB("", totalSql+whereSql, params)
+ if err != nil {
+ log.Errorf("total err => %v", err)
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return map[string]any{
+ "total": total,
+ "rows": []model.SysConfig{},
+ }
+ }
+
+ // 分页
+ pageNum, pageSize := datasource.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ }
+
+ // 转换实体
+ rows := r.convertResultRows(results)
+ return map[string]any{
+ "total": total,
+ "rows": rows,
+ }
+}
+
+// SelectConfigList 查询参数配置列表
+func (r *RepoSysConfig) SelectConfigList(sysConfig model.SysConfig) []model.SysConfig {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysConfig.ConfigName != "" {
+ conditions = append(conditions, "config_name like concat(?, '%')")
+ params = append(params, sysConfig.ConfigName)
+ }
+ if sysConfig.ConfigType != "" {
+ conditions = append(conditions, "config_type = ?")
+ params = append(params, sysConfig.ConfigType)
+ }
+ if sysConfig.ConfigKey != "" {
+ conditions = append(conditions, "config_key like concat(?, '%')")
+ params = append(params, sysConfig.ConfigKey)
+ }
+ if sysConfig.CreateTime > 0 {
+ conditions = append(conditions, "create_time >= ?")
+ params = append(params, sysConfig.CreateTime)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ querySql := r.selectSql + whereSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ return []model.SysConfig{}
+ }
+
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// SelectConfigValueByKey 通过参数键名查询参数键值
+func (r *RepoSysConfig) SelectConfigValueByKey(configKey string) string {
+ querySql := "select config_value as 'str' from sys_config where config_key = ?"
+ results, err := datasource.RawDB("", querySql, []any{configKey})
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ return ""
+ }
+ if len(results) > 0 {
+ return fmt.Sprint(results[0]["str"])
+ }
+ return ""
+}
+
+// SelectConfigByIds 通过配置ID查询参数配置信息
+func (r *RepoSysConfig) SelectConfigByIds(configIds []string) []model.SysConfig {
+ placeholder := datasource.KeyPlaceholderByQuery(len(configIds))
+ querySql := r.selectSql + " where config_id in (" + placeholder + ")"
+ parameters := datasource.ConvertIdsSlice(configIds)
+ results, err := datasource.RawDB("", querySql, parameters)
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ return []model.SysConfig{}
+ }
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// CheckUniqueConfig 校验配置参数是否唯一
+func (r *RepoSysConfig) CheckUniqueConfig(sysConfig model.SysConfig) string {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysConfig.ConfigKey != "" {
+ conditions = append(conditions, "config_key = ?")
+ params = append(params, sysConfig.ConfigKey)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ } else {
+ return ""
+ }
+
+ // 查询数据
+ querySql := "select config_id as 'str' from sys_config " + whereSql + " limit 1"
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ log.Errorf("query err %v", err)
+ return ""
+ }
+ if len(results) > 0 {
+ return fmt.Sprint(results[0]["str"])
+ }
+ return ""
+}
+
+// InsertConfig 新增参数配置
+func (r *RepoSysConfig) InsertConfig(sysConfig model.SysConfig) string {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysConfig.ConfigName != "" {
+ params["config_name"] = sysConfig.ConfigName
+ }
+ if sysConfig.ConfigKey != "" {
+ params["config_key"] = sysConfig.ConfigKey
+ }
+ if sysConfig.ConfigValue != "" {
+ params["config_value"] = sysConfig.ConfigValue
+ }
+ if sysConfig.ConfigType != "" {
+ params["config_type"] = sysConfig.ConfigType
+ }
+ if sysConfig.Remark != "" {
+ params["remark"] = sysConfig.Remark
+ }
+ if sysConfig.CreateBy != "" {
+ params["create_by"] = sysConfig.CreateBy
+ params["create_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, placeholder, values := datasource.KeyPlaceholderValueByInsert(params)
+ sql := "insert into sys_config (" + strings.Join(keys, ",") + ")values(" + placeholder + ")"
+
+ // 执行插入
+ results, err := datasource.ExecDB("", sql, values)
+ if err != nil {
+ log.Errorf("insert row : %v", err.Error())
+ return ""
+ }
+
+ insertId, err := results.LastInsertId()
+ if err != nil {
+ log.Errorf("insert row : %v", err.Error())
+ return ""
+ }
+ return fmt.Sprint(insertId)
+}
+
+// UpdateConfig 修改参数配置
+func (r *RepoSysConfig) UpdateConfig(sysConfig model.SysConfig) int64 {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysConfig.ConfigName != "" {
+ params["config_name"] = sysConfig.ConfigName
+ }
+ if sysConfig.ConfigKey != "" {
+ params["config_key"] = sysConfig.ConfigKey
+ }
+ if sysConfig.ConfigValue != "" {
+ params["config_value"] = sysConfig.ConfigValue
+ }
+ if sysConfig.ConfigType != "" {
+ params["config_type"] = sysConfig.ConfigType
+ }
+ if sysConfig.Remark != "" {
+ params["remark"] = sysConfig.Remark
+ }
+ if sysConfig.UpdateBy != "" {
+ params["update_by"] = sysConfig.UpdateBy
+ params["update_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, values := datasource.KeyValueByUpdate(params)
+ sql := "update sys_config set " + strings.Join(keys, ",") + " where config_id = ?"
+
+ // 执行更新
+ values = append(values, sysConfig.ConfigID)
+ results, err := datasource.ExecDB("", sql, values)
+ if err != nil {
+ log.Errorf("update row : %v", err.Error())
+ return 0
+ }
+ affected, err := results.RowsAffected()
+ if err != nil {
+ log.Errorf("update err => %v", err)
+ return 0
+ }
+ return affected
+}
+
+// DeleteConfigByIds 批量删除参数配置信息
+func (r *RepoSysConfig) DeleteConfigByIds(configIds []string) int64 {
+ placeholder := datasource.KeyPlaceholderByQuery(len(configIds))
+ sql := "delete from sys_config where config_id in (" + placeholder + ")"
+ parameters := datasource.ConvertIdsSlice(configIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ log.Errorf("delete err => %v", err)
+ return 0
+ }
+ affected, err := results.RowsAffected()
+ if err != nil {
+ log.Errorf("delete err => %v", err)
+ return 0
+ }
+ return affected
+}
diff --git a/features/sys_config/service/service_sys_config.go b/features/sys_config/service/service_sys_config.go
new file mode 100644
index 0000000..8e52875
--- /dev/null
+++ b/features/sys_config/service/service_sys_config.go
@@ -0,0 +1,155 @@
+package service
+
+import (
+ "errors"
+
+ "nms_nbi/features/sys_config/model"
+ "nms_nbi/lib/core/cache"
+ "nms_nbi/lib/core/constants/cachekey"
+)
+
+// 实例化服务层 ServiceSysConfig 结构体
+var NewServiceSysConfig = &ServiceSysConfig{
+ sysConfigRepository: NewRepoSysConfig,
+}
+
+// ServiceSysConfig 参数配置 服务层处理
+type ServiceSysConfig struct {
+ // 参数配置表
+ sysConfigRepository *RepoSysConfig
+}
+
+// SelectDictDataPage 分页查询参数配置列表数据
+func (r *ServiceSysConfig) SelectConfigPage(query map[string]any) map[string]any {
+ return r.sysConfigRepository.SelectConfigPage(query)
+}
+
+// SelectConfigList 查询参数配置列表
+func (r *ServiceSysConfig) SelectConfigList(sysConfig model.SysConfig) []model.SysConfig {
+ return r.sysConfigRepository.SelectConfigList(sysConfig)
+}
+
+// SelectConfigValueByKey 通过参数键名查询参数键值
+func (r *ServiceSysConfig) SelectConfigValueByKey(configKey string) string {
+ cacheKey := r.getCacheKey(configKey)
+ // 从缓存中读取
+ cacheValue, ok := cache.GetLocal(cacheKey)
+ if cacheValue != nil && ok {
+ return cacheValue.(string)
+ }
+ // 无缓存时读取数据放入缓存中
+ configValue := r.sysConfigRepository.SelectConfigValueByKey(configKey)
+ if configValue != "" {
+ cache.SetLocal(cacheKey, configValue)
+ return configValue
+ }
+ return ""
+}
+
+// SelectConfigById 通过配置ID查询参数配置信息
+func (r *ServiceSysConfig) SelectConfigById(configId string) model.SysConfig {
+ if configId == "" {
+ return model.SysConfig{}
+ }
+ configs := r.sysConfigRepository.SelectConfigByIds([]string{configId})
+ if len(configs) > 0 {
+ return configs[0]
+ }
+ return model.SysConfig{}
+}
+
+// CheckUniqueConfigKey 校验参数键名是否唯一
+func (r *ServiceSysConfig) CheckUniqueConfigKey(configKey, configId string) bool {
+ uniqueId := r.sysConfigRepository.CheckUniqueConfig(model.SysConfig{
+ ConfigKey: configKey,
+ })
+ if uniqueId == configId {
+ return true
+ }
+ return uniqueId == ""
+}
+
+// InsertConfig 新增参数配置
+func (r *ServiceSysConfig) InsertConfig(sysConfig model.SysConfig) string {
+ configId := r.sysConfigRepository.InsertConfig(sysConfig)
+ if configId != "" {
+ r.loadingConfigCache(sysConfig.ConfigKey)
+ }
+ return configId
+}
+
+// UpdateConfig 修改参数配置
+func (r *ServiceSysConfig) UpdateConfig(sysConfig model.SysConfig) int64 {
+ rows := r.sysConfigRepository.UpdateConfig(sysConfig)
+ if rows > 0 {
+ r.loadingConfigCache(sysConfig.ConfigKey)
+ }
+ return rows
+}
+
+// DeleteConfigByIds 批量删除参数配置信息
+func (r *ServiceSysConfig) DeleteConfigByIds(configIds []string) (int64, error) {
+ // 检查是否存在
+ configs := r.sysConfigRepository.SelectConfigByIds(configIds)
+ if len(configs) <= 0 {
+ return 0, errors.New("does not have permission to access parameter configuration data")
+ }
+ for _, config := range configs {
+ // 检查是否为内置参数
+ if config.ConfigType == "Y" {
+ return 0, errors.New(config.ConfigID + " Configuration parameters are built-in parameters and their deletion is prohibited!")
+ }
+ // 清除缓存
+ r.clearConfigCache(config.ConfigKey)
+ }
+ if len(configs) == len(configIds) {
+ rows := r.sysConfigRepository.DeleteConfigByIds(configIds)
+ return rows, nil
+ }
+ return 0, errors.New("failed to delete parameter configuration information")
+}
+
+// ResetConfigCache 重置参数缓存数据
+func (r *ServiceSysConfig) ResetConfigCache() {
+ r.clearConfigCache("*")
+ r.loadingConfigCache("*")
+}
+
+// getCacheKey 组装缓存key
+func (r *ServiceSysConfig) getCacheKey(configKey string) string {
+ return cachekey.SYS_CONFIG_KEY + configKey
+}
+
+// loadingConfigCache 加载参数缓存数据
+func (r *ServiceSysConfig) loadingConfigCache(configKey string) {
+ // 查询全部参数
+ if configKey == "*" {
+ sysConfigs := r.SelectConfigList(model.SysConfig{})
+ for _, v := range sysConfigs {
+ key := r.getCacheKey(v.ConfigKey)
+ cache.DeleteLocal(key)
+ cache.SetLocal(key, v.ConfigValue)
+ }
+ return
+ }
+ // 指定参数
+ if configKey != "" {
+ cacheValue := r.sysConfigRepository.SelectConfigValueByKey(configKey)
+ if cacheValue != "" {
+ key := r.getCacheKey(configKey)
+ cache.DeleteLocal(key)
+ cache.SetLocal(key, cacheValue)
+ }
+ return
+ }
+}
+
+// clearConfigCache 清空参数缓存数据
+func (r *ServiceSysConfig) clearConfigCache(configKey string) bool {
+ key := r.getCacheKey(configKey)
+ keys := cache.GetLocalKeys(key)
+ for _, v := range keys {
+ cache.DeleteLocal(v)
+ }
+ return len(keys) > 0
+}
diff --git a/features/sys_dict_data/api_sys_dict_data.go b/features/sys_dict_data/api_sys_dict_data.go
new file mode 100644
index 0000000..756c3ad
--- /dev/null
+++ b/features/sys_dict_data/api_sys_dict_data.go
@@ -0,0 +1,247 @@
+package sysdictdata
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+
+ "nms_nbi/features/sys_dict_data/model"
+ sysDictDataService "nms_nbi/features/sys_dict_data/service"
+ sysDictTypeService "nms_nbi/features/sys_dict_type/service"
+ "nms_nbi/lib/core/utils/ctx"
+ "nms_nbi/lib/core/utils/parse"
+ "nms_nbi/lib/core/vo/result"
+ "nms_nbi/lib/midware"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+)
+
+// 字典类型对应的字典数据信息接口添加到路由
+func Routers() []services.RouterItem {
+ // 实例化控制层 SysDictDataApi 结构体
+ var apis = &SysDictDataApi{
+ sysDictDataService: sysDictDataService.NewServiceSysDictData,
+ sysDictTypeService: sysDictTypeService.NewServiceSysDictType,
+ }
+
+ rs := [...]services.RouterItem{
+ {
+ Method: "GET",
+ Pattern: "/dictDatas",
+ Handler: apis.List,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "GET",
+ Pattern: "/dictData/{dictCode}",
+ Handler: apis.Info,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "POST",
+ Pattern: "/dictData",
+ Handler: apis.Add,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "PUT",
+ Pattern: "/dictData",
+ Handler: apis.Edit,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "DELETE",
+ Pattern: "/dictData/{dictCodes}",
+ Handler: apis.Remove,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "GET",
+ Pattern: "/dictData/type/{dictType}",
+ Handler: apis.DictType,
+ Middleware: midware.Authorize(nil),
+ },
+ // 添加更多的 Router 对象...
+ }
+
+ // 生成两组前缀路由
+ rsPrefix := []services.RouterItem{}
+ for _, v := range rs {
+ path := "/dictDataManage/{apiVersion}" + v.Pattern
+ // 固定前缀
+ v.Pattern = config.DefaultUriPrefix + path
+ rsPrefix = append(rsPrefix, v)
+ // 可配置
+ v.Pattern = config.UriPrefix + path
+ rsPrefix = append(rsPrefix, v)
+ }
+ return rsPrefix
+}
+
+// 字典类型对应的字典数据信息
+//
+// PATH /dictDataManage
+type SysDictDataApi struct {
+ // 字典数据服务
+ sysDictDataService *sysDictDataService.ServiceSysDictData
+ // 字典类型服务
+ sysDictTypeService *sysDictTypeService.ServiceSysDictType
+}
+
+// 字典数据列表
+//
+// GET /list
+func (s *SysDictDataApi) List(w http.ResponseWriter, r *http.Request) {
+ querys := ctx.QueryMap(r)
+ data := s.sysDictDataService.SelectDictDataPage(querys)
+ ctx.JSON(w, 200, result.Ok(data))
+}
+
+// 字典数据详情
+//
+// GET /:dictCode
+func (s *SysDictDataApi) Info(w http.ResponseWriter, r *http.Request) {
+ dictCode := ctx.Param(r, "dictCode")
+ if dictCode == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+ data := s.sysDictDataService.SelectDictDataByCode(dictCode)
+ if data.DictCode == dictCode {
+ ctx.JSON(w, 200, result.OkData(data))
+ return
+ }
+ ctx.JSON(w, 200, result.Err(nil))
+}
+
+// 字典数据新增
+//
+// POST /
+func (s *SysDictDataApi) Add(w http.ResponseWriter, r *http.Request) {
+ var body model.SysDictData
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil || body.DictCode != "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ // 检查字典类型是否存在
+ sysDictType := s.sysDictTypeService.SelectDictTypeByType(body.DictType)
+ if sysDictType.DictType != body.DictType {
+ ctx.JSON(w, 200, result.ErrMsg("No permission to access dictionary type data!"))
+ return
+ }
+
+ // 检查字典标签唯一
+ uniqueDictLabel := s.sysDictDataService.CheckUniqueDictLabel(body.DictType, body.DictLabel, "")
+ if !uniqueDictLabel {
+ msg := fmt.Sprintf("[%s] The subscript signature of this dictionary type already exists", body.DictLabel)
+ ctx.JSON(w, 200, result.ErrMsg(msg))
+ return
+ }
+
+ // 检查字典键值唯一
+ uniqueDictValue := s.sysDictDataService.CheckUniqueDictValue(body.DictType, body.DictValue, "")
+ if !uniqueDictValue {
+ msg := fmt.Sprintf("[%s] The label value under this dictionary type already exists", body.DictValue)
+ ctx.JSON(w, 200, result.ErrMsg(msg))
+ return
+ }
+
+ body.CreateBy = ctx.LoginUserToUserName(r)
+ insertId := s.sysDictDataService.InsertDictData(body)
+ if insertId != "" {
+ ctx.JSON(w, 200, result.Ok(nil))
+ return
+ }
+ ctx.JSON(w, 200, result.Err(nil))
+}
+
+// 字典类型修改
+//
+// PUT /
+func (s *SysDictDataApi) Edit(w http.ResponseWriter, r *http.Request) {
+ var body model.SysDictData
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil || body.DictCode == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ // 检查字典类型是否存在
+ sysDictType := s.sysDictTypeService.SelectDictTypeByType(body.DictType)
+ if sysDictType.DictType != body.DictType {
+ ctx.JSON(w, 200, result.ErrMsg("No permission to access dictionary type data!"))
+ return
+ }
+
+ // 检查字典编码是否存在
+ SysDictDataApi := s.sysDictDataService.SelectDictDataByCode(body.DictCode)
+ if SysDictDataApi.DictCode != body.DictCode {
+ ctx.JSON(w, 200, result.ErrMsg("No permission to access dictionary encoding data!"))
+ return
+ }
+
+ // 检查字典标签唯一
+ uniqueDictLabel := s.sysDictDataService.CheckUniqueDictLabel(body.DictType, body.DictLabel, body.DictCode)
+ if !uniqueDictLabel {
+ msg := fmt.Sprintf("Data modification failed for [%s], the dictionary type subscript signature already exists", body.DictLabel)
+ ctx.JSON(w, 200, result.ErrMsg(msg))
+ return
+ }
+
+ // 检查字典键值唯一
+ uniqueDictValue := s.sysDictDataService.CheckUniqueDictValue(body.DictType, body.DictValue, body.DictCode)
+ if !uniqueDictValue {
+ msg := fmt.Sprintf("Data modification failed for [%s], label value already exists under this dictionary type", body.DictValue)
+ ctx.JSON(w, 200, result.ErrMsg(msg))
+ return
+ }
+
+ body.UpdateBy = ctx.LoginUserToUserName(r)
+ rows := s.sysDictDataService.UpdateDictData(body)
+ if rows > 0 {
+ ctx.JSON(w, 200, result.Ok(nil))
+ return
+ }
+ ctx.JSON(w, 200, result.Err(nil))
+}
+
+// 字典数据删除
+//
+// DELETE /:dictCodes
+func (s *SysDictDataApi) Remove(w http.ResponseWriter, r *http.Request) {
+ dictCodes := ctx.Param(r, "dictCodes")
+ if dictCodes == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+ // 处理字符转id数组后去重
+ ids := strings.Split(dictCodes, ",")
+ uniqueIDs := parse.RemoveDuplicates(ids)
+ if len(uniqueIDs) <= 0 {
+ ctx.JSON(w, 200, result.Err(nil))
+ return
+ }
+ rows, err := s.sysDictDataService.DeleteDictDataByCodes(uniqueIDs)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+ msg := fmt.Sprintf("Successfully deleted: %d", rows)
+ ctx.JSON(w, 200, result.OkMsg(msg))
+}
+
+// 字典数据列表(指定字典类型)
+//
+// GET /type/:dictType
+func (s *SysDictDataApi) DictType(w http.ResponseWriter, r *http.Request) {
+ dictType := ctx.Param(r, "dictType")
+ if dictType == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ data := s.sysDictDataService.SelectDictDataByType(dictType)
+ ctx.JSON(w, 200, result.OkData(data))
+}
diff --git a/features/sys_dict_data/model/sys_dict_data.go b/features/sys_dict_data/model/sys_dict_data.go
new file mode 100644
index 0000000..7c8cd06
--- /dev/null
+++ b/features/sys_dict_data/model/sys_dict_data.go
@@ -0,0 +1,31 @@
+package model
+
+// SysDictData 字典数据对象 sys_dict_data
+type SysDictData struct {
+ // 字典编码
+ DictCode string `json:"dictCode"`
+ // 字典排序
+ DictSort int `json:"dictSort"`
+ // 字典标签
+ DictLabel string `json:"dictLabel" binding:"required"`
+ // 字典键值
+ DictValue string `json:"dictValue" binding:"required"`
+ // 字典类型
+ DictType string `json:"dictType" binding:"required"`
+ // 样式属性(样式扩展)
+ TagClass string `json:"tagClass"`
+ // 标签类型(预设颜色)
+ TagType string `json:"tagType"`
+ // 状态(0停用 1正常)
+ Status string `json:"status"`
+ // 创建者
+ CreateBy string `json:"createBy"`
+ // 创建时间
+ CreateTime int64 `json:"createTime"`
+ // 更新者
+ UpdateBy string `json:"updateBy"`
+ // 更新时间
+ UpdateTime int64 `json:"updateTime"`
+ // 备注
+ Remark string `json:"remark"`
+}
diff --git a/features/sys_dict_data/repo/repo_sys_dict_data.go b/features/sys_dict_data/repo/repo_sys_dict_data.go
new file mode 100644
index 0000000..0b16330
--- /dev/null
+++ b/features/sys_dict_data/repo/repo_sys_dict_data.go
@@ -0,0 +1,369 @@
+package repo
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "nms_nbi/features/sys_dict_data/model"
+ "nms_nbi/lib/core/datasource"
+ "nms_nbi/lib/core/utils/parse"
+ "nms_nbi/lib/log"
+)
+
+// 实例化数据层 RepoSysDictData 结构体
+var NewRepoSysDictData = &RepoSysDictData{
+ selectSql: `select
+ dict_code, dict_sort, dict_label, dict_value, dict_type, tag_class, tag_type, status, create_by, create_time, remark
+ from sys_dict_data`,
+
+ resultMap: map[string]string{
+ "dict_code": "DictCode",
+ "dict_sort": "DictSort",
+ "dict_label": "DictLabel",
+ "dict_value": "DictValue",
+ "dict_type": "DictType",
+ "tag_class": "TagClass",
+ "tag_type": "TagType",
+ "status": "Status",
+ "remark": "Remark",
+ "create_by": "CreateBy",
+ "create_time": "CreateTime",
+ "update_by": "UpdateBy",
+ "update_time": "UpdateTime",
+ },
+}
+
+// RepoSysDictData 字典类型数据表 数据层处理
+type RepoSysDictData struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *RepoSysDictData) convertResultRows(rows []map[string]any) []model.SysDictData {
+ arr := make([]model.SysDictData, 0)
+ for _, row := range rows {
+ sysDictData := model.SysDictData{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ datasource.SetFieldValue(&sysDictData, keyMapper, value)
+ }
+ }
+ arr = append(arr, sysDictData)
+ }
+ return arr
+}
+
+// SelectDictDataPage 根据条件分页查询字典数据
+func (r *RepoSysDictData) SelectDictDataPage(query map[string]any) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if v, ok := query["dictType"]; ok && v != "" {
+ conditions = append(conditions, "dict_type = ?")
+ params = append(params, v)
+ }
+ if v, ok := query["dictLabel"]; ok && v != "" {
+ conditions = append(conditions, "dict_label like concat(?, '%')")
+ params = append(params, v)
+ }
+ if v, ok := query["status"]; ok && v != "" {
+ conditions = append(conditions, "status = ?")
+ params = append(params, v)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := "select count(1) as 'total' from sys_dict_data"
+ totalRows, err := datasource.RawDB("", totalSql+whereSql, params)
+ if err != nil {
+ log.Errorf("total err => %v", err)
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return map[string]any{
+ "total": total,
+ "rows": []model.SysDictData{},
+ }
+ }
+
+ // 分页
+ pageNum, pageSize := datasource.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " order by dict_sort asc limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ }
+
+ // 转换实体
+ rows := r.convertResultRows(results)
+ return map[string]any{
+ "total": total,
+ "rows": rows,
+ }
+}
+
+// SelectDictDataList 根据条件查询字典数据
+func (r *RepoSysDictData) SelectDictDataList(sysDictData model.SysDictData) []model.SysDictData {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysDictData.DictLabel != "" {
+ conditions = append(conditions, "dict_label like concat(?, '%')")
+ params = append(params, sysDictData.DictLabel)
+ }
+ if sysDictData.DictType != "" {
+ conditions = append(conditions, "dict_type = ?")
+ params = append(params, sysDictData.DictType)
+ }
+ if sysDictData.Status != "" {
+ conditions = append(conditions, "status = ?")
+ params = append(params, sysDictData.Status)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ orderSql := " order by dict_sort asc "
+ querySql := r.selectSql + whereSql + orderSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ return []model.SysDictData{}
+ }
+
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// SelectDictDataByCodes 根据字典数据编码查询信息
+func (r *RepoSysDictData) SelectDictDataByCodes(dictCodes []string) []model.SysDictData {
+ placeholder := datasource.KeyPlaceholderByQuery(len(dictCodes))
+ querySql := r.selectSql + " where dict_code in (" + placeholder + ")"
+ parameters := datasource.ConvertIdsSlice(dictCodes)
+ results, err := datasource.RawDB("", querySql, parameters)
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ return []model.SysDictData{}
+ }
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// CountDictDataByType 查询字典数据
+func (r *RepoSysDictData) CountDictDataByType(dictType string) int64 {
+ querySql := "select count(1) as 'total' from sys_dict_data where dict_type = ?"
+ results, err := datasource.RawDB("", querySql, []any{dictType})
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ return 0
+ }
+ if len(results) > 0 {
+ return parse.Number(results[0]["total"])
+ }
+ return 0
+}
+
+// CheckUniqueDictData 校验字典数据是否唯一
+func (r *RepoSysDictData) CheckUniqueDictData(sysDictData model.SysDictData) string {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysDictData.DictType != "" {
+ conditions = append(conditions, "dict_type = ?")
+ params = append(params, sysDictData.DictType)
+ }
+ if sysDictData.DictLabel != "" {
+ conditions = append(conditions, "dict_label = ?")
+ params = append(params, sysDictData.DictLabel)
+ }
+ if sysDictData.DictValue != "" {
+ conditions = append(conditions, "dict_value = ?")
+ params = append(params, sysDictData.DictValue)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ } else {
+ return ""
+ }
+
+ // 查询数据
+ querySql := "select dict_code as 'str' from sys_dict_data " + whereSql + " limit 1"
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ log.Errorf("query err %v", err)
+ return ""
+ }
+ if len(results) > 0 {
+ return fmt.Sprint(results[0]["str"])
+ }
+ return ""
+}
+
+// DeleteDictDataByCodes 批量删除字典数据信息
+func (r *RepoSysDictData) DeleteDictDataByCodes(dictCodes []string) int64 {
+ placeholder := datasource.KeyPlaceholderByQuery(len(dictCodes))
+ sql := "delete from sys_dict_data where dict_code in (" + placeholder + ")"
+ parameters := datasource.ConvertIdsSlice(dictCodes)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ log.Errorf("delete err => %v", err)
+ return 0
+ }
+ affected, err := results.RowsAffected()
+ if err != nil {
+ log.Errorf("delete err => %v", err)
+ return 0
+ }
+ return affected
+}
+
+// InsertDictData 新增字典数据信息
+func (r *RepoSysDictData) InsertDictData(sysDictData model.SysDictData) string {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysDictData.DictSort > 0 {
+ params["dict_sort"] = sysDictData.DictSort
+ }
+ if sysDictData.DictLabel != "" {
+ params["dict_label"] = sysDictData.DictLabel
+ }
+ if sysDictData.DictValue != "" {
+ params["dict_value"] = sysDictData.DictValue
+ }
+ if sysDictData.DictType != "" {
+ params["dict_type"] = sysDictData.DictType
+ }
+ if sysDictData.TagClass != "" {
+ params["tag_class"] = sysDictData.TagClass
+ }
+ if sysDictData.TagType != "" {
+ params["tag_type"] = sysDictData.TagType
+ }
+ if sysDictData.Status != "" {
+ params["status"] = sysDictData.Status
+ }
+ if sysDictData.Remark != "" {
+ params["remark"] = sysDictData.Remark
+ }
+ if sysDictData.CreateBy != "" {
+ params["create_by"] = sysDictData.CreateBy
+ params["create_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, placeholder, values := datasource.KeyPlaceholderValueByInsert(params)
+ sql := "insert into sys_dict_data (" + strings.Join(keys, ",") + ")values(" + placeholder + ")"
+
+ // 执行插入
+ results, err := datasource.ExecDB("", sql, values)
+ if err != nil {
+ log.Errorf("insert row : %v", err.Error())
+ return ""
+ }
+ insertId, err := results.LastInsertId()
+ if err != nil {
+ log.Errorf("insert row : %v", err.Error())
+ return ""
+ }
+ return fmt.Sprint(insertId)
+}
+
+// UpdateDictData 修改字典数据信息
+func (r *RepoSysDictData) UpdateDictData(sysDictData model.SysDictData) int64 {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysDictData.DictSort > 0 {
+ params["dict_sort"] = sysDictData.DictSort
+ }
+ if sysDictData.DictLabel != "" {
+ params["dict_label"] = sysDictData.DictLabel
+ }
+ if sysDictData.DictValue != "" {
+ params["dict_value"] = sysDictData.DictValue
+ }
+ if sysDictData.DictType != "" {
+ params["dict_type"] = sysDictData.DictType
+ }
+ if sysDictData.TagClass != "" {
+ params["tag_class"] = sysDictData.TagClass
+ }
+ if sysDictData.TagType != "" {
+ params["tag_type"] = sysDictData.TagType
+ }
+ if sysDictData.Status != "" {
+ params["status"] = sysDictData.Status
+ }
+ if sysDictData.Remark != "" {
+ params["remark"] = sysDictData.Remark
+ }
+ if sysDictData.UpdateBy != "" {
+ params["update_by"] = sysDictData.UpdateBy
+ params["update_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, values := datasource.KeyValueByUpdate(params)
+ sql := "update sys_dict_data set " + strings.Join(keys, ",") + " where dict_code = ?"
+
+ // 执行更新
+ values = append(values, sysDictData.DictCode)
+ results, err := datasource.ExecDB("", sql, values)
+ if err != nil {
+ log.Errorf("update row : %v", err.Error())
+ return 0
+ }
+ affected, err := results.RowsAffected()
+ if err != nil {
+ log.Errorf("update err => %v", err)
+ return 0
+ }
+ return affected
+}
+
+// UpdateDictDataType 同步修改字典类型
+func (r *RepoSysDictData) UpdateDictDataType(oldDictType string, newDictType string) int64 {
+ // 参数拼接
+ params := make([]any, 0)
+ if oldDictType == "" || newDictType == "" {
+ return 0
+ }
+ params = append(params, newDictType)
+ params = append(params, oldDictType)
+
+ // 构建执行语句
+ sql := "update sys_dict_data set dict_type = ? where dict_type = ?"
+
+ // 执行更新
+ results, err := datasource.ExecDB("", sql, params)
+ if err != nil {
+ log.Errorf("update row : %v", err.Error())
+ return 0
+ }
+ affected, err := results.RowsAffected()
+ if err != nil {
+ log.Errorf("update err => %v", err)
+ return 0
+ }
+ return affected
+}
diff --git a/features/sys_dict_data/service/service_sys_dict_data.go b/features/sys_dict_data/service/service_sys_dict_data.go
new file mode 100644
index 0000000..1b663f9
--- /dev/null
+++ b/features/sys_dict_data/service/service_sys_dict_data.go
@@ -0,0 +1,111 @@
+package service
+
+import (
+ "errors"
+
+ "nms_nbi/features/sys_dict_data/model"
+ "nms_nbi/features/sys_dict_data/repo"
+ sysDictTypeService "nms_nbi/features/sys_dict_type/service"
+)
+
+// 实例化服务层 ServiceSysDictData 结构体
+var NewServiceSysDictData = &ServiceSysDictData{
+ sysDictDataRepository: *repo.NewRepoSysDictData,
+ sysDictTypeService: *sysDictTypeService.NewServiceSysDictType,
+}
+
+// ServiceSysDictData 字典类型数据 服务层处理
+type ServiceSysDictData struct {
+ // 字典数据服务
+ sysDictDataRepository repo.RepoSysDictData
+ // 字典类型服务
+ sysDictTypeService sysDictTypeService.ServiceSysDictType
+}
+
+// SelectDictDataPage 根据条件分页查询字典数据
+func (r *ServiceSysDictData) SelectDictDataPage(query map[string]any) map[string]any {
+ return r.sysDictDataRepository.SelectDictDataPage(query)
+}
+
+// SelectDictDataList 根据条件查询字典数据
+func (r *ServiceSysDictData) SelectDictDataList(sysDictData model.SysDictData) []model.SysDictData {
+ return r.sysDictDataRepository.SelectDictDataList(sysDictData)
+}
+
+// SelectDictDataByCode 根据字典数据编码查询信息
+func (r *ServiceSysDictData) SelectDictDataByCode(dictCode string) model.SysDictData {
+ if dictCode == "" {
+ return model.SysDictData{}
+ }
+ dictCodes := r.sysDictDataRepository.SelectDictDataByCodes([]string{dictCode})
+ if len(dictCodes) > 0 {
+ return dictCodes[0]
+ }
+ return model.SysDictData{}
+}
+
+// SelectDictDataByType 根据字典类型查询信息
+func (r *ServiceSysDictData) SelectDictDataByType(dictType string) []model.SysDictData {
+ return r.sysDictTypeService.DictDataCache(dictType)
+}
+
+// CheckUniqueDictLabel 校验字典标签是否唯一
+func (r *ServiceSysDictData) CheckUniqueDictLabel(dictType, dictLabel, dictCode string) bool {
+ uniqueId := r.sysDictDataRepository.CheckUniqueDictData(model.SysDictData{
+ DictType: dictType,
+ DictLabel: dictLabel,
+ })
+ if uniqueId == dictCode {
+ return true
+ }
+ return uniqueId == ""
+}
+
+// CheckUniqueDictValue 校验字典键值是否唯一
+func (r *ServiceSysDictData) CheckUniqueDictValue(dictType, dictValue, dictCode string) bool {
+ uniqueId := r.sysDictDataRepository.CheckUniqueDictData(model.SysDictData{
+ DictType: dictType,
+ DictValue: dictValue,
+ })
+ if uniqueId == dictCode {
+ return true
+ }
+ return uniqueId == ""
+}
+
+// DeleteDictDataByCodes 批量删除字典数据信息
+func (r *ServiceSysDictData) DeleteDictDataByCodes(dictCodes []string) (int64, error) {
+ // 检查是否存在
+ dictDatas := r.sysDictDataRepository.SelectDictDataByCodes(dictCodes)
+ if len(dictDatas) <= 0 {
+ return 0, errors.New("does not have permission to access dictionary-encoded data")
+ }
+ if len(dictDatas) == len(dictCodes) {
+ for _, v := range dictDatas {
+ // 刷新缓存
+ r.sysDictTypeService.ClearDictCache(v.DictType)
+ r.sysDictTypeService.LoadingDictCache(v.DictType)
+ }
+ rows := r.sysDictDataRepository.DeleteDictDataByCodes(dictCodes)
+ return rows, nil
+ }
+ return 0, errors.New("failed to delete dictionary data information")
+}
+
+// InsertDictData 新增字典数据信息
+func (r *ServiceSysDictData) InsertDictData(sysDictData model.SysDictData) string {
+ insertId := r.sysDictDataRepository.InsertDictData(sysDictData)
+ if insertId != "" {
+ r.sysDictTypeService.LoadingDictCache(sysDictData.DictType)
+ }
+ return insertId
+}
+
+// UpdateDictData 修改字典数据信息
+func (r *ServiceSysDictData) UpdateDictData(sysDictData model.SysDictData) int64 {
+ rows := r.sysDictDataRepository.UpdateDictData(sysDictData)
+ if rows > 0 {
+ r.sysDictTypeService.LoadingDictCache(sysDictData.DictType)
+ }
+ return rows
+}
diff --git a/features/sys_dict_type/api_sys_dict_type.go b/features/sys_dict_type/api_sys_dict_type.go
new file mode 100644
index 0000000..3da9cba
--- /dev/null
+++ b/features/sys_dict_type/api_sys_dict_type.go
@@ -0,0 +1,253 @@
+package sysdicttype
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+
+ "nms_nbi/features/sys_dict_type/model"
+ sysDictTypeService "nms_nbi/features/sys_dict_type/service"
+ "nms_nbi/lib/core/utils/ctx"
+ "nms_nbi/lib/core/utils/parse"
+ "nms_nbi/lib/core/vo/result"
+ "nms_nbi/lib/midware"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+)
+
+// 字典类型信息接口添加到路由
+func Routers() []services.RouterItem {
+ // 实例化控制层 SysDictTypeApi 结构体
+ var apis = &SysDictTypeApi{
+ sysDictTypeService: *sysDictTypeService.NewServiceSysDictType,
+ }
+
+ rs := [...]services.RouterItem{
+ {
+ Method: "GET",
+ Pattern: "/dictTypes",
+ Handler: apis.List,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "GET",
+ Pattern: "/dictType/{dictId}",
+ Handler: apis.Info,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "POST",
+ Pattern: "/dictType",
+ Handler: apis.Add,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "PUT",
+ Pattern: "/dictType",
+ Handler: apis.Edit,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "DELETE",
+ Pattern: "/dictType/{dictIds}",
+ Handler: apis.Remove,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "PUT",
+ Pattern: "/dictType/refreshCache",
+ Handler: apis.RefreshCache,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "GET",
+ Pattern: "/dictTypes/optionselect",
+ Handler: apis.DictOptionselect,
+ Middleware: midware.Authorize(nil),
+ },
+ // 添加更多的 Router 对象...
+ }
+
+ // 生成两组前缀路由
+ rsPrefix := []services.RouterItem{}
+ for _, v := range rs {
+ path := "/dictTypegManage/{apiVersion}" + v.Pattern
+ // 固定前缀
+ v.Pattern = config.DefaultUriPrefix + path
+ rsPrefix = append(rsPrefix, v)
+ // 可配置
+ v.Pattern = config.UriPrefix + path
+ rsPrefix = append(rsPrefix, v)
+ }
+ return rsPrefix
+}
+
+// 字典类型信息
+//
+// PATH /dictTypegManage
+type SysDictTypeApi struct {
+ // 字典类型服务
+ sysDictTypeService sysDictTypeService.ServiceSysDictType
+}
+
+// 字典类型列表
+//
+// GET /list
+func (s *SysDictTypeApi) List(w http.ResponseWriter, r *http.Request) {
+ querys := ctx.QueryMap(r)
+ data := s.sysDictTypeService.SelectDictTypePage(querys)
+ ctx.JSON(w, 200, result.Ok(data))
+}
+
+// 字典类型信息
+//
+// GET /:dictId
+func (s *SysDictTypeApi) Info(w http.ResponseWriter, r *http.Request) {
+ dictId := ctx.Param(r, "dictId")
+ if dictId == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+ data := s.sysDictTypeService.SelectDictTypeByID(dictId)
+ if data.DictID == dictId {
+ ctx.JSON(w, 200, result.OkData(data))
+ return
+ }
+ ctx.JSON(w, 200, result.Err(nil))
+}
+
+// 字典类型新增
+//
+// POST /
+func (s *SysDictTypeApi) Add(w http.ResponseWriter, r *http.Request) {
+ var body model.SysDictType
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil || body.DictID != "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ // 检查字典名称唯一
+ uniqueDictName := s.sysDictTypeService.CheckUniqueDictName(body.DictName, "")
+ if !uniqueDictName {
+ msg := fmt.Sprintf("Failed to add dictionary '%s', dictionary name already exists", body.DictName)
+ ctx.JSON(w, 200, result.ErrMsg(msg))
+ return
+ }
+
+ // 检查字典类型唯一
+ uniqueDictType := s.sysDictTypeService.CheckUniqueDictType(body.DictType, "")
+ if !uniqueDictType {
+ msg := fmt.Sprintf("Failed to add dictionary '%s', dictionary type already exists", body.DictType)
+ ctx.JSON(w, 200, result.ErrMsg(msg))
+ return
+ }
+
+ body.CreateBy = ctx.LoginUserToUserName(r)
+ insertId := s.sysDictTypeService.InsertDictType(body)
+ if insertId != "" {
+ ctx.JSON(w, 200, result.Ok(nil))
+ return
+ }
+ ctx.JSON(w, 200, result.Err(nil))
+}
+
+// 字典类型修改
+//
+// PUT /
+func (s *SysDictTypeApi) Edit(w http.ResponseWriter, r *http.Request) {
+ var body model.SysDictType
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil || body.DictID == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ // 检查数据是否存在
+ dictInfo := s.sysDictTypeService.SelectDictTypeByID(body.DictID)
+ if dictInfo.DictID != body.DictID {
+ ctx.JSON(w, 200, result.ErrMsg("No permission to access dictionary type data!"))
+ return
+ }
+
+ // 检查字典名称唯一
+ uniqueDictName := s.sysDictTypeService.CheckUniqueDictName(body.DictName, body.DictID)
+ if !uniqueDictName {
+ msg := fmt.Sprintf("Dictionary modification failed for [%s], dictionary name already exists", body.DictName)
+ ctx.JSON(w, 200, result.ErrMsg(msg))
+ return
+ }
+
+ // 检查字典类型唯一
+ uniqueDictType := s.sysDictTypeService.CheckUniqueDictType(body.DictType, body.DictID)
+ if !uniqueDictType {
+ msg := fmt.Sprintf("Dictionary modification failed for [%s], dictionary type already exists", body.DictType)
+ ctx.JSON(w, 200, result.ErrMsg(msg))
+ return
+ }
+
+ body.UpdateBy = ctx.LoginUserToUserName(r)
+ rows := s.sysDictTypeService.UpdateDictType(body)
+ if rows > 0 {
+ ctx.JSON(w, 200, result.Ok(nil))
+ return
+ }
+ ctx.JSON(w, 200, result.Err(nil))
+}
+
+// 字典类型删除
+//
+// DELETE /:dictIds
+func (s *SysDictTypeApi) Remove(w http.ResponseWriter, r *http.Request) {
+ dictIds := ctx.Param(r, "dictIds")
+ if dictIds == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+ // 处理字符转id数组后去重
+ ids := strings.Split(dictIds, ",")
+ uniqueIDs := parse.RemoveDuplicates(ids)
+ if len(uniqueIDs) <= 0 {
+ ctx.JSON(w, 200, result.Err(nil))
+ return
+ }
+ rows, err := s.sysDictTypeService.DeleteDictTypeByIDs(uniqueIDs)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+ msg := fmt.Sprintf("Successfully deleted: %d", rows)
+ ctx.JSON(w, 200, result.OkMsg(msg))
+}
+
+// 字典类型刷新缓存
+//
+// PUT /refreshCache
+func (s *SysDictTypeApi) RefreshCache(w http.ResponseWriter, r *http.Request) {
+ s.sysDictTypeService.ResetDictCache()
+ ctx.JSON(w, 200, result.Ok(nil))
+}
+
+// 字典类型选择框列表
+//
+// GET /getDictOptionselect
+func (s *SysDictTypeApi) DictOptionselect(w http.ResponseWriter, r *http.Request) {
+ data := s.sysDictTypeService.SelectDictTypeList(model.SysDictType{
+ Status: "1",
+ })
+
+ type labelValue struct {
+ Label string `json:"label"`
+ Value string `json:"value"`
+ }
+
+ // 数据组
+ arr := []labelValue{}
+ for _, v := range data {
+ arr = append(arr, labelValue{
+ Label: v.DictName,
+ Value: v.DictType,
+ })
+ }
+ ctx.JSON(w, 200, result.OkData(arr))
+}
diff --git a/features/sys_dict_type/model/sys_dict_type.go b/features/sys_dict_type/model/sys_dict_type.go
new file mode 100644
index 0000000..0ad0b1a
--- /dev/null
+++ b/features/sys_dict_type/model/sys_dict_type.go
@@ -0,0 +1,23 @@
+package model
+
+// SysDictType 字典类型对象 sys_dict_type
+type SysDictType struct {
+ // 字典主键
+ DictID string `json:"dictId"`
+ // 字典名称
+ DictName string `json:"dictName" binding:"required"`
+ // 字典类型
+ DictType string `json:"dictType" binding:"required"`
+ // 状态(0停用 1正常)
+ Status string `json:"status"`
+ // 创建者
+ CreateBy string `json:"createBy"`
+ // 创建时间
+ CreateTime int64 `json:"createTime"`
+ // 更新者
+ UpdateBy string `json:"updateBy"`
+ // 更新时间
+ UpdateTime int64 `json:"updateTime"`
+ // 备注
+ Remark string `json:"remark"`
+}
diff --git a/features/sys_dict_type/repo/repo_sys_dict_type.go b/features/sys_dict_type/repo/repo_sys_dict_type.go
new file mode 100644
index 0000000..30f2212
--- /dev/null
+++ b/features/sys_dict_type/repo/repo_sys_dict_type.go
@@ -0,0 +1,330 @@
+package repo
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "nms_nbi/features/sys_dict_type/model"
+ "nms_nbi/lib/core/datasource"
+ "nms_nbi/lib/core/utils/date"
+ "nms_nbi/lib/core/utils/parse"
+ "nms_nbi/lib/log"
+)
+
+// 实例化数据层 RepoSysDictType 结构体
+var NewRepoSysDictType = &RepoSysDictType{
+ selectSql: `select
+ dict_id, dict_name, dict_type, status, create_by, create_time, remark
+ from sys_dict_type`,
+
+ resultMap: map[string]string{
+ "dict_id": "DictID",
+ "dict_name": "DictName",
+ "dict_type": "DictType",
+ "remark": "Remark",
+ "status": "Status",
+ "create_by": "CreateBy",
+ "create_time": "CreateTime",
+ "update_by": "UpdateBy",
+ "update_time": "UpdateTime",
+ },
+}
+
+// RepoSysDictType 字典类型表 数据层处理
+type RepoSysDictType struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *RepoSysDictType) convertResultRows(rows []map[string]any) []model.SysDictType {
+ arr := make([]model.SysDictType, 0)
+ for _, row := range rows {
+ sysDictType := model.SysDictType{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ datasource.SetFieldValue(&sysDictType, keyMapper, value)
+ }
+ }
+ arr = append(arr, sysDictType)
+ }
+ return arr
+}
+
+// SelectDictTypePage 根据条件分页查询字典类型
+func (r *RepoSysDictType) SelectDictTypePage(query map[string]any) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if v, ok := query["dictName"]; ok && v != "" {
+ conditions = append(conditions, "dict_name like concat(?, '%')")
+ params = append(params, v)
+ }
+ if v, ok := query["dictType"]; ok && v != "" {
+ conditions = append(conditions, "dict_type like concat(?, '%')")
+ params = append(params, v)
+ }
+ if v, ok := query["status"]; ok && v != "" {
+ conditions = append(conditions, "status = ?")
+ params = append(params, v)
+ }
+ beginTime, ok := query["beginTime"]
+ if !ok {
+ beginTime, ok = query["params[beginTime]"]
+ }
+ if ok && beginTime != "" {
+ conditions = append(conditions, "create_time >= ?")
+ beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD)
+ params = append(params, beginDate.UnixMilli())
+ }
+ endTime, ok := query["endTime"]
+ if !ok {
+ endTime, ok = query["params[endTime]"]
+ }
+ if ok && endTime != "" {
+ conditions = append(conditions, "create_time <= ?")
+ endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD)
+ params = append(params, endDate.UnixMilli())
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := "select count(1) as 'total' from sys_dict_type"
+ totalRows, err := datasource.RawDB("", totalSql+whereSql, params)
+ if err != nil {
+ log.Errorf("total err => %v", err)
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return map[string]any{
+ "total": total,
+ "rows": []model.SysDictType{},
+ }
+ }
+
+ // 分页
+ pageNum, pageSize := datasource.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ }
+
+ // 转换实体
+ rows := r.convertResultRows(results)
+ return map[string]any{
+ "total": total,
+ "rows": rows,
+ }
+}
+
+// SelectDictTypeList 根据条件查询字典类型
+func (r *RepoSysDictType) SelectDictTypeList(sysDictType model.SysDictType) []model.SysDictType {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysDictType.DictName != "" {
+ conditions = append(conditions, "dict_name like concat(?, '%')")
+ params = append(params, sysDictType.DictName)
+ }
+ if sysDictType.DictType != "" {
+ conditions = append(conditions, "dict_type like concat(?, '%')")
+ params = append(params, sysDictType.DictType)
+ }
+ if sysDictType.Status != "" {
+ conditions = append(conditions, "status = ?")
+ params = append(params, sysDictType.Status)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ querySql := r.selectSql + whereSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ return []model.SysDictType{}
+ }
+
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// SelectDictTypeByIDs 根据字典类型ID查询信息
+func (r *RepoSysDictType) SelectDictTypeByIDs(dictIDs []string) []model.SysDictType {
+ placeholder := datasource.KeyPlaceholderByQuery(len(dictIDs))
+ querySql := r.selectSql + " where dict_id in (" + placeholder + ")"
+ parameters := datasource.ConvertIdsSlice(dictIDs)
+ results, err := datasource.RawDB("", querySql, parameters)
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ return []model.SysDictType{}
+ }
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// SelectDictTypeByType 根据字典类型查询信息
+func (r *RepoSysDictType) SelectDictTypeByType(dictType string) model.SysDictType {
+ querySql := r.selectSql + " where dict_type = ?"
+ results, err := datasource.RawDB("", querySql, []any{dictType})
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ return model.SysDictType{}
+ }
+ // 转换实体
+ rows := r.convertResultRows(results)
+ if len(rows) > 0 {
+ return rows[0]
+ }
+ return model.SysDictType{}
+}
+
+// CheckUniqueDictType 校验字典是否唯一
+func (r *RepoSysDictType) CheckUniqueDictType(sysDictType model.SysDictType) string {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysDictType.DictName != "" {
+ conditions = append(conditions, "dict_name = ?")
+ params = append(params, sysDictType.DictName)
+ }
+ if sysDictType.DictType != "" {
+ conditions = append(conditions, "dict_type = ?")
+ params = append(params, sysDictType.DictType)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ } else {
+ return ""
+ }
+
+ // 查询数据
+ querySql := "select dict_id as 'str' from sys_dict_type " + whereSql + " limit 1"
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ log.Errorf("query err %v", err)
+ return ""
+ }
+ if len(results) > 0 {
+ return fmt.Sprint(results[0]["str"])
+ }
+ return ""
+}
+
+// InsertDictType 新增字典类型信息
+func (r *RepoSysDictType) InsertDictType(sysDictType model.SysDictType) string {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysDictType.DictName != "" {
+ params["dict_name"] = sysDictType.DictName
+ }
+ if sysDictType.DictType != "" {
+ params["dict_type"] = sysDictType.DictType
+ }
+ if sysDictType.Status != "" {
+ params["status"] = sysDictType.Status
+ }
+ if sysDictType.Remark != "" {
+ params["remark"] = sysDictType.Remark
+ }
+ if sysDictType.CreateBy != "" {
+ params["create_by"] = sysDictType.CreateBy
+ params["create_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, placeholder, values := datasource.KeyPlaceholderValueByInsert(params)
+ sql := "insert into sys_dict_type (" + strings.Join(keys, ",") + ")values(" + placeholder + ")"
+
+ // 执行插入
+ results, err := datasource.ExecDB("", sql, values)
+ if err != nil {
+ log.Errorf("insert row : %v", err.Error())
+ return ""
+ }
+ insertId, err := results.LastInsertId()
+ if err != nil {
+ log.Errorf("insert row : %v", err.Error())
+ return ""
+ }
+ return fmt.Sprint(insertId)
+}
+
+// UpdateDictType 修改字典类型信息
+func (r *RepoSysDictType) UpdateDictType(sysDictType model.SysDictType) int64 {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysDictType.DictName != "" {
+ params["dict_name"] = sysDictType.DictName
+ }
+ if sysDictType.DictType != "" {
+ params["dict_type"] = sysDictType.DictType
+ }
+ if sysDictType.Status != "" {
+ params["status"] = sysDictType.Status
+ }
+ if sysDictType.Remark != "" {
+ params["remark"] = sysDictType.Remark
+ }
+ if sysDictType.UpdateBy != "" {
+ params["update_by"] = sysDictType.UpdateBy
+ params["update_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, values := datasource.KeyValueByUpdate(params)
+ sql := "update sys_dict_type set " + strings.Join(keys, ",") + " where dict_id = ?"
+
+ // 执行更新
+ values = append(values, sysDictType.DictID)
+ results, err := datasource.ExecDB("", sql, values)
+ if err != nil {
+ log.Errorf("update row : %v", err.Error())
+ return 0
+ }
+ affected, err := results.RowsAffected()
+ if err != nil {
+ log.Errorf("update err => %v", err)
+ return 0
+ }
+ return affected
+}
+
+// DeleteDictTypeByIDs 批量删除字典类型信息
+func (r *RepoSysDictType) DeleteDictTypeByIDs(dictIDs []string) int64 {
+ placeholder := datasource.KeyPlaceholderByQuery(len(dictIDs))
+ sql := "delete from sys_dict_type where dict_id in (" + placeholder + ")"
+ parameters := datasource.ConvertIdsSlice(dictIDs)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ log.Errorf("delete err => %v", err)
+ return 0
+ }
+ affected, err := results.RowsAffected()
+ if err != nil {
+ log.Errorf("delete err => %v", err)
+ return 0
+ }
+ return affected
+}
diff --git a/features/sys_dict_type/service/service_sys_dict_type.go b/features/sys_dict_type/service/service_sys_dict_type.go
new file mode 100644
index 0000000..29f2765
--- /dev/null
+++ b/features/sys_dict_type/service/service_sys_dict_type.go
@@ -0,0 +1,211 @@
+package service
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+
+ sysDictDataModel "nms_nbi/features/sys_dict_data/model"
+ sysDictDataRepo "nms_nbi/features/sys_dict_data/repo"
+ sysDictTypeModel "nms_nbi/features/sys_dict_type/model"
+ "nms_nbi/features/sys_dict_type/repo"
+ "nms_nbi/lib/core/cache"
+ "nms_nbi/lib/core/constants/cachekey"
+)
+
+// 实例化服务层 ServiceSysDictType 结构体
+var NewServiceSysDictType = &ServiceSysDictType{
+ sysDictTypeRepository: *repo.NewRepoSysDictType,
+ sysDictDataRepository: *sysDictDataRepo.NewRepoSysDictData,
+}
+
+// ServiceSysDictType 字典类型 服务层处理
+type ServiceSysDictType struct {
+ // 字典类型服务
+ sysDictTypeRepository repo.RepoSysDictType
+ // 字典数据服务
+ sysDictDataRepository sysDictDataRepo.RepoSysDictData
+}
+
+// SelectDictTypePage 根据条件分页查询字典类型
+func (r *ServiceSysDictType) SelectDictTypePage(query map[string]any) map[string]any {
+ return r.sysDictTypeRepository.SelectDictTypePage(query)
+}
+
+// SelectDictTypeList 根据条件查询字典类型
+func (r *ServiceSysDictType) SelectDictTypeList(sysDictType sysDictTypeModel.SysDictType) []sysDictTypeModel.SysDictType {
+ return r.sysDictTypeRepository.SelectDictTypeList(sysDictType)
+}
+
+// SelectDictTypeByID 根据字典类型ID查询信息
+func (r *ServiceSysDictType) SelectDictTypeByID(dictID string) sysDictTypeModel.SysDictType {
+ if dictID == "" {
+ return sysDictTypeModel.SysDictType{}
+ }
+ dictTypes := r.sysDictTypeRepository.SelectDictTypeByIDs([]string{dictID})
+ if len(dictTypes) > 0 {
+ return dictTypes[0]
+ }
+ return sysDictTypeModel.SysDictType{}
+}
+
+// SelectDictTypeByType 根据字典类型查询信息
+func (r *ServiceSysDictType) SelectDictTypeByType(dictType string) sysDictTypeModel.SysDictType {
+ return r.sysDictTypeRepository.SelectDictTypeByType(dictType)
+}
+
+// CheckUniqueDictName 校验字典名称是否唯一
+func (r *ServiceSysDictType) CheckUniqueDictName(dictName, dictID string) bool {
+ uniqueId := r.sysDictTypeRepository.CheckUniqueDictType(sysDictTypeModel.SysDictType{
+ DictName: dictName,
+ })
+ if uniqueId == dictID {
+ return true
+ }
+ return uniqueId == ""
+}
+
+// CheckUniqueDictType 校验字典类型是否唯一
+func (r *ServiceSysDictType) CheckUniqueDictType(dictType, dictID string) bool {
+ uniqueId := r.sysDictTypeRepository.CheckUniqueDictType(sysDictTypeModel.SysDictType{
+ DictType: dictType,
+ })
+ if uniqueId == dictID {
+ return true
+ }
+ return uniqueId == ""
+}
+
+// InsertDictType 新增字典类型信息
+func (r *ServiceSysDictType) InsertDictType(sysDictType sysDictTypeModel.SysDictType) string {
+ insertId := r.sysDictTypeRepository.InsertDictType(sysDictType)
+ if insertId != "" {
+ r.LoadingDictCache(sysDictType.DictType)
+ }
+ return insertId
+}
+
+// UpdateDictType 修改字典类型信息
+func (r *ServiceSysDictType) UpdateDictType(sysDictType sysDictTypeModel.SysDictType) int64 {
+ data := r.sysDictTypeRepository.SelectDictTypeByIDs([]string{sysDictType.DictID})
+ if len(data) == 0 {
+ return 0
+ }
+ // 修改字典类型key时同步更新其字典数据的类型key
+ oldDictType := data[0].DictType
+ rows := r.sysDictTypeRepository.UpdateDictType(sysDictType)
+ if rows > 0 && oldDictType != "" && oldDictType != sysDictType.DictType {
+ r.sysDictDataRepository.UpdateDictDataType(oldDictType, sysDictType.DictType)
+ }
+ // 刷新缓存
+ r.ClearDictCache(oldDictType)
+ r.LoadingDictCache(sysDictType.DictType)
+ return rows
+}
+
+// DeleteDictTypeByIDs 批量删除字典类型信息
+func (r *ServiceSysDictType) DeleteDictTypeByIDs(dictIDs []string) (int64, error) {
+ // 检查是否存在
+ dictTypes := r.sysDictTypeRepository.SelectDictTypeByIDs(dictIDs)
+ if len(dictTypes) <= 0 {
+ return 0, errors.New("no permission to access dictionary type data")
+ }
+ for _, v := range dictTypes {
+ // 字典类型下级含有数据
+ useCount := r.sysDictDataRepository.CountDictDataByType(v.DictType)
+ if useCount > 0 {
+ msg := fmt.Sprintf("[%s] Dictionary data exists and cannot be deleted. ", v.DictName)
+ return 0, errors.New(msg)
+ }
+ // 清除缓存
+ r.ClearDictCache(v.DictType)
+ }
+ if len(dictTypes) == len(dictIDs) {
+ rows := r.sysDictTypeRepository.DeleteDictTypeByIDs(dictIDs)
+ return rows, nil
+ }
+ return 0, errors.New("failed to delete dictionary data information")
+}
+
+// ResetDictCache 重置字典缓存数据
+func (r *ServiceSysDictType) ResetDictCache() {
+ r.ClearDictCache("*")
+ r.LoadingDictCache("")
+}
+
+// getCacheKey 组装缓存key
+func (r *ServiceSysDictType) getDictCache(dictType string) string {
+ return cachekey.SYS_DICT_KEY + dictType
+}
+
+// LoadingDictCache 加载字典缓存数据
+func (r *ServiceSysDictType) LoadingDictCache(dictType string) {
+ sysDictData := sysDictDataModel.SysDictData{
+ Status: "1",
+ }
+
+ // 指定字典类型
+ if dictType != "" {
+ sysDictData.DictType = dictType
+ // 删除缓存
+ key := r.getDictCache(dictType)
+ cache.DeleteLocal(key)
+ }
+
+ sysDictDataList := r.sysDictDataRepository.SelectDictDataList(sysDictData)
+ if len(sysDictDataList) == 0 {
+ return
+ }
+
+ // 将字典数据按类型分组
+ m := make(map[string][]sysDictDataModel.SysDictData, 0)
+ for _, v := range sysDictDataList {
+ key := v.DictType
+ if item, ok := m[key]; ok {
+ m[key] = append(item, v)
+ } else {
+ m[key] = []sysDictDataModel.SysDictData{v}
+ }
+ }
+
+ // 放入缓存
+ for k, v := range m {
+ key := r.getDictCache(k)
+ values, _ := json.Marshal(v)
+ cache.SetLocal(key, string(values))
+ }
+}
+
+// ClearDictCache 清空字典缓存数据
+func (r *ServiceSysDictType) ClearDictCache(dictType string) bool {
+ key := r.getDictCache(dictType)
+ keys := cache.GetLocalKeys(key)
+ for _, v := range keys {
+ cache.DeleteLocal(v)
+ }
+ return len(keys) > 0
+}
+
+// DictDataCache 获取字典数据缓存数据
+func (r *ServiceSysDictType) DictDataCache(dictType string) []sysDictDataModel.SysDictData {
+ data := []sysDictDataModel.SysDictData{}
+ key := r.getDictCache(dictType)
+ jsonAny, ok := cache.GetLocal(key)
+ if jsonAny != nil && ok {
+ err := json.Unmarshal([]byte(jsonAny.(string)), &data)
+ if err != nil {
+ data = []sysDictDataModel.SysDictData{}
+ }
+ } else {
+ data = r.sysDictDataRepository.SelectDictDataList(sysDictDataModel.SysDictData{
+ Status: "1",
+ DictType: dictType,
+ })
+ if len(data) > 0 {
+ cache.DeleteLocal(key)
+ values, _ := json.Marshal(data)
+ cache.SetLocal(key, string(values))
+ }
+ }
+ return data
+}
diff --git a/features/sys_menu/api_sys_menu.go b/features/sys_menu/api_sys_menu.go
new file mode 100644
index 0000000..1d03dd2
--- /dev/null
+++ b/features/sys_menu/api_sys_menu.go
@@ -0,0 +1,354 @@
+package sysmenu
+
+import (
+ "fmt"
+ "net/http"
+
+ "nms_nbi/features/sys_menu/consts"
+ "nms_nbi/features/sys_menu/model"
+ "nms_nbi/features/sys_menu/service"
+ "nms_nbi/lib/core/utils/ctx"
+ "nms_nbi/lib/core/utils/regular"
+ "nms_nbi/lib/core/vo/result"
+ "nms_nbi/lib/midware"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+ srcConfig "nms_nbi/src/framework/config"
+)
+
+// 菜单接口添加到路由
+func Routers() []services.RouterItem {
+ // 实例化控制层 SysMenuApi 结构体
+ var apis = &SysMenuApi{
+ sysMenuService: service.NewServiceSysMenu,
+ }
+
+ rs := [...]services.RouterItem{
+ {
+ Method: "GET",
+ Pattern: "/menus",
+ Handler: apis.List,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "GET",
+ Pattern: "/menu/{menuId}",
+ Handler: apis.Info,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "POST",
+ Pattern: "/menu",
+ Handler: apis.Add,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "PUT",
+ Pattern: "/menu",
+ Handler: apis.Edit,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "DELETE",
+ Pattern: "/menu/{menuId}",
+ Handler: apis.Remove,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "GET",
+ Pattern: "/menus/treeSelect",
+ Handler: apis.TreeSelect,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "GET",
+ Pattern: "/menu/roleMenuTreeSelect/{roleId}",
+ Handler: apis.RoleMenuTreeSelect,
+ Middleware: midware.Authorize(nil),
+ },
+ // 添加更多的 Router 对象...
+ }
+
+ // 生成两组前缀路由
+ rsPrefix := []services.RouterItem{}
+ for _, v := range rs {
+ path := "/menuManage/{apiVersion}" + v.Pattern
+ // 固定前缀
+ v.Pattern = config.DefaultUriPrefix + path
+ rsPrefix = append(rsPrefix, v)
+ // 可配置
+ v.Pattern = config.UriPrefix + path
+ rsPrefix = append(rsPrefix, v)
+ }
+ return rsPrefix
+}
+
+// // 实例化控制层 SysMenuApi 结构体
+// var NewSysMenu = &SysMenuApi{
+// sysMenuService: NewServiceSysMenu,
+// }
+
+// 菜单信息
+//
+// PATH /menuManage
+type SysMenuApi struct {
+ // 菜单服务
+ sysMenuService *service.ServiceSysMenu
+}
+
+// 菜单列表
+//
+// GET /list
+func (s *SysMenuApi) List(w http.ResponseWriter, r *http.Request) {
+ query := model.SysMenu{}
+ if v := ctx.GetQuery(r, "menuName"); v != "" {
+ query.MenuName = v
+ }
+ if v := ctx.GetQuery(r, "status"); v != "" {
+ query.Status = v
+ }
+
+ userId := ctx.LoginUserToUserID(r)
+ if srcConfig.IsAdmin(userId) {
+ userId = "*"
+ }
+ data := s.sysMenuService.SelectMenuList(query, userId)
+ ctx.JSON(w, 200, result.OkData(data))
+}
+
+// 菜单信息
+//
+// GET /:menuId
+func (s *SysMenuApi) Info(w http.ResponseWriter, r *http.Request) {
+ menuId := ctx.Param(r, "menuId")
+ if menuId == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+ data := s.sysMenuService.SelectMenuById(menuId)
+ if data.MenuID == menuId {
+ ctx.JSON(w, 200, result.OkData(data))
+ return
+ }
+ ctx.JSON(w, 200, result.Err(nil))
+}
+
+// 菜单新增
+//
+// POST /
+func (s *SysMenuApi) Add(w http.ResponseWriter, r *http.Request) {
+ var body model.SysMenu
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil || body.MenuID != "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ // 目录和菜单检查地址唯一
+ if consts.TYPE_DIR == body.MenuType || consts.TYPE_MENU == body.MenuType {
+ uniqueNenuPath := s.sysMenuService.CheckUniqueMenuPath(body.Path, "")
+ if !uniqueNenuPath {
+ msg := fmt.Sprintf("菜单新增【%s】失败,菜单路由地址已存在", body.MenuName)
+ ctx.JSON(w, 200, result.ErrMsg(msg))
+ return
+ }
+ }
+
+ // 检查名称唯一
+ uniqueNenuName := s.sysMenuService.CheckUniqueMenuName(body.MenuName, body.ParentID, "")
+ if !uniqueNenuName {
+ msg := fmt.Sprintf("菜单新增【%s】失败,菜单名称已存在", body.MenuName)
+ ctx.JSON(w, 200, result.ErrMsg(msg))
+ return
+ }
+
+ // 外链菜单需要符合网站http(s)开头
+ if body.IsFrame == "0" && !regular.ValidHttp(body.Path) {
+ msg := fmt.Sprintf("菜单新增【%s】失败,非内部地址必须以http(s)://开头", body.MenuName)
+ ctx.JSON(w, 200, result.ErrMsg(msg))
+ return
+ }
+
+ body.CreateBy = ctx.LoginUserToUserName(r)
+ insertId := s.sysMenuService.InsertMenu(body)
+ if insertId != "" {
+ ctx.JSON(w, 200, result.Ok(nil))
+ return
+ }
+ ctx.JSON(w, 200, result.Err(nil))
+}
+
+// 菜单修改
+//
+// PUT /
+func (s *SysMenuApi) Edit(w http.ResponseWriter, r *http.Request) {
+ var body model.SysMenu
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil || body.MenuID == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ // 上级菜单不能选自己
+ if body.MenuID == body.ParentID {
+ msg := fmt.Sprintf("Menu modification failed for [%s], parent menu cannot select itself", body.MenuName)
+ ctx.JSON(w, 200, result.ErrMsg(msg))
+ return
+ }
+
+ // 检查数据是否存在
+ menuInfo := s.sysMenuService.SelectMenuById(body.MenuID)
+ if menuInfo.MenuID != body.MenuID {
+ ctx.JSON(w, 200, result.ErrMsg("No permission to access menu data"))
+ return
+ }
+ // 父级ID不为0是要检查
+ if body.ParentID != "0" {
+ menuParent := s.sysMenuService.SelectMenuById(body.ParentID)
+ if menuParent.MenuID != body.ParentID {
+ ctx.JSON(w, 200, result.ErrMsg("No permission to access menu data"))
+ return
+ }
+ // 禁用菜单时检查父菜单是否使用
+ if body.Status == "1" && menuParent.Status == "0" {
+ ctx.JSON(w, 200, result.ErrMsg("Parent menu not enabled!"))
+ return
+ }
+ }
+
+ // 目录和菜单检查地址唯一
+ if consts.TYPE_DIR == body.MenuType || consts.TYPE_MENU == body.MenuType {
+ uniqueNenuPath := s.sysMenuService.CheckUniqueMenuPath(body.Path, body.MenuID)
+ if !uniqueNenuPath {
+ msg := fmt.Sprintf("菜单修改【%s】失败,菜单路由地址已存在", body.MenuName)
+ ctx.JSON(w, 200, result.ErrMsg(msg))
+ return
+ }
+ }
+
+ // 检查名称唯一
+ uniqueNenuName := s.sysMenuService.CheckUniqueMenuName(body.MenuName, body.ParentID, body.MenuID)
+ if !uniqueNenuName {
+ msg := fmt.Sprintf("菜单修改【%s】失败,菜单名称已存在", body.MenuName)
+ ctx.JSON(w, 200, result.ErrMsg(msg))
+ return
+ }
+
+ // 外链菜单需要符合网站http(s)开头
+ if body.IsFrame == "0" && !regular.ValidHttp(body.Path) {
+ msg := fmt.Sprintf("菜单修改【%s】失败,非内部地址必须以http(s)://开头", body.MenuName)
+ ctx.JSON(w, 200, result.ErrMsg(msg))
+ return
+ }
+
+ // 禁用菜单时检查子菜单是否使用
+ if body.Status == "0" {
+ hasStatus := s.sysMenuService.HasChildByMenuIdAndStatus(body.MenuID, "1")
+ if hasStatus > 0 {
+ msg := fmt.Sprintf("不允许禁用,存在使用子菜单数:%d", hasStatus)
+ ctx.JSON(w, 200, result.ErrMsg(msg))
+ return
+ }
+ }
+
+ body.UpdateBy = ctx.LoginUserToUserName(r)
+ rows := s.sysMenuService.UpdateMenu(body)
+ if rows > 0 {
+ ctx.JSON(w, 200, result.Ok(nil))
+ return
+ }
+ ctx.JSON(w, 200, result.Err(nil))
+}
+
+// 菜单删除
+//
+// DELETE /:menuId
+func (s *SysMenuApi) Remove(w http.ResponseWriter, r *http.Request) {
+ menuId := ctx.Param(r, "menuId")
+ if menuId == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ // 检查数据是否存在
+ menu := s.sysMenuService.SelectMenuById(menuId)
+ if menu.MenuID != menuId {
+ ctx.JSON(w, 200, result.ErrMsg("No permission to access menu data!"))
+ return
+ }
+
+ // 检查是否存在子菜单
+ hasChild := s.sysMenuService.HasChildByMenuIdAndStatus(menuId, "")
+ if hasChild > 0 {
+ msg := fmt.Sprintf("Deletion not allowed, there are sub orders: %d", hasChild)
+ ctx.JSON(w, 200, result.ErrMsg(msg))
+ return
+ }
+
+ // 检查是否分配给角色
+ existRole := s.sysMenuService.CheckMenuExistRole(menuId)
+ if existRole > 0 {
+ msg := fmt.Sprintf("Deletion not allowed, menu already assigned to roles: %d", existRole)
+ ctx.JSON(w, 200, result.ErrMsg(msg))
+ return
+ }
+
+ rows := s.sysMenuService.DeleteMenuById(menuId)
+ if rows > 0 {
+ msg := fmt.Sprintf("Successfully deleted: %d", rows)
+ ctx.JSON(w, 200, result.OkMsg(msg))
+ return
+ }
+ ctx.JSON(w, 200, result.Err(nil))
+}
+
+// 菜单树结构列表
+//
+// GET /treeSelect
+func (s *SysMenuApi) TreeSelect(w http.ResponseWriter, r *http.Request) {
+ query := model.SysMenu{}
+ if v := ctx.GetQuery(r, "menuName"); v != "" {
+ query.MenuName = v
+ }
+ if v := ctx.GetQuery(r, "status"); v != "" {
+ query.Status = v
+ }
+
+ userId := ctx.LoginUserToUserID(r)
+ if srcConfig.IsAdmin(userId) {
+ userId = "*"
+ }
+ data := s.sysMenuService.SelectMenuTreeSelectByUserId(query, userId)
+ ctx.JSON(w, 200, result.OkData(data))
+
+}
+
+// 菜单树结构列表(指定角色)
+//
+// GET /roleMenuTreeSelect/:roleId
+func (s *SysMenuApi) RoleMenuTreeSelect(w http.ResponseWriter, r *http.Request) {
+ roleId := ctx.Param(r, "roleId")
+ if roleId == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ query := model.SysMenu{}
+ if v := ctx.GetQuery(r, "menuName"); v != "" {
+ query.MenuName = v
+ }
+ if v := ctx.GetQuery(r, "status"); v != "" {
+ query.Status = v
+ }
+
+ userId := ctx.LoginUserToUserID(r)
+ if srcConfig.IsAdmin(userId) {
+ userId = "*"
+ }
+ menuTreeSelect := s.sysMenuService.SelectMenuTreeSelectByUserId(query, userId)
+ checkedKeys := s.sysMenuService.SelectMenuListByRoleId(roleId)
+ ctx.JSON(w, 200, result.OkData(map[string]any{
+ "menus": menuTreeSelect,
+ "checkedKeys": checkedKeys,
+ }))
+}
diff --git a/features/sys_menu/consts/consts_menu.go b/features/sys_menu/consts/consts_menu.go
new file mode 100644
index 0000000..6a1dca3
--- /dev/null
+++ b/features/sys_menu/consts/consts_menu.go
@@ -0,0 +1,24 @@
+package consts
+
+// 系统菜单常量信息
+
+const (
+ // 组件布局类型-基础布局组件标识
+ COMPONENT_LAYOUT_BASIC = "BasicLayout"
+ // 组件布局类型-空白布局组件标识
+ COMPONENT_LAYOUT_BLANK = "BlankLayout"
+ // 组件布局类型-内链接布局组件标识
+ COMPONENT_LAYOUT_LINK = "LinkLayout"
+)
+
+const (
+ // 菜单类型-目录
+ TYPE_DIR = "D"
+ // 菜单类型-菜单
+ TYPE_MENU = "M"
+ // 菜单类型-按钮
+ TYPE_BUTTON = "B"
+)
+
+// 菜单内嵌地址标识-带/前缀
+const PATH_INLINE = "/inline"
diff --git a/features/sys_menu/model/sys_menu.go b/features/sys_menu/model/sys_menu.go
new file mode 100644
index 0000000..397e30e
--- /dev/null
+++ b/features/sys_menu/model/sys_menu.go
@@ -0,0 +1,46 @@
+package model
+
+// SysMenu 菜单权限对象 sys_menu
+type SysMenu struct {
+ // 菜单ID
+ MenuID string `json:"menuId"`
+ // 菜单名称
+ MenuName string `json:"menuName" binding:"required"`
+ // 父菜单ID 默认0
+ ParentID string `json:"parentId" binding:"required"`
+ // 显示顺序
+ MenuSort int `json:"menuSort"`
+ // 路由地址
+ Path string `json:"path"`
+ // 组件路径
+ Component string `json:"component"`
+ // 是否内部跳转(0否 1是)
+ IsFrame string `json:"isFrame"`
+ // 是否缓存(0不缓存 1缓存)
+ IsCache string `json:"isCache"`
+ // 菜单类型(D目录 M菜单 B按钮)
+ MenuType string `json:"menuType" binding:"required"`
+ // 是否显示(0隐藏 1显示)
+ Visible string `json:"visible"`
+ // 菜单状态(0停用 1正常)
+ Status string `json:"status"`
+ // 权限标识
+ Perms string `json:"perms"`
+ // 菜单图标(#无图标)
+ Icon string `json:"icon"`
+ // 创建者
+ CreateBy string `json:"createBy"`
+ // 创建时间
+ CreateTime int64 `json:"createTime"`
+ // 更新者
+ UpdateBy string `json:"updateBy"`
+ // 更新时间
+ UpdateTime int64 `json:"updateTime"`
+ // 备注
+ Remark string `json:"remark"`
+
+ // ====== 非数据库字段属性 ======
+
+ // 子菜单
+ Children []SysMenu `json:"children,omitempty"`
+}
diff --git a/features/sys_menu/service/repo_sys_menu.go b/features/sys_menu/service/repo_sys_menu.go
new file mode 100644
index 0000000..319c839
--- /dev/null
+++ b/features/sys_menu/service/repo_sys_menu.go
@@ -0,0 +1,475 @@
+package service
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "nms_nbi/features/sys_menu/consts"
+ "nms_nbi/features/sys_menu/model"
+ "nms_nbi/lib/core/datasource"
+ "nms_nbi/lib/core/utils/parse"
+ "nms_nbi/lib/log"
+)
+
+// 实例化数据层 RepoSysMenu 结构体
+var NewRepoSysMenu = &RepoSysMenu{
+ selectSql: `select
+ m.menu_id, m.menu_name, m.parent_id, m.menu_sort, m.path, m.component, m.is_frame, m.is_cache, m.menu_type, m.visible, m.status, ifnull(m.perms,'') as perms, m.icon, m.create_time, m.remark
+ from sys_menu m`,
+
+ selectSqlByUser: `select distinct
+ m.menu_id, m.menu_name, m.parent_id, m.menu_sort, m.path, m.component, m.is_frame, m.is_cache, m.menu_type, m.visible, m.status, ifnull(m.perms,'') as perms, m.icon, m.create_time, m.remark
+ from sys_menu m
+ left join sys_role_menu rm on m.menu_id = rm.menu_id
+ left join sys_user_role ur on rm.role_id = ur.role_id
+ left join sys_role ro on ur.role_id = ro.role_id`,
+
+ resultMap: map[string]string{
+ "menu_id": "MenuID",
+ "menu_name": "MenuName",
+ "parent_name": "ParentName",
+ "parent_id": "ParentID",
+ "path": "Path",
+ "menu_sort": "MenuSort",
+ "component": "Component",
+ "is_frame": "IsFrame",
+ "is_cache": "IsCache",
+ "menu_type": "MenuType",
+ "visible": "Visible",
+ "status": "Status",
+ "perms": "Perms",
+ "icon": "Icon",
+ "create_by": "CreateBy",
+ "create_time": "CreateTime",
+ "update_by": "UpdateBy",
+ "update_time": "UpdateTime",
+ "remark": "Remark",
+ },
+}
+
+// RepoSysMenu 菜单表 数据层处理
+type RepoSysMenu struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 查询视图用户对象SQL
+ selectSqlByUser string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *RepoSysMenu) convertResultRows(rows []map[string]any) []model.SysMenu {
+ arr := make([]model.SysMenu, 0)
+ for _, row := range rows {
+ sysMenu := model.SysMenu{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ datasource.SetFieldValue(&sysMenu, keyMapper, value)
+ }
+ }
+ arr = append(arr, sysMenu)
+ }
+ return arr
+}
+
+// SelectMenuList 查询系统菜单列表
+func (r *RepoSysMenu) SelectMenuList(sysMenu model.SysMenu, userId string) []model.SysMenu {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysMenu.MenuName != "" {
+ conditions = append(conditions, "m.menu_name like concat('%', concat(?, '%'))")
+ params = append(params, sysMenu.MenuName)
+ }
+ if sysMenu.Visible != "" {
+ conditions = append(conditions, "m.visible = ?")
+ params = append(params, sysMenu.Visible)
+ }
+ if sysMenu.Status != "" {
+ conditions = append(conditions, "m.status = ?")
+ params = append(params, sysMenu.Status)
+ }
+
+ fromSql := r.selectSql
+
+ // 个人菜单
+ if userId != "*" {
+ fromSql = r.selectSqlByUser
+ conditions = append(conditions, "ur.user_id = ?")
+ params = append(params, userId)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ orderSql := " order by m.parent_id, m.menu_sort"
+ querySql := fromSql + whereSql + orderSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ return []model.SysMenu{}
+ }
+
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// SelectMenuPermsByUserId 根据用户ID查询权限
+func (r *RepoSysMenu) SelectMenuPermsByUserId(userId string) []string {
+ querySql := `select distinct m.perms as 'str' from sys_menu m
+ left join sys_role_menu rm on m.menu_id = rm.menu_id
+ left join sys_user_role ur on rm.role_id = ur.role_id
+ left join sys_role r on r.role_id = ur.role_id
+ where m.status = '1' and m.perms != '' and r.status = '1' and ur.user_id = ? `
+
+ // 查询结果
+ results, err := datasource.RawDB("", querySql, []any{userId})
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ return []string{}
+ }
+
+ // 读取结果
+ rows := make([]string, 0)
+ for _, m := range results {
+ rows = append(rows, fmt.Sprintf("%v", m["str"]))
+ }
+ return rows
+}
+
+// SelectMenuTreeByUserId 根据用户ID查询菜单
+func (r *RepoSysMenu) SelectMenuTreeByUserId(userId string) []model.SysMenu {
+ var params []any
+ var querySql string
+
+ if userId == "*" {
+ // 管理员全部菜单
+ querySql = r.selectSql + ` where
+ m.menu_type in (?,?) and m.status = '1'
+ order by m.parent_id, m.menu_sort`
+ params = append(params, consts.TYPE_DIR)
+ params = append(params, consts.TYPE_MENU)
+ } else {
+ // 用户ID权限
+ querySql = r.selectSqlByUser + ` where
+ m.menu_type in (?, ?) and m.status = '1'
+ and ur.user_id = ? and ro.status = '1'
+ order by m.parent_id, m.menu_sort`
+ params = append(params, consts.TYPE_DIR)
+ params = append(params, consts.TYPE_MENU)
+ params = append(params, userId)
+ }
+
+ // 查询结果
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ return []model.SysMenu{}
+ }
+
+ return r.convertResultRows(results)
+}
+
+// SelectMenuListByRoleId 根据角色ID查询菜单树信息
+func (r *RepoSysMenu) SelectMenuListByRoleId(roleId string, menuCheckStrictly bool) []string {
+ querySql := `select m.menu_id as 'str' from sys_menu m
+ left join sys_role_menu rm on m.menu_id = rm.menu_id
+ where rm.role_id = ? `
+ var params []any
+ params = append(params, roleId)
+ // 展开
+ if menuCheckStrictly {
+ querySql += ` and m.menu_id not in
+ (select m.parent_id from sys_menu m
+ inner join sys_role_menu rm on m.menu_id = rm.menu_id
+ and rm.role_id = ?) `
+ params = append(params, roleId)
+ }
+
+ // 查询结果
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ return []string{}
+ }
+
+ if len(results) > 0 {
+ ids := make([]string, 0)
+ for _, v := range results {
+ ids = append(ids, fmt.Sprintf("%v", v["str"]))
+ }
+ return ids
+ }
+ return []string{}
+}
+
+// SelectMenuByIds 根据菜单ID查询信息
+func (r *RepoSysMenu) SelectMenuByIds(menuIds []string) []model.SysMenu {
+ placeholder := datasource.KeyPlaceholderByQuery(len(menuIds))
+ querySql := r.selectSql + " where m.menu_id in (" + placeholder + ")"
+ parameters := datasource.ConvertIdsSlice(menuIds)
+ results, err := datasource.RawDB("", querySql, parameters)
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ return []model.SysMenu{}
+ }
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// HasChildByMenuIdAndStatus 存在菜单子节点数量与状态
+func (r *RepoSysMenu) HasChildByMenuIdAndStatus(menuId, status string) int64 {
+ querySql := "select count(1) as 'total' from sys_menu where parent_id = ?"
+ params := []any{menuId}
+
+ // 菜单状态
+ if status != "" {
+ querySql += " and status = ?"
+ params = append(params, status)
+ }
+
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ return 0
+ }
+ if len(results) > 0 {
+ return parse.Number(results[0]["total"])
+ }
+ return 0
+}
+
+// InsertMenu 新增菜单信息
+func (r *RepoSysMenu) InsertMenu(sysMenu model.SysMenu) string {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysMenu.MenuID != "" {
+ params["menu_id"] = sysMenu.MenuID
+ }
+ if sysMenu.ParentID != "" {
+ params["parent_id"] = sysMenu.ParentID
+ }
+ if sysMenu.MenuName != "" {
+ params["menu_name"] = sysMenu.MenuName
+ }
+ if sysMenu.MenuSort > 0 {
+ params["menu_sort"] = sysMenu.MenuSort
+ }
+ if sysMenu.Path != "" {
+ params["path"] = sysMenu.Path
+ }
+ if sysMenu.Component != "" {
+ params["component"] = sysMenu.Component
+ }
+ if sysMenu.IsFrame != "" {
+ params["is_frame"] = sysMenu.IsFrame
+ }
+ if sysMenu.IsCache != "" {
+ params["is_cache"] = sysMenu.IsCache
+ }
+ if sysMenu.MenuType != "" {
+ params["menu_type"] = sysMenu.MenuType
+ }
+ if sysMenu.Visible != "" {
+ params["visible"] = sysMenu.Visible
+ }
+ if sysMenu.Status != "" {
+ params["status"] = sysMenu.Status
+ }
+ if sysMenu.Perms != "" {
+ params["perms"] = sysMenu.Perms
+ }
+ if sysMenu.Icon != "" {
+ params["icon"] = sysMenu.Icon
+ } else {
+ params["icon"] = "#"
+ }
+ if sysMenu.Remark != "" {
+ params["remark"] = sysMenu.Remark
+ }
+ if sysMenu.CreateBy != "" {
+ params["create_by"] = sysMenu.CreateBy
+ params["create_time"] = time.Now().UnixMilli()
+ }
+
+ // 根据菜单类型重置参数
+ if sysMenu.MenuType == consts.TYPE_BUTTON {
+ params["component"] = ""
+ params["path"] = ""
+ params["icon"] = "#"
+ params["is_cache"] = "1"
+ params["is_frame"] = "1"
+ params["visible"] = "1"
+ params["status"] = "1"
+ }
+ if sysMenu.MenuType == consts.TYPE_DIR {
+ params["component"] = ""
+ params["perms"] = ""
+ }
+
+ // 构建执行语句
+ keys, placeholder, values := datasource.KeyPlaceholderValueByInsert(params)
+ sql := "insert into sys_menu (" + strings.Join(keys, ",") + ")values(" + placeholder + ")"
+
+ // 执行插入
+ results, err := datasource.ExecDB("", sql, values)
+ if err != nil {
+ log.Errorf("insert row : %v", err.Error())
+ return ""
+ }
+
+ insertId, err := results.LastInsertId()
+ if err != nil {
+ log.Errorf("insert row : %v", err.Error())
+ return ""
+ }
+ return fmt.Sprint(insertId)
+}
+
+// UpdateMenu 修改菜单信息
+func (r *RepoSysMenu) UpdateMenu(sysMenu model.SysMenu) int64 {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysMenu.MenuID != "" {
+ params["menu_id"] = sysMenu.MenuID
+ }
+ if sysMenu.ParentID != "" {
+ params["parent_id"] = sysMenu.ParentID
+ }
+ if sysMenu.MenuName != "" {
+ params["menu_name"] = sysMenu.MenuName
+ }
+ if sysMenu.MenuSort > 0 {
+ params["menu_sort"] = sysMenu.MenuSort
+ }
+ if sysMenu.Path != "" {
+ params["path"] = sysMenu.Path
+ }
+ if sysMenu.Component != "" {
+ params["component"] = sysMenu.Component
+ }
+ if sysMenu.IsFrame != "" {
+ params["is_frame"] = sysMenu.IsFrame
+ }
+ if sysMenu.IsCache != "" {
+ params["is_cache"] = sysMenu.IsCache
+ }
+ if sysMenu.MenuType != "" {
+ params["menu_type"] = sysMenu.MenuType
+ }
+ if sysMenu.Visible != "" {
+ params["visible"] = sysMenu.Visible
+ }
+ if sysMenu.Status != "" {
+ params["status"] = sysMenu.Status
+ }
+ if sysMenu.Perms != "" {
+ params["perms"] = sysMenu.Perms
+ }
+ if sysMenu.Icon != "" {
+ params["icon"] = sysMenu.Icon
+ } else {
+ params["icon"] = "#"
+ }
+ if sysMenu.Remark != "" {
+ params["remark"] = sysMenu.Remark
+ }
+ if sysMenu.UpdateBy != "" {
+ params["update_by"] = sysMenu.UpdateBy
+ params["update_time"] = time.Now().UnixMilli()
+ }
+
+ // 根据菜单类型重置参数
+ if sysMenu.MenuType == consts.TYPE_BUTTON {
+ params["component"] = ""
+ params["path"] = ""
+ params["icon"] = "#"
+ params["is_cache"] = "1"
+ params["is_frame"] = "1"
+ params["visible"] = "1"
+ params["status"] = "1"
+ }
+ if sysMenu.MenuType == consts.TYPE_DIR {
+ params["component"] = ""
+ params["perms"] = ""
+ }
+
+ // 构建执行语句
+ keys, values := datasource.KeyValueByUpdate(params)
+ sql := "update sys_menu set " + strings.Join(keys, ",") + " where menu_id = ?"
+
+ // 执行更新
+ values = append(values, sysMenu.MenuID)
+ results, err := datasource.ExecDB("", sql, values)
+ if err != nil {
+ log.Errorf("update row : %v", err.Error())
+ return 0
+ }
+ affected, err := results.RowsAffected()
+ if err != nil {
+ log.Errorf("update err => %v", err)
+ return 0
+ }
+ return affected
+}
+
+// DeleteMenuById 删除菜单管理信息
+func (r *RepoSysMenu) DeleteMenuById(menuId string) int64 {
+ sql := "delete from sys_menu where menu_id = ?"
+ results, err := datasource.ExecDB("", sql, []any{menuId})
+ if err != nil {
+ log.Errorf("delete err => %v", err)
+ return 0
+ }
+ affected, err := results.RowsAffected()
+ if err != nil {
+ log.Errorf("delete err => %v", err)
+ return 0
+ }
+ return affected
+}
+
+// CheckUniqueMenu 校验菜单是否唯一
+func (r *RepoSysMenu) CheckUniqueMenu(sysMenu model.SysMenu) string {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysMenu.MenuName != "" {
+ conditions = append(conditions, "menu_name = ?")
+ params = append(params, sysMenu.MenuName)
+ }
+ if sysMenu.ParentID != "" {
+ conditions = append(conditions, "parent_id = ?")
+ params = append(params, sysMenu.ParentID)
+ }
+ if sysMenu.Path != "" {
+ conditions = append(conditions, "path = ?")
+ params = append(params, sysMenu.Path)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+ if whereSql == "" {
+ return ""
+ }
+
+ // 查询数据
+ querySql := "select menu_id as 'str' from sys_menu " + whereSql + " limit 1"
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ log.Errorf("query err %v", err)
+ return ""
+ }
+ if len(results) > 0 {
+ return fmt.Sprint(results[0]["str"])
+ }
+ return ""
+}
diff --git a/features/sys_menu/service/service_sys_menu.go b/features/sys_menu/service/service_sys_menu.go
new file mode 100644
index 0000000..7401e9c
--- /dev/null
+++ b/features/sys_menu/service/service_sys_menu.go
@@ -0,0 +1,408 @@
+package service
+
+import (
+ "encoding/base64"
+ "strings"
+
+ "nms_nbi/features/sys_menu/consts"
+ "nms_nbi/features/sys_menu/model"
+ sysRoleService "nms_nbi/features/sys_role/service"
+ sysrolemenu "nms_nbi/features/sys_role_menu"
+ "nms_nbi/lib/core/utils/parse"
+ "nms_nbi/lib/core/utils/regular"
+ "nms_nbi/lib/core/vo"
+)
+
+// 实例化服务层 ServiceSysMenu 结构体
+var NewServiceSysMenu = &ServiceSysMenu{
+ sysMenuRepository: NewRepoSysMenu,
+ sysRoleMenuRepository: sysrolemenu.NewRepoSysRoleMenu,
+ sysRoleRepository: sysRoleService.NewRepoSysRole,
+}
+
+// ServiceSysMenu 菜单 服务层处理
+type ServiceSysMenu struct {
+ // 菜单服务
+ sysMenuRepository *RepoSysMenu
+ // 角色与菜单关联服务
+ sysRoleMenuRepository *sysrolemenu.RepoSysRoleMenu
+ // 角色服务
+ sysRoleRepository *sysRoleService.RepoSysRole
+}
+
+// SelectMenuList 查询系统菜单列表
+func (r *ServiceSysMenu) SelectMenuList(sysMenu model.SysMenu, userId string) []model.SysMenu {
+ return r.sysMenuRepository.SelectMenuList(sysMenu, userId)
+}
+
+// SelectMenuPermsByUserId 根据用户ID查询权限
+func (r *ServiceSysMenu) SelectMenuPermsByUserId(userId string) []string {
+ return r.sysMenuRepository.SelectMenuPermsByUserId(userId)
+}
+
+// SelectMenuTreeByUserId 根据用户ID查询菜单
+func (r *ServiceSysMenu) SelectMenuTreeByUserId(userId string) []model.SysMenu {
+ sysMenus := r.sysMenuRepository.SelectMenuTreeByUserId(userId)
+ return r.parseDataToTree(sysMenus)
+}
+
+// SelectMenuTreeSelectByUserId 根据用户ID查询菜单树结构信息
+func (r *ServiceSysMenu) SelectMenuTreeSelectByUserId(sysMenu model.SysMenu, userId string) []vo.TreeSelect {
+ sysMenus := r.sysMenuRepository.SelectMenuList(sysMenu, userId)
+
+ // 过滤旧前端菜单
+ sysMenusF := []model.SysMenu{}
+ for _, v := range sysMenus {
+ if v.Perms != "page" {
+ continue
+ }
+ sysMenusF = append(sysMenusF, v)
+ }
+
+ menus := r.parseDataToTree(sysMenusF)
+ tree := make([]vo.TreeSelect, 0)
+ for _, menu := range menus {
+ tree = append(tree, sysMenuTreeSelect(menu))
+ }
+ return tree
+}
+
+// sysMenuTreeSelect 使用给定的 SysMenu 对象解析为 TreeSelect 对象
+func sysMenuTreeSelect(sysMenu model.SysMenu) vo.TreeSelect {
+ t := vo.TreeSelect{}
+ t.ID = sysMenu.MenuID
+ t.Label = sysMenu.MenuName
+ t.Title = sysMenu.MenuName
+
+ if len(sysMenu.Children) > 0 {
+ for _, menu := range sysMenu.Children {
+ child := sysMenuTreeSelect(menu)
+ t.Children = append(t.Children, child)
+ }
+ } else {
+ t.Children = []vo.TreeSelect{}
+ }
+
+ return t
+}
+
+// SelectMenuListByRoleId 根据角色ID查询菜单树信息 TODO
+func (r *ServiceSysMenu) SelectMenuListByRoleId(roleId string) []string {
+ roles := r.sysRoleRepository.SelectRoleByIds([]string{roleId})
+ if len(roles) > 0 {
+ role := roles[0]
+ if role.RoleID == roleId {
+ return r.sysMenuRepository.SelectMenuListByRoleId(
+ role.RoleID,
+ role.MenuCheckStrictly == "1",
+ )
+ }
+ }
+ return []string{}
+}
+
+// SelectMenuById 根据菜单ID查询信息
+func (r *ServiceSysMenu) SelectMenuById(menuId string) model.SysMenu {
+ if menuId == "" {
+ return model.SysMenu{}
+ }
+ menus := r.sysMenuRepository.SelectMenuByIds([]string{menuId})
+ if len(menus) > 0 {
+ return menus[0]
+ }
+ return model.SysMenu{}
+}
+
+// HasChildByMenuIdAndStatus 存在菜单子节点数量与状态
+func (r *ServiceSysMenu) HasChildByMenuIdAndStatus(menuId, status string) int64 {
+ return r.sysMenuRepository.HasChildByMenuIdAndStatus(menuId, status)
+}
+
+// CheckMenuExistRole 查询菜单是否存在角色
+func (r *ServiceSysMenu) CheckMenuExistRole(menuId string) int64 {
+ return r.sysRoleMenuRepository.CheckMenuExistRole(menuId)
+}
+
+// InsertMenu 新增菜单信息
+func (r *ServiceSysMenu) InsertMenu(sysMenu model.SysMenu) string {
+ return r.sysMenuRepository.InsertMenu(sysMenu)
+}
+
+// UpdateMenu 修改菜单信息
+func (r *ServiceSysMenu) UpdateMenu(sysMenu model.SysMenu) int64 {
+ return r.sysMenuRepository.UpdateMenu(sysMenu)
+}
+
+// DeleteMenuById 删除菜单管理信息
+func (r *ServiceSysMenu) DeleteMenuById(menuId string) int64 {
+ // 删除菜单与角色关联
+ r.sysRoleMenuRepository.DeleteMenuRole([]string{menuId})
+ return r.sysMenuRepository.DeleteMenuById(menuId)
+}
+
+// CheckUniqueMenuName 校验菜单名称是否唯一
+func (r *ServiceSysMenu) CheckUniqueMenuName(menuName, parentId, menuId string) bool {
+ uniqueId := r.sysMenuRepository.CheckUniqueMenu(model.SysMenu{
+ MenuName: menuName,
+ ParentID: parentId,
+ })
+ if uniqueId == menuId {
+ return true
+ }
+ return uniqueId == ""
+}
+
+// CheckUniqueMenuPath 校验路由地址是否唯一(针对目录和菜单)
+func (r *ServiceSysMenu) CheckUniqueMenuPath(path, menuId string) bool {
+ uniqueId := r.sysMenuRepository.CheckUniqueMenu(model.SysMenu{
+ Path: path,
+ })
+ if uniqueId == menuId {
+ return true
+ }
+ return uniqueId == ""
+}
+
+// BuildRouteMenus 构建前端路由所需要的菜单
+func (r *ServiceSysMenu) BuildRouteMenus(sysMenus []model.SysMenu, prefix string) []vo.Router {
+ routers := []vo.Router{}
+ for _, item := range sysMenus {
+ router := vo.Router{}
+ router.Name = r.getRouteName(item)
+ router.Path = r.getRouterPath(item)
+ router.Component = r.getComponent(item)
+ router.Meta = r.getRouteMeta(item)
+
+ // 子项菜单 目录类型 非路径链接
+ cMenus := item.Children
+ if len(cMenus) > 0 && item.MenuType == consts.TYPE_DIR && !regular.ValidHttp(item.Path) {
+ // 获取重定向地址
+ redirectPrefix, redirectPath := r.getRouteRedirect(
+ cMenus,
+ router.Path,
+ prefix,
+ )
+ router.Redirect = redirectPath
+ // 子菜单进入递归
+ router.Children = r.BuildRouteMenus(cMenus, redirectPrefix)
+ } else if item.ParentID == "0" && item.IsFrame == "1" && item.MenuType == consts.TYPE_MENU {
+ // 父菜单 内部跳转 菜单类型
+ menuPath := "/" + item.MenuID
+ childPath := menuPath + r.getRouterPath(item)
+ children := vo.Router{
+ Name: r.getRouteName(item),
+ Path: childPath,
+ Component: item.Component,
+ Meta: r.getRouteMeta(item),
+ }
+ router.Meta.HideChildInMenu = true
+ router.Children = append(router.Children, children)
+ router.Name = item.MenuID
+ router.Path = menuPath
+ router.Redirect = childPath
+ router.Component = consts.COMPONENT_LAYOUT_BASIC
+ } else if item.ParentID == "0" && item.IsFrame == "1" && regular.ValidHttp(item.Path) {
+ // 父菜单 内部跳转 路径链接
+ menuPath := "/" + item.MenuID
+ childPath := menuPath + r.getRouterPath(item)
+ children := vo.Router{
+ Name: r.getRouteName(item),
+ Path: childPath,
+ Component: consts.COMPONENT_LAYOUT_LINK,
+ Meta: r.getRouteMeta(item),
+ }
+ router.Meta.HideChildInMenu = true
+ router.Children = append(router.Children, children)
+ router.Name = item.MenuID
+ router.Path = menuPath
+ router.Redirect = childPath
+ router.Component = consts.COMPONENT_LAYOUT_BASIC
+ }
+
+ routers = append(routers, router)
+ }
+ return routers
+}
+
+// getRouteName 获取路由名称 路径英文首字母大写
+func (r *ServiceSysMenu) getRouteName(sysMenu model.SysMenu) string {
+ routerName := parse.FirstUpper(sysMenu.Path)
+ // 路径链接
+ if regular.ValidHttp(sysMenu.Path) {
+ return routerName[:5] + "Link" + sysMenu.MenuID
+ }
+ return routerName
+}
+
+// getRouterPath 获取路由地址
+func (r *ServiceSysMenu) getRouterPath(sysMenu model.SysMenu) string {
+ routerPath := sysMenu.Path
+
+ // 显式路径
+ if routerPath == "" || strings.HasPrefix(routerPath, "/") {
+ return routerPath
+ }
+
+ // 路径链接 内部跳转
+ if regular.ValidHttp(routerPath) && sysMenu.IsFrame == "1" {
+ routerPath = regular.Replace(routerPath, `/^http(s)?:\/\/+/`, "")
+ routerPath = base64.StdEncoding.EncodeToString([]byte(routerPath))
+ }
+
+ // 父菜单 内部跳转
+ if sysMenu.ParentID == "0" && sysMenu.IsFrame == "1" {
+ routerPath = "/" + routerPath
+ }
+
+ return routerPath
+}
+
+// getComponent 获取组件信息
+func (r *ServiceSysMenu) getComponent(sysMenu model.SysMenu) string {
+ // 内部跳转 路径链接
+ if sysMenu.IsFrame == "1" && regular.ValidHttp(sysMenu.Path) {
+ return consts.COMPONENT_LAYOUT_LINK
+ }
+
+ // 非父菜单 目录类型
+ if sysMenu.ParentID != "0" && sysMenu.MenuType == consts.TYPE_DIR {
+ return consts.COMPONENT_LAYOUT_BLANK
+ }
+
+ // 组件路径 内部跳转 菜单类型
+ if sysMenu.Component != "" && sysMenu.IsFrame == "1" && sysMenu.MenuType == consts.TYPE_MENU {
+ // 父菜单套外层布局
+ if sysMenu.ParentID == "0" {
+ return consts.COMPONENT_LAYOUT_BASIC
+ }
+ return sysMenu.Component
+ }
+
+ return consts.COMPONENT_LAYOUT_BASIC
+}
+
+// getRouteMeta 获取路由元信息
+func (r *ServiceSysMenu) getRouteMeta(sysMenu model.SysMenu) vo.RouterMeta {
+ meta := vo.RouterMeta{}
+ if sysMenu.Icon == "#" {
+ meta.Icon = ""
+ } else {
+ meta.Icon = sysMenu.Icon
+ }
+ meta.Title = sysMenu.MenuName
+ meta.HideChildInMenu = false
+ meta.HideInMenu = sysMenu.Visible == "0"
+ meta.Cache = sysMenu.IsCache == "1"
+ meta.Target = ""
+
+ // 路径链接 非内部跳转
+ if regular.ValidHttp(sysMenu.Path) && sysMenu.IsFrame == "0" {
+ meta.Target = "_blank"
+ }
+
+ return meta
+}
+
+// getRouteRedirect 获取路由重定向地址(针对目录)
+//
+// cMenus 子菜单数组
+// routerPath 当前菜单路径
+// prefix 菜单重定向路径前缀
+func (r *ServiceSysMenu) getRouteRedirect(cMenus []model.SysMenu, routerPath string, prefix string) (string, string) {
+ redirectPath := ""
+
+ // 重定向为首个显示并启用的子菜单
+ var firstChild *model.SysMenu
+ for _, item := range cMenus {
+ if item.IsFrame == "1" && item.Visible == "1" {
+ firstChild = &item
+ break
+ }
+ }
+
+ // 检查内嵌隐藏菜单是否可做重定向
+ if firstChild == nil {
+ for _, item := range cMenus {
+ if item.IsFrame == "1" && item.Visible == "1" && strings.Contains(item.Path, consts.PATH_INLINE) {
+ firstChild = &item
+ break
+ }
+ }
+ }
+
+ if firstChild != nil {
+ firstChildPath := r.getRouterPath(*firstChild)
+ if strings.HasPrefix(firstChildPath, "/") {
+ redirectPath = firstChildPath
+ } else {
+ // 拼接追加路径
+ if !strings.HasPrefix(routerPath, "/") {
+ prefix += "/"
+ }
+ prefix = prefix + routerPath
+ redirectPath = prefix + "/" + firstChildPath
+ }
+ }
+
+ return prefix, redirectPath
+}
+
+// parseDataToTree 将数据解析为树结构,构建前端所需要下拉树结构
+func (r *ServiceSysMenu) parseDataToTree(sysMenus []model.SysMenu) []model.SysMenu {
+ // 节点分组
+ nodesMap := make(map[string][]model.SysMenu)
+ // 节点id
+ treeIds := []string{}
+ // 树节点
+ tree := []model.SysMenu{}
+
+ for _, item := range sysMenus {
+ parentID := item.ParentID
+ // 分组
+ mapItem, ok := nodesMap[parentID]
+ if !ok {
+ mapItem = []model.SysMenu{}
+ }
+ mapItem = append(mapItem, item)
+ nodesMap[parentID] = mapItem
+ // 记录节点ID
+ treeIds = append(treeIds, item.MenuID)
+ }
+
+ for key, value := range nodesMap {
+ // 选择不是节点ID的作为树节点
+ found := false
+ for _, id := range treeIds {
+ if id == key {
+ found = true
+ break
+ }
+ }
+ if !found {
+ tree = append(tree, value...)
+ }
+ }
+
+ for i, node := range tree {
+ iN := r.parseDataToTreeComponet(node, &nodesMap)
+ tree[i] = iN
+ }
+
+ return tree
+}
+
+// parseDataToTreeComponet 递归函数处理子节点
+func (r *ServiceSysMenu) parseDataToTreeComponet(node model.SysMenu, nodesMap *map[string][]model.SysMenu) model.SysMenu {
+ id := node.MenuID
+ children, ok := (*nodesMap)[id]
+ if ok {
+ node.Children = children
+ }
+ if len(node.Children) > 0 {
+ for i, child := range node.Children {
+ icN := r.parseDataToTreeComponet(child, nodesMap)
+ node.Children[i] = icN
+ }
+ }
+ return node
+}
diff --git a/features/sys_role/api_sys_role.go b/features/sys_role/api_sys_role.go
new file mode 100644
index 0000000..b972e2e
--- /dev/null
+++ b/features/sys_role/api_sys_role.go
@@ -0,0 +1,368 @@
+package sysrole
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+
+ "nms_nbi/features/sys_role/model"
+ "nms_nbi/features/sys_role/service"
+ userService "nms_nbi/features/sys_user/service"
+ "nms_nbi/lib/core/utils/ctx"
+ "nms_nbi/lib/core/utils/parse"
+ "nms_nbi/lib/core/vo/result"
+ "nms_nbi/lib/midware"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+)
+
+// 角色接口添加到路由
+func Routers() []services.RouterItem {
+ // 实例化控制层 SysRoleApi 结构体
+ var apis = &SysRoleApi{
+ sysRoleService: service.NewServiceSysRole,
+ sysUserService: userService.NewServiceSysUser,
+ }
+
+ rs := [...]services.RouterItem{
+ {
+ Method: "GET",
+ Pattern: "/roles",
+ Handler: apis.List,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "GET",
+ Pattern: "/role/{roleId}",
+ Handler: apis.Info,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "POST",
+ Pattern: "/role",
+ Handler: apis.Add,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "PUT",
+ Pattern: "/role",
+ Handler: apis.Edit,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "DELETE",
+ Pattern: "/role/{roleIds}",
+ Handler: apis.Remove,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "PUT",
+ Pattern: "/role/changeStatus",
+ Handler: apis.Status,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "GET",
+ Pattern: "/role/authUser/allocatedList",
+ Handler: apis.AuthUserAllocatedList,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "PUT",
+ Pattern: "/role/authUser/checked",
+ Handler: apis.AuthUserChecked,
+ Middleware: midware.Authorize(nil),
+ },
+ // 添加更多的 Router 对象...
+ }
+
+ // 生成两组前缀路由
+ rsPrefix := []services.RouterItem{}
+ for _, v := range rs {
+ path := "/roleManage/{apiVersion}" + v.Pattern
+ // 固定前缀
+ v.Pattern = config.DefaultUriPrefix + path
+ rsPrefix = append(rsPrefix, v)
+ // 可配置
+ v.Pattern = config.UriPrefix + path
+ rsPrefix = append(rsPrefix, v)
+ }
+ return rsPrefix
+}
+
+// // 实例化控制层 SysRoleApi 结构体
+// var NewSysRole = &SysRoleApi{
+// sysRoleService: sysrole.NewServiceSysRole,
+// sysUserService: sysuser.NewServiceSysUser,
+// }
+
+// 角色信息
+//
+// PATH /roleManage
+type SysRoleApi struct {
+ // 角色服务
+ sysRoleService *service.ServiceSysRole
+ // 用户服务
+ sysUserService *userService.ServiceSysUser
+}
+
+// 角色列表
+//
+// GET /list
+func (s *SysRoleApi) List(w http.ResponseWriter, r *http.Request) {
+ querys := ctx.QueryMap(r)
+ data := s.sysRoleService.SelectRolePage(querys)
+ ctx.JSON(w, 200, result.Ok(data))
+}
+
+// 角色信息详情
+//
+// GET /:roleId
+func (s *SysRoleApi) Info(w http.ResponseWriter, r *http.Request) {
+ roleId := ctx.Param(r, "roleId")
+ if roleId == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+ data := s.sysRoleService.SelectRoleById(roleId)
+ if data.RoleID == roleId {
+ ctx.JSON(w, 200, result.OkData(data))
+ return
+ }
+ ctx.JSON(w, 200, result.Err(nil))
+}
+
+// 角色信息新增
+//
+// POST /
+func (s *SysRoleApi) Add(w http.ResponseWriter, r *http.Request) {
+ var body model.SysRole
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil || body.RoleID != "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ // 判断角色名称是否唯一
+ uniqueRoleName := s.sysRoleService.CheckUniqueRoleName(body.RoleName, "")
+ if !uniqueRoleName {
+ msg := fmt.Sprintf("[%s] Role name already exists", body.RoleName)
+ ctx.JSON(w, 200, result.ErrMsg(msg))
+ return
+ }
+
+ // 判断角色键值是否唯一
+ uniqueRoleKey := s.sysRoleService.CheckUniqueRoleKey(body.RoleKey, "")
+ if !uniqueRoleKey {
+ msg := fmt.Sprintf("[%s] The role key value already exists", body.RoleName)
+ ctx.JSON(w, 200, result.ErrMsg(msg))
+ return
+ }
+
+ body.CreateBy = ctx.LoginUserToUserName(r)
+ insertId := s.sysRoleService.InsertRole(body)
+ if insertId != "" {
+ ctx.JSON(w, 200, result.Ok(nil))
+ return
+ }
+ ctx.JSON(w, 200, result.Err(nil))
+}
+
+// 角色信息修改
+//
+// PUT /
+func (s *SysRoleApi) Edit(w http.ResponseWriter, r *http.Request) {
+ var body model.SysRole
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil || body.RoleID == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ // 检查是否管理员角色
+ if body.RoleID == "1" {
+ ctx.JSON(w, 200, result.ErrMsg("Operation of administrator role is not allowed"))
+ return
+ }
+
+ // 检查是否存在
+ role := s.sysRoleService.SelectRoleById(body.RoleID)
+ if role.RoleID != body.RoleID {
+ ctx.JSON(w, 200, result.ErrMsg("No permission to access role data!"))
+ return
+ }
+
+ // 判断角色名称是否唯一
+ uniqueRoleName := s.sysRoleService.CheckUniqueRoleName(body.RoleName, body.RoleID)
+ if !uniqueRoleName {
+ msg := fmt.Sprintf("[%s] Role name already exists", body.RoleName)
+ ctx.JSON(w, 200, result.ErrMsg(msg))
+ return
+ }
+
+ // 判断角色键值是否唯一
+ uniqueRoleKey := s.sysRoleService.CheckUniqueRoleKey(body.RoleKey, body.RoleID)
+ if !uniqueRoleKey {
+ msg := fmt.Sprintf("[%s] The role key value already exists", body.RoleName)
+ ctx.JSON(w, 200, result.ErrMsg(msg))
+ return
+ }
+
+ body.UpdateBy = ctx.LoginUserToUserName(r)
+ rows := s.sysRoleService.UpdateRole(body)
+ if rows > 0 {
+ ctx.JSON(w, 200, result.Ok(nil))
+ return
+ }
+ ctx.JSON(w, 200, result.Err(nil))
+}
+
+// 角色信息删除
+//
+// DELETE /:roleIds
+func (s *SysRoleApi) Remove(w http.ResponseWriter, r *http.Request) {
+ roleIds := ctx.Param(r, "roleIds")
+ if roleIds == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+ // 处理字符转id数组后去重
+ ids := strings.Split(roleIds, ",")
+ uniqueIDs := parse.RemoveDuplicates(ids)
+ if len(uniqueIDs) <= 0 {
+ ctx.JSON(w, 200, result.Err(nil))
+ return
+ }
+ // 检查是否管理员角色
+ for _, id := range uniqueIDs {
+ if id == "1" {
+ ctx.JSON(w, 200, result.ErrMsg("Operation of administrator role is not allowed"))
+ return
+ }
+ }
+ rows, err := s.sysRoleService.DeleteRoleByIds(uniqueIDs)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+ msg := fmt.Sprintf("Successfully deleted: %d", rows)
+ ctx.JSON(w, 200, result.OkMsg(msg))
+}
+
+// 角色状态变更
+//
+// PUT /changeStatus
+func (s *SysRoleApi) Status(w http.ResponseWriter, r *http.Request) {
+ var body struct {
+ // 角色ID
+ RoleID string `json:"roleId" binding:"required"`
+ // 状态
+ Status string `json:"status" binding:"required"`
+ }
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ // 检查是否管理员角色
+ if body.RoleID == "1" {
+ ctx.JSON(w, 200, result.ErrMsg("Operation of administrator role is not allowed"))
+ return
+ }
+
+ // 检查是否存在
+ role := s.sysRoleService.SelectRoleById(body.RoleID)
+ if role.RoleID != body.RoleID {
+ ctx.JSON(w, 200, result.ErrMsg("No permission to access role data!"))
+ return
+ }
+
+ // 与旧值相等不变更
+ if role.Status == body.Status {
+ ctx.JSON(w, 200, result.ErrMsg("Change status equals old value!"))
+ return
+ }
+
+ // 更新状态不刷新缓存
+ userName := ctx.LoginUserToUserName(r)
+ SysRoleApi := model.SysRole{
+ RoleID: body.RoleID,
+ Status: body.Status,
+ UpdateBy: userName,
+ }
+ rows := s.sysRoleService.UpdateRole(SysRoleApi)
+ if rows > 0 {
+ ctx.JSON(w, 200, result.Ok(nil))
+ return
+ }
+ ctx.JSON(w, 200, result.Err(nil))
+}
+
+// 角色分配用户列表
+//
+// GET /authUser/allocatedList
+func (s *SysRoleApi) AuthUserAllocatedList(w http.ResponseWriter, r *http.Request) {
+ querys := ctx.QueryMap(r)
+ roleId, ok := querys["roleId"]
+ if !ok || roleId == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ // 检查是否存在
+ role := s.sysRoleService.SelectRoleById(roleId.(string))
+ if role.RoleID != roleId {
+ ctx.JSON(w, 200, result.ErrMsg("No permission to access role data!"))
+ return
+ }
+
+ data := s.sysUserService.SelectAllocatedPage(querys)
+ ctx.JSON(w, 200, result.Ok(data))
+}
+
+// 角色分配选择授权
+//
+// PUT /authUser/checked
+func (s *SysRoleApi) AuthUserChecked(w http.ResponseWriter, r *http.Request) {
+ var body struct {
+ // 角色ID
+ RoleID string `json:"roleId" binding:"required"`
+ // 用户ID组
+ UserIDs string `json:"userIds" binding:"required"`
+ // 选择操作 添加true 取消false
+ Checked bool `json:"checked"`
+ }
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ // 处理字符转id数组后去重
+ ids := strings.Split(body.UserIDs, ",")
+ uniqueIDs := parse.RemoveDuplicates(ids)
+ if len(uniqueIDs) <= 0 {
+ ctx.JSON(w, 200, result.Err(nil))
+ return
+ }
+
+ // 检查是否存在
+ role := s.sysRoleService.SelectRoleById(body.RoleID)
+ if role.RoleID != body.RoleID {
+ ctx.JSON(w, 200, result.ErrMsg("No permission to access role data!"))
+ return
+ }
+
+ var rows int64
+ if body.Checked {
+ rows = s.sysRoleService.InsertAuthUsers(body.RoleID, uniqueIDs)
+ } else {
+ rows = s.sysRoleService.DeleteAuthUsers(body.RoleID, uniqueIDs)
+ }
+ if rows > 0 {
+ ctx.JSON(w, 200, result.Ok(nil))
+ return
+ }
+ ctx.JSON(w, 200, result.Err(nil))
+}
diff --git a/features/sys_role/model/sys_role.go b/features/sys_role/model/sys_role.go
new file mode 100644
index 0000000..921fd25
--- /dev/null
+++ b/features/sys_role/model/sys_role.go
@@ -0,0 +1,38 @@
+package model
+
+// SysRole 角色对象 sys_role
+type SysRole struct {
+ // 角色ID
+ RoleID string `json:"roleId"`
+ // 角色名称
+ RoleName string `json:"roleName" binding:"required"`
+ // 角色键值
+ RoleKey string `json:"roleKey" binding:"required"`
+ // 显示顺序
+ RoleSort int `json:"roleSort"`
+ // 菜单树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示)
+ MenuCheckStrictly string `json:"menuCheckStrictly"`
+ // 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示)
+ DeptCheckStrictly string `json:"deptCheckStrictly"`
+ // 角色状态(0停用 1正常)
+ Status string `json:"status"`
+ // 删除标志(0代表存在 1代表删除)
+ DelFlag string `json:"delFlag"`
+ // 创建者
+ CreateBy string `json:"createBy"`
+ // 创建时间
+ CreateTime int64 `json:"createTime"`
+ // 更新者
+ UpdateBy string `json:"updateBy"`
+ // 更新时间
+ UpdateTime int64 `json:"updateTime"`
+ // 备注
+ Remark string `json:"remark"`
+
+ // ====== 非数据库字段属性 ======
+
+ // 菜单组
+ MenuIds []string `json:"menuIds,omitempty"`
+ // 部门组(数据权限)
+ DeptIds []string `json:"deptIds,omitempty"`
+}
diff --git a/features/sys_role/service/repo_sys_role.go b/features/sys_role/service/repo_sys_role.go
new file mode 100644
index 0000000..64a715f
--- /dev/null
+++ b/features/sys_role/service/repo_sys_role.go
@@ -0,0 +1,362 @@
+package service
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "nms_nbi/features/sys_role/model"
+ "nms_nbi/lib/core/datasource"
+ "nms_nbi/lib/core/utils/date"
+ "nms_nbi/lib/core/utils/parse"
+ "nms_nbi/lib/log"
+)
+
+// 实例化数据层 RepoSysRole 结构体
+var NewRepoSysRole = &RepoSysRole{
+ selectSql: `select distinct
+ r.role_id, r.role_name, r.role_key, r.role_sort, r.menu_check_strictly,
+ r.dept_check_strictly, r.status, r.del_flag, r.create_time, r.remark
+ from sys_role r
+ left join sys_user_role ur on ur.role_id = r.role_id
+ left join user u on u.id = ur.user_id`,
+
+ resultMap: map[string]string{
+ "role_id": "RoleID",
+ "role_name": "RoleName",
+ "role_key": "RoleKey",
+ "role_sort": "RoleSort",
+ "menu_check_strictly": "MenuCheckStrictly",
+ "dept_check_strictly": "DeptCheckStrictly",
+ "status": "Status",
+ "del_flag": "DelFlag",
+ "create_by": "CreateBy",
+ "create_time": "CreateTime",
+ "update_by": "UpdateBy",
+ "update_time": "UpdateTime",
+ "remark": "Remark",
+ },
+}
+
+// RepoSysRole 角色表 数据层处理
+type RepoSysRole struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *RepoSysRole) convertResultRows(rows []map[string]any) []model.SysRole {
+ arr := make([]model.SysRole, 0)
+ for _, row := range rows {
+ sysRole := model.SysRole{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ datasource.SetFieldValue(&sysRole, keyMapper, value)
+ }
+ }
+ arr = append(arr, sysRole)
+ }
+ return arr
+}
+
+// SelectRolePage 根据条件分页查询角色数据
+func (r *RepoSysRole) SelectRolePage(query map[string]any) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if v, ok := query["roleId"]; ok && v != "" {
+ conditions = append(conditions, "r.role_id = ?")
+ params = append(params, v)
+ }
+ if v, ok := query["roleName"]; ok && v != "" {
+ conditions = append(conditions, "r.role_name like concat('%', concat(?, '%'))")
+ params = append(params, v)
+ }
+ if v, ok := query["roleKey"]; ok && v != "" {
+ conditions = append(conditions, "r.role_key like concat('%', concat(?, '%'))")
+ params = append(params, v)
+ }
+ if v, ok := query["status"]; ok && v != "" {
+ conditions = append(conditions, "r.status = ?")
+ params = append(params, v)
+ }
+ beginTime, ok := query["beginTime"]
+ if !ok {
+ beginTime, ok = query["params[beginTime]"]
+ }
+ if ok && beginTime != "" {
+ conditions = append(conditions, "r.create_time >= ?")
+ beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD)
+ params = append(params, beginDate.UnixMilli())
+ }
+ endTime, ok := query["endTime"]
+ if !ok {
+ endTime, ok = query["params[endTime]"]
+ }
+ if ok && endTime != "" {
+ conditions = append(conditions, "r.create_time <= ?")
+ endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD)
+ params = append(params, endDate.UnixMilli())
+ }
+
+ // 构建查询条件语句
+ whereSql := " where r.del_flag = '0' "
+ if len(conditions) > 0 {
+ whereSql += " and " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := `select count(distinct r.role_id) as 'total' from sys_role r
+ left join sys_user_role ur on ur.role_id = r.role_id
+ left join user u on u.id = ur.user_id`
+ totalRows, err := datasource.RawDB("", totalSql+whereSql, params)
+ if err != nil {
+ log.Errorf("total err => %v", err)
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return map[string]any{
+ "total": total,
+ "rows": []model.SysRole{},
+ }
+ }
+
+ // 分页
+ pageNum, pageSize := datasource.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " order by r.role_sort asc limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ }
+
+ // 转换实体
+ rows := r.convertResultRows(results)
+ return map[string]any{
+ "total": total,
+ "rows": rows,
+ }
+}
+
+// SelectRoleList 根据条件查询角色数据
+func (r *RepoSysRole) SelectRoleList(sysRole model.SysRole) []model.SysRole {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysRole.RoleID != "" {
+ conditions = append(conditions, "r.role_id = ?")
+ params = append(params, sysRole.RoleID)
+ }
+ if sysRole.RoleKey != "" {
+ conditions = append(conditions, "r.role_key like concat('%', concat(?, '%'))")
+ params = append(params, sysRole.RoleKey)
+ }
+ if sysRole.RoleName != "" {
+ conditions = append(conditions, "r.role_name like concat('%', concat(?, '%'))")
+ params = append(params, sysRole.RoleName)
+ }
+ if sysRole.Status != "" {
+ conditions = append(conditions, "r.status = ?")
+ params = append(params, sysRole.Status)
+ }
+
+ // 构建查询条件语句
+ whereSql := " where r.del_flag = '0' "
+ if len(conditions) > 0 {
+ whereSql += " and " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ orderSql := " order by r.role_sort"
+ querySql := r.selectSql + whereSql + orderSql
+ rows, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ return []model.SysRole{}
+ }
+ return r.convertResultRows(rows)
+}
+
+// SelectRoleListByUserId 根据用户ID获取角色选择框列表
+func (r *RepoSysRole) SelectRoleListByUserId(userId string) []model.SysRole {
+ querySql := r.selectSql + " where r.del_flag = '0' and ur.user_id = ?"
+ results, err := datasource.RawDB("", querySql, []any{userId})
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ return []model.SysRole{}
+ }
+ return r.convertResultRows(results)
+}
+
+// SelectRoleByIds 通过角色ID查询角色
+func (r *RepoSysRole) SelectRoleByIds(roleIds []string) []model.SysRole {
+ placeholder := datasource.KeyPlaceholderByQuery(len(roleIds))
+ querySql := r.selectSql + " where r.role_id in (" + placeholder + ")"
+ parameters := datasource.ConvertIdsSlice(roleIds)
+ results, err := datasource.RawDB("", querySql, parameters)
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ return []model.SysRole{}
+ }
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// UpdateRole 修改角色信息
+func (r *RepoSysRole) UpdateRole(sysRole model.SysRole) int64 {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysRole.RoleName != "" {
+ params["role_name"] = sysRole.RoleName
+ }
+ if sysRole.RoleKey != "" {
+ params["role_key"] = sysRole.RoleKey
+ }
+ if sysRole.RoleSort > 0 {
+ params["role_sort"] = sysRole.RoleSort
+ }
+ if sysRole.MenuCheckStrictly != "" {
+ params["menu_check_strictly"] = sysRole.MenuCheckStrictly
+ }
+ if sysRole.DeptCheckStrictly != "" {
+ params["dept_check_strictly"] = sysRole.DeptCheckStrictly
+ }
+ if sysRole.Status != "" {
+ params["status"] = sysRole.Status
+ }
+ if sysRole.Remark != "" {
+ params["remark"] = sysRole.Remark
+ }
+ if sysRole.UpdateBy != "" {
+ params["update_by"] = sysRole.UpdateBy
+ params["update_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, values := datasource.KeyValueByUpdate(params)
+ sql := "update sys_role set " + strings.Join(keys, ",") + " where role_id = ?"
+
+ // 执行更新
+ values = append(values, sysRole.RoleID)
+ results, err := datasource.ExecDB("", sql, values)
+ if err != nil {
+ log.Errorf("update row : %v", err.Error())
+ return 0
+ }
+ affected, err := results.RowsAffected()
+ if err != nil {
+ log.Errorf("update err => %v", err)
+ return 0
+ }
+ return affected
+}
+
+// InsertRole 新增角色信息
+func (r *RepoSysRole) InsertRole(sysRole model.SysRole) string {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysRole.RoleID != "" {
+ params["role_id"] = sysRole.RoleID
+ }
+ if sysRole.RoleName != "" {
+ params["role_name"] = sysRole.RoleName
+ }
+ if sysRole.RoleKey != "" {
+ params["role_key"] = sysRole.RoleKey
+ }
+ if sysRole.RoleSort > 0 {
+ params["role_sort"] = sysRole.RoleSort
+ }
+ if sysRole.MenuCheckStrictly != "" {
+ params["menu_check_strictly"] = sysRole.MenuCheckStrictly
+ }
+ if sysRole.DeptCheckStrictly != "" {
+ params["dept_check_strictly"] = sysRole.DeptCheckStrictly
+ }
+ if sysRole.Status != "" {
+ params["status"] = sysRole.Status
+ }
+ if sysRole.Remark != "" {
+ params["remark"] = sysRole.Remark
+ }
+ if sysRole.CreateBy != "" {
+ params["create_by"] = sysRole.CreateBy
+ params["create_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, placeholder, values := datasource.KeyPlaceholderValueByInsert(params)
+ sql := "insert into sys_role (" + strings.Join(keys, ",") + ")values(" + placeholder + ")"
+
+ // 执行插入
+ results, err := datasource.ExecDB("", sql, values)
+ if err != nil {
+ log.Errorf("insert row : %v", err.Error())
+ return ""
+ }
+ insertId, err := results.LastInsertId()
+ if err != nil {
+ log.Errorf("insert row : %v", err.Error())
+ return ""
+ }
+ return fmt.Sprint(insertId)
+}
+
+// DeleteRoleByIds 批量删除角色信息
+func (r *RepoSysRole) DeleteRoleByIds(roleIds []string) int64 {
+ placeholder := datasource.KeyPlaceholderByQuery(len(roleIds))
+ sql := "update sys_role set del_flag = '1' where role_id in (" + placeholder + ")"
+ parameters := datasource.ConvertIdsSlice(roleIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ log.Errorf("delete err => %v", err)
+ return 0
+ }
+ affected, err := results.RowsAffected()
+ if err != nil {
+ log.Errorf("delete err => %v", err)
+ return 0
+ }
+ return affected
+}
+
+// CheckUniqueRole 校验角色是否唯一
+func (r *RepoSysRole) CheckUniqueRole(sysRole model.SysRole) string {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysRole.RoleName != "" {
+ conditions = append(conditions, "r.role_name = ?")
+ params = append(params, sysRole.RoleName)
+ }
+ if sysRole.RoleKey != "" {
+ conditions = append(conditions, "r.role_key = ?")
+ params = append(params, sysRole.RoleKey)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ } else {
+ return ""
+ }
+
+ // 查询数据
+ querySql := "select role_id as 'str' from sys_role r " + whereSql + " and r.del_flag = '0' limit 1"
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ log.Errorf("query err %v", err)
+ return ""
+ }
+ if len(results) > 0 {
+ return fmt.Sprint(results[0]["str"])
+ }
+ return ""
+}
diff --git a/features/sys_role/service/service_sys_role.go b/features/sys_role/service/service_sys_role.go
new file mode 100644
index 0000000..4e3023e
--- /dev/null
+++ b/features/sys_role/service/service_sys_role.go
@@ -0,0 +1,165 @@
+package service
+
+import (
+ "errors"
+ "fmt"
+
+ "nms_nbi/features/sys_role/model"
+ sysrolemenu "nms_nbi/features/sys_role_menu"
+ sysuserrole "nms_nbi/features/sys_user_role"
+)
+
+// 实例化服务层 ServiceSysRole 结构体
+var NewServiceSysRole = &ServiceSysRole{
+ sysRoleRepository: NewRepoSysRole,
+ sysUserRoleRepository: sysuserrole.NewRepoSysUserRole,
+ sysRoleMenuRepository: sysrolemenu.NewRepoSysRoleMenu,
+}
+
+// ServiceSysRole 角色 服务层处理
+type ServiceSysRole struct {
+ // 角色服务
+ sysRoleRepository *RepoSysRole
+ // 用户与角色关联服务
+ sysUserRoleRepository *sysuserrole.RepoSysUserRole
+ // 角色与菜单关联服务
+ sysRoleMenuRepository *sysrolemenu.RepoSysRoleMenu
+}
+
+// SelectRolePage 根据条件分页查询角色数据
+func (r *ServiceSysRole) SelectRolePage(query map[string]any) map[string]any {
+ return r.sysRoleRepository.SelectRolePage(query)
+}
+
+// SelectRoleList 根据条件查询角色数据
+func (r *ServiceSysRole) SelectRoleList(sysRole model.SysRole) []model.SysRole {
+ return r.sysRoleRepository.SelectRoleList(sysRole)
+}
+
+// SelectRoleListByUserId 根据用户ID获取角色选择框列表
+func (r *ServiceSysRole) SelectRoleListByUserId(userId string) []model.SysRole {
+ return r.sysRoleRepository.SelectRoleListByUserId(userId)
+}
+
+// SelectRoleById 通过角色ID查询角色
+func (r *ServiceSysRole) SelectRoleById(roleId string) model.SysRole {
+ if roleId == "" {
+ return model.SysRole{}
+ }
+ posts := r.sysRoleRepository.SelectRoleByIds([]string{roleId})
+ if len(posts) > 0 {
+ return posts[0]
+ }
+ return model.SysRole{}
+}
+
+// UpdateRole 修改角色信息
+func (r *ServiceSysRole) UpdateRole(sysRole model.SysRole) int64 {
+ rows := r.sysRoleRepository.UpdateRole(sysRole)
+ if rows > 0 {
+ // 删除角色与菜单关联
+ r.sysRoleMenuRepository.DeleteRoleMenu([]string{sysRole.RoleID})
+ if len(sysRole.MenuIds) > 0 {
+ r.insertRoleMenu(sysRole.RoleID, sysRole.MenuIds)
+ }
+ }
+ return rows
+}
+
+// InsertRole 新增角色信息
+func (r *ServiceSysRole) InsertRole(sysRole model.SysRole) string {
+ insertId := r.sysRoleRepository.InsertRole(sysRole)
+ if insertId != "" && len(sysRole.MenuIds) > 0 {
+ r.insertRoleMenu(insertId, sysRole.MenuIds)
+ }
+ return insertId
+}
+
+// insertRoleMenu 新增角色菜单信息
+func (r *ServiceSysRole) insertRoleMenu(roleId string, menuIds []string) int64 {
+ if roleId == "" || len(menuIds) <= 0 {
+ return 0
+ }
+
+ sysRoleMenus := []sysrolemenu.SysRoleMenu{}
+ for _, menuId := range menuIds {
+ if menuId == "" {
+ continue
+ }
+ sysRoleMenus = append(sysRoleMenus, sysrolemenu.NewSysRoleMenu(roleId, menuId))
+ }
+
+ return r.sysRoleMenuRepository.BatchRoleMenu(sysRoleMenus)
+}
+
+// DeleteRoleByIds 批量删除角色信息
+func (r *ServiceSysRole) DeleteRoleByIds(roleIds []string) (int64, error) {
+ // 检查是否存在
+ roles := r.sysRoleRepository.SelectRoleByIds(roleIds)
+ if len(roles) <= 0 {
+ return 0, errors.New("no permission to access role data")
+ }
+ for _, role := range roles {
+ // 检查是否为已删除
+ if role.DelFlag == "1" {
+ return 0, errors.New(role.RoleID + " The character information has been deleted")
+ }
+ // 检查分配用户
+ userCount := r.sysUserRoleRepository.CountUserRoleByRoleId(role.RoleID)
+ if userCount > 0 {
+ msg := fmt.Sprintf("[%s] has been assigned to a user and cannot be deleted", role.RoleName)
+ return 0, errors.New(msg)
+ }
+ }
+ if len(roles) == len(roleIds) {
+ // 删除角色与菜单关联
+ r.sysRoleMenuRepository.DeleteRoleMenu(roleIds)
+ rows := r.sysRoleRepository.DeleteRoleByIds(roleIds)
+ return rows, nil
+ }
+ return 0, errors.New("failed to delete role information")
+}
+
+// CheckUniqueRoleName 校验角色名称是否唯一
+func (r *ServiceSysRole) CheckUniqueRoleName(roleName, roleId string) bool {
+ uniqueId := r.sysRoleRepository.CheckUniqueRole(model.SysRole{
+ RoleName: roleName,
+ })
+ if uniqueId == roleId {
+ return true
+ }
+ return uniqueId == ""
+}
+
+// CheckUniqueRoleKey 校验角色权限是否唯一
+func (r *ServiceSysRole) CheckUniqueRoleKey(roleKey, roleId string) bool {
+ uniqueId := r.sysRoleRepository.CheckUniqueRole(model.SysRole{
+ RoleKey: roleKey,
+ })
+ if uniqueId == roleId {
+ return true
+ }
+ return uniqueId == ""
+}
+
+// DeleteAuthUsers 批量取消授权用户角色
+func (r *ServiceSysRole) DeleteAuthUsers(roleId string, userIds []string) int64 {
+ return r.sysUserRoleRepository.DeleteUserRoleByRoleId(roleId, userIds)
+}
+
+// InsertAuthUsers 批量新增授权用户角色
+func (r *ServiceSysRole) InsertAuthUsers(roleId string, userIds []string) int64 {
+ if roleId == "" || len(userIds) <= 0 {
+ return 0
+ }
+
+ sysUserRoles := []sysuserrole.SysUserRole{}
+ for _, userId := range userIds {
+ if userId == "" {
+ continue
+ }
+ sysUserRoles = append(sysUserRoles, sysuserrole.NewSysUserRole(userId, roleId))
+ }
+
+ return r.sysUserRoleRepository.BatchUserRole(sysUserRoles)
+}
diff --git a/features/sys_role_menu/model_sys_role_menu.go b/features/sys_role_menu/model_sys_role_menu.go
new file mode 100644
index 0000000..755e66d
--- /dev/null
+++ b/features/sys_role_menu/model_sys_role_menu.go
@@ -0,0 +1,15 @@
+package sysrolemenu
+
+// SysRoleMenu 角色和菜单关联对象 sys_role_menu
+type SysRoleMenu struct {
+ RoleID string `json:"roleId"` // 角色ID
+ MenuID string `json:"menuId"` // 菜单ID
+}
+
+// NewSysRoleMenu 创建角色和菜单关联对象的构造函数
+func NewSysRoleMenu(roleID string, menuID string) SysRoleMenu {
+ return SysRoleMenu{
+ RoleID: roleID,
+ MenuID: menuID,
+ }
+}
diff --git a/features/sys_role_menu/repo_sys_role_menu.go b/features/sys_role_menu/repo_sys_role_menu.go
new file mode 100644
index 0000000..1b143bc
--- /dev/null
+++ b/features/sys_role_menu/repo_sys_role_menu.go
@@ -0,0 +1,86 @@
+package sysrolemenu
+
+import (
+ "fmt"
+ "strings"
+
+ "nms_nbi/lib/core/datasource"
+ "nms_nbi/lib/core/utils/parse"
+ "nms_nbi/lib/log"
+)
+
+// 实例化数据层 RepoSysRoleMenu 结构体
+var NewRepoSysRoleMenu = &RepoSysRoleMenu{}
+
+// RepoSysRoleMenu 角色与菜单关联表 数据层处理
+type RepoSysRoleMenu struct{}
+
+// CheckMenuExistRole 查询菜单分配给角色使用数量
+func (r *RepoSysRoleMenu) CheckMenuExistRole(menuId string) int64 {
+ querySql := "select count(1) as 'total' from sys_role_menu where menu_id = ?"
+ results, err := datasource.RawDB("", querySql, []any{menuId})
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ return 0
+ }
+ if len(results) > 0 {
+ return parse.Number(results[0]["total"])
+ }
+ return 0
+}
+
+// DeleteRoleMenu 批量删除角色和菜单关联
+func (r *RepoSysRoleMenu) DeleteRoleMenu(roleIds []string) int64 {
+ placeholder := datasource.KeyPlaceholderByQuery(len(roleIds))
+ sql := "delete from sys_role_menu where role_id in (" + placeholder + ")"
+ parameters := datasource.ConvertIdsSlice(roleIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ log.Errorf("delete err => %v", err)
+ return 0
+ }
+ affected, err := results.RowsAffected()
+ if err != nil {
+ log.Errorf("delete err => %v", err)
+ return 0
+ }
+ return affected
+}
+
+// DeleteMenuRole 批量删除菜单和角色关联
+func (r *RepoSysRoleMenu) DeleteMenuRole(menuIds []string) int64 {
+ placeholder := datasource.KeyPlaceholderByQuery(len(menuIds))
+ sql := "delete from sys_role_menu where menu_id in (" + placeholder + ")"
+ parameters := datasource.ConvertIdsSlice(menuIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ log.Errorf("delete err => %v", err)
+ return 0
+ }
+ affected, err := results.RowsAffected()
+ if err != nil {
+ log.Errorf("delete err => %v", err)
+ return 0
+ }
+ return affected
+}
+
+// BatchRoleMenu 批量新增角色菜单信息
+func (r *RepoSysRoleMenu) BatchRoleMenu(sysRoleMenus []SysRoleMenu) int64 {
+ keyValues := make([]string, 0)
+ for _, item := range sysRoleMenus {
+ keyValues = append(keyValues, fmt.Sprintf("(%s,%s)", item.RoleID, item.MenuID))
+ }
+ sql := "insert into sys_role_menu(role_id, menu_id) values " + strings.Join(keyValues, ",")
+ results, err := datasource.ExecDB("", sql, nil)
+ if err != nil {
+ log.Errorf("insert err => %v", err)
+ return 0
+ }
+ insertId, err := results.LastInsertId()
+ if err != nil {
+ log.Errorf("insert row : %v", err.Error())
+ return 0
+ }
+ return insertId
+}
diff --git a/features/sys_user/api_sys_user.go b/features/sys_user/api_sys_user.go
new file mode 100644
index 0000000..2c5f554
--- /dev/null
+++ b/features/sys_user/api_sys_user.go
@@ -0,0 +1,323 @@
+package sysuser
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+
+ sysRoleModel "nms_nbi/features/sys_role/model"
+ sysRoleService "nms_nbi/features/sys_role/service"
+ sysUserModel "nms_nbi/features/sys_user/model"
+ "nms_nbi/features/sys_user/service"
+ "nms_nbi/lib/core/utils/ctx"
+ "nms_nbi/lib/core/utils/parse"
+ "nms_nbi/lib/core/vo/result"
+ "nms_nbi/lib/midware"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+ srcConfig "nms_nbi/src/framework/config"
+)
+
+// 用户接口添加到路由
+func Routers() []services.RouterItem {
+ // 实例化控制层 SysUserApi 结构体
+ var apis = &SysUserApi{
+ sysUserService: service.NewServiceSysUser,
+ sysRoleService: sysRoleService.NewServiceSysRole,
+ }
+
+ rs := [...]services.RouterItem{
+ {
+ Method: "GET",
+ Pattern: "/users",
+ Handler: apis.List,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "GET",
+ Pattern: "/user/{userId}",
+ Handler: apis.Info,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "POST",
+ Pattern: "/user",
+ Handler: apis.Add,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "PUT",
+ Pattern: "/user",
+ Handler: apis.Edit,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "DELETE",
+ Pattern: "/user/{userIds}",
+ Handler: apis.Remove,
+ Middleware: midware.Authorize(nil),
+ },
+ // 添加更多的 Router 对象...
+ }
+
+ // 生成两组前缀路由
+ rsPrefix := []services.RouterItem{}
+ for _, v := range rs {
+ path := "/userManage/{apiVersion}" + v.Pattern
+ // 固定前缀
+ v.Pattern = config.DefaultUriPrefix + path
+ rsPrefix = append(rsPrefix, v)
+ // 可配置
+ v.Pattern = config.UriPrefix + path
+ rsPrefix = append(rsPrefix, v)
+ }
+ return rsPrefix
+}
+
+// // 实例化控制层 SysUserApi 结构体
+// var NewSysUser = &SysUserApi{
+// sysUserService: service.NewSysUserImpl,
+// sysRoleService: service.NewSysRoleImpl,
+// sysPostService: service.NewSysPostImpl,
+// sysDictDataService: service.NewSysDictDataImpl,
+// }
+
+// 用户信息
+//
+// PATH /system/user
+type SysUserApi struct {
+ // 用户服务
+ sysUserService *service.ServiceSysUser
+ // 角色服务
+ sysRoleService *sysRoleService.ServiceSysRole
+}
+
+// 用户信息列表
+//
+// GET /list
+func (s *SysUserApi) List(w http.ResponseWriter, r *http.Request) {
+ querys := ctx.QueryMap(r)
+ data := s.sysUserService.SelectUserPage(querys)
+ ctx.JSON(w, 200, result.Ok(data))
+}
+
+// 用户信息详情
+//
+// GET /:userId
+func (s *SysUserApi) Info(w http.ResponseWriter, r *http.Request) {
+ userId := ctx.Param(r, "userId")
+ if userId == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+ // 查询系统角色列表
+ roles := s.sysRoleService.SelectRoleList(sysRoleModel.SysRole{})
+
+ // 不是系统指定管理员需要排除其角色
+ if !srcConfig.IsAdmin(userId) {
+ rolesFilter := make([]sysRoleModel.SysRole, 0)
+ for _, r := range roles {
+ if r.RoleID != "1" {
+ rolesFilter = append(rolesFilter, r)
+ }
+ }
+ roles = rolesFilter
+ }
+
+ // 新增用户时,用户ID为0
+ if userId == "0" {
+ ctx.JSON(w, 200, result.OkData(map[string]any{
+ "user": map[string]any{},
+ "roleIds": []string{},
+ "roles": roles,
+ }))
+ return
+ }
+
+ // 检查用户是否存在
+ user := s.sysUserService.SelectUserById(userId)
+ if user.Id != userId {
+ ctx.JSON(w, 200, result.ErrMsg("No permission to access user data!"))
+ return
+ }
+
+ // 角色ID组
+ roleIds := make([]string, 0)
+ for _, r := range user.Roles {
+ roleIds = append(roleIds, r.RoleID)
+ }
+
+ ctx.JSON(w, 200, result.OkData(map[string]any{
+ "user": user,
+ "roleIds": roleIds,
+ "roles": roles,
+ }))
+}
+
+// 用户信息新增
+//
+// POST /
+func (s *SysUserApi) Add(w http.ResponseWriter, r *http.Request) {
+ var body sysUserModel.SysUser
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil || body.Id != "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ // 检查用户登录账号是否唯一
+ uniqueUserName := s.sysUserService.CheckUniqueUserName(body.AccountId, "")
+ if !uniqueUserName {
+ msg := fmt.Sprintf("[%s] Login account already exists", body.AccountId)
+ ctx.JSON(w, 200, result.ErrMsg(msg))
+ return
+ }
+
+ insertId := s.sysUserService.InsertUser(body)
+ if insertId != "" {
+ ctx.JSON(w, 200, result.Ok(nil))
+ return
+ }
+ ctx.JSON(w, 200, result.Err(nil))
+}
+
+// 用户信息修改
+//
+// POST /
+func (s *SysUserApi) Edit(w http.ResponseWriter, r *http.Request) {
+ var body sysUserModel.SysUser
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil || body.Id == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ // 检查是否管理员用户
+ // if srcConfig.IsAdmin(body.Id) {
+ // ctx.JSON(w, 200, result.ErrMsg("不允许操作管理员用户"))
+ // return
+ // }
+
+ user := s.sysUserService.SelectUserById(body.Id)
+ if user.Id != body.Id {
+ ctx.JSON(w, 200, result.ErrMsg("No permission to access user data!"))
+ return
+ }
+
+ // 检查用户登录账号是否唯一
+ uniqueUserName := s.sysUserService.CheckUniqueUserName(body.AccountId, body.Id)
+ if !uniqueUserName {
+ msg := fmt.Sprintf("[%s] Login account already exists", body.AccountId)
+ ctx.JSON(w, 200, result.ErrMsg(msg))
+ return
+ }
+
+ body.AccountId = "" // 忽略修改登录用户名称
+ // body.Password = "" // 忽略修改密码
+ rows := s.sysUserService.UpdateUserAndRolePost(body)
+ if rows > 0 {
+ ctx.JSON(w, 200, result.Ok(nil))
+ return
+ }
+ ctx.JSON(w, 200, result.Err(nil))
+}
+
+// 用户信息删除
+//
+// DELETE /:userIds
+func (s *SysUserApi) Remove(w http.ResponseWriter, r *http.Request) {
+ userIds := ctx.Param(r, "userIds")
+ if userIds == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+ // 处理字符转id数组后去重
+ ids := strings.Split(userIds, ",")
+ uniqueIDs := parse.RemoveDuplicates(ids)
+ if len(uniqueIDs) <= 0 {
+ ctx.JSON(w, 200, result.Err(nil))
+ return
+ }
+ rows, err := s.sysUserService.DeleteUserByIds(uniqueIDs)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+ msg := fmt.Sprintf("Successfully deleted: %d", rows)
+ ctx.JSON(w, 200, result.OkMsg(msg))
+}
+
+// 用户重置密码
+//
+// PUT /resetPwd
+func (s *SysUserApi) ResetPwd(w http.ResponseWriter, r *http.Request) {
+ var body struct {
+ UserID string `json:"userId" binding:"required"`
+ Password string `json:"password" binding:"required"`
+ }
+ if err := ctx.ShouldBindJSON(r, &body); err != nil {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ // 检查是否管理员用户
+ if srcConfig.IsAdmin(body.UserID) {
+ ctx.JSON(w, 200, result.ErrMsg("No permission to access user data!"))
+ return
+ }
+
+ user := s.sysUserService.SelectUserById(body.UserID)
+ if user.Id != body.UserID {
+ ctx.JSON(w, 200, result.ErrMsg("No permission to access user data!"))
+ return
+ }
+
+ SysUserApi := sysUserModel.SysUser{
+ Id: body.UserID,
+ Password: body.Password,
+ }
+ rows := s.sysUserService.UpdateUser(SysUserApi)
+ if rows > 0 {
+ ctx.JSON(w, 200, result.Ok(nil))
+ return
+ }
+ ctx.JSON(w, 200, result.Err(nil))
+}
+
+// 用户状态修改
+//
+// PUT /changeStatus
+func (s *SysUserApi) Status(w http.ResponseWriter, r *http.Request) {
+ var body struct {
+ UserID string `json:"userId" binding:"required"`
+ Status string `json:"status" binding:"required"`
+ }
+ if err := ctx.ShouldBindJSON(r, &body); err != nil {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ // 检查是否存在
+ user := s.sysUserService.SelectUserById(body.UserID)
+ if user.Id != body.UserID {
+ ctx.JSON(w, 200, result.ErrMsg("No permission to access user data!"))
+ return
+ }
+
+ // 与旧值相等不变更
+ if user.Status == body.Status {
+ ctx.JSON(w, 200, result.ErrMsg("Change status equals old value!"))
+ return
+ }
+
+ SysUserApi := sysUserModel.SysUser{
+ Id: body.UserID,
+ Status: body.Status,
+ }
+ rows := s.sysUserService.UpdateUser(SysUserApi)
+ if rows > 0 {
+ ctx.JSON(w, 200, result.Ok(nil))
+ return
+ }
+ ctx.JSON(w, 200, result.Err(nil))
+}
diff --git a/features/sys_user/model/sys_user.go b/features/sys_user/model/sys_user.go
new file mode 100644
index 0000000..286f223
--- /dev/null
+++ b/features/sys_user/model/sys_user.go
@@ -0,0 +1,42 @@
+package model
+
+import "nms_nbi/features/sys_role/model"
+
+type SysUser struct {
+ Id string `json:"id" xorm:"pk 'id' autoincr"`
+ AccountId string `json:"accountId" xorm:"account_id"`
+ Name string `json:"name" xorm:"name"`
+ Sn string `json:"sn"`
+ Gender string `json:"gender"`
+ Description string `json:"description"`
+ TelephoneNumber string `json:"telephoneNumber" xorm:"telephone_number"`
+ Mobile string `json:"mobile"`
+ Email string `json:"email" xorm:"email"`
+ StartTime string `json:"startTime" xorm:"start_time"`
+ EndTime string `json:"endTime" xorm:"end_time"`
+ IdCardNumber string `json:"idCardNumber"`
+ EmployeeNumber string `json:"employeeNumber"`
+ Organize string `json:"organize"`
+ EmployeeType string `json:"employeeType"`
+ SupporterCorpName string `json:"supporterCorpName"`
+ RealName string `json:"realName" xorm:"real_name"`
+ Password string `json:"password" xorm:"-"`
+ PasswordSha512 string `json:"-" xorm:"-"`
+ ChangePasswordFlag int `json:"changePasswordFlag"`
+ PasswordExpiration string `json:"passwordExpiration" xorm:"password_expiration"`
+ Status string `json:"status"`
+ UserExpiration string `json:"userExpiration" xorm:"user_expiration"`
+ GroupName string `json:"groupName" xorm:"group_name"`
+ Profile string `json:"-" xorm:"profile"`
+ Phone string `json:"phone" xorm:"phone"`
+ CreateTime string `json:"createTime" xorm:"create_time"`
+ UpdateTime string `json:"updateTime" xorm:"update_time"`
+ Unit string `json:"unit" xorm:"unit"`
+
+ // 角色对象组
+ Roles []model.SysRole `json:"roles"`
+ // 角色ID - 参数提交绑定
+ RoleID string `json:"roleId,omitempty"`
+ // 角色组 - 参数提交绑定
+ RoleIDs []string `json:"roleIds,omitempty"`
+}
diff --git a/features/sys_user/service/repo_sys_user.go b/features/sys_user/service/repo_sys_user.go
new file mode 100644
index 0000000..0acbee6
--- /dev/null
+++ b/features/sys_user/service/repo_sys_user.go
@@ -0,0 +1,578 @@
+package service
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ sysRoleModel "nms_nbi/features/sys_role/model"
+ sysUserModel "nms_nbi/features/sys_user/model"
+ "nms_nbi/lib/core/datasource"
+ "nms_nbi/lib/core/utils/date"
+ "nms_nbi/lib/core/utils/parse"
+ "nms_nbi/lib/log"
+ "nms_nbi/src/framework/utils/crypto"
+)
+
+// 实例化数据层 RepoSysUser 结构体
+var NewRepoSysUser = &RepoSysUser{
+ selectSql: `select u.id,
+ u.account_id, u.name, u.sn, u.gender, u.description, u.telephone_number, u.mobile, u.email,
+ u.start_time, u.end_time, u.id_card_number, u.employee_number,
+ u.organize, u.employee_type, u.supporter_corp_name, u.real_name, u.password, u.password_sha512,
+ u.change_password_flag,u.password_expiration, u.status, u.user_expiration, u.group_name,
+ u.profile, u.phone, u.create_time, u.update_time, u.unit,
+ r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status
+ from user u
+ left join sys_user_role ur on u.id = ur.user_id
+ left join sys_role r on r.role_id = ur.role_id`,
+
+ sysUserMap: map[string]string{
+ "id": "Id",
+ "account_id": "AccountId",
+ "name": "Name",
+ "sn": "Sn",
+ "gender": "Gender",
+ "description": "Description",
+ "telephone_number": "TelephoneNumber",
+ "mobile": "Mobile",
+ "email": "Email",
+ "start_time": "StartTime",
+ "end_time": "EndTime",
+ "id_card_number": "IdCardNumber",
+ "employee_number": "EmployeeNumber",
+ "organize": "Organize",
+ "employee_type": "EmployeeType",
+ "supporter_corp_name": "SupporterCorpName",
+ "real_name": "RealName",
+ "password": "Password",
+ "password_sha512": "PasswordSha512",
+ "change_password_flag": "ChangePasswordFlag",
+ "password_expiration": "PasswordExpiration",
+ "status": "Status",
+ "user_expiration": "UserExpiration",
+ "group_name": "GroupName",
+ "profile": "Profile",
+ "phone": "Phone",
+ "create_time": "CreateTime",
+ "update_time": "UpdateTime",
+ "unit": "Unit",
+ },
+
+ sysRoleMap: map[string]string{
+ "role_id": "RoleID",
+ "role_name": "RoleName",
+ "role_key": "RoleKey",
+ "role_sort": "RoleSort",
+ "data_scope": "DataScope",
+ "role_status": "Status",
+ },
+}
+
+// RepoSysUser 用户表 数据层处理
+type RepoSysUser struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 用户信息实体映射
+ sysUserMap map[string]string
+ // 用户角色实体映射 一对多
+ sysRoleMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *RepoSysUser) convertResultRows(rows []map[string]any) []sysUserModel.SysUser {
+ arr := make([]sysUserModel.SysUser, 0)
+
+ for _, row := range rows {
+ sysUser := sysUserModel.SysUser{}
+ sysRole := sysRoleModel.SysRole{}
+ sysUser.Roles = []sysRoleModel.SysRole{}
+
+ for key, value := range row {
+ if keyMapper, ok := r.sysUserMap[key]; ok {
+ datasource.SetFieldValue(&sysUser, keyMapper, value)
+ }
+ if keyMapper, ok := r.sysRoleMap[key]; ok {
+ datasource.SetFieldValue(&sysRole, keyMapper, value)
+ }
+ }
+
+ if sysRole.RoleKey != "" {
+ sysUser.Roles = append(sysUser.Roles, sysRole)
+ }
+
+ one := true
+ for i, a := range arr {
+ if a.Id == sysUser.Id {
+ arrUser := &arr[i]
+ arrUser.Roles = append(arrUser.Roles, sysUser.Roles...)
+ one = false
+ break
+ }
+ }
+ if one {
+ arr = append(arr, sysUser)
+ }
+ }
+
+ return arr
+}
+
+// SelectUserPage 根据条件分页查询用户列表
+func (r *RepoSysUser) SelectUserPage(query map[string]any) map[string]any {
+ selectUserSql := `select u.id,
+ u.account_id, u.name, u.sn, u.gender, u.description, u.telephone_number, u.mobile, u.email,
+ u.start_time, u.end_time, u.id_card_number, u.employee_number,
+ u.organize, u.employee_type, u.supporter_corp_name, u.real_name,
+ u.change_password_flag,u.password_expiration, u.status, u.user_expiration, u.group_name,
+ u.profile, u.phone, u.create_time, u.update_time, u.unit
+ from user u`
+ selectUserTotalSql := `select count(distinct u.id) as 'total' from user u`
+
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if v, ok := query["accountId"]; ok && v != "" {
+ conditions = append(conditions, "u.account_id = ?")
+ params = append(params, v)
+ }
+ if v, ok := query["name"]; ok && v != "" {
+ conditions = append(conditions, "u.name concat('%', concat(?, '%'))")
+ params = append(params, v)
+ }
+ if v, ok := query["status"]; ok && v != "" {
+ conditions = append(conditions, "u.status = ?")
+ params = append(params, v)
+ }
+ if v, ok := query["phonenumber"]; ok && v != "" {
+ conditions = append(conditions, "u.phonenumber like concat('%', concat(?, '%'))")
+ params = append(params, v)
+ }
+ beginTime, ok := query["beginTime"]
+ if !ok {
+ beginTime, ok = query["params[beginTime]"]
+ }
+ if ok && beginTime != "" {
+ conditions = append(conditions, "u.login_date >= ?")
+ beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD)
+ params = append(params, beginDate.UnixMilli())
+ }
+ endTime, ok := query["endTime"]
+ if !ok {
+ endTime, ok = query["params[endTime]"]
+ }
+ if ok && endTime != "" {
+ conditions = append(conditions, "u.login_date <= ?")
+ endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD)
+ params = append(params, endDate.UnixMilli())
+ }
+ if v, ok := query["deptId"]; ok && v != "" {
+ conditions = append(conditions, "(u.dept_id = ? or u.dept_id in ( select t.dept_id from sys_dept t where find_in_set(?, ancestors) ))")
+ params = append(params, v)
+ params = append(params, v)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := selectUserTotalSql + whereSql
+ totalRows, err := datasource.RawDB("", totalSql, params)
+ if err != nil {
+ log.Errorf("total err => %v", err)
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return map[string]any{
+ "total": total,
+ "rows": []sysUserModel.SysUser{},
+ }
+ }
+
+ // 分页
+ pageNum, pageSize := datasource.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 查询数据
+ querySql := selectUserSql + whereSql + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ }
+
+ // 转换实体
+ rows := r.convertResultRows(results)
+ return map[string]any{
+ "total": total,
+ "rows": rows,
+ }
+}
+
+// SelectAllocatedPage 根据条件分页查询分配用户角色列表
+func (r *RepoSysUser) SelectAllocatedPage(query map[string]any) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if v, ok := query["name"]; ok && v != "" {
+ conditions = append(conditions, "u.name like concat('%', concat(?, '%'))")
+ params = append(params, v)
+ }
+ if v, ok := query["phone"]; ok && v != "" {
+ conditions = append(conditions, "u.phone like concat('%', concat(?, '%'))")
+ params = append(params, v)
+ }
+ if v, ok := query["status"]; ok && v != "" {
+ conditions = append(conditions, "u.status = ?")
+ params = append(params, v)
+ }
+ // 分配角色用户
+ if allocated, ok := query["allocated"]; ok && allocated != "" {
+ if roleId, ok := query["roleId"]; ok && roleId != "" {
+ if parse.Boolean(allocated) {
+ conditions = append(conditions, "r.role_id = ?")
+ params = append(params, roleId)
+ } else {
+ conditions = append(conditions, `(r.role_id != ? or r.role_id IS NULL)
+ and u.id not in (
+ select u.id from user u
+ inner join sys_user_role ur on u.id = ur.user_id
+ and ur.role_id = ?
+ )`)
+ params = append(params, roleId)
+ params = append(params, roleId)
+ }
+
+ }
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := `select count(distinct u.id) as 'total' from user u
+ left join sys_user_role ur on u.id = ur.user_id
+ left join sys_role r on r.role_id = ur.role_id`
+ totalRows, err := datasource.RawDB("", totalSql+whereSql, params)
+ if err != nil {
+ log.Errorf("total err => %v", err)
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return map[string]any{
+ "total": total,
+ "rows": []sysUserModel.SysUser{},
+ }
+ }
+
+ // 分页
+ pageNum, pageSize := datasource.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 查询数据
+ querySql := `select distinct
+ u.id, u.account_id, u.name, u.gender, u.email,
+ u.phone, u.status, u.create_time, u.real_name
+ from user u
+ left join sys_user_role ur on u.id = ur.user_id
+ left join sys_role r on r.role_id = ur.role_id`
+ querySql = querySql + whereSql + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ }
+
+ // 转换实体
+ rows := r.convertResultRows(results)
+ return map[string]any{
+ "total": total,
+ "rows": rows,
+ }
+}
+
+// SelectUserList 根据条件查询用户列表
+func (r *RepoSysUser) SelectUserList(sysUser sysUserModel.SysUser, dataScopeSQL string) []sysUserModel.SysUser {
+ selectUserSql := `select
+ u.id, u.account_id, u.name, u.real_name, u.email, u.gender, u.phone, u.create_time, u.status, u.description
+ from user u`
+
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysUser.AccountId != "" {
+ conditions = append(conditions, "u.account_id = ?")
+ params = append(params, sysUser.AccountId)
+ }
+ if sysUser.Name != "" {
+ conditions = append(conditions, "u.name like concat('%', concat(?, '%'))")
+ params = append(params, sysUser.Name)
+ }
+ if sysUser.Status != "" {
+ conditions = append(conditions, "u.status = ?")
+ params = append(params, sysUser.Status)
+ }
+ if sysUser.Phone != "" {
+ conditions = append(conditions, "u.phone like concat('%', concat(?, '%'))")
+ params = append(params, sysUser.Phone)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ querySql := selectUserSql + whereSql + dataScopeSQL
+ rows, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ return []sysUserModel.SysUser{}
+ }
+ return r.convertResultRows(rows)
+}
+
+// SelectUserByIds 通过用户ID查询用户
+func (r *RepoSysUser) SelectUserByIds(userIds []string) []sysUserModel.SysUser {
+ placeholder := datasource.KeyPlaceholderByQuery(len(userIds))
+ querySql := r.selectSql + " where u.id in (" + placeholder + ")"
+ parameters := datasource.ConvertIdsSlice(userIds)
+ results, err := datasource.RawDB("", querySql, parameters)
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ return []sysUserModel.SysUser{}
+ }
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// SelectUserByUserName 通过用户登录账号查询用户
+func (r *RepoSysUser) SelectUserByUserName(userName string) sysUserModel.SysUser {
+ querySql := r.selectSql + " where u.name = ?"
+ results, err := datasource.RawDB("", querySql, []any{userName})
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ return sysUserModel.SysUser{}
+ }
+ // 转换实体
+ rows := r.convertResultRows(results)
+ if len(rows) > 0 {
+ return rows[0]
+ }
+ return sysUserModel.SysUser{}
+}
+
+// InsertUser 新增用户信息
+func (r *RepoSysUser) InsertUser(sysUser sysUserModel.SysUser) string {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysUser.AccountId != "" {
+ params["account_id"] = sysUser.AccountId
+ }
+ if sysUser.Name != "" {
+ params["name"] = sysUser.Name
+ }
+ if sysUser.Sn != "" {
+ params["sn"] = sysUser.Sn
+ } else {
+ params["sn"] = ""
+ }
+ if sysUser.RealName != "" {
+ params["real_name"] = sysUser.RealName
+ } else {
+ params["real_name"] = ""
+ }
+ if sysUser.Gender != "" {
+ params["gender"] = sysUser.Gender
+ }
+ if sysUser.Email != "" {
+ params["email"] = sysUser.Email
+ } else {
+ params["email"] = ""
+ }
+ if sysUser.Phone != "" {
+ params["phone"] = sysUser.Phone
+ } else {
+ params["phone"] = ""
+ }
+ if sysUser.Unit != "" {
+ params["unit"] = sysUser.Unit
+ } else {
+ params["unit"] = ""
+ }
+
+ if sysUser.Organize != "" {
+ params["organize"] = sysUser.Organize
+ }
+ if sysUser.Password != "" {
+ password := crypto.BcryptHash(sysUser.Password)
+ params["password"] = password
+ }
+ if sysUser.Status != "" {
+ params["status"] = sysUser.Status
+ }
+ if sysUser.PasswordExpiration != "" {
+ params["password_expiration"] = sysUser.PasswordExpiration
+ }
+ if sysUser.UserExpiration != "" {
+ params["user_expiration"] = sysUser.UserExpiration
+ }
+ if sysUser.GroupName != "" {
+ params["group_name"] = sysUser.GroupName
+ }
+ params["create_time"] = time.Now()
+
+ // 构建执行语句
+ keys, placeholder, values := datasource.KeyPlaceholderValueByInsert(params)
+ sql := "insert into user (" + strings.Join(keys, ",") + ")values(" + placeholder + ")"
+
+ // 执行插入
+ results, err := datasource.ExecDB("", sql, values)
+ if err != nil {
+ log.Errorf("insert row : %v", err.Error())
+ return ""
+ }
+ insertId, err := results.LastInsertId()
+ if err != nil {
+ log.Errorf("insert row : %v", err.Error())
+ return ""
+ }
+ return fmt.Sprint(insertId)
+}
+
+// UpdateUser 修改用户信息
+func (r *RepoSysUser) UpdateUser(sysUser sysUserModel.SysUser) int64 {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysUser.Name != "" {
+ params["name"] = sysUser.Name
+ }
+ if sysUser.Sn != "" {
+ params["sn"] = sysUser.Sn
+ } else {
+ params["sn"] = ""
+ }
+ if sysUser.RealName != "" {
+ params["real_name"] = sysUser.RealName
+ } else {
+ params["real_name"] = ""
+ }
+ if sysUser.Gender != "" {
+ params["gender"] = sysUser.Gender
+ }
+ if sysUser.Email != "" {
+ params["email"] = sysUser.Email
+ } else {
+ params["email"] = ""
+ }
+ if sysUser.Phone != "" {
+ params["phone"] = sysUser.Phone
+ } else {
+ params["phone"] = ""
+ }
+ if sysUser.Unit != "" {
+ params["unit"] = sysUser.Unit
+ } else {
+ params["unit"] = ""
+ }
+
+ if sysUser.Organize != "" {
+ params["organize"] = sysUser.Organize
+ }
+ if sysUser.Password != "" {
+ password := crypto.BcryptHash(sysUser.Password)
+ params["password"] = password
+ }
+ if sysUser.Status != "" {
+ params["status"] = sysUser.Status
+ }
+ if sysUser.PasswordExpiration != "" {
+ params["password_expiration"] = sysUser.PasswordExpiration
+ }
+ if sysUser.UserExpiration != "" {
+ params["user_expiration"] = sysUser.UserExpiration
+ }
+ if sysUser.GroupName != "" {
+ params["group_name"] = sysUser.GroupName
+ }
+ params["update_time"] = time.Now()
+
+ // 构建执行语句
+ keys, values := datasource.KeyValueByUpdate(params)
+ sql := "update user set " + strings.Join(keys, ",") + " where id = ?"
+
+ // 执行更新
+ values = append(values, sysUser.Id)
+ results, err := datasource.ExecDB("", sql, values)
+ if err != nil {
+ log.Errorf("update row : %v", err.Error())
+ return 0
+ }
+ affected, err := results.RowsAffected()
+ if err != nil {
+ log.Errorf("update err => %v", err)
+ return 0
+ }
+ return affected
+}
+
+// DeleteUserByIds 批量删除用户信息
+func (r *RepoSysUser) DeleteUserByIds(userIds []string) int64 {
+ placeholder := datasource.KeyPlaceholderByQuery(len(userIds))
+ sql := "delete from user where id in (" + placeholder + ")"
+ parameters := datasource.ConvertIdsSlice(userIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ log.Errorf("delete err => %v", err)
+ return 0
+ }
+ affected, err := results.RowsAffected()
+ if err != nil {
+ log.Errorf("delete err => %v", err)
+ return 0
+ }
+ return affected
+}
+
+// CheckUniqueUser 校验用户信息是否唯一
+func (r *RepoSysUser) CheckUniqueUser(sysUser sysUserModel.SysUser) string {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysUser.Name != "" {
+ conditions = append(conditions, "name = ?")
+ params = append(params, sysUser.Name)
+ }
+ if sysUser.AccountId != "" {
+ conditions = append(conditions, "account_id = ?")
+ params = append(params, sysUser.AccountId)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ } else {
+ return ""
+ }
+
+ // 查询数据
+ querySql := "select id as 'str' from user " + whereSql + " limit 1"
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ log.Errorf("query err %v", err)
+ }
+ if len(results) > 0 {
+ return fmt.Sprint(results[0]["str"])
+ }
+ return ""
+}
diff --git a/features/sys_user/service/service_sys_user.go b/features/sys_user/service/service_sys_user.go
new file mode 100644
index 0000000..725239f
--- /dev/null
+++ b/features/sys_user/service/service_sys_user.go
@@ -0,0 +1,150 @@
+package service
+
+import (
+ "errors"
+ "fmt"
+
+ sysUserModel "nms_nbi/features/sys_user/model"
+ sysuserrole "nms_nbi/features/sys_user_role"
+)
+
+// 实例化服务层 ServiceSysUser 结构体
+var NewServiceSysUser = &ServiceSysUser{
+ sysUserRepository: NewRepoSysUser,
+ sysUserRoleRepository: sysuserrole.NewRepoSysUserRole,
+}
+
+// ServiceSysUser 用户 服务层处理
+type ServiceSysUser struct {
+ // 用户服务
+ sysUserRepository *RepoSysUser
+ // 用户与角色服务
+ sysUserRoleRepository *sysuserrole.RepoSysUserRole
+}
+
+// SelectUserPage 根据条件分页查询用户列表
+func (r *ServiceSysUser) SelectUserPage(query map[string]any) map[string]any {
+ return r.sysUserRepository.SelectUserPage(query)
+}
+
+// SelectUserList 根据条件查询用户列表
+func (r *ServiceSysUser) SelectUserList(sysUser sysUserModel.SysUser) []sysUserModel.SysUser {
+ return []sysUserModel.SysUser{}
+}
+
+// SelectAllocatedPage 根据条件分页查询分配用户角色列表
+func (r *ServiceSysUser) SelectAllocatedPage(query map[string]any) map[string]any {
+ return r.sysUserRepository.SelectAllocatedPage(query)
+}
+
+// SelectUserByUserName 通过用户名查询用户
+func (r *ServiceSysUser) SelectUserByUserName(userName string) sysUserModel.SysUser {
+ return r.sysUserRepository.SelectUserByUserName(userName)
+}
+
+// SelectUserById 通过用户ID查询用户
+func (r *ServiceSysUser) SelectUserById(userId string) sysUserModel.SysUser {
+ if userId == "" {
+ return sysUserModel.SysUser{}
+ }
+ users := r.sysUserRepository.SelectUserByIds([]string{userId})
+ if len(users) > 0 {
+ return users[0]
+ }
+ return sysUserModel.SysUser{}
+}
+
+// InsertUser 新增用户信息
+func (r *ServiceSysUser) InsertUser(sysUser sysUserModel.SysUser) string {
+ // 新增用户信息
+ insertId := r.sysUserRepository.InsertUser(sysUser)
+ if insertId != "" {
+ // 新增用户角色信息
+ r.insertUserRole(insertId, sysUser.RoleIDs)
+ }
+ return insertId
+}
+
+// insertUserRole 新增用户角色信息
+func (r *ServiceSysUser) insertUserRole(userId string, roleIds []string) int64 {
+ if userId == "" || len(roleIds) <= 0 {
+ return 0
+ }
+
+ sysUserRoles := []sysuserrole.SysUserRole{}
+ for _, roleId := range roleIds {
+ // 管理员角色禁止操作,只能通过配置指定用户ID分配
+ if roleId == "" || roleId == "1" {
+ continue
+ }
+ sysUserRoles = append(sysUserRoles, sysuserrole.NewSysUserRole(userId, roleId))
+ }
+
+ return r.sysUserRoleRepository.BatchUserRole(sysUserRoles)
+}
+
+// UpdateUser 修改用户信息
+func (r *ServiceSysUser) UpdateUser(sysUser sysUserModel.SysUser) int64 {
+ return r.sysUserRepository.UpdateUser(sysUser)
+}
+
+// UpdateUserAndRolePost 修改用户信息同时更新角色和岗位
+func (r *ServiceSysUser) UpdateUserAndRolePost(sysUser sysUserModel.SysUser) int64 {
+ userId := fmt.Sprint(sysUser.Id)
+ // 删除用户与角色关联
+ r.sysUserRoleRepository.DeleteUserRole([]string{userId})
+ // 新增用户角色信息
+ r.insertUserRole(userId, sysUser.RoleIDs)
+ return r.sysUserRepository.UpdateUser(sysUser)
+}
+
+// DeleteUserByIds 批量删除用户信息
+func (r *ServiceSysUser) DeleteUserByIds(userIds []string) (int64, error) {
+ // 检查是否存在
+ users := r.sysUserRepository.SelectUserByIds(userIds)
+ if len(users) <= 0 {
+ return 0, errors.New("no permission to access user data")
+ }
+ if len(users) == len(userIds) {
+ // 删除用户与角色关联
+ r.sysUserRoleRepository.DeleteUserRole(userIds)
+ // ... 注意其他userId进行关联的表
+ // 删除用户
+ rows := r.sysUserRepository.DeleteUserByIds(userIds)
+ return rows, nil
+ }
+ return 0, errors.New("failed to delete user information")
+}
+
+// CheckUniqueUserName 校验用户名称是否唯一
+func (r *ServiceSysUser) CheckUniqueUserName(accountId, userId string) bool {
+ uniqueId := r.sysUserRepository.CheckUniqueUser(sysUserModel.SysUser{
+ AccountId: accountId,
+ })
+ if uniqueId == userId {
+ return true
+ }
+ return uniqueId == ""
+}
+
+// CheckUniquePhone 校验手机号码是否唯一
+func (r *ServiceSysUser) CheckUniquePhone(phonenumber, userId string) bool {
+ uniqueId := r.sysUserRepository.CheckUniqueUser(sysUserModel.SysUser{
+ Phone: phonenumber,
+ })
+ if uniqueId == userId {
+ return true
+ }
+ return uniqueId == ""
+}
+
+// CheckUniqueEmail 校验email是否唯一
+func (r *ServiceSysUser) CheckUniqueEmail(email, userId string) bool {
+ uniqueId := r.sysUserRepository.CheckUniqueUser(sysUserModel.SysUser{
+ Email: email,
+ })
+ if uniqueId == userId {
+ return true
+ }
+ return uniqueId == ""
+}
diff --git a/features/sys_user_role/model_sys_user_role.go b/features/sys_user_role/model_sys_user_role.go
new file mode 100644
index 0000000..bc493eb
--- /dev/null
+++ b/features/sys_user_role/model_sys_user_role.go
@@ -0,0 +1,15 @@
+package sysuserrole
+
+// SysUserRole 用户和角色关联对象 sys_user_role
+type SysUserRole struct {
+ UserID string `json:"userId"` // 用户ID
+ RoleID string `json:"roleId"` // 角色ID
+}
+
+// NewSysUserRole 创建用户和角色关联对象的构造函数
+func NewSysUserRole(userID string, roleID string) SysUserRole {
+ return SysUserRole{
+ UserID: userID,
+ RoleID: roleID,
+ }
+}
diff --git a/features/sys_user_role/repo_sys_user_role.go b/features/sys_user_role/repo_sys_user_role.go
new file mode 100644
index 0000000..64d870b
--- /dev/null
+++ b/features/sys_user_role/repo_sys_user_role.go
@@ -0,0 +1,87 @@
+package sysuserrole
+
+import (
+ "fmt"
+ "strings"
+
+ "nms_nbi/lib/core/datasource"
+ "nms_nbi/lib/core/utils/parse"
+ "nms_nbi/lib/log"
+)
+
+// 实例化数据层 RepoSysUserRole 结构体
+var NewRepoSysUserRole = &RepoSysUserRole{}
+
+// RepoSysUserRole 用户与角色关联表 数据层处理
+type RepoSysUserRole struct{}
+
+// CountUserRoleByRoleId 通过角色ID查询角色使用数量
+func (r *RepoSysUserRole) CountUserRoleByRoleId(roleId string) int64 {
+ querySql := "select count(1) as total from sys_user_role where role_id = ?"
+ results, err := datasource.RawDB("", querySql, []any{roleId})
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ return 0
+ }
+ if len(results) > 0 {
+ return parse.Number(results[0]["total"])
+ }
+ return 0
+}
+
+// BatchUserRole 批量新增用户角色信息
+func (r *RepoSysUserRole) BatchUserRole(sysUserRoles []SysUserRole) int64 {
+ keyValues := make([]string, 0)
+ for _, item := range sysUserRoles {
+ keyValues = append(keyValues, fmt.Sprintf("(%s,%s)", item.UserID, item.RoleID))
+ }
+ sql := "insert into sys_user_role(user_id, role_id) values " + strings.Join(keyValues, ",")
+ results, err := datasource.ExecDB("", sql, nil)
+ if err != nil {
+ log.Errorf("delete err => %v", err)
+ return 0
+ }
+ affected, err := results.RowsAffected()
+ if err != nil {
+ log.Errorf("delete err => %v", err)
+ return 0
+ }
+ return affected
+}
+
+// DeleteUserRole 批量删除用户和角色关联
+func (r *RepoSysUserRole) DeleteUserRole(userIds []string) int64 {
+ placeholder := datasource.KeyPlaceholderByQuery(len(userIds))
+ sql := "delete from sys_user_role where user_id in (" + placeholder + ")"
+ parameters := datasource.ConvertIdsSlice(userIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ log.Errorf("delete err => %v", err)
+ return 0
+ }
+ affected, err := results.RowsAffected()
+ if err != nil {
+ log.Errorf("delete err => %v", err)
+ return 0
+ }
+ return affected
+}
+
+// DeleteUserRoleByRoleId 批量取消授权用户角色
+func (r *RepoSysUserRole) DeleteUserRoleByRoleId(roleId string, userIds []string) int64 {
+ placeholder := datasource.KeyPlaceholderByQuery(len(userIds))
+ sql := "delete from sys_user_role where role_id= ? and user_id in (" + placeholder + ")"
+ parameters := datasource.ConvertIdsSlice(userIds)
+ parameters = append([]any{roleId}, parameters...)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ log.Errorf("delete err => %v", err)
+ return 0
+ }
+ affected, err := results.RowsAffected()
+ if err != nil {
+ log.Errorf("delete err => %v", err)
+ return 0
+ }
+ return affected
+}
diff --git a/features/trace/tcpdump.go b/features/trace/tcpdump.go
new file mode 100644
index 0000000..d7e490b
--- /dev/null
+++ b/features/trace/tcpdump.go
@@ -0,0 +1,339 @@
+package trace
+
+import (
+ "fmt"
+ "net"
+ "net/http"
+ "strings"
+ "time"
+
+ "nms_nbi/lib/core/conf"
+ "nms_nbi/lib/core/utils/ctx"
+ "nms_nbi/lib/core/vo/result"
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/log"
+ "nms_nbi/restagent/config"
+ "nms_nbi/src/framework/utils/cmd"
+ "nms_nbi/src/framework/utils/ssh"
+)
+
+var (
+ UriTcpdumpTask = config.DefaultUriPrefix + "/traceManagement/{apiVersion}/tcpdumpNeTask"
+ CustomUriTcpdumpTask = config.UriPrefix + "/traceManagement/{apiVersion}/tcpdumpNeTask" // decode message api
+
+ UriTcpdumpPcapDownload = config.DefaultUriPrefix + "/traceManagement/{apiVersion}/tcpdumpPcapDownload"
+ CustomUriTcpdumpPcapDownload = config.UriPrefix + "/traceManagement/{apiVersion}/tcpdumpPcapDownload" // decode message api
+
+ UriTcpdumpNeUPFTask = config.DefaultUriPrefix + "/traceManagement/{apiVersion}/tcpdumpNeUPFTask"
+ CustomUriTcpdumpNeUPFTask = config.UriPrefix + "/traceManagement/{apiVersion}/tcpdumpNeUPFTask" // decode message api
+)
+
+// NeInfo 网元信息
+func NeInfo(neType, neId string) (*dborm.NeInfo, error) {
+ neInfo, err := dborm.XormGetNeInfo(neType, neId)
+ if err != nil {
+ log.Error("dborm.XormGetNeInfo is failed:", err)
+ return nil, err
+ }
+ if neInfo == nil || neInfo.Ip == "" {
+ return nil, fmt.Errorf("not ne_info or not IP")
+ }
+ return neInfo, nil
+}
+
+// TcpdumpNeTask 网元发送执行 pcap
+func TcpdumpNeTask(w http.ResponseWriter, r *http.Request) {
+ var body struct {
+ NeType string `json:"neType"` // 网元类型
+ NeId string `json:"neId"` // 网元ID
+ Timeout int `json:"timeout"` // 超时时间
+ Cmd string `json:"cmd"` // 命令
+ }
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil || body.NeType == "" || body.NeId == "" || body.Timeout < 5 || body.Cmd == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ neInfo, err := NeInfo(body.NeType, body.NeId)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ filePcapName := fmt.Sprintf("tmp_%s_%s_%d.pcap", body.NeType, body.NeId, time.Now().UnixMilli())
+ fileLogName := fmt.Sprintf("tmp_%s_%s_%d.log", body.NeType, body.NeId, time.Now().UnixMilli())
+ 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 := conf.Get("ne.user").(string) // 网元统一用户
+ sshHost := fmt.Sprintf("%s@%s", usernameNe, neInfo.Ip)
+ msg, err := cmd.ExecWithCheck("ssh", sshHost, cmdStr+writeLog)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ ctx.JSON(w, 200, result.OkData(map[string]any{
+ "cmd": cmdStr,
+ "msg": msg,
+ "fileName": filePcapName,
+ }))
+}
+
+// TcpdumpPcapDownload 网元抓包pcap文件下载
+func TcpdumpPcapDownload(w http.ResponseWriter, r *http.Request) {
+ var body struct {
+ NeType string `json:"neType"` // 网元类型
+ NeId string `json:"neId"` // 网元ID
+ FileName string `json:"fileName"` // 文件名
+ }
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil || body.NeType == "" || body.NeId == "" || body.FileName == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ neInfo, err := NeInfo(body.NeType, body.NeId)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ nePath := fmt.Sprintf("/tmp/%s", body.FileName)
+ localPath := fmt.Sprintf("%s/tcpdump/pcap/%s", conf.Get("ne.omcdir"), body.FileName)
+ err = ssh.FileSCPNeToLocal(neInfo.Ip, nePath, localPath)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ ctx.FileAttachment(w, r, localPath, body.FileName)
+}
+
+// TcpdumpNeUPFTask 网元UPF发送执行 pcap
+func TcpdumpNeUPFTask(w http.ResponseWriter, r *http.Request) {
+ var body struct {
+ NeType string `json:"neType"` // 网元类型
+ NeId string `json:"neId"` // 网元ID
+ Cmd string `json:"cmd"` // 命令
+ RunType string `json:"runType"` // 执行开始start还是停止stop
+ }
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil || body.NeType != "UPF" || body.NeId == "" || body.Cmd == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ neInfo, err := NeInfo(body.NeType, body.NeId)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 开始telnet
+ if body.RunType == "start_telnet" {
+ // 创建TCP连接
+ conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", neInfo.Ip, 5002))
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+ defer conn.Close()
+
+ filePcapName := fmt.Sprintf("tmp_%s_%s.pcap", body.NeType, body.NeId)
+ cmdStr := fmt.Sprintf("pcap dispatch trace on max 100000 file %s", filePcapName)
+
+ fmt.Fprintln(conn, cmdStr)
+
+ // 读取内容
+ time.Sleep(time.Duration(200) * time.Millisecond)
+ buf := make([]byte, 1024*8)
+ n, err := conn.Read(buf)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ } else {
+ str := string(buf[0:n])
+ s := strings.Index(str, "pcap dispatch trace:")
+ if s != -1 {
+ e := strings.Index(str, "\r\nupfd1#")
+ str = str[s:e]
+ } else {
+ str = fmt.Sprintf("Executed, please stop before proceeding %d", n)
+ }
+ ctx.JSON(w, 200, result.OkData(map[string]any{
+ "cmd": cmdStr,
+ "msg": str,
+ "fileName": filePcapName,
+ }))
+ }
+ conn.Close()
+ return
+ }
+ // 停止telnet
+ if body.RunType == "stop_telnet" {
+ // 创建TCP连接
+ conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", neInfo.Ip, 5002))
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+ defer conn.Close()
+
+ filePcapName := fmt.Sprintf("tmp_%s_%s.pcap", body.NeType, body.NeId)
+ cmdStr := "pcap dispatch trace off"
+
+ fmt.Fprintln(conn, cmdStr)
+
+ // 读取内容
+ time.Sleep(time.Duration(200) * time.Millisecond)
+ buf := make([]byte, 1024*8)
+ n, err := conn.Read(buf)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ } else {
+ str := string(buf[0:n])
+ s := strings.Index(str, "pcap dispatch trace:")
+ if s == -1 {
+ s = strings.Index(str, "Write ")
+ }
+ if s != -1 {
+ e := strings.Index(str, "\r\nupfd1#")
+ str = str[s:e]
+ } else {
+ str = "No stoppable found"
+ }
+
+ ctx.JSON(w, 200, result.OkData(map[string]any{
+ "cmd": cmdStr,
+ "msg": str,
+ "fileName": filePcapName,
+ }))
+ }
+ conn.Close()
+ return
+ }
+
+ // 开始 -脚本
+ if body.RunType == "start2" {
+ fileLogName := fmt.Sprintf("tmp_%s_%s.log", body.NeType, body.NeId)
+ filePcapName := fmt.Sprintf("tmp_%s_%s.pcap", body.NeType, body.NeId)
+ // 复制文件到网元上
+ 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
+ }
+ writeLog := fmt.Sprintf(" >> %s 2>&1 \ncat %s", fileLogName, fileLogName) // 执行信息写入日志文件,放置弹出code 127
+ cmdStr := fmt.Sprintf("cd /tmp \nchmod +x upf_pcap\n./upf_pcap '192.168.4.139' 'root' 'Admin123@pl' 'pcap dispatch trace on max 100000 file %s' %s ", fileLogName, writeLog)
+
+ usernameNe := conf.Get("ne.user").(string) // 网元统一用户
+ sshHost := fmt.Sprintf("%s@%s", usernameNe, neInfo.Ip)
+ msg, err := cmd.ExecWithCheck("ssh", sshHost, cmdStr)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ } else {
+ ctx.JSON(w, 200, result.OkData(map[string]any{
+ "cmd": cmdStr,
+ "msg": msg,
+ "fileName": filePcapName,
+ }))
+ }
+ return
+ }
+ // 停止 -脚本
+ if body.RunType == "stop2" {
+ 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 := 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
+ }
+ writeLog := fmt.Sprintf(" >> %s 2>&1 \ncat %s", fileLogName, fileLogName) // 执行信息写入日志文件,放置弹出code 127
+ cmdStr := fmt.Sprintf("cd /tmp \nchmod +x upf_pcap\n./upf_pcap '192.168.4.139' 'root' 'Admin123@pl' 'pcap dispatch trace off' %s ", writeLog)
+
+ usernameNe := conf.Get("ne.user").(string) // 网元统一用户
+ sshHost := fmt.Sprintf("%s@%s", usernameNe, neInfo.Ip)
+ msg, err := cmd.ExecWithCheck("ssh", sshHost, cmdStr)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ } else {
+ ctx.JSON(w, 200, result.OkData(map[string]any{
+ "cmd": cmdStr,
+ "msg": msg,
+ "fileName": filePcapName,
+ }))
+ }
+ return
+ }
+
+ // 开始-脚本字符串
+ if body.RunType == "start_str" {
+ fileLogName := fmt.Sprintf("tmp_%s_%s.log", body.NeType, body.NeId)
+ filePcapName := fmt.Sprintf("tmp_%s_%s.pcap", 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 := conf.Get("ne.user").(string) // 网元统一用户
+ sshHost := fmt.Sprintf("%s@%s", usernameNe, neInfo.Ip)
+ msg, err := cmd.ExecWithCheck("ssh", sshHost, cmdStr)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ } else {
+ 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"
+ }
+ ctx.JSON(w, 200, result.OkData(map[string]any{
+ "cmd": capCmdStr,
+ "msg": msg,
+ "fileName": filePcapName,
+ }))
+ }
+ return
+ }
+ // 停止-脚本字符串
+ if body.RunType == "stop_str" {
+ fileLogName := fmt.Sprintf("tmp_%s_%s.log", body.NeType, body.NeId)
+ filePcapName := fmt.Sprintf("tmp_%s_%s.pcap", 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 := conf.Get("ne.user").(string) // 网元统一用户
+ sshHost := fmt.Sprintf("%s@%s", usernameNe, neInfo.Ip)
+ msg, err := cmd.ExecWithCheck("ssh", sshHost, cmdStr)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ } else {
+ s := strings.Index(msg, "pcap dispatch trace:")
+ if s == -1 {
+ s = strings.Index(msg, "Write ")
+ }
+ if s != -1 {
+ e := strings.Index(msg, "\r\nupfd1#")
+ msg = msg[s:e]
+ } else {
+ msg = "No stoppable found"
+ }
+ ctx.JSON(w, 200, result.OkData(map[string]any{
+ "cmd": capCmdStr,
+ "msg": msg,
+ "fileName": filePcapName,
+ }))
+ }
+ return
+ }
+
+ ctx.JSON(w, 200, result.ErrMsg("runType is start or stop"))
+}
diff --git a/features/trace/trace.go b/features/trace/trace.go
new file mode 100644
index 0000000..5678261
--- /dev/null
+++ b/features/trace/trace.go
@@ -0,0 +1,437 @@
+package trace
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/go-resty/resty/v2"
+ "github.com/gorilla/mux"
+
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/global"
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/run"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+)
+
+var (
+ UriTraceTaskV1 = config.DefaultUriPrefix + "/traceManagement/v1/subscriptions"
+ UriTraceTask = config.DefaultUriPrefix + "/traceManagement/{apiVersion}/subscriptions"
+ UriTraceRawMsg = config.DefaultUriPrefix + "/traceManagement/{apiVersion}/rawMessage/{id}"
+ UriTraceDecMsg = config.DefaultUriPrefix + "/traceManagement/{apiVersion}/decMessage/{id}" // decode message api
+
+ CustomUriTraceTaskV1 = config.UriPrefix + "/traceManagement/v1/subscriptions"
+ CustomUriTraceTask = config.UriPrefix + "/traceManagement/{apiVersion}/subscriptions"
+)
+
+type TraceTask struct {
+ Id int `json:"id" xorm:"pk 'id' autoincr"`
+ TraceType string `json:"traceType"`
+ StartTime string `json:"startTime"`
+ EndTime string `json:"endTime"`
+ Imsi string `json:"imsi"`
+ Msisdn string `json:"msisdn"`
+ SrcIp string `json:"srcIp"`
+ DstIp string `json:"dstIp"`
+ SignalPort int16 `json:"signalPort"`
+ NeType string `json:"neType"`
+ NeId string `json:"neId"`
+ UeIp string `json:"ueIp"`
+ Interfaces []string `json:"interfaces"`
+ NotifyUrl string `json:"notifyUrl" xorm:"-"`
+ Status string `json:"-" xorm:"status"`
+ SuccNEs []string `json:"-" xorm:"succ_nes"`
+ FailNEs []string `json:"-" xorm:"fail_nes"`
+ AccountID string `json:"accountId" xorm:"account_id"`
+ Comment string `json:"comment" xorm:"comment"`
+ UpdateTime string `json:"-" xorm:"-"`
+}
+
+var client = resty.New()
+
+/*
+func init() {
+ client.SetTimeout(3 * time.Second)
+}
+*/
+
+// Post trace task to NF/NFs
+func PostTraceTaskToNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("PostTraceTaskToNF processing... ")
+
+ //vars := mux.Vars(r)
+
+ token, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+ log.Debug("AccessToken:", token)
+
+ body, err := io.ReadAll(io.LimitReader(r.Body, int64(config.GetYamlConfig().Params.UriMaxLen)))
+ if err != nil {
+ log.Error("io.ReadAll is failed:", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ log.Trace("body:", string(body))
+
+ traceTask := new(TraceTask)
+ _ = json.Unmarshal(body, traceTask)
+ log.Debug("traceTask:", traceTask)
+
+ var neTypes []string
+ // do not set device
+ if traceTask.NeType == "" {
+ // query neType by interface
+ if len(traceTask.Interfaces) > 0 {
+ err := dborm.XormGetSingleColStringArrayByIn("trace_info", "ne_type", "interface", traceTask.Interfaces, &neTypes)
+ if err != nil {
+ log.Error("Failed to dborm.XormGetSingleCol:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ } else {
+ neTypes = []string{"AMF", "SMF", "UDM", "AUSF", "UPF"}
+ }
+ } else {
+ neTypes = append(neTypes, traceTask.NeType)
+ }
+ log.Debug("neTypes:", neTypes)
+
+ traceTask.Status = "Inactive"
+ _, err = dborm.XormInsertTableOne("trace_task", traceTask)
+ if err != nil {
+ log.Error("Failed to dborm.XormInsertTableOne:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ traceTask.NotifyUrl = config.GetYamlConfig().OMC.GtpUri
+ log.Trace("traceTask:", traceTask)
+
+ for _, neType := range neTypes {
+ var neInfos []dborm.NeInfo
+ if traceTask.NeId == "" {
+ err := dborm.XormGetNeInfoByNeType(neType, &neInfos)
+ if err != nil {
+ log.Error("Failed to dborm.XormGetNeInfoByNeType:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ } else {
+ neInfo, err := dborm.XormGetNeInfo(neType, traceTask.NeId)
+ if err != nil {
+ log.Error("Failed to dborm.XormGetNeInfoByNeType:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ neInfos = append(neInfos, *neInfo)
+ }
+ for _, neInfo := range neInfos {
+ hostUri := fmt.Sprintf("http://%s:%v", neInfo.Ip, neInfo.Port)
+ requestURI2NF := fmt.Sprintf("%s%s", hostUri, UriTraceTaskV1)
+ log.Debug("requestURI2NF:", requestURI2NF)
+
+ body, _ := json.Marshal(traceTask)
+ log.Debug("body:", string(body))
+ resp, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ SetBody(body).
+ Post(requestURI2NF)
+ if err != nil {
+ log.Error("Failed to Post:", err)
+ failNE := fmt.Sprintf("%s.%s", neInfo.NeType, neInfo.NeId)
+ traceTask.FailNEs = append(traceTask.FailNEs, failNE)
+ } else {
+ switch resp.StatusCode() {
+ case http.StatusOK, http.StatusCreated, http.StatusNoContent, http.StatusAccepted:
+ succNE := fmt.Sprintf("%s.%s", neInfo.NeType, neInfo.NeId)
+ traceTask.SuccNEs = append(traceTask.SuccNEs, succNE)
+ default:
+ log.Warnf("Post return code:%d, message:%s", resp.StatusCode(), string(resp.Body()))
+ failNE := fmt.Sprintf("%s.%s", neInfo.NeType, neInfo.NeId)
+ traceTask.FailNEs = append(traceTask.FailNEs, failNE)
+ }
+ }
+ }
+ }
+
+ if len(traceTask.SuccNEs) > 0 {
+ traceTask.Status = "Active"
+ _, err = dborm.XormUpdateTableById(traceTask.Id, "trace_task", traceTask)
+ if err != nil {
+ log.Error("Failed to dborm.XormUpdateTableById:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ services.ResponseStatusOK204NoContent(w)
+ } else {
+ traceTask.Status = "Failed"
+ _, err = dborm.XormUpdateTableById(traceTask.Id, "trace_task", traceTask)
+ if err != nil {
+ log.Error("Failed to dborm.XormUpdateTableById:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ err = global.ErrTraceFailedDistributeToNEs
+ log.Error(err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+}
+
+func PutTraceTaskToNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("PutTraceTaskToNF processing... ")
+
+ //vars := mux.Vars(r)
+
+ token, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+ log.Debug("AccessToken:", token)
+
+ body, err := io.ReadAll(io.LimitReader(r.Body, int64(config.GetYamlConfig().Params.UriMaxLen)))
+ if err != nil {
+ log.Error("io.ReadAll is failed:", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ traceTask := new(TraceTask)
+ _ = json.Unmarshal(body, traceTask)
+
+ traceTask.NotifyUrl = config.GetYamlConfig().OMC.GtpUri
+ log.Debug("traceTask:", traceTask)
+
+ var neTypes []string
+ // do not set device
+ if traceTask.NeType == "" {
+ // query neType by interface
+ if len(traceTask.Interfaces) > 0 {
+ err := dborm.XormGetSingleColStringArrayByIn("trace_info", "ne_type", "interface", traceTask.Interfaces, &neTypes)
+ if err != nil {
+ log.Error("Failed to dborm.XormGetSingleColStringArrayByIn:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ } else {
+ neTypes = []string{"AMF", "SMF", "UDM", "AUSF", "UPF"}
+ }
+
+ } else {
+ neTypes = append(neTypes, traceTask.NeType)
+ }
+ log.Debug("neTypes:", neTypes)
+
+ for _, neType := range neTypes {
+ var neInfos []dborm.NeInfo
+ if traceTask.NeId == "" {
+ err := dborm.XormGetNeInfoByNeType(neType, &neInfos)
+ if err != nil {
+ log.Error("Failed to dborm.XormGetNeInfoByNeType:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ } else {
+ neInfo, err := dborm.XormGetNeInfo(neType, traceTask.NeId)
+ if err != nil {
+ log.Error("Failed to dborm.XormGetNeInfoByNeType:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ neInfos = append(neInfos, *neInfo)
+ }
+ for _, neInfo := range neInfos {
+ hostUri := fmt.Sprintf("http://%s:%v", neInfo.Ip, neInfo.Port)
+ requestURI2NF := fmt.Sprintf("%s%s", hostUri, UriTraceTaskV1)
+ log.Debug("requestURI2NF:", requestURI2NF)
+
+ body, _ := json.Marshal(traceTask)
+ log.Debug("body:", string(body))
+ resp, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ SetBody(body).
+ Put(requestURI2NF)
+ if err != nil {
+ log.Error("Failed to Put:", err)
+ failNE := fmt.Sprintf("%s.%s", neInfo.NeType, neInfo.NeId)
+ traceTask.FailNEs = append(traceTask.FailNEs, failNE)
+ } else {
+ switch resp.StatusCode() {
+ case http.StatusOK, http.StatusCreated, http.StatusNoContent, http.StatusAccepted:
+ succNE := fmt.Sprintf("%s.%s", neInfo.NeType, neInfo.NeId)
+ traceTask.SuccNEs = append(traceTask.SuccNEs, succNE)
+ default:
+ log.Warn("Post return code:%d, message:%s", resp.StatusCode(), string(resp.Body()))
+ failNE := fmt.Sprintf("%s.%s", neInfo.NeType, neInfo.NeId)
+ traceTask.FailNEs = append(traceTask.FailNEs, failNE)
+ }
+ }
+
+ }
+ }
+
+ if len(traceTask.SuccNEs) > 0 {
+ traceTask.Status = "Active"
+ _, err = dborm.XormUpdateTableById(traceTask.Id, "trace_task", traceTask)
+ if err != nil {
+ log.Error("Failed to dborm.XormUpdateTableById:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ services.ResponseStatusOK204NoContent(w)
+ } else {
+ traceTask.Status = "Failed"
+ _, err = dborm.XormUpdateTableById(traceTask.Id, "trace_task", traceTask)
+ if err != nil {
+ log.Error("Failed to dborm.XormUpdateTableById:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ err = global.ErrTraceFailedDistributeToNEs
+ log.Error(err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+}
+
+func DeleteTraceTaskToNF(w http.ResponseWriter, r *http.Request) {
+ log.Debug("DeleteTraceTaskToNF processing... ")
+
+ token, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+ log.Debug("AccessToken:", token)
+
+ vars := r.URL.Query()
+ ids, ok := vars["id"]
+ if !ok || len(ids) == 0 {
+ err = global.ErrTraceNotCarriedTaskID
+ log.Error(err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Debug("ids:", ids)
+
+ for _, id := range ids {
+ log.Debug("id:", id)
+
+ var succNes []string
+ err = dborm.XormGetColStringArrayByWhere("trace_task", "succ_nes", fmt.Sprintf("id=%s", id), &succNes)
+ if err != nil {
+ log.Error("Failed to dborm.XormGetSingleColStringArrayByWhere:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Debug("succNes:", succNes)
+ nes := new([]string)
+ if len(succNes) > 0 {
+ _ = json.Unmarshal([]byte(succNes[0]), nes)
+ }
+ log.Debug("nes:", nes)
+
+ for _, ne := range *nes {
+ i := strings.Index(ne, ".")
+ neType := ne[0:i]
+ neId := ne[i+1:]
+ log.Debugf("ne:%s neType:%s neId:%s", ne, neType, neId)
+ neInfo, err := dborm.XormGetNeInfo(neType, neId)
+ if err != nil {
+ log.Error("Failed to dborm.XormGetNeInfo:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ hostUri := fmt.Sprintf("http://%s:%v", neInfo.Ip, neInfo.Port)
+ requestURI2NF := fmt.Sprintf("%s%s?id=%s", hostUri, UriTraceTaskV1, id)
+ log.Debug("requestURI2NF:", requestURI2NF)
+
+ _, err = client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ Delete(requestURI2NF)
+ if err != nil {
+ log.Error("Failed to Delete:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ }
+
+ _, err = dborm.XormDeleteDataByWhere(fmt.Sprintf("id=%s", id), "trace_task")
+ if err != nil {
+ log.Error("Failed to dborm.XormDeleteDataByWhere:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ }
+ services.ResponseStatusOK204NoContent(w)
+}
+
+func GetRawMessage(w http.ResponseWriter, r *http.Request) {
+ log.Debug("GetRawMessage processing... ")
+
+}
+
+func ParseRawMsg2Html(w http.ResponseWriter, r *http.Request) {
+ log.Debug("ParseRawMsg2Html processing... ")
+
+ vars := mux.Vars(r)
+ idStr := vars["id"]
+ id, _ := strconv.Atoi(idStr)
+
+ traceData, err := dborm.XormGetTraceData(id)
+ if err != nil {
+ log.Error("Failed to dborm.XormGetTraceRawMsg:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Trace("traceData:", traceData)
+ filePath := traceData.DecMsg
+ if traceData.DecMsg == "" {
+ htmlFile := fmt.Sprintf("traceDecMessage-%d-%d.html", traceData.TaskID, traceData.ID)
+ filePath = config.GetYamlConfig().OMC.FrontTraceDir + "/" + htmlFile
+ command := fmt.Sprintf("/usr/local/omc/bin/data2html -f %s -t %d -i N%d -d %x", filePath, traceData.Timestamp, traceData.IfType, traceData.RawMsg)
+ out, err := run.ExecCmd(command, "/")
+ log.Tracef("Exec output: %v", string(out))
+ if err != nil {
+ log.Errorf("Faile to ipdate2html:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ exist, err := global.FilePathExists(filePath)
+ if err != nil {
+ log.Errorf("Failed to stat:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ if !exist {
+ err = errors.New(string(strings.ReplaceAll(string(out), "\n", "")))
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ traceData.DecMsg = filePath
+ _, err = dborm.XormUpdateTraceData(id, traceData)
+ if err != nil {
+ log.Errorf("Faile to XormUpdateTraceData:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ }
+ services.ResponseHtmlContent(w, http.StatusOK, filePath)
+}
diff --git a/features/udm_user/api_udm_user.go b/features/udm_user/api_udm_user.go
new file mode 100644
index 0000000..11ee0f1
--- /dev/null
+++ b/features/udm_user/api_udm_user.go
@@ -0,0 +1,1211 @@
+package udmuser
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+ "time"
+
+ "nms_nbi/features/udm_user/model"
+ "nms_nbi/features/udm_user/service"
+ "nms_nbi/lib/core/conf"
+ mmlclient "nms_nbi/lib/core/mml_client"
+ "nms_nbi/lib/core/utils/ctx"
+ "nms_nbi/lib/core/vo/result"
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/midware"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+ "nms_nbi/src/framework/middleware/collectlogs"
+ "nms_nbi/src/framework/utils/file"
+ "nms_nbi/src/framework/utils/ssh"
+)
+
+// UDM 用户信息接口添加到路由
+func Routers() []services.RouterItem {
+ // 实例化控制层 SysDictTypeApi 结构体
+ var apis = &UdmUserApi{
+ authUser: *service.NewServiceUdmAuthUser,
+ subUser: *service.NewServiceUdmSubUser,
+ }
+
+ rs := [...]services.RouterItem{
+ // UDM鉴权用户
+ {
+ Method: "GET",
+ Pattern: "/auths",
+ Handler: apis.UdmAuthUserList,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "POST",
+ Pattern: "/authSave/{neId}",
+ Handler: apis.UdmAuthUserSave,
+ Middleware: midware.LogOperate(collectlogs.OptionNew("UDM Authentication User", collectlogs.BUSINESS_TYPE_INSERT)),
+ },
+ {
+ Method: "GET",
+ Pattern: "/auth/{neId}/{imsi}",
+ Handler: apis.UdmAuthUserInfo,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "POST",
+ Pattern: "/auth/{neId}",
+ Handler: apis.UdmAuthUserAdd,
+ Middleware: midware.LogOperate(collectlogs.OptionNew("UDM Authentication User", collectlogs.BUSINESS_TYPE_INSERT)),
+ },
+ {
+ Method: "POST",
+ Pattern: "/auth/{neId}/{num}",
+ Handler: apis.UdmAuthUserAdds,
+ Middleware: midware.LogOperate(collectlogs.OptionNew("UDM Authentication User", collectlogs.BUSINESS_TYPE_INSERT)),
+ },
+ {
+ Method: "PUT",
+ Pattern: "/auth/{neId}",
+ Handler: apis.UdmAuthUserEdit,
+ Middleware: midware.LogOperate(collectlogs.OptionNew("UDM Authentication User", collectlogs.BUSINESS_TYPE_UPDATE)),
+ },
+ {
+ Method: "DELETE",
+ Pattern: "/auth/{neId}/{imsi}",
+ Handler: apis.UdmAuthUserRemove,
+ Middleware: midware.LogOperate(collectlogs.OptionNew("UDM Authentication User", collectlogs.BUSINESS_TYPE_DELETE)),
+ },
+ {
+ Method: "DELETE",
+ Pattern: "/auth/{neId}/{imsi}/{num}",
+ Handler: apis.UdmAuthUserRemoves,
+ Middleware: midware.LogOperate(collectlogs.OptionNew("UDM Authentication User", collectlogs.BUSINESS_TYPE_DELETE)),
+ },
+ {
+ Method: "POST",
+ Pattern: "/authExport",
+ Handler: apis.UdmAuthUserExport,
+ Middleware: midware.LogOperate(collectlogs.OptionNew("UDM Authentication User", collectlogs.BUSINESS_TYPE_EXPORT)),
+ },
+ {
+ Method: "POST",
+ Pattern: "/authImport/{neId}",
+ Handler: apis.UdmAuthUserImport,
+ Middleware: midware.LogOperate(collectlogs.OptionNew("UDM Authentication User", collectlogs.BUSINESS_TYPE_IMPORT)),
+ },
+ // UDM签约用户
+ {
+ Method: "GET",
+ Pattern: "/subs",
+ Handler: apis.UdmSubUserList,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "POST",
+ Pattern: "/subSave/{neId}",
+ Handler: apis.UdmSubUserSave,
+ Middleware: midware.LogOperate(collectlogs.OptionNew("UDM Contracted User", collectlogs.BUSINESS_TYPE_INSERT)),
+ },
+ {
+ Method: "GET",
+ Pattern: "/subInfo/{neId}/{imsi}",
+ Handler: apis.UdmSubUserInfo,
+ Middleware: midware.Authorize(nil),
+ },
+ {
+ Method: "POST",
+ Pattern: "/sub/{neId}",
+ Handler: apis.UdmSubUserAdd,
+ Middleware: midware.LogOperate(collectlogs.OptionNew("UDM Contracted User", collectlogs.BUSINESS_TYPE_INSERT)),
+ },
+ {
+ Method: "POST",
+ Pattern: "/sub/{neId}/{num}",
+ Handler: apis.UdmSubUserAdds,
+ Middleware: midware.LogOperate(collectlogs.OptionNew("UDM Contracted User", collectlogs.BUSINESS_TYPE_INSERT)),
+ },
+ {
+ Method: "PUT",
+ Pattern: "/sub/{neId}",
+ Handler: apis.UdmSubUserEdit,
+ Middleware: midware.LogOperate(collectlogs.OptionNew("UDM Contracted User", collectlogs.BUSINESS_TYPE_UPDATE)),
+ },
+ {
+ Method: "PUT",
+ Pattern: "/sub4gIP/{neId}",
+ Handler: apis.UdmSubUser4GIP,
+ Middleware: midware.LogOperate(collectlogs.OptionNew("UDM Contracted User", collectlogs.BUSINESS_TYPE_UPDATE)),
+ },
+ {
+ Method: "PUT",
+ Pattern: "/subSmData/{neId}",
+ Handler: apis.UdmSubUserSmData,
+ Middleware: midware.LogOperate(collectlogs.OptionNew("UDM Contracted User", collectlogs.BUSINESS_TYPE_UPDATE)),
+ },
+ {
+ Method: "DELETE",
+ Pattern: "/sub/{neId}/{imsi}",
+ Handler: apis.UdmSubUserRemove,
+ Middleware: midware.LogOperate(collectlogs.OptionNew("UDM Contracted User", collectlogs.BUSINESS_TYPE_DELETE)),
+ },
+ {
+ Method: "DELETE",
+ Pattern: "/sub/{neId}/{imsi}/{num}",
+ Handler: apis.UdmSubUserRemoves,
+ Middleware: midware.LogOperate(collectlogs.OptionNew("UDM Contracted User", collectlogs.BUSINESS_TYPE_DELETE)),
+ },
+ {
+ Method: "POST",
+ Pattern: "/subExport",
+ Handler: apis.UdmSubUserExport,
+ Middleware: midware.LogOperate(collectlogs.OptionNew("UDM Contracted User", collectlogs.BUSINESS_TYPE_EXPORT)),
+ },
+ {
+ Method: "POST",
+ Pattern: "/subImport/{neId}",
+ Handler: apis.UdmSubUserImport,
+ Middleware: midware.LogOperate(collectlogs.OptionNew("UDM Contracted User", collectlogs.BUSINESS_TYPE_IMPORT)),
+ },
+ // 添加更多的 Router 对象...
+ }
+
+ // 生成两组前缀路由
+ rsPrefix := []services.RouterItem{}
+ for _, v := range rs {
+ path := "/udmUserManage/{apiVersion}" + v.Pattern
+ // 固定前缀
+ v.Pattern = config.DefaultUriPrefix + path
+ rsPrefix = append(rsPrefix, v)
+ // 可配置
+ v.Pattern = config.UriPrefix + path
+ rsPrefix = append(rsPrefix, v)
+ }
+ return rsPrefix
+}
+
+// NeInfoByUDM 网元信息
+func NeInfoByUDM(neId string) (*dborm.NeInfo, error) {
+ neInfo, err := dborm.XormGetNeInfo("UDM", neId)
+ if err != nil {
+ log.Error("dborm.XormGetNeInfo is failed:", err)
+ return nil, err
+ }
+ if neInfo == nil || neInfo.Ip == "" {
+ return nil, fmt.Errorf("not ne_info or not IP")
+ }
+ return neInfo, nil
+}
+
+// UDM 用户
+//
+// PATH /udmUserManage
+type UdmUserApi struct {
+ authUser service.ServiceUdmAuthUser
+ subUser service.ServiceUdmSubUser
+}
+
+// UDM鉴权用户
+//
+// GET /auths
+func (s *UdmUserApi) UdmAuthUserList(w http.ResponseWriter, r *http.Request) {
+ querys := ctx.QueryMap(r)
+ querys["neId"] = ""
+ data := s.authUser.Page(querys)
+ ctx.JSON(w, 200, result.Ok(data))
+}
+
+// UDM鉴权用户-获取全部保存数据库
+//
+// POST /authSave/{neId}
+func (s *UdmUserApi) UdmAuthUserSave(w http.ResponseWriter, r *http.Request) {
+ neId := ctx.Param(r, "neId")
+ if neId == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ neId = ""
+ data := s.authUser.Save(neId)
+ ctx.JSON(w, 200, result.OkData(data))
+}
+
+// UDM鉴权用户-信息
+//
+// GET /authInfo/{neId}/{imsi}
+func (s *UdmUserApi) UdmAuthUserInfo(w http.ResponseWriter, r *http.Request) {
+ neId := ctx.Param(r, "neId")
+ imsi := ctx.Param(r, "imsi")
+ if neId == "" || imsi == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ neInfo, err := NeInfoByUDM(neId)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ msg := fmt.Sprintf("dsp authdat:imsi=%s", imsi)
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToMap(neInfo.Ip, msg)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 查询数据库是否存在并存入
+ neId = ""
+ var userInfo model.UdmAuthUser
+ list := s.authUser.List(model.UdmAuthUser{NeID: neId, Imsi: imsi})
+ if len(list) > 0 {
+ userInfo = list[0]
+ // 返回查询的用户信息
+ userInfo.Amf = data["amf"]
+ userInfo.AlgoIndex = data["algo"]
+ userInfo.Opc = data["opc"]
+ userInfo.Ki = data["ki"]
+ } else {
+ userInfo := model.UdmAuthUser{
+ Imsi: imsi,
+ Amf: data["amf"],
+ AlgoIndex: data["algo"],
+ Opc: data["opc"],
+ Ki: data["ki"],
+ }
+ s.authUser.Insert(neId, userInfo)
+ }
+ ctx.JSON(w, 200, result.OkData(userInfo))
+
+}
+
+// UDM鉴权用户-增加
+//
+// POST /{neId}
+func (s *UdmUserApi) UdmAuthUserAdd(w http.ResponseWriter, r *http.Request) {
+ neId := ctx.Param(r, "neId")
+ if neId == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ var body model.UdmAuthUser
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil || body.Imsi == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ neInfo, err := NeInfoByUDM(neId)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ msg := fmt.Sprintf("add authdat:imsi=%s,ki=%s,amf=%s,algo=%s,opc=%s", body.Imsi, body.Ki, body.Amf, body.AlgoIndex, body.Opc)
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.Ip, msg)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ neId = ""
+ s.authUser.Insert(neId, body)
+ }
+ ctx.JSON(w, 200, result.OkData(data))
+}
+
+// UDM鉴权用户-批量添加
+//
+// POST /{neId}/{num}
+func (s *UdmUserApi) UdmAuthUserAdds(w http.ResponseWriter, r *http.Request) {
+ neId := ctx.Param(r, "neId")
+ num := ctx.Param(r, "num")
+ if neId == "" || num == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ var body model.UdmAuthUser
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil || body.Imsi == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ neInfo, err := NeInfoByUDM(neId)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ msg := fmt.Sprintf("bad authdat:start_imsi=%s,sub_num=%s,ki=%s,amf=%s,algo=%s,opc=%s", body.Imsi, num, body.Ki, body.Amf, body.AlgoIndex, body.Opc)
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.Ip, msg)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ neId = ""
+ s.authUser.Inserts(neId, body, num)
+ }
+ ctx.JSON(w, 200, result.OkData(data))
+}
+
+// UDM鉴权用户-修改
+//
+// PUT /{neId}
+func (s *UdmUserApi) UdmAuthUserEdit(w http.ResponseWriter, r *http.Request) {
+ neId := ctx.Param(r, "neId")
+ if neId == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ var body model.UdmAuthUser
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil || body.Imsi == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ neInfo, err := NeInfoByUDM(neId)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ msg := fmt.Sprintf("mod authdata:imsi=%s", body.Imsi)
+ // 修改的参数名称
+ if body.Ki != "" {
+ msg += fmt.Sprintf(",ki=%s", body.Ki)
+ }
+ if body.Amf != "" {
+ msg += fmt.Sprintf(",amf=%s", body.Amf)
+ }
+ if body.AlgoIndex != "" {
+ msg += fmt.Sprintf(",algo=%s", body.AlgoIndex)
+ }
+ if body.Opc != "" {
+ msg += fmt.Sprintf(",opc=%s", body.Opc)
+ }
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.Ip, msg)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ neId = ""
+ s.authUser.Update(neId, body)
+ }
+ ctx.JSON(w, 200, result.OkData(data))
+}
+
+// UDM鉴权用户-删除
+//
+// DELETE /{neId}/{imsi}
+func (s *UdmUserApi) UdmAuthUserRemove(w http.ResponseWriter, r *http.Request) {
+ neId := ctx.Param(r, "neId")
+ imsi := ctx.Param(r, "imsi")
+ if neId == "" || imsi == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ neInfo, err := NeInfoByUDM(neId)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ msg := fmt.Sprintf("del authdat:imsi=%s", imsi)
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.Ip, msg)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ neId = ""
+ s.authUser.Delete(neId, imsi)
+ }
+ ctx.JSON(w, 200, result.OkData(data))
+}
+
+// UDM鉴权用户-批量删除
+//
+// DELETE /{neId}/{imsi}/{num}
+func (s *UdmUserApi) UdmAuthUserRemoves(w http.ResponseWriter, r *http.Request) {
+ neId := ctx.Param(r, "neId")
+ imsi := ctx.Param(r, "imsi")
+ num := ctx.Param(r, "num")
+ if neId == "" || imsi == "" || num == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ neInfo, err := NeInfoByUDM(neId)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ msg := fmt.Sprintf("bde authdat:start_imsi=%s,sub_num=%s", imsi, num)
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.Ip, msg)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ neId = ""
+ s.authUser.Deletes(neId, imsi, num)
+ }
+ ctx.JSON(w, 200, result.OkData(data))
+}
+
+// UDM鉴权用户-导出
+//
+// POST /authExport
+func (s *UdmUserApi) UdmAuthUserExport(w http.ResponseWriter, r *http.Request) {
+ var body struct {
+ NeId string `json:"neId"`
+ Type string `json:"type"`
+ }
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil || body.NeId == "" || body.Type == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ if !(body.Type == "csv" || body.Type == "txt") {
+ ctx.JSON(w, 200, result.ErrMsg("Export file types support CSV and txt"))
+ return
+ }
+
+ neId := ""
+ list := s.authUser.List(model.UdmAuthUser{NeID: neId})
+ // 文件名
+ fileName := fmt.Sprintf("OMC_AUTH_USER_EXPORT_%s_%d.%s", neId, time.Now().UnixMilli(), body.Type)
+ filePath := fmt.Sprintf("%s/upload/mml/%s", conf.Get("ne.omcdir"), fileName)
+
+ if body.Type == "csv" {
+ // 转换数据
+ data := [][]string{}
+ data = append(data, []string{"imsi", "ki", "algo", "amf", "opc"})
+ for _, v := range list {
+ data = append(data, []string{v.Imsi, v.Ki, v.AlgoIndex, v.Amf, v.Opc})
+ }
+ // 输出到文件
+ err := file.WriterCSVFile(data, filePath)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+ }
+
+ if body.Type == "txt" {
+ // 转换数据
+ data := [][]string{}
+ for _, v := range list {
+ data = append(data, []string{v.Imsi, v.Ki, v.AlgoIndex, v.Amf, v.Opc})
+ }
+ // 输出到文件
+ err = file.WriterTxtFile(data, filePath)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+ }
+
+ ctx.FileAttachment(w, r, filePath, fileName)
+}
+
+// UDM鉴权用户-导入
+//
+// POST /authImport/{neId}
+func (s *UdmUserApi) UdmAuthUserImport(w http.ResponseWriter, r *http.Request) {
+ neId := ctx.Param(r, "neId")
+ if neId == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ // 获取文件名
+ _, fileHeader, err := r.FormFile("file")
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+ if !(strings.HasSuffix(fileHeader.Filename, ".csv") || strings.HasSuffix(fileHeader.Filename, ".txt")) {
+ ctx.JSON(w, 200, result.ErrMsg("Please upload files in. csv and. txt formats with content fields: imsi,ki,algo,amf,opc"))
+ return
+ }
+
+ neInfo, err := NeInfoByUDM(neId)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 文件名
+ fileName := fmt.Sprintf("OMC_AUTH_USER_IMPORT_%s_%d_%s", neId, time.Now().UnixMilli(), fileHeader.Filename)
+ localPath := fmt.Sprintf("%s/upload/mml/%s", conf.Get("ne.omcdir"), fileName)
+ nePath := conf.Get("mml.upload").(string)
+ // 输出保存文件
+ err = ctx.SaveUploadedFile(r, localPath)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 复制到远程
+ err = ssh.FileSCPLocalToNe(neInfo.Ip, localPath, nePath)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ msg := fmt.Sprintf("import authdat:path=%s", fmt.Sprintf("%s/%s", nePath, fileName))
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.Ip, msg)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ if strings.HasSuffix(fileHeader.Filename, ".csv") {
+ data := file.ReadCSVFile(localPath)
+ neId = ""
+ go s.authUser.InsertCSV(neId, data)
+ }
+ if strings.HasSuffix(fileHeader.Filename, ".txt") {
+ data := file.ReadTxtFile(localPath)
+ neId = ""
+ go s.authUser.InsertTxt(neId, data)
+ }
+ }
+ ctx.JSON(w, 200, result.OkMsg(data))
+}
+
+// UDM签约用户
+//
+// GET /subs
+func (s *UdmUserApi) UdmSubUserList(w http.ResponseWriter, r *http.Request) {
+ querys := ctx.QueryMap(r)
+ querys["neId"] = ""
+ data := s.subUser.Page(querys)
+ ctx.JSON(w, 200, result.Ok(data))
+}
+
+// UDM签约用户-获取全部保存数据库
+//
+// POST /subSave/{neId}
+func (s *UdmUserApi) UdmSubUserSave(w http.ResponseWriter, r *http.Request) {
+ neId := ctx.Param(r, "neId")
+ if neId == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ neId = ""
+ data := s.subUser.Save(neId)
+ ctx.JSON(w, 200, result.OkData(data))
+}
+
+// UDM签约用户-信息
+//
+// GET /subInfo/{neId}/{imsi}
+func (s *UdmUserApi) UdmSubUserInfo(w http.ResponseWriter, r *http.Request) {
+ neId := ctx.Param(r, "neId")
+ imsi := ctx.Param(r, "imsi")
+ if neId == "" || imsi == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ neInfo, err := NeInfoByUDM(neId)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ msg := fmt.Sprintf("dsp udmuser:imsi=%s", imsi)
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToMap(neInfo.Ip, msg)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 解析返回的数据
+ cnType, _ := strconv.ParseInt(data["CNType"][:4], 0, 64)
+ rat, _ := strconv.ParseInt(data["RAT"][:4], 0, 64)
+ msisdn := data["MSISDN"]
+ imsMsisdnLen := strings.Index(msisdn, ",")
+ if imsMsisdnLen != -1 {
+ msisdn = msisdn[:imsMsisdnLen]
+ }
+ userInfo := model.UdmSubUser{
+ Imsi: imsi,
+ Msisdn: msisdn,
+ Ambr: data["AMBR"],
+ Arfb: data["AreaForbidden"],
+ Cn: fmt.Sprint(cnType),
+ SmData: data["SM-Data(snssai+dnn[1..n])"],
+ Sar: data["ServiceAreaRestriction"],
+ Nssai: data["NSSAI"],
+ SmfSel: data["Smf-Selection"],
+ Rat: fmt.Sprint(rat),
+ }
+ // 1,64,24,65,def_eps,1,2,010200000000,-
+ if v, ok := data["EPS-Data"]; ok {
+ userInfo.EpsDat = v
+ arr := strings.Split(v, ",")
+ userInfo.EpsFlag = arr[0]
+ userInfo.EpsOdb = arr[1]
+ userInfo.HplmnOdb = arr[2]
+ userInfo.Ard = arr[3]
+ userInfo.Epstpl = arr[4]
+ userInfo.ContextId = arr[5]
+ userInfo.ApnContext = arr[7]
+ userInfo.StaticIp = arr[8]
+ }
+
+ // 查询数据库是否存在并存入更新
+ neId = ""
+ list := s.subUser.List(model.UdmSubUser{NeID: neId, Imsi: imsi})
+ if len(list) > 0 {
+ listItme := list[0]
+ userInfo.ID = listItme.ID
+ s.subUser.Update(neId, userInfo)
+ } else {
+ s.subUser.Insert(neId, userInfo)
+ }
+ ctx.JSON(w, 200, result.OkData(userInfo))
+}
+
+// UDM签约用户-增加
+//
+// POST /{neId}
+func (s *UdmUserApi) UdmSubUserAdd(w http.ResponseWriter, r *http.Request) {
+ neId := ctx.Param(r, "neId")
+ if neId == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ var body model.UdmSubUser
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil || body.Imsi == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ neInfo, err := NeInfoByUDM(neId)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ msg := fmt.Sprintf("add udmuser:imsi=%s,msisdn=%s,ambr=%s,nssai=%s,arfb=%s,sar=%s,rat=%s,cn=%s,smf_sel=%s,sm_data=%s,eps_flag=%s,eps_odb=%s,hplmn_odb=%s,ard=%s,epstpl=%s,context_id=%s,apn_context=%s",
+ body.Imsi, body.Msisdn, body.Ambr, body.Nssai, body.Arfb, body.Sar, body.Rat, body.Cn, body.SmfSel, body.SmData, body.EpsFlag, body.EpsOdb, body.HplmnOdb, body.Ard, body.Epstpl, body.ContextId, body.ApnContext)
+ // static_ip指给4G UE分配的静态IP,没有可不带此字段名,批量添加IP会自动递增
+ if body.StaticIp != "" {
+ msg += fmt.Sprintf(",static_ip=%s", body.StaticIp)
+ }
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.Ip, msg)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ neId = ""
+ s.subUser.Insert(neId, body)
+ }
+ ctx.JSON(w, 200, result.OkData(data))
+}
+
+// UDM签约用户-批量添加
+//
+// POST /{neId}/{num}
+func (s *UdmUserApi) UdmSubUserAdds(w http.ResponseWriter, r *http.Request) {
+ neId := ctx.Param(r, "neId")
+ num := ctx.Param(r, "num")
+ if neId == "" || num == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ var body model.UdmSubUser
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil || body.Imsi == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ neInfo, err := NeInfoByUDM(neId)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ msg := fmt.Sprintf("bad udmuser:start_imsi=%s,start_msisdn=%s,sub_num=%s,ambr=%s,nssai=%s,arfb=%s,sar=%s,rat=%s,cn=%s,smf_sel=%s,sm_data=%s,eps_flag=%s,eps_odb=%s,hplmn_odb=%s,ard=%s,epstpl=%s,context_id=%s,apn_context=%s",
+ body.Imsi, body.Msisdn, num, body.Ambr, body.Nssai, body.Arfb, body.Sar, body.Rat, body.Cn, body.SmfSel, body.SmData, body.EpsFlag, body.EpsOdb, body.HplmnOdb, body.Ard, body.Epstpl, body.ContextId, body.ApnContext)
+ // static_ip指给4G UE分配的静态IP,没有可不带此字段名,批量添加IP会自动递增
+ if body.StaticIp != "" {
+ msg += fmt.Sprintf(",static_ip=%s", body.StaticIp)
+ }
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.Ip, msg)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ neId = ""
+ s.subUser.Inserts(neId, body, num)
+ }
+ ctx.JSON(w, 200, result.OkData(data))
+}
+
+// UDM签约用户-批量添加4G用户
+//
+// POST /sub4G/{neId}
+func (s *UdmUserApi) UdmSubUserAdd4G(w http.ResponseWriter, r *http.Request) {
+ neId := ctx.Param(r, "neId")
+ if neId == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ var body model.UdmSubUser
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil || body.Imsi == "" || body.SubNum == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ neInfo, err := NeInfoByUDM(neId)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ msg := fmt.Sprintf("bmd udmuser:start_imsi=%s,sub_num=%s,eps_flag=%s", body.Imsi, body.SubNum, body.EpsFlag)
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.Ip, msg)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ neId = ""
+ s.subUser.Insert4G(neId, body)
+ }
+ ctx.JSON(w, 200, result.OkData(data))
+}
+
+// UDM签约用户-修改
+//
+// PUT /{neId}
+func (s *UdmUserApi) UdmSubUserEdit(w http.ResponseWriter, r *http.Request) {
+ neId := ctx.Param(r, "neId")
+ if neId == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ var body model.UdmSubUser
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil || body.Imsi == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ neInfo, err := NeInfoByUDM(neId)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ msg := fmt.Sprintf("mod udmuser:imsi=%s", body.Imsi)
+ // 修改的参数名称
+ if body.Msisdn != "" {
+ msg += fmt.Sprintf(",msisdn=%s", body.Msisdn)
+ }
+ if body.Ambr != "" {
+ msg += fmt.Sprintf(",ambr=%s", body.Ambr)
+ }
+ if body.Nssai != "" {
+ msg += fmt.Sprintf(",nssai=%s", body.Nssai)
+ }
+ if body.Arfb != "" {
+ msg += fmt.Sprintf(",arfb=%s", body.Arfb)
+ }
+ if body.Sar != "" {
+ msg += fmt.Sprintf(",sar=%s", body.Sar)
+ }
+ if body.Rat != "" {
+ msg += fmt.Sprintf(",rat=%s", body.Rat)
+ }
+ if body.Cn != "" {
+ msg += fmt.Sprintf(",cn=%s", body.Cn)
+ }
+ if body.SmfSel != "" {
+ msg += fmt.Sprintf(",smf_sel=%s", body.SmfSel)
+ }
+ if body.SmData != "" {
+ msg += fmt.Sprintf(",sm_data=%s", body.SmData)
+ }
+ if body.EpsDat != "" {
+ msg += fmt.Sprintf(",eps_dat=%s", body.EpsDat)
+ }
+ if body.EpsFlag != "" {
+ msg += fmt.Sprintf(",eps_flag=%s", body.EpsFlag)
+ }
+ if body.EpsOdb != "" {
+ msg += fmt.Sprintf(",eps_odb=%s", body.EpsOdb)
+ }
+ if body.HplmnOdb != "" {
+ msg += fmt.Sprintf(",hplmn_odb=%s", body.HplmnOdb)
+ }
+ if body.Epstpl != "" {
+ msg += fmt.Sprintf(",epstpl=%s", body.Epstpl)
+ }
+ if body.Ard != "" {
+ msg += fmt.Sprintf(",ard=%s", body.Ard)
+ }
+ if body.ContextId != "" {
+ msg += fmt.Sprintf(",context_id=%s", body.ContextId)
+ }
+ if body.ApnContext != "" {
+ msg += fmt.Sprintf(",apn_context=%s", body.ApnContext)
+ }
+ if body.StaticIp != "" {
+ msg += fmt.Sprintf(",static_ip=%s", body.StaticIp)
+ }
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.Ip, msg)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ neId = ""
+ s.subUser.Update(neId, body)
+ }
+ ctx.JSON(w, 200, result.OkData(data))
+}
+
+// UDM签约用户-批量修改4G IP
+//
+// PUT /sub4gIP/{neId}
+func (s *UdmUserApi) UdmSubUser4GIP(w http.ResponseWriter, r *http.Request) {
+ neId := ctx.Param(r, "neId")
+ if neId == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ var body model.UdmSubUser
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil || body.Imsi == "" || body.SubNum == "" || body.StaticIp == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ neInfo, err := NeInfoByUDM(neId)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ msg := fmt.Sprintf("bmd udmuser:start_imsi=%s,sub_num=%s,static_ip=%s", body.Imsi, body.SubNum, body.StaticIp)
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.Ip, msg)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ neId = ""
+ s.subUser.Update4GIP(neId, body)
+ }
+ ctx.JSON(w, 200, result.OkData(data))
+}
+
+// UDM签约用户-批量修改sm-data
+//
+// PUT /subSmData/{neId}
+func (s *UdmUserApi) UdmSubUserSmData(w http.ResponseWriter, r *http.Request) {
+ neId := ctx.Param(r, "neId")
+ if neId == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ var body model.UdmSubUser
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil || body.Imsi == "" || body.SubNum == "" || body.SmData == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ neInfo, err := NeInfoByUDM(neId)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 3-000003&internet-10.10.1.1&ims-10.11.1.1
+ msg := fmt.Sprintf("bmd udmuser:start_imsi=%s,sub_num=%s,sm_data=%s", body.Imsi, body.SubNum, body.SmData)
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.Ip, msg)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ neId = ""
+ s.subUser.UpdateSmData(neId, body)
+ }
+ ctx.JSON(w, 200, result.OkData(data))
+}
+
+// UDM签约用户-删除
+//
+// DELETE /{neId}/{imsi}
+func (s *UdmUserApi) UdmSubUserRemove(w http.ResponseWriter, r *http.Request) {
+ neId := ctx.Param(r, "neId")
+ imsi := ctx.Param(r, "imsi")
+ if neId == "" || imsi == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ neInfo, err := NeInfoByUDM(neId)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ msg := fmt.Sprintf("del udmuser:imsi=%s", imsi)
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.Ip, msg)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ neId = ""
+ s.subUser.Delete(neId, imsi)
+ }
+ ctx.JSON(w, 200, result.OkData(data))
+}
+
+// UDM签约用户-批量删除
+//
+// DELETE /{neId}/{imsi}/{num}
+func (s *UdmUserApi) UdmSubUserRemoves(w http.ResponseWriter, r *http.Request) {
+ neId := ctx.Param(r, "neId")
+ imsi := ctx.Param(r, "imsi")
+ num := ctx.Param(r, "num")
+ if neId == "" || imsi == "" || num == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ neInfo, err := NeInfoByUDM(neId)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ msg := fmt.Sprintf("bde udmuser:start_imsi=%s,sub_num=%s", imsi, num)
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.Ip, msg)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ neId = ""
+ s.subUser.Deletes(neId, imsi, num)
+ }
+ ctx.JSON(w, 200, result.OkData(data))
+}
+
+// UDM签约用户-导出
+//
+// POST /subExport
+func (s *UdmUserApi) UdmSubUserExport(w http.ResponseWriter, r *http.Request) {
+ var body struct {
+ NeId string `json:"neId"`
+ Type string `json:"type"`
+ }
+ err := ctx.ShouldBindJSON(r, &body)
+ if err != nil || body.NeId == "" || body.Type == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ if !(body.Type == "csv" || body.Type == "txt") {
+ ctx.JSON(w, 200, result.ErrMsg("Export file type support csv、txt"))
+ return
+ }
+
+ neId := ""
+ list := s.subUser.List(model.UdmSubUser{NeID: neId})
+ // 文件名
+ fileName := fmt.Sprintf("OMC_AUTH_USER_EXPORT_%s_%d.%s", neId, time.Now().UnixMilli(), body.Type)
+ filePath := fmt.Sprintf("%s/upload/mml/%s", conf.Get("ne.omcdir"), fileName)
+
+ if body.Type == "csv" {
+ // 转换数据
+ data := [][]string{}
+ data = append(data, []string{"imsi", "msisdn", "ambr", "nssai", "arfb", "sar", "rat", "cn", "smf_sel", "sm_dat", "eps_dat"})
+ for _, v := range list {
+ epsDat := fmt.Sprintf("%s,%s,%s,%s,%s,%s,%s,%s", v.EpsFlag, v.EpsOdb, v.HplmnOdb, v.Ard, v.Epstpl, v.ContextId, v.ApnContext, v.StaticIp)
+ data = append(data, []string{v.Imsi, v.Msisdn, v.Ambr, v.Nssai, v.Arfb, v.Sar, v.Rat, v.Cn, v.SmfSel, v.SmData, epsDat})
+ }
+ // 输出到文件
+ err = file.WriterCSVFile(data, filePath)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+ }
+
+ if body.Type == "txt" {
+ // 转换数据
+ data := [][]string{}
+ for _, v := range list {
+ epsDat := fmt.Sprintf("%s,%s,%s,%s,%s,%s,%s,%s", v.EpsFlag, v.EpsOdb, v.HplmnOdb, v.Ard, v.Epstpl, v.ContextId, v.ApnContext, v.StaticIp)
+ data = append(data, []string{v.Imsi, v.Msisdn, v.Ambr, v.Nssai, v.Arfb, v.Sar, v.Rat, v.Cn, v.SmfSel, v.SmData, epsDat})
+ }
+ // 输出到文件
+ err = file.WriterTxtFile(data, filePath)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+ }
+
+ ctx.FileAttachment(w, r, filePath, fileName)
+}
+
+// UDM签约用户-导入
+//
+// POST /subImport/{neId}
+func (s *UdmUserApi) UdmSubUserImport(w http.ResponseWriter, r *http.Request) {
+ neId := ctx.Param(r, "neId")
+ if neId == "" {
+ ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
+ return
+ }
+
+ // 获取文件名
+ _, fileHeader, err := r.FormFile("file")
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+ if !(strings.HasSuffix(fileHeader.Filename, ".csv") || strings.HasSuffix(fileHeader.Filename, ".txt")) {
+ ctx.JSON(w, 200, result.ErrMsg("Please upload files in. csv and. txt formats with content fields: imsi,msisdn,ambr,nssai,arfb,sar,rat,cn,smf_sel,sm_dat,eps_dat"))
+ return
+ }
+
+ neInfo, err := NeInfoByUDM(neId)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 文件名
+ fileName := fmt.Sprintf("OMC_SUB_USER_IMPORT_%s_%d_%s", neId, time.Now().UnixMilli(), fileHeader.Filename)
+ localPath := fmt.Sprintf("%s/upload/mml/%s", conf.Get("ne.omcdir"), fileName)
+ nePath := conf.Get("mml.upload").(string)
+ // 输出保存文件
+ err = ctx.SaveUploadedFile(r, localPath)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 复制到远程
+ err = ssh.FileSCPLocalToNe(neInfo.Ip, localPath, nePath)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ msg := fmt.Sprintf("import udmuser:path=%s", fmt.Sprintf("%s/%s", nePath, fileName))
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.Ip, msg)
+ if err != nil {
+ ctx.JSON(w, 200, result.ErrMsg(err.Error()))
+ return
+ }
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ if strings.HasSuffix(fileHeader.Filename, ".csv") {
+ data := file.ReadCSVFile(localPath)
+ neId = ""
+ go s.subUser.InsertCSV(neId, data)
+ }
+ if strings.HasSuffix(fileHeader.Filename, ".txt") {
+ data := file.ReadTxtFile(localPath)
+ neId = ""
+ go s.subUser.InsertTxt(neId, data)
+ }
+ }
+ ctx.JSON(w, 200, result.OkMsg(data))
+}
diff --git a/features/udm_user/model/udm_auth_user.go b/features/udm_user/model/udm_auth_user.go
new file mode 100644
index 0000000..520d346
--- /dev/null
+++ b/features/udm_user/model/udm_auth_user.go
@@ -0,0 +1,13 @@
+package model
+
+// UdmAuthUser UDM鉴权用户
+type UdmAuthUser struct {
+ ID string `json:"id" xorm:"pk 'id' autoincr"`
+ Imsi string `json:"imsi" xorm:"imsi"` // SIM卡号
+ Amf string `json:"amf" xorm:"amf"` // ANF
+ Status string `json:"status" xorm:"status"` // 状态
+ Ki string `json:"ki" xorm:"ki"` // ki
+ AlgoIndex string `json:"algoIndex" xorm:"algo_index"` //
+ Opc string `json:"opc" xorm:"opc"`
+ NeID string `json:"neId" xorm:"ne_id"` // UDM网元标识-子系统
+}
diff --git a/features/udm_user/model/udm_sub_user.go b/features/udm_user/model/udm_sub_user.go
new file mode 100644
index 0000000..466fce1
--- /dev/null
+++ b/features/udm_user/model/udm_sub_user.go
@@ -0,0 +1,29 @@
+package model
+
+// UdmSubUser UDM签约用户
+type UdmSubUser struct {
+ ID string `json:"id" xorm:"pk 'id' autoincr"`
+ Msisdn string `json:"msisdn" xorm:"msisdn"` // 相当手机号
+ Imsi string `json:"imsi" xorm:"imsi"` // SIM卡号
+ Ambr string `json:"ambr" xorm:"ambr"`
+ Nssai string `json:"nssai" xorm:"nssai"`
+ Rat string `json:"rat" xorm:"rat"`
+ Arfb string `json:"arfb" xorm:"arfb"`
+ Sar string `json:"sar" xorm:"sar"`
+ Cn string `json:"cn" xorm:"cn"`
+ SmData string `json:"smData" xorm:"sm_data"`
+ SmfSel string `json:"smfSel" xorm:"smf_sel"`
+ EpsDat string `json:"epsDat" xorm:"eps_dat"`
+ NeID string `json:"neId" xorm:"ne_id"` // UDM网元标识-子系统
+
+ EpsFlag string `json:"epsFlag" xorm:"eps_flag"`
+ EpsOdb string `json:"epsOdb" xorm:"eps_odb"`
+ HplmnOdb string `json:"hplmnOdb" xorm:"hplmn_odb"`
+ Ard string `json:"ard" xorm:"ard"`
+ Epstpl string `json:"epstpl" xorm:"epstpl"`
+ ContextId string `json:"contextId" xorm:"context_id"`
+ ApnContext string `json:"apnContext" xorm:"apn_context"`
+ StaticIp string `json:"staticIp" xorm:"static_ip"`
+
+ SubNum string `json:"subNum,omitempty" xorm:"-"` // 批量数
+}
diff --git a/features/udm_user/repo/repo_udm_auth_user.go b/features/udm_user/repo/repo_udm_auth_user.go
new file mode 100644
index 0000000..fcd5d18
--- /dev/null
+++ b/features/udm_user/repo/repo_udm_auth_user.go
@@ -0,0 +1,276 @@
+package repo
+
+import (
+ "strconv"
+ "strings"
+
+ "nms_nbi/features/udm_user/model"
+ "nms_nbi/lib/core/datasource"
+ "nms_nbi/lib/core/utils/parse"
+ "nms_nbi/lib/log"
+)
+
+// 实例化数据层 RepoUdmAuthUser 结构体
+var NewRepoUdmAuthUser = &RepoUdmAuthUser{
+ selectSql: `select
+ id, imsi, amf, status, ki, algo_index, opc, ne_id
+ from u_auth_user`,
+
+ resultMap: map[string]string{
+ "id": "ID",
+ "imsi": "Imsi",
+ "amf": "Amf",
+ "status": "Status",
+ "ki": "Ki",
+ "algo_index": "AlgoIndex",
+ "opc": "Opc",
+ "ne_id": "NeID",
+ },
+}
+
+// RepoUdmAuthUser UDM鉴权用户 数据层处理
+type RepoUdmAuthUser struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *RepoUdmAuthUser) convertResultRows(rows []map[string]any) []model.UdmAuthUser {
+ arr := make([]model.UdmAuthUser, 0)
+ for _, row := range rows {
+ UdmUser := model.UdmAuthUser{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ datasource.SetFieldValue(&UdmUser, keyMapper, value)
+ }
+ }
+ arr = append(arr, UdmUser)
+ }
+ return arr
+}
+
+// SelectPage 根据条件分页查询
+func (r *RepoUdmAuthUser) SelectPage(query map[string]any) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if v, ok := query["imsi"]; ok && v != "" {
+ conditions = append(conditions, "imsi like concat(concat('%', ?), '%')")
+ params = append(params, v)
+ }
+ if v, ok := query["neId"]; ok && v != "" {
+ conditions = append(conditions, "ne_id = ?")
+ params = append(params, v)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ result := map[string]any{
+ "total": 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 {
+ log.Errorf("total err => %v", err)
+ return result
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return result
+ } else {
+ result["total"] = total
+ }
+
+ // 分页
+ pageNum, pageSize := datasource.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 排序
+ sortSql := ""
+ if v, ok := query["sortField"]; ok && v != "" {
+ if v == "imsi" {
+ sortSql += " order by imsi "
+ }
+ if o, ok := query["sortOrder"]; ok && o != nil && v != "" {
+ if o == "desc" {
+ sortSql += " desc "
+ } else {
+ sortSql += " asc "
+ }
+ }
+ }
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + sortSql + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ }
+
+ // 转换实体
+ result["rows"] = r.convertResultRows(results)
+ return result
+}
+
+// SelectList 根据实体查询
+func (r *RepoUdmAuthUser) SelectList(auth model.UdmAuthUser) []model.UdmAuthUser {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if auth.Imsi != "" {
+ conditions = append(conditions, "imsi = ?")
+ params = append(params, auth.Imsi)
+ }
+ if auth.NeID != "" {
+ conditions = append(conditions, "ne_id = ?")
+ params = append(params, auth.NeID)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + " order by imsi asc "
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ }
+
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// ClearAndInsert 清空ne_id后新增实体
+func (r *RepoUdmAuthUser) ClearAndInsert(neID string, authArr []model.UdmAuthUser) int64 {
+ var num int64 = 0
+
+ // 清空指定ne_id
+ _, 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 {
+ log.Errorf("TRUNCATE err => %v", err)
+ }
+
+ n := len(authArr)
+ batchSize := 2000
+ for i := 0; i < n; i += batchSize {
+ end := i + batchSize
+ if end > n {
+ end = n
+ }
+ batch := authArr[i:end]
+
+ // 调用 InsertMulti 函数将批量数据插入数据库
+ results, err := datasource.DefaultDB().Table("u_auth_user").InsertMulti(batch)
+ if err != nil {
+ log.Errorf("InsertMulti err => %v", err)
+ continue
+ }
+ num += results
+ }
+ return num
+}
+
+// Insert 新增实体
+func (r *RepoUdmAuthUser) Insert(authUser model.UdmAuthUser) int64 {
+ results, err := datasource.DefaultDB().Table("u_auth_user").Insert(authUser)
+ if err != nil {
+ log.Errorf("Insert err => %v", err)
+ return results
+ }
+ return results
+}
+
+// Insert 批量添加
+func (r *RepoUdmAuthUser) Inserts(authUsers []model.UdmAuthUser) int64 {
+ var num int64
+ n := len(authUsers)
+ batchSize := 2000
+ for i := 0; i < n; i += batchSize {
+ end := i + batchSize
+ if end > n {
+ end = n
+ }
+ batch := authUsers[i:end]
+
+ // 调用 InsertMulti 函数将批量数据插入数据库
+ results, err := datasource.DefaultDB().Table("u_auth_user").InsertMulti(batch)
+ if err != nil {
+ log.Errorf("Insert err => %v", err)
+ continue
+ }
+ num += results
+ }
+ return num
+}
+
+// Update 修改更新
+func (r *RepoUdmAuthUser) Update(neID string, authUser model.UdmAuthUser) int64 {
+ // 查询先
+ var user model.UdmAuthUser
+ has, err := datasource.DefaultDB().Table("u_auth_user").Where("imsi = ? and ne_id = ?", authUser.Imsi, neID).Get(&user)
+ if !has || err != nil {
+ return 0
+ }
+
+ if authUser.Ki != "" && authUser.Ki != user.Ki {
+ user.Ki = authUser.Ki
+ }
+ if authUser.Amf != "" && authUser.Amf != user.Amf {
+ user.Amf = authUser.Amf
+ }
+ if authUser.AlgoIndex != "" && authUser.AlgoIndex != user.AlgoIndex {
+ user.AlgoIndex = authUser.AlgoIndex
+ }
+ if authUser.Opc != "" && authUser.Opc != user.Opc {
+ user.Opc = authUser.Opc
+ }
+
+ results, err := datasource.DefaultDB().Table("u_auth_user").Where("imsi = ? and ne_id = ?", user.Imsi, user.NeID).Update(user)
+ if err != nil {
+ return 0
+ }
+ return results
+}
+
+// Delete 删除实体
+func (r *RepoUdmAuthUser) Delete(neID, imsi string) int64 {
+ results, err := datasource.DefaultDB().Table("u_auth_user").Where("imsi = ? and ne_id = ?", imsi, neID).Delete()
+ if err != nil {
+ return results
+ }
+ return results
+}
+
+// Delete 删除范围实体
+func (r *RepoUdmAuthUser) Deletes(neID, imsi, num string) int64 {
+ imsiV, err := strconv.Atoi(imsi)
+ if err != nil {
+ return 0
+ }
+
+ numV, err := strconv.Atoi(num)
+ if err != nil {
+ return 0
+ }
+
+ results, err := datasource.DefaultDB().Table("u_auth_user").Where("imsi >= ? and imsi < ? and ne_id = ?", imsiV, imsiV+numV, neID).Delete()
+ if err != nil {
+ return results
+ }
+ return results
+}
diff --git a/features/udm_user/repo/repo_udm_sub_user.go b/features/udm_user/repo/repo_udm_sub_user.go
new file mode 100644
index 0000000..e67761c
--- /dev/null
+++ b/features/udm_user/repo/repo_udm_sub_user.go
@@ -0,0 +1,450 @@
+package repo
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+
+ "nms_nbi/features/udm_user/model"
+ "nms_nbi/lib/core/datasource"
+ "nms_nbi/lib/core/utils/parse"
+ "nms_nbi/lib/log"
+)
+
+// 实例化数据层 RepoUdmSubUser 结构体
+var NewRepoUdmSubUser = &RepoUdmSubUser{
+ selectSql: `select
+ id, msisdn, imsi, ambr, nssai, rat, arfb, sar, cn, sm_data, smf_sel, eps_dat, ne_id, eps_flag, eps_odb, hplmn_odb, ard, epstpl, context_id, apn_context, static_ip
+ from u_sub_user`,
+
+ resultMap: map[string]string{
+ "id": "ID",
+ "msisdn": "Msisdn",
+ "imsi": "Imsi",
+ "ambr": "Ambr",
+ "nssai": "Nssai",
+ "rat": "Rat",
+ "arfb": "Arfb",
+ "sar": "Sar",
+ "cn": "Cn",
+ "sm_data": "SmData",
+ "smf_sel": "SmfSel",
+ "eps_dat": "EpsDat",
+ "ne_id": "NeID",
+ "eps_flag": "EpsFlag",
+ "eps_odb": "EpsOdb",
+ "hplmn_odb": "HplmnOdb",
+ "ard": "Ard",
+ "epstpl": "Epstpl",
+ "context_id": "ContextId",
+ "apn_context": "ApnContext",
+ "static_ip": "StaticIp",
+ },
+}
+
+// RepoUdmSubUser UDM签约用户 数据层处理
+type RepoUdmSubUser struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *RepoUdmSubUser) convertResultRows(rows []map[string]any) []model.UdmSubUser {
+ arr := make([]model.UdmSubUser, 0)
+ for _, row := range rows {
+ UdmUser := model.UdmSubUser{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ datasource.SetFieldValue(&UdmUser, keyMapper, value)
+ }
+ }
+ arr = append(arr, UdmUser)
+ }
+ return arr
+}
+
+// SelectPage 根据条件分页查询字典类型
+func (r *RepoUdmSubUser) SelectPage(query map[string]any) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if v, ok := query["msisdn"]; ok && v != "" {
+ conditions = append(conditions, "msisdn like concat(concat('%', ?), '%')")
+ params = append(params, v)
+ }
+ if v, ok := query["imsi"]; ok && v != "" {
+ conditions = append(conditions, "imsi like concat(concat('%', ?), '%')")
+ params = append(params, v)
+ }
+ if v, ok := query["neId"]; ok && v != "" {
+ conditions = append(conditions, "ne_id = ?")
+ params = append(params, v)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ result := map[string]any{
+ "total": 0,
+ "rows": []model.UdmAuthUser{},
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := "select count(1) as 'total' from u_sub_user"
+ totalRows, err := datasource.RawDB("", totalSql+whereSql, params)
+ if err != nil {
+ log.Errorf("total err => %v", err)
+ return result
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return result
+ } else {
+ result["total"] = total
+ }
+
+ // 分页
+ pageNum, pageSize := datasource.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 排序
+ sortSql := ""
+ if v, ok := query["sortField"]; ok && v != "" {
+ if v == "imsi" {
+ sortSql += " order by imsi "
+ }
+ if v == "msisdn" {
+ sortSql += " order by msisdn "
+ }
+ if o, ok := query["sortOrder"]; ok && o != nil && v != "" {
+ if o == "desc" {
+ sortSql += " desc "
+ } else {
+ sortSql += " asc "
+ }
+ }
+ }
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + sortSql + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ return result
+ }
+
+ // 转换实体
+ result["rows"] = r.convertResultRows(results)
+ return result
+}
+
+// SelectList 根据实体查询
+func (r *RepoUdmSubUser) SelectList(auth model.UdmSubUser) []model.UdmSubUser {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if auth.Imsi != "" {
+ conditions = append(conditions, "imsi = ?")
+ params = append(params, auth.Imsi)
+ }
+ if auth.NeID != "" {
+ conditions = append(conditions, "ne_id = ?")
+ params = append(params, auth.NeID)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + " order by imsi asc "
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ log.Errorf("query err => %v", err)
+ }
+
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// ClearAndInsert 清空ne_id后新增实体
+func (r *RepoUdmSubUser) ClearAndInsert(neID string, subArr []model.UdmSubUser) int64 {
+ var num int64 = 0
+
+ // 清空指定ne_id
+ _, 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 {
+ log.Errorf("TRUNCATE err => %v", err)
+ }
+
+ n := len(subArr)
+ batchSize := 2000
+ for i := 0; i < n; i += batchSize {
+ end := i + batchSize
+ if end > n {
+ end = n
+ }
+ batch := subArr[i:end]
+
+ // 调用 InsertMulti 函数将批量数据插入数据库
+ results, err := datasource.DefaultDB().Table("u_sub_user").InsertMulti(batch)
+ if err != nil {
+ log.Errorf("InsertMulti err => %v", err)
+ continue
+ }
+ num += results
+ }
+
+ // for _, u := range subArr {
+ // u.NeID = neID
+ // results, err := datasource.DefaultDB().Table("u_sub_user").Insert(u)
+ // if err != nil {
+ // return num
+ // }
+ // num += results
+ // }
+ return num
+}
+
+// Insert 新增实体
+func (r *RepoUdmSubUser) Insert(subUser model.UdmSubUser) int64 {
+ results, err := datasource.DefaultDB().Table("u_sub_user").Insert(subUser)
+ if err != nil {
+ log.Errorf("Insert err => %v", err)
+ return results
+ }
+ return results
+}
+
+// Insert 批量添加
+func (r *RepoUdmSubUser) Inserts(subUser []model.UdmSubUser) int64 {
+ var num int64
+ n := len(subUser)
+ batchSize := 2000
+ for i := 0; i < n; i += batchSize {
+ end := i + batchSize
+ if end > n {
+ end = n
+ }
+ batch := subUser[i:end]
+
+ // 调用 InsertMulti 函数将批量数据插入数据库
+ results, err := datasource.DefaultDB().Table("u_sub_user").InsertMulti(batch)
+ if err != nil {
+ log.Errorf("Insert err => %v", err)
+ continue
+ }
+ num += results
+ }
+ return num
+}
+
+// Insert4G 批量添加4G用户
+func (r *RepoUdmSubUser) Insert4G(neID string, subUser model.UdmSubUser) int64 {
+ var insertNum int64
+
+ imsiV, err := strconv.Atoi(subUser.Imsi)
+ if err != nil {
+ return 0
+ }
+ numV, err := strconv.Atoi(subUser.SubNum)
+ if err != nil {
+ return 0
+ }
+
+ subUser.NeID = neID
+ for i := 0; i < numV; i++ {
+ subUser.Imsi = fmt.Sprint(imsiV + i)
+
+ results, err := datasource.DefaultDB().Table("u_sub_user").Insert(subUser)
+ if err == nil {
+ log.Errorf("Insert err => %v", err)
+ insertNum += results
+ }
+ }
+
+ return insertNum
+}
+
+// Update 修改更新
+func (r *RepoUdmSubUser) Update(neID string, authUser model.UdmSubUser) int64 {
+ // 查询先
+ var user model.UdmSubUser
+ has, err := datasource.DefaultDB().Table("u_sub_user").Where("imsi = ? and ne_id = ?", authUser.Imsi, neID).Get(&user)
+ if !has || err != nil {
+ return 0
+ }
+
+ if authUser.Msisdn != "" && authUser.Msisdn != user.Msisdn {
+ user.Msisdn = authUser.Msisdn
+ }
+ if authUser.Ambr != "" && authUser.Ambr != user.Ambr {
+ user.Ambr = authUser.Ambr
+ }
+ if authUser.Arfb != "" && authUser.Arfb != user.Arfb {
+ user.Arfb = authUser.Arfb
+ }
+ if authUser.Sar != "" && authUser.Sar != user.Sar {
+ user.Sar = authUser.Sar
+ }
+ if authUser.Rat != "" && authUser.Rat != user.Rat {
+ user.Rat = authUser.Rat
+ }
+ if authUser.Cn != "" && authUser.Cn != user.Cn {
+ user.Cn = authUser.Cn
+ }
+ if authUser.SmfSel != "" && authUser.SmfSel != user.SmfSel {
+ user.SmfSel = authUser.SmfSel
+ }
+ if authUser.SmData != "" && authUser.SmData != user.SmData {
+ user.SmData = authUser.SmData
+ }
+ if authUser.EpsDat != "" && authUser.EpsDat != user.EpsDat {
+ user.EpsDat = authUser.EpsDat
+ }
+ if authUser.EpsFlag != "" && authUser.EpsFlag != user.EpsFlag {
+ user.EpsFlag = authUser.EpsFlag
+ }
+ if authUser.EpsOdb != "" && authUser.EpsDat != user.EpsDat {
+ user.EpsOdb = authUser.EpsOdb
+ }
+ if authUser.HplmnOdb != "" && authUser.HplmnOdb != user.HplmnOdb {
+ user.HplmnOdb = authUser.HplmnOdb
+ }
+ if authUser.Epstpl != "" && authUser.Epstpl != user.Epstpl {
+ user.Epstpl = authUser.Epstpl
+ }
+ if authUser.Ard != "" && authUser.Ard != user.Ard {
+ user.Ard = authUser.Ard
+ }
+ if authUser.ContextId != "" && authUser.ContextId != user.ContextId {
+ user.ContextId = authUser.ContextId
+ }
+ if authUser.ApnContext != "" && authUser.ApnContext != user.ApnContext {
+ user.ApnContext = authUser.ApnContext
+ }
+ if authUser.StaticIp != "" && authUser.StaticIp != user.StaticIp {
+ user.StaticIp = authUser.StaticIp
+ }
+
+ results, err := datasource.DefaultDB().Table("u_sub_user").Where("imsi = ? and ne_id = ?", user.Imsi, user.NeID).Update(user)
+ if err != nil {
+ log.Errorf("Update err => %v", err)
+ return 0
+ }
+ return results
+}
+
+// Update4GIP 批量修改4G IP
+func (r *RepoUdmSubUser) Update4GIP(neID string, subUser model.UdmSubUser) int64 {
+ var insertNum int64
+
+ imsiV, err := strconv.Atoi(subUser.Imsi)
+ if err != nil || subUser.StaticIp == "" {
+ return insertNum
+ }
+ numV, err := strconv.Atoi(subUser.SubNum)
+ if err != nil {
+ return insertNum
+ }
+
+ for i := 0; i < numV; i++ {
+ subUser.Imsi = fmt.Sprint(imsiV + i)
+
+ // 查询先
+ var user model.UdmSubUser
+ has, err := datasource.DefaultDB().Table("u_sub_user").Where("imsi = ? and ne_id = ?", subUser.Imsi, neID).Get(&user)
+ if has && err == nil {
+ // IP会自动递增
+ parts := strings.Split(subUser.StaticIp, ".")
+ lastPart := parts[3]
+ lastNum, _ := strconv.Atoi(lastPart)
+ lastNum += i
+ newLastPart := strconv.Itoa(lastNum)
+ parts[3] = newLastPart
+ newIP := strings.Join(parts, ".")
+ user.StaticIp = newIP
+ // 更新
+ results, err := datasource.DefaultDB().Table("u_sub_user").Update(user)
+ if err == nil {
+ log.Errorf("Update err => %v", err)
+ insertNum += results
+ }
+ }
+ }
+ return insertNum
+}
+
+// UpdateSmData 批量修改sm-data
+func (r *RepoUdmSubUser) UpdateSmData(neID string, subUser model.UdmSubUser) int64 {
+ var insertNum int64
+
+ imsiV, err := strconv.Atoi(subUser.Imsi)
+ if err != nil || subUser.StaticIp == "" {
+ return insertNum
+ }
+ numV, err := strconv.Atoi(subUser.SubNum)
+ if err != nil {
+ return insertNum
+ }
+
+ for i := 0; i < numV; i++ {
+ subUser.Imsi = fmt.Sprint(imsiV + i)
+
+ // 查询先
+ var user model.UdmSubUser
+ has, err := datasource.DefaultDB().Table("u_sub_user").Where("imsi = ? and ne_id = ?", subUser.Imsi, neID).Get(&user)
+ if has && err == nil {
+ // IP会自动递增,需提前规划好DNN对应的IP;如dnn不需要绑定IP则不带此字段名
+ // parts := strings.Split(subUser.SmData, "&")
+ user.SmData = subUser.SmData
+ // 更新
+ results, err := datasource.DefaultDB().Table("u_sub_user").Update(user)
+ if err == nil {
+ log.Errorf("Update err => %v", err)
+ insertNum += results
+ }
+ }
+ }
+ return insertNum
+}
+
+// Delete 删除实体
+func (r *RepoUdmSubUser) Delete(neID, imsi string) int64 {
+ results, err := datasource.DefaultDB().Table("u_sub_user").Where("imsi = ? and ne_id = ?", imsi, neID).Delete()
+ if err != nil {
+ log.Errorf("Delete err => %v", err)
+ return results
+ }
+ return results
+}
+
+// Delete 删除范围实体
+func (r *RepoUdmSubUser) Deletes(neID, imsi, num string) int64 {
+ imsiV, err := strconv.Atoi(imsi)
+ if err != nil {
+ return 0
+ }
+
+ numV, err := strconv.Atoi(num)
+ if err != nil {
+ return 0
+ }
+
+ results, err := datasource.DefaultDB().Table("u_sub_user").Where("imsi >= ? and imsi < ? and ne_id = ?", imsiV, imsiV+numV, neID).Delete()
+ if err != nil {
+ log.Errorf("Delete err => %v", err)
+ return results
+ }
+ return results
+}
diff --git a/features/udm_user/service/service_redis_data.go b/features/udm_user/service/service_redis_data.go
new file mode 100644
index 0000000..f5f864e
--- /dev/null
+++ b/features/udm_user/service/service_redis_data.go
@@ -0,0 +1,119 @@
+package service
+
+import (
+ "strings"
+
+ "nms_nbi/features/udm_user/model"
+ "nms_nbi/src/framework/redis"
+)
+
+// phoneImsiList 获取所有imsi
+// func phoneImsiList() map[string]string {
+// phoneAndImsiArr := make(map[string]string, 0)
+// phoneKeys, err := redis.GetKeys("udmuser", "1*********")
+// if err != nil {
+// return phoneAndImsiArr
+// }
+// for _, phone := range phoneKeys {
+// imsi, err := redis.Get("udmuser", phone)
+// if err != nil {
+// continue
+// }
+// phoneAndImsiArr[phone] = imsi
+// }
+// return phoneAndImsiArr
+// }
+
+// redisUdmAuthUserList UDM鉴权用户
+func redisUdmAuthUserList() []model.UdmAuthUser {
+ user := []model.UdmAuthUser{}
+ ausfArr, err := redis.GetKeys("udmuser", "ausf:*")
+ if err != nil {
+ return user
+ }
+ for _, key := range ausfArr {
+ m, err := redis.GetHash("udmuser", key)
+ if err != nil {
+ continue
+ }
+
+ // 跳过-号数据
+ imsi := key[5:]
+ if strings.Contains(imsi, "-") {
+ continue
+ }
+
+ status := "0"
+ if _, ok := m["auth_success"]; ok {
+ status = "1"
+ }
+ amf := ""
+ if v, ok := m["amf"]; ok {
+ amf = strings.Replace(v, "\r\n", "", 1)
+ }
+ a := model.UdmAuthUser{
+ Imsi: imsi,
+ Amf: amf,
+ Status: status,
+ Ki: m["ki"],
+ AlgoIndex: m["algo"],
+ Opc: m["opc"],
+ }
+ user = append(user, a)
+ }
+ return user
+}
+
+// redisUdmSubUserList UDM签约用户
+func redisUdmSubUserList() []model.UdmSubUser {
+ user := []model.UdmSubUser{}
+ udmsdArr, err := redis.GetKeys("udmuser", "udm-sd:*")
+ if err != nil {
+ return user
+ }
+ for _, key := range udmsdArr {
+ m, err := redis.GetHash("udmuser", key)
+ if err != nil {
+ continue
+ }
+
+ a := model.UdmSubUser{
+ Imsi: key[7:],
+ Msisdn: m["gpsi"], // 46003550072 strings.TrimPrefix(m["gpsi"], "86"),
+ SmfSel: m["smf-sel"],
+ SmData: m["sm-dat"], // 1-000001&cmnet&ims&3gnet
+ }
+
+ // def_ambr,def_nssai,0,def_arfb,def_sar,3,1,12000,1,1000,0,1,-
+ if v, ok := m["am-dat"]; ok {
+ arr := strings.Split(v, ",")
+ a.Ambr = arr[0]
+ a.Nssai = arr[1]
+ a.Rat = arr[2]
+ a.Arfb = arr[3]
+ a.Sar = arr[4]
+ a.Cn = arr[5]
+ }
+ // 1,64,24,65,def_eps,1,2,010200000000,-
+ if v, ok := m["eps-dat"]; ok {
+ arr := strings.Split(v, ",")
+ // 跳过非常规数据
+ if len(arr) > 9 {
+ continue
+ }
+ a.EpsDat = v
+ a.EpsFlag = arr[0]
+ a.EpsOdb = arr[1]
+ a.HplmnOdb = arr[2]
+ a.Ard = arr[3]
+ a.Epstpl = arr[4]
+ a.ContextId = arr[5]
+ a.ApnContext = arr[7]
+ // [6] 是不要的,导入和导出不用
+ a.StaticIp = arr[8]
+ }
+
+ user = append(user, a)
+ }
+ return user
+}
diff --git a/features/udm_user/service/service_udm_auth_user.go b/features/udm_user/service/service_udm_auth_user.go
new file mode 100644
index 0000000..59ad888
--- /dev/null
+++ b/features/udm_user/service/service_udm_auth_user.go
@@ -0,0 +1,151 @@
+package service
+
+import (
+ "fmt"
+ "strconv"
+
+ "nms_nbi/features/udm_user/model"
+ "nms_nbi/features/udm_user/repo"
+)
+
+// 实例化服务层 ServiceUdmAuthUser 结构体
+var NewServiceUdmAuthUser = &ServiceUdmAuthUser{
+ repoAuthUser: *repo.NewRepoUdmAuthUser,
+}
+
+// ServiceUdmAuthUser UDM鉴权用户 服务层处理
+type ServiceUdmAuthUser struct {
+ repoAuthUser repo.RepoUdmAuthUser
+}
+
+// Save UDM鉴权用户-获取全部保存数据库
+func (r *ServiceUdmAuthUser) Save(neID string) int64 {
+ var num int64 = 0
+ authArr := redisUdmAuthUserList()
+ // 有数据才清空
+ if len(authArr) == 0 {
+ return num
+ }
+ go r.repoAuthUser.ClearAndInsert(neID, authArr)
+ return int64(len(authArr))
+}
+
+// Page UDM签约用户-分页查询数据库
+func (r *ServiceUdmAuthUser) Page(query map[string]any) map[string]any {
+ return r.repoAuthUser.SelectPage(query)
+}
+
+// List UDM签约用户-查询数据库
+func (r *ServiceUdmAuthUser) List(authUser model.UdmAuthUser) []model.UdmAuthUser {
+ return r.repoAuthUser.SelectList(authUser)
+}
+
+// Insert UDM鉴权用户-新增单个
+// imsi长度15,ki长度32,opc长度0或者32
+func (r *ServiceUdmAuthUser) Insert(neID string, authUser model.UdmAuthUser) int64 {
+ authUser.NeID = neID
+ authUser.Status = "0"
+ return r.repoAuthUser.Insert(authUser)
+}
+
+// Insert UDM鉴权用户-批量添加
+func (r *ServiceUdmAuthUser) Inserts(neID string, authUser model.UdmAuthUser, num string) int64 {
+ var arr []model.UdmAuthUser
+
+ imsiVlen := len(authUser.Imsi)
+ imsiV, err := strconv.Atoi(authUser.Imsi)
+ if err != nil {
+ return 0
+ }
+
+ numV, err := strconv.Atoi(num)
+ if err != nil {
+ return 0
+ }
+
+ authUser.NeID = neID
+ authUser.Status = "0"
+ for i := 0; i < numV; i++ {
+ imsi := fmt.Sprint(imsiV + i)
+ if len(imsi) < imsiVlen {
+ imsi = fmt.Sprintf("%0*s", imsiVlen, imsi)
+ }
+ authUser.Imsi = imsi
+ arr = append(arr, authUser)
+ }
+
+ return r.repoAuthUser.Inserts(arr)
+}
+
+// InsertCSV UDM鉴权用户-批量添加
+func (r *ServiceUdmAuthUser) InsertCSV(neID string, data []map[string]string) int64 {
+ var arr []model.UdmAuthUser
+ for _, v := range data {
+ var authUser model.UdmAuthUser
+ authUser.NeID = neID
+ authUser.Status = "0"
+ if s, ok := v["imsi"]; ok {
+ authUser.Imsi = s
+ }
+ if s, ok := v["ki"]; ok {
+ authUser.Ki = s
+ }
+ if s, ok := v["algo"]; ok {
+ authUser.AlgoIndex = s
+ }
+ if s, ok := v["amf"]; ok {
+ authUser.Amf = s
+ }
+ if s, ok := v["opc"]; ok {
+ authUser.Opc = s
+ }
+
+ if authUser.Imsi == "" {
+ continue
+ }
+ arr = append(arr, authUser)
+ }
+ return r.repoAuthUser.Inserts(arr)
+}
+
+// InsertTxt UDM鉴权用户-批量添加
+func (r *ServiceUdmAuthUser) InsertTxt(neID string, data [][]string) int64 {
+ var arr []model.UdmAuthUser
+ for _, v := range data {
+ if len(v) < 4 {
+ continue
+ }
+ var authUser model.UdmAuthUser
+ authUser.NeID = neID
+ authUser.Status = "0"
+ authUser.Imsi = v[0]
+ authUser.Ki = v[1]
+ authUser.AlgoIndex = v[2]
+ authUser.Amf = v[3]
+ if len(v) == 5 {
+ authUser.Opc = v[4]
+ }
+
+ if authUser.Imsi == "" {
+ continue
+ }
+ arr = append(arr, authUser)
+ }
+
+ return r.repoAuthUser.Inserts(arr)
+}
+
+// Insert UDM鉴权用户-修改更新
+func (r *ServiceUdmAuthUser) Update(neID string, authUser model.UdmAuthUser) int64 {
+ return r.repoAuthUser.Update(neID, authUser)
+}
+
+// Insert UDM鉴权用户-删除单个
+func (r *ServiceUdmAuthUser) Delete(neID, imsi string) int64 {
+ return r.repoAuthUser.Delete(neID, imsi)
+}
+
+// Insert UDM鉴权用户-删除范围
+func (r *ServiceUdmAuthUser) Deletes(neID, imsi, num string) int64 {
+ return r.repoAuthUser.Deletes(neID, imsi, num)
+}
diff --git a/features/udm_user/service/service_udm_sub_user.go b/features/udm_user/service/service_udm_sub_user.go
new file mode 100644
index 0000000..4e8f9b2
--- /dev/null
+++ b/features/udm_user/service/service_udm_sub_user.go
@@ -0,0 +1,231 @@
+package service
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+
+ "nms_nbi/features/udm_user/model"
+ "nms_nbi/features/udm_user/repo"
+)
+
+// 实例化服务层 ServiceUdmSubUser 结构体
+var NewServiceUdmSubUser = &ServiceUdmSubUser{
+ repoSunUser: *repo.NewRepoUdmSubUser,
+}
+
+// ServiceUdmSubUser UDM签约用户 服务层处理
+type ServiceUdmSubUser struct {
+ repoSunUser repo.RepoUdmSubUser
+}
+
+// Save UDM签约用户-获取全部保存数据库
+func (r *ServiceUdmSubUser) Save(neID string) int64 {
+ var num int64 = 0
+ subArr := redisUdmSubUserList()
+ // 有数据才清空
+ if len(subArr) == 0 {
+ return num
+ }
+ go r.repoSunUser.ClearAndInsert(neID, subArr)
+ return int64(len(subArr))
+}
+
+// Page UDM签约用户-分页查询数据库
+func (r *ServiceUdmSubUser) Page(query map[string]any) map[string]any {
+ return r.repoSunUser.SelectPage(query)
+}
+
+// List UDM签约用户-查询数据库
+func (r *ServiceUdmSubUser) List(subUser model.UdmSubUser) []model.UdmSubUser {
+ return r.repoSunUser.SelectList(subUser)
+}
+
+// Insert UDM签约用户-新增单个
+// imsi长度15,ki长度32,opc长度0或者32
+func (r *ServiceUdmSubUser) Insert(neID string, subUser model.UdmSubUser) int64 {
+ subUser.NeID = neID
+ return r.repoSunUser.Insert(subUser)
+}
+
+// Insert UDM签约用户-批量添加
+func (r *ServiceUdmSubUser) Inserts(neID string, subUser model.UdmSubUser, num string) int64 {
+ var arr []model.UdmSubUser
+
+ imsiVlen := len(subUser.Imsi)
+ imsiV, err := strconv.Atoi(subUser.Imsi)
+ if err != nil {
+ return 0
+ }
+ msisdnVlen := len(subUser.Msisdn)
+ msisdnV, err := strconv.Atoi(subUser.Msisdn)
+ if err != nil {
+ return 0
+ }
+
+ numV, err := strconv.Atoi(num)
+ if err != nil {
+ return 0
+ }
+
+ subUser.NeID = neID
+ for i := 0; i < numV; i++ {
+ msisdn := fmt.Sprint(msisdnV + i)
+ if len(msisdn) < msisdnVlen {
+ msisdn = fmt.Sprintf("%0*s", msisdnVlen, msisdn)
+ }
+ subUser.Msisdn = msisdn
+
+ imsi := fmt.Sprint(imsiV + i)
+ if len(imsi) < imsiVlen {
+ imsi = fmt.Sprintf("%0*s", imsiVlen, imsi)
+ }
+ subUser.Imsi = imsi
+
+ // IP会自动递增
+ if len(subUser.StaticIp) > 5 {
+ parts := strings.Split(subUser.StaticIp, ".")
+ lastPart := parts[3]
+ lastNum, _ := strconv.Atoi(lastPart)
+ lastNum += i
+ newLastPart := strconv.Itoa(lastNum)
+ parts[3] = newLastPart
+ newIP := strings.Join(parts, ".")
+ subUser.StaticIp = newIP
+ }
+
+ subUser.EpsDat = fmt.Sprintf("%s,%s,%s,%s,%s,%s,2,%s,%s", subUser.EpsFlag, subUser.EpsOdb, subUser.HplmnOdb, subUser.Ard, subUser.Epstpl, subUser.ContextId, subUser.ApnContext, subUser.StaticIp)
+
+ arr = append(arr, subUser)
+ }
+
+ return r.repoSunUser.Inserts(arr)
+}
+
+// InsertCSV UDM签约用户-批量添加
+func (r *ServiceUdmSubUser) InsertCSV(neID string, data []map[string]string) int64 {
+ var arr []model.UdmSubUser
+ for _, v := range data {
+ var subUser model.UdmSubUser
+ subUser.NeID = neID
+ if s, ok := v["imsi"]; ok {
+ subUser.Imsi = s
+ }
+ if s, ok := v["msisdn"]; ok {
+ subUser.Msisdn = s
+ }
+ if s, ok := v["ambr"]; ok {
+ subUser.Ambr = s
+ }
+ if s, ok := v["nssai"]; ok {
+ subUser.Nssai = s
+ }
+ if s, ok := v["arfb"]; ok {
+ subUser.Arfb = s
+ }
+ if s, ok := v["sar"]; ok {
+ subUser.Sar = s
+ }
+ if s, ok := v["rat"]; ok {
+ subUser.Rat = s
+ }
+ if s, ok := v["cn"]; ok {
+ subUser.Cn = s
+ }
+ if s, ok := v["smf_sel"]; ok {
+ subUser.SmfSel = s
+ }
+ if s, ok := v["sm_dat"]; ok {
+ subUser.SmData = s
+ }
+ if s, ok := v["eps_dat"]; ok {
+ subUser.EpsDat = s
+ arr := strings.Split(s, ",")
+ if len(arr) == 8 {
+ subUser.EpsFlag = arr[0]
+ subUser.EpsOdb = arr[1]
+ subUser.HplmnOdb = arr[2]
+ subUser.Ard = arr[3]
+ subUser.Epstpl = arr[4]
+ subUser.ContextId = arr[5]
+ subUser.ApnContext = arr[6]
+ subUser.StaticIp = arr[7]
+ }
+ }
+
+ if subUser.Imsi == "" {
+ continue
+ }
+ arr = append(arr, subUser)
+ }
+ return r.repoSunUser.Inserts(arr)
+}
+
+// InsertTxt UDM签约用户-批量添加
+func (r *ServiceUdmSubUser) InsertTxt(neID string, data [][]string) int64 {
+ var arr []model.UdmSubUser
+ for _, v := range data {
+ if len(v) < 10 {
+ continue
+ }
+ var subUser model.UdmSubUser
+ subUser.NeID = neID
+ subUser.Imsi = v[0]
+ subUser.Msisdn = v[1]
+ subUser.Ambr = v[2]
+ subUser.Nssai = v[3]
+ subUser.Arfb = v[4]
+ subUser.Sar = v[5]
+ subUser.Rat = v[6]
+ subUser.Cn = v[7]
+ subUser.SmfSel = v[8]
+ subUser.SmData = v[9]
+ if len(v) > 17 {
+ subUser.EpsFlag = v[10]
+ subUser.EpsOdb = v[11]
+ subUser.HplmnOdb = v[12]
+ subUser.Ard = v[13]
+ subUser.Epstpl = v[14]
+ subUser.ContextId = v[15]
+ subUser.ApnContext = v[16]
+ subUser.StaticIp = v[17]
+ subUser.EpsDat = strings.Join(v[10:], ",")
+ }
+
+ if subUser.Imsi == "" {
+ continue
+ }
+ arr = append(arr, subUser)
+ }
+ return r.repoSunUser.Inserts(arr)
+}
+
+// Insert UDM签约用户-批量添加4G用户
+func (r *ServiceUdmSubUser) Insert4G(neID string, subUser model.UdmSubUser) int64 {
+ return r.repoSunUser.Insert4G(neID, subUser)
+}
+
+// Insert UDM签约用户-修改更新
+func (r *ServiceUdmSubUser) Update(neID string, subUser model.UdmSubUser) int64 {
+ return r.repoSunUser.Update(neID, subUser)
+}
+
+// Update4GIP UDM签约用户-批量修改4G IP
+func (r *ServiceUdmSubUser) Update4GIP(neID string, subUser model.UdmSubUser) int64 {
+ return r.repoSunUser.Update4GIP(neID, subUser)
+}
+
+// Update4GIP UDM签约用户-批量修改sm-data
+func (r *ServiceUdmSubUser) UpdateSmData(neID string, subUser model.UdmSubUser) int64 {
+ return r.repoSunUser.UpdateSmData(neID, subUser)
+}
+
+// Insert UDM签约用户-删除单个
+func (r *ServiceUdmSubUser) Delete(neID, imsi string) int64 {
+ return r.repoSunUser.Delete(neID, imsi)
+}
+
+// Insert UDM签约用户-删除范围
+func (r *ServiceUdmSubUser) Deletes(neID, imsi, num string) int64 {
+ return r.repoSunUser.Deletes(neID, imsi, num)
+}
diff --git a/features/ue/ue.go b/features/ue/ue.go
new file mode 100644
index 0000000..2ed92d4
--- /dev/null
+++ b/features/ue/ue.go
@@ -0,0 +1,789 @@
+package ue
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "strings"
+ "time"
+
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/global"
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/services"
+ "nms_nbi/restagent/config"
+ tokenConst "nms_nbi/src/framework/constants/token"
+
+ "github.com/go-resty/resty/v2"
+ "github.com/gorilla/mux"
+)
+
+// AmfNBInfo AMF的NodeB信息
+type AmfNBInfo struct {
+ ID string `json:"id"` //NodeB ID
+ Name string `json:"name"` // NodeB name
+ Address string `json:"address"` // 基站地址
+ UENum int `jons:"ueNum"` // UE数量
+}
+
+// SmfUENum SMF在线用户数
+type SmfUENum struct {
+ UENum int `json:"ueNum"` // 当前在线用户数
+}
+
+// SmfUEInfo SMF在线用户信息
+type SmfUEInfo struct {
+ IMSI string `json:"imsi"`
+ MSISDN string `json:"msisdn"`
+ RatType string `json:"ratType"`
+ PduSessionInfo []struct {
+ PduSessionID int `json:"pduSessionID"`
+ IPv4 string `json:"ipv4"`
+ IPv6 string `json:"ipv6"`
+ Dnn string `json:"dnn"`
+ Tai string `json:"tai"`
+ SstSD string `json:"sstSD"`
+ UpfN3IP string `json:"upfN3IP"`
+ RanN3IP string `json:"ranN3IP"`
+ Activetime string `json:"activeTime"`
+ } `json:"pduSessionInfo"`
+}
+
+// ImsUEInfo IMS在线用户信息
+type ImsUEInfo struct {
+ IMSI string `json:"imsi"`
+ MSISDN string `json:"msisdn"`
+ IMPU string `json:"impu"`
+ Barring int `json:"barring"`
+ RegState int `json:"regState"`
+ Activetime string `json:"activeTime"`
+}
+
+// N3iwfUEInfo N3IWF在线用户信息
+type N3iwfUEInfo struct {
+ IMSI string `json:"imsi"`
+
+ NAI string `json:"nai"`
+ RegState int `json:"regState"`
+ Activetime string `json:"activeTime"`
+}
+
+type N3iwfUEData struct {
+ Data []N3iwfUEInfo `json:"data"`
+}
+
+var (
+ UriNBInfo = config.DefaultUriPrefix + "/ueManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/nbInfo"
+ UriUEInfo = config.DefaultUriPrefix + "/ueManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/ueInfo"
+ UriUENum = config.DefaultUriPrefix + "/ueManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/ueNum"
+ UriPCFUser = config.DefaultUriPrefix + "/ueManagement/{apiVersion}/elementType/pcf/objectType/ueInfo"
+ UriPCFUserM = config.DefaultUriPrefix + "/ueManagement/{apiVersion}/elementType/pcf/objectType/ueInfo/batch/{number}"
+ UriPCFUserFileImport = config.DefaultUriPrefix + "/ueManagement/{apiVersion}/elementType/pcf/objectType/ueInfo/file/import"
+ UriPCFUserFileExport = config.DefaultUriPrefix + "/ueManagement/{apiVersion}/elementType/pcf/objectType/ueInfo/file/export"
+ UriNSSFAvailableAMFs = config.DefaultUriPrefix + "/ueManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/availableAMFs"
+ UriNSSFSubscriptions = config.DefaultUriPrefix + "/ueManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/subscriptions"
+
+ CustomUriNBInfo = config.UriPrefix + "/ueManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/nbInfo"
+ CustomUriUEInfo = config.UriPrefix + "/ueManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/ueInfo"
+ CustomUriUENum = config.UriPrefix + "/ueManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/ueNum"
+ CustomUriPCFUser = config.UriPrefix + "/ueManagement/{apiVersion}/elementType/pcf/objectType/ueInfo"
+ CustomUriPCFUserM = config.UriPrefix + "/ueManagement/{apiVersion}/elementType/pcf/objectType/ueInfo/batch/{number}"
+ CustomUriPCFUserFileImport = config.UriPrefix + "/ueManagement/{apiVersion}/elementType/pcf/objectType/ueInfo/file/import"
+ CustomUriPCFUserFileExport = config.UriPrefix + "/ueManagement/{apiVersion}/elementType/pcf/objectType/ueInfo/file/export"
+ CustomUriNSSFAvailableAMFs = config.UriPrefix + "/ueManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/availableAMFs"
+ CustomUriNSSFSubscriptions = config.UriPrefix + "/ueManagement/{apiVersion}/elementType/{elementTypeValue}/objectType/subscriptions"
+)
+
+var client = resty.New()
+
+func init() {
+ /*
+ client.
+ SetTimeout(10 * time.Second).
+ SetRetryCount(1).
+ SetRetryWaitTime(1 * time.Second).
+ SetRetryMaxWaitTime(2 * time.Second).
+ SetRetryAfter(func(client *resty.Client, resp *resty.Response) (time.Duration, error) {
+ return 0, errors.New("quota exceeded")
+ })
+ */
+ client.SetTimeout(500 * time.Millisecond)
+}
+
+// Get AvailableAMFs from NSSF
+func GetAvailableAMFsFromNSSF(w http.ResponseWriter, r *http.Request) {
+ log.Info("GetAvailableAMFsFromNSSF processing... ")
+
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+ if strings.ToLower(neType) != "nssf" {
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ //neTypeLower := strings.ToLower(neType)
+ var neId string
+ neIds := services.GetParamsArrByName("neId", r)
+ if len(neIds) == 1 {
+ neId = neIds[0]
+ } else {
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ token, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+ log.Debug("token:", token)
+
+ neInfo, err := dborm.XormGetNeInfo(neType, neId)
+ if err != nil {
+ log.Error("Failed to XormGetNeInfo:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else if neInfo == nil {
+ err := global.ErrCMNotFoundTargetNE
+ log.Error(global.ErrCMNotFoundTargetNE)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Trace("neInfo:", neInfo)
+
+ hostUri := fmt.Sprintf("http://%s:%v", neInfo.Ip, neInfo.Port)
+ requestURI2NF := fmt.Sprintf("%s%s", hostUri, r.RequestURI)
+
+ log.Debug("requestURI2NF:", requestURI2NF)
+
+ resp, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}).
+ SetHeaders(map[string]string{"accessToken": token}).
+ 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)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else {
+ var response services.MapResponse
+ _ = json.Unmarshal(resp.Body(), &response)
+ services.ResponseWithJson(w, resp.StatusCode(), response)
+ return
+ }
+}
+
+// Get Subscriptions from NSSF
+func GetSubscriptionsFromNSSF(w http.ResponseWriter, r *http.Request) {
+ log.Info("GetSubscriptionsFromNSSF processing... ")
+
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+ if strings.ToLower(neType) != "nssf" {
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ //neTypeLower := strings.ToLower(neType)
+ var neId string
+ neIds := services.GetParamsArrByName("neId", r)
+ if len(neIds) == 1 {
+ neId = neIds[0]
+ } else {
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ token, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+ log.Debug("token:", token)
+
+ neInfo, err := dborm.XormGetNeInfo(neType, neId)
+ if err != nil {
+ log.Error("Failed to XormGetNeInfo:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else if neInfo == nil {
+ err := global.ErrCMNotFoundTargetNE
+ log.Error(global.ErrCMNotFoundTargetNE)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Trace("neInfo:", neInfo)
+
+ hostUri := fmt.Sprintf("http://%s:%v", neInfo.Ip, neInfo.Port)
+ requestURI2NF := fmt.Sprintf("%s%s", hostUri, r.RequestURI)
+
+ log.Debug("requestURI2NF:", requestURI2NF)
+
+ resp, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}).
+ SetHeaders(map[string]string{"accessToken": token}).
+ 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)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else {
+ var response services.MapResponse
+ _ = json.Unmarshal(resp.Body(), &response)
+ services.ResponseWithJson(w, resp.StatusCode(), response)
+ return
+ }
+}
+
+// Get UEInfo from NF/NFs
+func GetUEInfoFromNF(w http.ResponseWriter, r *http.Request) {
+ log.Info("GetUEInfoFromNF processing... ")
+
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+ if neType == "" {
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ //neTypeLower := strings.ToLower(neType)
+ var neId string
+ neIds := services.GetParamsArrByName("neId", r)
+ if len(neIds) == 1 {
+ neId = neIds[0]
+ } else {
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ token, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+ log.Debug("token:", token)
+
+ neInfo, err := dborm.XormGetNeInfo(neType, neId)
+ if err != nil {
+ log.Error("Failed to XormGetNeInfo:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else if neInfo == nil {
+ err := global.ErrCMNotFoundTargetNE
+ log.Error(global.ErrCMNotFoundTargetNE)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Trace("neInfo:", neInfo)
+
+ hostUri := fmt.Sprintf("http://%s:%v", neInfo.Ip, neInfo.Port)
+ requestURI2NF := fmt.Sprintf("%s%s", hostUri, r.RequestURI)
+
+ log.Debug("requestURI2NF:", requestURI2NF)
+
+ resp, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}).
+ SetHeaders(map[string]string{"accessToken": token}).
+ 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)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else {
+ var response services.MapResponse
+ _ = json.Unmarshal(resp.Body(), &response)
+ services.ResponseWithJson(w, resp.StatusCode(), response)
+ return
+ }
+}
+
+// POST User Info from NF/NFs
+func PostPCFUserInfo(w http.ResponseWriter, r *http.Request) {
+ log.Info("PostPCFUserInfo processing... ")
+
+ // vars := mux.Vars(r)
+ // neType := vars["elementTypeValue"]
+ // if neType == "" {
+ // services.ResponseNotFound404UriNotExist(w, r)
+ // return
+ // }
+ //neTypeLower := strings.ToLower(neType)
+ neType := "PCF"
+ var neId string
+ neIds := services.GetParamsArrByName("neId", r)
+ if len(neIds) == 1 {
+ neId = neIds[0]
+ } else {
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ // token, err := services.CheckFrontValidRequest(w, r)
+ // if err != nil {
+ // log.Error("Request error:", err)
+ // return
+ // }
+ // log.Debug("token:", token)
+
+ neInfo, err := dborm.XormGetNeInfo(neType, neId)
+ if err != nil {
+ log.Error("Failed to XormGetNeInfo:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else if neInfo == nil {
+ err := global.ErrCMNotFoundTargetNE
+ log.Error(global.ErrCMNotFoundTargetNE)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Trace("neInfo:", neInfo)
+
+ hostUri := fmt.Sprintf("http://%s:%v", neInfo.Ip, neInfo.Port)
+ requestURI2NF := fmt.Sprintf("%s%s", hostUri, r.RequestURI)
+
+ log.Debug("requestURI2NF:", requestURI2NF)
+
+ body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen)) //io.LimitReader限制大小
+ if err != nil {
+ log.Error("io.ReadAll is failed:", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ client.SetTimeout(1 * time.Minute)
+ resp, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}).
+ //SetHeaders(map[string]string{"accessToken": token}).
+ SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ SetBody(body).
+ Post(requestURI2NF)
+ if err != nil {
+ log.Error("Failed to post:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else {
+ var response services.MapResponse
+ _ = json.Unmarshal(resp.Body(), &response)
+ services.ResponseWithJson(w, resp.StatusCode(), response)
+ return
+ }
+}
+
+// PUT PCF User Info from NF/NFs
+func PutPCFUserInfo(w http.ResponseWriter, r *http.Request) {
+ log.Info("PutPCFUserInfo processing... ")
+
+ // vars := mux.Vars(r)
+ // neType := vars["elementTypeValue"]
+ // if neType == "" {
+ // services.ResponseNotFound404UriNotExist(w, r)
+ // return
+ // }
+ //neTypeLower := strings.ToLower(neType)
+ neType := "PCF"
+ var neId string
+ neIds := services.GetParamsArrByName("neId", r)
+ if len(neIds) == 1 {
+ neId = neIds[0]
+ } else {
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ // token, err := services.CheckFrontValidRequest(w, r)
+ // if err != nil {
+ // log.Error("Request error:", err)
+ // return
+ // }
+ // log.Debug("token:", token)
+
+ neInfo, err := dborm.XormGetNeInfo(neType, neId)
+ if err != nil {
+ log.Error("Failed to XormGetNeInfo:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else if neInfo == nil {
+ err := global.ErrCMNotFoundTargetNE
+ log.Error(global.ErrCMNotFoundTargetNE)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Trace("neInfo:", neInfo)
+
+ hostUri := fmt.Sprintf("http://%s:%v", neInfo.Ip, neInfo.Port)
+ requestURI2NF := fmt.Sprintf("%s%s", hostUri, r.RequestURI)
+
+ log.Debug("requestURI2NF:", requestURI2NF)
+
+ body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen)) //io.LimitReader限制大小
+ if err != nil {
+ log.Error("io.ReadAll is failed:", err)
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ client.SetTimeout(1 * time.Minute)
+ resp, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}).
+ //SetHeaders(map[string]string{"accessToken": token}).
+ SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ SetBody(body).
+ Put(requestURI2NF)
+ if err != nil {
+ log.Error("Failed to put:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else {
+ var response services.MapResponse
+ _ = json.Unmarshal(resp.Body(), &response)
+ services.ResponseWithJson(w, resp.StatusCode(), response)
+ return
+ }
+}
+
+// Get UEInfo from NF/NFs
+func DeletePCFUserInfo(w http.ResponseWriter, r *http.Request) {
+ log.Info("DeletePCFUserInfo processing... ")
+
+ // vars := mux.Vars(r)
+ // neType := vars["elementTypeValue"]
+ // if neType == "" {
+ // services.ResponseNotFound404UriNotExist(w, r)
+ // return
+ // }
+ //neTypeLower := strings.ToLower(neType)
+ neType := "PCF"
+ var neId string
+ neIds := services.GetParamsArrByName("neId", r)
+ if len(neIds) == 1 {
+ neId = neIds[0]
+ } else {
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ // token, err := services.CheckFrontValidRequest(w, r)
+ // if err != nil {
+ // log.Error("Request error:", err)
+ // return
+ // }
+ // log.Debug("token:", token)
+
+ neInfo, err := dborm.XormGetNeInfo(neType, neId)
+ if err != nil {
+ log.Error("Failed to XormGetNeInfo:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else if neInfo == nil {
+ err := global.ErrCMNotFoundTargetNE
+ log.Error(global.ErrCMNotFoundTargetNE)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Trace("neInfo:", neInfo)
+
+ hostUri := fmt.Sprintf("http://%s:%v", neInfo.Ip, neInfo.Port)
+ requestURI2NF := fmt.Sprintf("%s%s", hostUri, r.RequestURI)
+
+ log.Debug("requestURI2NF:", requestURI2NF)
+
+ resp, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}).
+ //SetHeaders(map[string]string{"accessToken": token}).
+ SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ Delete(requestURI2NF)
+ if err != nil {
+ log.Error("Failed to delete:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else {
+ w.WriteHeader(resp.StatusCode())
+ w.Write(resp.Body())
+ // var response services.MapResponse
+ // _ = json.Unmarshal(resp.Body(), &response)
+ // services.ResponseWithJson(w, resp.StatusCode(), response)
+ return
+ }
+}
+
+// Get UEInfo from SMF
+func GetUENumFromNF(w http.ResponseWriter, r *http.Request) {
+ log.Info("GetUENumFromNF processing... ")
+
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+ if neType == "" {
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ //neTypeLower := strings.ToLower(neType)
+ var neId string
+ neIds := services.GetParamsArrByName("neId", r)
+ if len(neIds) == 1 {
+ neId = neIds[0]
+ } else {
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ token, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+ log.Debug("token:", token)
+
+ neInfo, err := dborm.XormGetNeInfo(neType, neId)
+ if err != nil {
+ log.Error("Failed to XormGetNeInfo:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else if neInfo == nil {
+ err := global.ErrCMNotFoundTargetNE
+ log.Error(global.ErrCMNotFoundTargetNE)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Trace("neInfo:", neInfo)
+
+ hostUri := fmt.Sprintf("http://%s:%v", neInfo.Ip, neInfo.Port)
+ requestURI2NF := fmt.Sprintf("%s%s", hostUri, r.RequestURI)
+
+ log.Debug("requestURI2NF:", requestURI2NF)
+
+ resp, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}).
+ SetHeaders(map[string]string{"accessToken": token}).
+ 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)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else {
+ var response services.MapResponse
+ _ = json.Unmarshal(resp.Body(), &response)
+ services.ResponseWithJson(w, resp.StatusCode(), response)
+ return
+ }
+}
+
+// Get Radio Info from NF/NFs
+func GetNBInfoFromNF(w http.ResponseWriter, r *http.Request) {
+ log.Info("GetNBInfoFromNF processing... ")
+
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+ if neType == "" {
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ //neTypeLower := strings.ToLower(neType)
+ var neId string
+ neIds := services.GetParamsArrByName("neId", r)
+ if len(neIds) == 1 {
+ neId = neIds[0]
+ } else {
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ token, err := services.CheckFrontValidRequest(w, r)
+ if err != nil {
+ log.Error("Request error:", err)
+ return
+ }
+ log.Debug("token:", token)
+
+ neInfo, err := dborm.XormGetNeInfo(neType, neId)
+ if err != nil {
+ log.Error("Failed to XormGetNeInfo:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else if neInfo == nil {
+ err := global.ErrCMNotFoundTargetNE
+ log.Error(global.ErrCMNotFoundTargetNE)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Trace("neInfo:", neInfo)
+
+ hostUri := fmt.Sprintf("http://%s:%v", neInfo.Ip, neInfo.Port)
+ requestURI2NF := fmt.Sprintf("%s%s", hostUri, r.RequestURI)
+
+ log.Debug("requestURI2NF:", requestURI2NF)
+
+ resp, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}).
+ SetHeaders(map[string]string{"accessToken": token}).
+ 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)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else {
+ var response services.MapResponse
+ _ = json.Unmarshal(resp.Body(), &response)
+ services.ResponseWithJson(w, resp.StatusCode(), response)
+ return
+ }
+}
+
+// Get Radio Info from NF/NFs
+func GetNBInfoAllFromNF(w http.ResponseWriter, r *http.Request) {
+ log.Info("GetNBInfoAllFromNF processing... ")
+
+ vars := mux.Vars(r)
+ neType := vars["elementTypeValue"]
+ if neType == "" {
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+ //neTypeLower := strings.ToLower(neType)
+ // var neID string
+ neIDs := services.GetParamsArrByName("neId", r)
+ // if len(neIDs) == 1 {
+ // neID = neIDs[0]
+ // } else {
+ // services.ResponseNotFound404UriNotExist(w, r)
+ // return
+ // }
+
+ // token, err := services.CheckFrontValidRequest(w, r)
+ // if err != nil {
+ // log.Error("Request error:", err)
+ // return
+ // }
+ // log.Debug("token:", token)
+
+ //var ret error
+ var statusCode int = 500
+ var dataResponse []services.MapResponse
+ var neInfos []dborm.NeInfo
+ dborm.XormGetNeInfo2(neType, neIDs, &neInfos)
+ for _, neInfo := range neInfos {
+ // neInfo, err := dborm.XormGetNeInfo(neType, neID)
+ // if err != nil {
+ // log.Error("Failed to XormGetNeInfo:", err)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // } else if neInfo == nil {
+ // err := global.ErrCMNotFoundTargetNE
+ // log.Error(global.ErrCMNotFoundTargetNE)
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ // }
+
+ hostUri := fmt.Sprintf("http://%s:%v", neInfo.Ip, neInfo.Port)
+ requestURI2NF := fmt.Sprintf("%s%s", hostUri, r.RequestURI)
+
+ log.Debug("requestURI2NF:", requestURI2NF)
+
+ resp, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}).
+ //SetHeaders(map[string]string{"accessToken": token}).
+ 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("Failed to GET:", err)
+ continue
+ // services.ResponseInternalServerError500ProcessError(w, err)
+ // return
+ } else {
+ switch resp.StatusCode() {
+ case http.StatusOK, http.StatusCreated, http.StatusNoContent, http.StatusAccepted:
+ var response services.MapResponse
+ _ = json.Unmarshal(resp.Body(), &response)
+ dataResponse = append(dataResponse, response)
+ statusCode = http.StatusOK
+ }
+ }
+ }
+ var response services.DataResponse
+ response.Data = dataResponse
+ services.ResponseWithJson(w, statusCode, response)
+}
+
+// Get GetUEInfoFileExportNF from NF/NFs
+func GetUEInfoFileExportNF(w http.ResponseWriter, r *http.Request) {
+ log.Info("GetUEInfoFromNF processing... ")
+
+ // vars := mux.Vars(r)
+ // neType := vars["elementTypeValue"]
+ // if neType == "" {
+ // services.ResponseNotFound404UriNotExist(w, r)
+ // return
+ // }
+ //neTypeLower := strings.ToLower(neType)
+ var neId string
+ neIds := services.GetParamsArrByName("neId", r)
+ if len(neIds) == 1 {
+ neId = neIds[0]
+ } else {
+ services.ResponseNotFound404UriNotExist(w, r)
+ return
+ }
+
+ // token, err := services.CheckFrontValidRequest(w, r)
+ // if err != nil {
+ // log.Error("Request error:", err)
+ // return
+ // }
+ // log.Debug("token:", token)
+
+ neInfo, err := dborm.XormGetNeInfo("PCF", neId)
+ if err != nil {
+ log.Error("Failed to XormGetNeInfo:", err)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else if neInfo == nil {
+ err := global.ErrCMNotFoundTargetNE
+ log.Error(global.ErrCMNotFoundTargetNE)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Trace("neInfo:", neInfo)
+
+ hostUri := fmt.Sprintf("http://%s:%v", neInfo.Ip, neInfo.Port)
+ requestURI2NF := fmt.Sprintf("%s%s", hostUri, r.RequestURI)
+
+ log.Debug("requestURI2NF:", requestURI2NF)
+
+ client.SetTimeout(3 * time.Minute)
+ resp, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}).
+ // SetHeaders(map[string]string{"accessToken": token}).
+ 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)
+ services.ResponseInternalServerError500ProcessError(w, err)
+ return
+ } else {
+ w.WriteHeader(resp.StatusCode())
+ w.Write(resp.Body())
+ // var response services.MapResponse
+ // _ = json.Unmarshal(resp.Body(), &response)
+ // services.ResponseWithJson(w, resp.StatusCode(), response)
+ return
+ }
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..c32ad50
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,148 @@
+module nms_nbi
+
+go 1.21
+
+toolchain go1.21.0
+
+require (
+ github.com/dgrijalva/jwt-go v3.2.0+incompatible
+ github.com/dlclark/regexp2 v1.10.0
+ github.com/gin-gonic/gin v1.9.1
+ github.com/go-admin-team/go-admin-core/sdk v1.5.1
+ github.com/go-resty/resty/v2 v2.7.0
+ github.com/go-sql-driver/mysql v1.7.1
+ github.com/golang-jwt/jwt/v5 v5.0.0
+ github.com/gorilla/mux v1.8.0
+ github.com/gorilla/websocket v1.5.0
+ github.com/gosnmp/gosnmp v1.35.0
+ github.com/jasonlvhit/gocron v0.0.1
+ github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f
+ github.com/matoous/go-nanoid/v2 v2.0.0
+ github.com/metaleap/go-xsd v0.0.0-20180330193350-61f7638f502f
+ github.com/mojocn/base64Captcha v1.3.5
+ github.com/mssola/user_agent v0.6.0
+ github.com/patrickmn/go-cache v2.1.0+incompatible
+ github.com/redis/go-redis/v9 v9.1.0
+ github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e
+ github.com/robfig/cron/v3 v3.0.1
+ github.com/shirou/gopsutil v3.21.11+incompatible
+ github.com/shirou/gopsutil/v3 v3.23.7
+ github.com/spf13/afero v1.9.5
+ github.com/spf13/viper v1.16.0
+ github.com/xuri/excelize/v2 v2.7.1
+ github.com/xuri/xgen v0.0.0-20230702070049-db840e1a4605
+ github.com/ziutek/telnet v0.0.0-20180329124119-c3b780dc415b
+ golang.org/x/crypto v0.16.0
+ golang.org/x/term v0.15.0
+ gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
+ gopkg.in/yaml.v3 v3.0.1
+ gorm.io/driver/mysql v1.5.1
+ gorm.io/gorm v1.25.2
+ xorm.io/xorm v1.3.2
+)
+
+require (
+ github.com/chzyer/readline v1.5.1 // indirect
+ github.com/creack/pty v1.1.9 // indirect
+ github.com/go-admin-team/go-admin-core v1.3.12-0.20221121065133-27b7dbe27a8f // indirect
+ github.com/go-gsm/charset v1.0.0 // indirect
+ github.com/go-gsm/ucp v0.0.1 // indirect
+ github.com/linxGnu/gosmpp v0.2.0 // indirect
+ golang.org/x/time v0.1.0 // indirect
+)
+
+require (
+ github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect
+ github.com/bsm/redislock v0.8.2 // indirect
+ github.com/bytedance/go-tagexpr/v2 v2.7.12 // indirect
+ github.com/bytedance/sonic v1.9.1 // indirect
+ github.com/casbin/casbin/v2 v2.54.0 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/chanxuehong/rand v0.0.0-20201110082127-2f19a1bdd973 // indirect
+ github.com/chanxuehong/wechat v0.0.0-20201110083048-0180211b69fd // indirect
+ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
+ github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
+ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
+ github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect
+ github.com/fatih/color v1.13.0 // indirect
+ github.com/fsnotify/fsnotify v1.6.0 // indirect
+ github.com/gabriel-vasile/mimetype v1.4.2 // indirect
+ github.com/gin-contrib/sse v0.1.0 // indirect
+ github.com/git-chglog/git-chglog v0.0.0-20190611050339-63a4e637021f // indirect
+ github.com/go-admin-team/redisqueue/v2 v2.0.0-20221119141731-97c556b0d5b7 // indirect
+ github.com/go-forks/fsnotify v1.4.7 // indirect
+ github.com/go-ole/go-ole v1.2.6 // indirect
+ github.com/go-playground/locales v0.14.1 // indirect
+ github.com/go-playground/universal-translator v0.18.1 // indirect
+ github.com/go-playground/validator/v10 v10.14.0 // indirect
+ github.com/go-redis/redis/v8 v8.11.5 // indirect
+ github.com/go-redis/redis/v9 v9.0.0-rc.1 // indirect
+ github.com/goccy/go-json v0.10.2 // indirect
+ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/golang/snappy v0.0.4 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/hashicorp/hcl v1.0.0 // indirect
+ github.com/henrylee2cn/ameda v1.4.10 // indirect
+ github.com/henrylee2cn/goutil v0.0.0-20210127050712-89660552f6f8 // indirect
+ github.com/imdario/mergo v0.3.9 // indirect
+ github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 // indirect
+ github.com/jinzhu/inflection v1.0.0 // indirect
+ github.com/jinzhu/now v1.1.5 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
+ github.com/klauspost/cpuid/v2 v2.2.4 // indirect
+ github.com/leodido/go-urn v1.2.4 // indirect
+ github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570 // indirect
+ github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect
+ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
+ github.com/magiconair/properties v1.8.7 // indirect
+ github.com/mattn/go-colorable v0.1.12 // indirect
+ github.com/mattn/go-isatty v0.0.19 // indirect
+ github.com/mattn/goveralls v0.0.2 // indirect
+ github.com/metaleap/go-util v0.0.0-20180330192724-a09253046f73 // indirect
+ github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
+ github.com/mitchellh/mapstructure v1.5.0 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
+ github.com/nsqio/go-nsq v1.0.8 // indirect
+ github.com/nyaruka/phonenumbers v1.0.55 // indirect
+ github.com/pelletier/go-toml/v2 v2.0.8 // indirect
+ github.com/pkg/errors v0.9.1 // indirect
+ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
+ github.com/reiver/go-oi v1.0.0 // indirect
+ github.com/richardlehane/mscfb v1.0.4 // indirect
+ github.com/richardlehane/msoleps v1.0.3 // indirect
+ github.com/russross/blackfriday/v2 v2.1.0 // indirect
+ github.com/shoenig/go-m1cpu v0.1.6 // indirect
+ github.com/spf13/cast v1.5.1 // indirect
+ github.com/spf13/jwalterweatherman v1.1.0 // indirect
+ github.com/spf13/pflag v1.0.5
+ github.com/subosito/gotenv v1.4.2 // indirect
+ github.com/syndtr/goleveldb v1.0.0 // indirect
+ github.com/tebeka/strftime v0.1.5 // indirect
+ github.com/tklauser/go-sysconf v0.3.11 // indirect
+ github.com/tklauser/numcpus v0.6.0 // indirect
+ github.com/tsuyoshiwada/go-gitcmd v0.0.0-20180205145712-5f1f5f9475df // indirect
+ github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+ github.com/ugorji/go/codec v1.2.11 // indirect
+ github.com/urfave/cli v1.22.1 // indirect
+ github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect
+ github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect
+ github.com/yusufpapurcu/wmi v1.2.3 // indirect
+ golang.org/x/arch v0.3.0 // indirect
+ golang.org/x/image v0.5.0 // indirect
+ golang.org/x/net v0.19.0
+ golang.org/x/sys v0.15.0 // indirect
+ golang.org/x/text v0.14.0
+ golang.org/x/tools v0.16.1 // indirect
+ google.golang.org/protobuf v1.30.0 // indirect
+ gopkg.in/AlecAivazis/survey.v1 v1.8.5 // indirect
+ gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
+ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
+ gopkg.in/ini.v1 v1.67.0 // indirect
+ gopkg.in/kyokomi/emoji.v1 v1.5.1 // indirect
+ gopkg.in/yaml.v2 v2.4.0 // indirect
+ xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..42347f5
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,1326 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
+cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
+cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
+cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
+cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
+cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
+cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
+cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
+cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
+cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
+cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
+cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
+cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
+cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
+cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
+cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
+cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
+cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
+cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
+cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
+gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
+gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
+github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
+github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
+github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
+github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
+github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
+github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
+github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
+github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
+github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
+github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/bsm/ginkgo/v2 v2.9.5 h1:rtVBYPs3+TC5iLUVOis1B9tjLTup7Cj5IfzosKtvTJ0=
+github.com/bsm/ginkgo/v2 v2.9.5/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
+github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
+github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
+github.com/bsm/redislock v0.8.2 h1:W0aDRjt6FNmAZovbG2fPyjl1YZZdlqMkCKKCffJew1o=
+github.com/bsm/redislock v0.8.2/go.mod h1:tC0JZxZCdJN4DCB31cGxgjgf/ye1R4LLNJQd5ecjg08=
+github.com/bytedance/go-tagexpr/v2 v2.7.12 h1:qL2f0j11S8DHQsUWUA6aacLNBcbPTbNKuzVjaW4kF/M=
+github.com/bytedance/go-tagexpr/v2 v2.7.12/go.mod h1:cKpo/rwg2Y5Njs8SX3FspMWEhAWCaF4xUr5LJYXibSU=
+github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
+github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
+github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
+github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
+github.com/casbin/casbin/v2 v2.54.0 h1:NFQ3Xkw6rfbD/rwEHMVRHVP5gUxhNQKdcZCb53pwSrA=
+github.com/casbin/casbin/v2 v2.54.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
+github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/chanxuehong/rand v0.0.0-20201110082127-2f19a1bdd973 h1:Js/7nHtkpvUW62passc9FOflXyLQDjSoYclkFeDdTEM=
+github.com/chanxuehong/rand v0.0.0-20201110082127-2f19a1bdd973/go.mod h1:9+sJ9zvvkXC5sPjPEZM3Jpb9n2Q2VtcrGZly0UHYF5I=
+github.com/chanxuehong/util v0.0.0-20200304121633-ca8141845b13/go.mod h1:XEYt99iTxMqkv+gW85JX/DdUINHUe43Sbe5AtqSaDAQ=
+github.com/chanxuehong/wechat v0.0.0-20201110083048-0180211b69fd h1:TM3wjEWel4U31J72dlhnwCBqPC0+FA0Ejm2NCbn5a5U=
+github.com/chanxuehong/wechat v0.0.0-20201110083048-0180211b69fd/go.mod h1:/dvhOIRCjjiZu6NV0QTTiMcc5XwoORbxfDSsRY2IfaM=
+github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
+github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
+github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
+github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
+github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
+github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
+github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
+github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
+github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
+github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
+github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
+github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
+github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 h1:Ghm4eQYC0nEPnSJdVkTrXpu9KtoVCSo1hg7mtI7G9KU=
+github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0yfBxPvZrHkprdPPTTS3N5rwmLE8T22KBXlw=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
+github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
+github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
+github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
+github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
+github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
+github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
+github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
+github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
+github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
+github.com/git-chglog/git-chglog v0.0.0-20190611050339-63a4e637021f h1:8l4Aw3Jmx0pLKYMkY+1b6yBPgE+rzRtA5T3vqFyI2Z8=
+github.com/git-chglog/git-chglog v0.0.0-20190611050339-63a4e637021f/go.mod h1:Dcsy1kii/xFyNad5JqY/d0GO5mu91sungp5xotbm3Yk=
+github.com/go-admin-team/go-admin-core v1.3.12-0.20221121065133-27b7dbe27a8f h1:2xHpluWqY/ZlYoUpOU8VwDponYSnukRDhkOr7rk3ffU=
+github.com/go-admin-team/go-admin-core v1.3.12-0.20221121065133-27b7dbe27a8f/go.mod h1:a9/XW1rCChPLVJ3bST13hB6R8YfVjYeF0GYjb8If6Yg=
+github.com/go-admin-team/go-admin-core/sdk v1.5.1 h1:U1VaXuMrMmeLiQyJipqnTxHE84vspaK++wkTWfYpcOI=
+github.com/go-admin-team/go-admin-core/sdk v1.5.1/go.mod h1:yNoHmYZtGc1WFWZwhAop0sSyZhsrsSGqvje2yvClXjE=
+github.com/go-admin-team/redisqueue/v2 v2.0.0-20221119141731-97c556b0d5b7 h1:p1cq6CN47zEbDt8EWBX3mOZpIa4zVwHCrXcy0NwD+So=
+github.com/go-admin-team/redisqueue/v2 v2.0.0-20221119141731-97c556b0d5b7/go.mod h1:XGwBJD4aMmHSoJFPKs4vcW4Y5s+8MV1gHR+P0Uxe9Eg=
+github.com/go-forks/fsnotify v1.4.7 h1:lyiU2Wqd4fNGCQDn9dZ4UxSiSFgeU1rM6yIfahk8haY=
+github.com/go-forks/fsnotify v1.4.7/go.mod h1:AU8mot+GznW5+B4jRJHxKg/2EeO+jMORGRkKSxs0biw=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gsm/charset v1.0.0 h1:6k6LOKHtxgCPXE15X0unRewjOksyhmHBIp6x7qLQ6Ls=
+github.com/go-gsm/charset v1.0.0/go.mod h1:sC8+2VpAM2sZDlxv11MxWIZiuf8MipOgM/hCYsRQRps=
+github.com/go-gsm/ucp v0.0.1 h1:vM5ly5iRNGMGGiVx4K3+puF9AhpwkC76BkiV9LFv0bw=
+github.com/go-gsm/ucp v0.0.1/go.mod h1:58/PrXWFcmaQyZ5p/Dp2YiCnhlVx0HdKXFGmqiVO/mI=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
+github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
+github.com/go-redis/redis v6.15.5+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
+github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
+github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
+github.com/go-redis/redis/v9 v9.0.0-rc.1 h1:/+bS+yeUnanqAbuD3QwlejzQZ+4eqgfUtFTG4b+QnXs=
+github.com/go-redis/redis/v9 v9.0.0-rc.1/go.mod h1:8et+z03j0l8N+DvsVnclzjf3Dl/pFHgRk+2Ct1qw66A=
+github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
+github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
+github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
+github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
+github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
+github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
+github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
+github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
+github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
+github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
+github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
+github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
+github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
+github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
+github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
+github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
+github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gosnmp/gosnmp v1.35.0 h1:EuWWNPxTCdAUx2/NbQcSa3WdNxjzpy4Phv57b4MWpJM=
+github.com/gosnmp/gosnmp v1.35.0/go.mod h1:2AvKZ3n9aEl5TJEo/fFmf/FGO4Nj4cVeEc5yuk88CYc=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
+github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
+github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
+github.com/henrylee2cn/ameda v1.4.8/go.mod h1:liZulR8DgHxdK+MEwvZIylGnmcjzQ6N6f2PlWe7nEO4=
+github.com/henrylee2cn/ameda v1.4.10 h1:JdvI2Ekq7tapdPsuhrc4CaFiqw6QXFvZIULWJgQyCAk=
+github.com/henrylee2cn/ameda v1.4.10/go.mod h1:liZulR8DgHxdK+MEwvZIylGnmcjzQ6N6f2PlWe7nEO4=
+github.com/henrylee2cn/goutil v0.0.0-20210127050712-89660552f6f8 h1:yE9ULgp02BhYIrO6sdV/FPe0xQM6fNHkVQW2IAymfM0=
+github.com/henrylee2cn/goutil v0.0.0-20210127050712-89660552f6f8/go.mod h1:Nhe/DM3671a5udlv2AdV2ni/MZzgfv2qrPL5nIi3EGQ=
+github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
+github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
+github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
+github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
+github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
+github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
+github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
+github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
+github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
+github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
+github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk=
+github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
+github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
+github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
+github.com/jackc/pgconn v1.8.1/go.mod h1:JV6m6b6jhjdmzchES0drzCcYcAHS1OPD5xu3OZ/lE2g=
+github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
+github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
+github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
+github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
+github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
+github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
+github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
+github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
+github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
+github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
+github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
+github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
+github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
+github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
+github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
+github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0=
+github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po=
+github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ=
+github.com/jackc/pgtype v1.7.0/go.mod h1:ZnHF+rMePVqDKaOfJVI4Q8IVvAQMryDlDkZnKOI75BE=
+github.com/jackc/pgtype v1.8.0/go.mod h1:PqDKcEBtllAtk/2p6z6SHdXW5UB+MhE75tUol2OKexE=
+github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
+github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
+github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
+github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA=
+github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
+github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
+github.com/jackc/pgx/v4 v4.11.0/go.mod h1:i62xJgdrtVDsnL3U8ekyrQXEwGNTRoG7/8r+CIdYfcc=
+github.com/jackc/pgx/v4 v4.12.0/go.mod h1:fE547h6VulLPA3kySjfnSG/e2D861g/50JlVUa/ub60=
+github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jasonlvhit/gocron v0.0.1 h1:qTt5qF3b3srDjeOIR4Le1LfeyvoYzJlYpqvG7tJX5YU=
+github.com/jasonlvhit/gocron v0.0.1/go.mod h1:k9a3TV8VcU73XZxfVHCHWMWF9SOqgoku0/QlY2yvlA4=
+github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 h1:IPJ3dvxmJ4uczJe5YQdrYB16oTJlGSC/OyZDqUk9xX4=
+github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
+github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
+github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
+github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI=
+github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
+github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
+github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570 h1:0iQektZGS248WXmGIYOwRXSQhD4qn3icjMpuxwO7qlo=
+github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570/go.mod h1:BLt8L9ld7wVsvEWQbuLrUZnCMnUmLZ+CGDzKtclrTlE=
+github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f h1:sgUSP4zdTUZYZgAGGtN5Lxk92rK+JUFOwf+FT99EEI4=
+github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f/go.mod h1:UGmTpUd3rjbtfIpwAPrcfmGf/Z1HS95TATB+m57TPB8=
+github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 h1:Bvq8AziQ5jFF4BHGAEDSqwPW1NJS3XshxbRCxtjFAZc=
+github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042/go.mod h1:TPpsiPUEh0zFL1Snz4crhMlBe60PYxRHr5oFF3rRYg0=
+github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
+github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
+github.com/linxGnu/gosmpp v0.2.0 h1:GVZdnO8pUsOrhFF092RQ2II+6RW5F2kLDlbCH+NBp7s=
+github.com/linxGnu/gosmpp v0.2.0/go.mod h1:UpJwpDhL0yq05gUqaLwZ8+Qc7qy1pl35EjxWtRLW6ks=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
+github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
+github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
+github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U=
+github.com/matoous/go-nanoid/v2 v2.0.0 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL+9Tj0=
+github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
+github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
+github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
+github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
+github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
+github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
+github.com/mattn/goveralls v0.0.2 h1:7eJB6EqsPhRVxvwEXGnqdO2sJI0PTsrWoTMXEk9/OQc=
+github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/metaleap/go-util v0.0.0-20180330192724-a09253046f73 h1:4vKVhAdype/dejZjdQZZXVTKLz8mD0oakKz128L+IFQ=
+github.com/metaleap/go-util v0.0.0-20180330192724-a09253046f73/go.mod h1:l71/5fppWP5A6nqhcxz6wQAYok6pr/vM2+KHIy50/LY=
+github.com/metaleap/go-xsd v0.0.0-20180330193350-61f7638f502f h1:eeJGcYszuvOpmuJxeq57LaOO8mJurfjpOHJJMfQSD0s=
+github.com/metaleap/go-xsd v0.0.0-20180330193350-61f7638f502f/go.mod h1:WK3zEKtwVd/v+NM3lh1ZE6MdDfHsdOFFOD5Ezi4Hutg=
+github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
+github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
+github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
+github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
+github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
+github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
+github.com/mojocn/base64Captcha v1.3.5 h1:Qeilr7Ta6eDtG4S+tQuZ5+hO+QHbiGAJdi4PfoagaA0=
+github.com/mojocn/base64Captcha v1.3.5/go.mod h1:/tTTXn4WTpX9CfrmipqRytCpJ27Uw3G6I7NcP2WwcmY=
+github.com/mssola/user_agent v0.6.0 h1:uwPR4rtWlCHRFyyP9u2KOV0u8iQXmS7Z7feTrstQwk4=
+github.com/mssola/user_agent v0.6.0/go.mod h1:TTPno8LPY3wAIEKRpAtkdMT0f8SE24pLRGPahjCH4uw=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
+github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
+github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
+github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
+github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
+github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
+github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
+github.com/nsqio/go-nsq v1.0.8 h1:3L2F8tNLlwXXlp2slDUrUWSBn2O3nMh8R1/KEDFTHPk=
+github.com/nsqio/go-nsq v1.0.8/go.mod h1:vKq36oyeVXgsS5Q8YEO7WghqidAVXQlcFxzQbQTuDEY=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
+github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
+github.com/nyaruka/phonenumbers v1.0.55 h1:bj0nTO88Y68KeUQ/n3Lo2KgK7lM1hF7L9NFuwcCl3yg=
+github.com/nyaruka/phonenumbers v1.0.55/go.mod h1:sDaTZ/KPX5f8qyV9qN+hIm+4ZBARJrupC6LuhshJq1U=
+github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
+github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
+github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
+github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
+github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
+github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
+github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
+github.com/onsi/gomega v1.21.1 h1:OB/euWYIExnPBohllTicTHmGTrMaqJ67nIu80j0/uEM=
+github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc=
+github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
+github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
+github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
+github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
+github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
+github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
+github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
+github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
+github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
+github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=
+github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
+github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
+github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
+github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
+github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
+github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
+github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
+github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
+github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY=
+github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c=
+github.com/reiver/go-oi v1.0.0 h1:nvECWD7LF+vOs8leNGV/ww+F2iZKf3EYjYZ527turzM=
+github.com/reiver/go-oi v1.0.0/go.mod h1:RrDBct90BAhoDTxB1fenZwfykqeGvhI6LsNfStJoEkI=
+github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e h1:quuzZLi72kkJjl+f5AQ93FMcadG19WkS7MO6TXFOSas=
+github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e/go.mod h1:+5vNVvEWwEIx86DB9Ke/+a5wBI464eDRo3eF0LcfpWg=
+github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
+github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
+github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
+github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
+github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
+github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM=
+github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
+github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
+github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
+github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
+github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
+github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
+github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
+github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
+github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
+github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4=
+github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
+github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
+github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
+github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
+github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
+github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
+github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
+github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
+github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
+github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
+github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
+github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
+github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
+github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
+github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc=
+github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg=
+github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
+github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
+github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
+github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
+github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
+github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
+github.com/tebeka/strftime v0.1.5 h1:1NQKN1NiQgkqd/2moD6ySP/5CoZQsKa1d3ZhJ44Jpmg=
+github.com/tebeka/strftime v0.1.5/go.mod h1:29/OidkoWHdEKZqzyDLUyC+LmgDgdHo4WAFCDT7D/Ig=
+github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
+github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
+github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
+github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
+github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
+github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
+github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/tsuyoshiwada/go-gitcmd v0.0.0-20180205145712-5f1f5f9475df h1:Y2l28Jr3vOEeYtxfVbMtVfOdAwuUqWaP9fvNKiBVeXY=
+github.com/tsuyoshiwada/go-gitcmd v0.0.0-20180205145712-5f1f5f9475df/go.mod h1:pnyouUty/nBr/zm3GYwTIt+qFTLWbdjeLjZmJdzJOu8=
+github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
+github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
+github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
+github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
+github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 h1:6932x8ltq1w4utjmfMPVj09jdMlkY0aiA6+Skbtl3/c=
+github.com/xuri/efp v0.0.0-20220603152613-6918739fd470/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
+github.com/xuri/excelize/v2 v2.7.1 h1:gm8q0UCAyaTt3MEF5wWMjVdmthm2EHAWesGSKS9tdVI=
+github.com/xuri/excelize/v2 v2.7.1/go.mod h1:qc0+2j4TvAUrBw36ATtcTeC1VCM0fFdAXZOmcF4nTpY=
+github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 h1:OAmKAfT06//esDdpi/DZ8Qsdt4+M5+ltca05dA5bG2M=
+github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
+github.com/xuri/xgen v0.0.0-20230702070049-db840e1a4605 h1:urU5p/tQOOUd9B0c6O+6aBk7udJI5HcN4VYmX2tMTcI=
+github.com/xuri/xgen v0.0.0-20230702070049-db840e1a4605/go.mod h1:tBkmlRf8JH62H5fAW+BPFj0LNZWRmKglWTLFfpXhhbg=
+github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
+github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
+github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
+github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
+github.com/ziutek/telnet v0.0.0-20180329124119-c3b780dc415b h1:VfPXB/wCGGt590QhD1bOpv2J/AmC/RJNTg/Q59HKSB0=
+github.com/ziutek/telnet v0.0.0-20180329124119-c3b780dc415b/go.mod h1:IZpXDfkJ6tWD3PhBK5YzgQT+xJWh7OsdwiG8hA2MkO4=
+go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
+go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
+go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
+go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
+go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
+go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
+go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
+golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
+golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
+golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
+golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
+golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
+golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
+golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
+golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
+golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
+golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
+golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180606202747-9527bec2660b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
+golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
+golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
+golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA=
+golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
+golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
+golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
+golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
+golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
+google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
+google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
+google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
+google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
+google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
+google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
+google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
+google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
+google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
+google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
+google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/AlecAivazis/survey.v1 v1.8.5 h1:QoEEmn/d5BbuPIL2qvXwzJdttFFhRQFkaq+tEKb7SMI=
+gopkg.in/AlecAivazis/survey.v1 v1.8.5/go.mod h1:iBNOmqKz/NUbZx3bA+4hAGLRC7fSK7tgtVDT4tB22XA=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
+gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
+gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
+gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/kyokomi/emoji.v1 v1.5.1 h1:beetH5mWDMzFznJ+Qzd5KVHp79YKhVUMcdO8LpRLeGw=
+gopkg.in/kyokomi/emoji.v1 v1.5.1/go.mod h1:N9AZ6hi1jHOPn34PsbpufQZUcKftSD7WgS2pgpmH4Lg=
+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw=
+gorm.io/driver/mysql v1.5.1/go.mod h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5o=
+gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
+gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho=
+gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
+honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU=
+lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
+modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.34.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.35.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.35.4/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.35.5/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.35.7/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.35.8/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/cc/v3 v3.35.18 h1:rMZhRcWrba0y3nVmdiQ7kxAgOOSq2m2f2VzjHLgEs6U=
+modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
+modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60=
+modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw=
+modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI=
+modernc.org/ccgo/v3 v3.11.1/go.mod h1:lWHxfsn13L3f7hgGsGlU28D9eUOf6y3ZYHKoPaKU0ag=
+modernc.org/ccgo/v3 v3.11.3/go.mod h1:0oHunRBMBiXOKdaglfMlRPBALQqsfrCKXgw9okQ3GEw=
+modernc.org/ccgo/v3 v3.12.4/go.mod h1:Bk+m6m2tsooJchP/Yk5ji56cClmN6R1cqc9o/YtbgBQ=
+modernc.org/ccgo/v3 v3.12.6/go.mod h1:0Ji3ruvpFPpz+yu+1m0wk68pdr/LENABhTrDkMDWH6c=
+modernc.org/ccgo/v3 v3.12.8/go.mod h1:Hq9keM4ZfjCDuDXxaHptpv9N24JhgBZmUG5q60iLgUo=
+modernc.org/ccgo/v3 v3.12.11/go.mod h1:0jVcmyDwDKDGWbcrzQ+xwJjbhZruHtouiBEvDfoIsdg=
+modernc.org/ccgo/v3 v3.12.14/go.mod h1:GhTu1k0YCpJSuWwtRAEHAol5W7g1/RRfS4/9hc9vF5I=
+modernc.org/ccgo/v3 v3.12.18/go.mod h1:jvg/xVdWWmZACSgOiAhpWpwHWylbJaSzayCqNOJKIhs=
+modernc.org/ccgo/v3 v3.12.20/go.mod h1:aKEdssiu7gVgSy/jjMastnv/q6wWGRbszbheXgWRHc8=
+modernc.org/ccgo/v3 v3.12.21/go.mod h1:ydgg2tEprnyMn159ZO/N4pLBqpL7NOkJ88GT5zNU2dE=
+modernc.org/ccgo/v3 v3.12.22/go.mod h1:nyDVFMmMWhMsgQw+5JH6B6o4MnZ+UQNw1pp52XYFPRk=
+modernc.org/ccgo/v3 v3.12.25/go.mod h1:UaLyWI26TwyIT4+ZFNjkyTbsPsY3plAEB6E7L/vZV3w=
+modernc.org/ccgo/v3 v3.12.29/go.mod h1:FXVjG7YLf9FetsS2OOYcwNhcdOLGt8S9bQ48+OP75cE=
+modernc.org/ccgo/v3 v3.12.36/go.mod h1:uP3/Fiezp/Ga8onfvMLpREq+KUjUmYMxXPO8tETHtA8=
+modernc.org/ccgo/v3 v3.12.38/go.mod h1:93O0G7baRST1vNj4wnZ49b1kLxt0xCW5Hsa2qRaZPqc=
+modernc.org/ccgo/v3 v3.12.43/go.mod h1:k+DqGXd3o7W+inNujK15S5ZYuPoWYLpF5PYougCmthU=
+modernc.org/ccgo/v3 v3.12.46/go.mod h1:UZe6EvMSqOxaJ4sznY7b23/k13R8XNlyWsO5bAmSgOE=
+modernc.org/ccgo/v3 v3.12.47/go.mod h1:m8d6p0zNps187fhBwzY/ii6gxfjob1VxWb919Nk1HUk=
+modernc.org/ccgo/v3 v3.12.50/go.mod h1:bu9YIwtg+HXQxBhsRDE+cJjQRuINuT9PUK4orOco/JI=
+modernc.org/ccgo/v3 v3.12.51/go.mod h1:gaIIlx4YpmGO2bLye04/yeblmvWEmE4BBBls4aJXFiE=
+modernc.org/ccgo/v3 v3.12.53/go.mod h1:8xWGGTFkdFEWBEsUmi+DBjwu/WLy3SSOrqEmKUjMeEg=
+modernc.org/ccgo/v3 v3.12.54/go.mod h1:yANKFTm9llTFVX1FqNKHE0aMcQb1fuPJx6p8AcUx+74=
+modernc.org/ccgo/v3 v3.12.55/go.mod h1:rsXiIyJi9psOwiBkplOaHye5L4MOOaCjHg1Fxkj7IeU=
+modernc.org/ccgo/v3 v3.12.56/go.mod h1:ljeFks3faDseCkr60JMpeDb2GSO3TKAmrzm7q9YOcMU=
+modernc.org/ccgo/v3 v3.12.57/go.mod h1:hNSF4DNVgBl8wYHpMvPqQWDQx8luqxDnNGCMM4NFNMc=
+modernc.org/ccgo/v3 v3.12.60/go.mod h1:k/Nn0zdO1xHVWjPYVshDeWKqbRWIfif5dtsIOCUVMqM=
+modernc.org/ccgo/v3 v3.12.65/go.mod h1:D6hQtKxPNZiY6wDBtehSGKFKmyXn53F8nGTpH+POmS4=
+modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/EhQ=
+modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84=
+modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ=
+modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY=
+modernc.org/ccgo/v3 v3.12.82 h1:wudcnJyjLj1aQQCXF3IM9Gz2X6UNjw+afIghzdtn0v8=
+modernc.org/ccgo/v3 v3.12.82/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w=
+modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
+modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
+modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
+modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q=
+modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg=
+modernc.org/libc v1.11.2/go.mod h1:ioIyrl3ETkugDO3SGZ+6EOKvlP3zSOycUETe4XM4n8M=
+modernc.org/libc v1.11.5/go.mod h1:k3HDCP95A6U111Q5TmG3nAyUcp3kR5YFZTeDS9v8vSU=
+modernc.org/libc v1.11.6/go.mod h1:ddqmzR6p5i4jIGK1d/EiSw97LBcE3dK24QEwCFvgNgE=
+modernc.org/libc v1.11.11/go.mod h1:lXEp9QOOk4qAYOtL3BmMve99S5Owz7Qyowzvg6LiZso=
+modernc.org/libc v1.11.13/go.mod h1:ZYawJWlXIzXy2Pzghaf7YfM8OKacP3eZQI81PDLFdY8=
+modernc.org/libc v1.11.16/go.mod h1:+DJquzYi+DMRUtWI1YNxrlQO6TcA5+dRRiq8HWBWRC8=
+modernc.org/libc v1.11.19/go.mod h1:e0dgEame6mkydy19KKaVPBeEnyJB4LGNb0bBH1EtQ3I=
+modernc.org/libc v1.11.24/go.mod h1:FOSzE0UwookyT1TtCJrRkvsOrX2k38HoInhw+cSCUGk=
+modernc.org/libc v1.11.26/go.mod h1:SFjnYi9OSd2W7f4ct622o/PAYqk7KHv6GS8NZULIjKY=
+modernc.org/libc v1.11.27/go.mod h1:zmWm6kcFXt/jpzeCgfvUNswM0qke8qVwxqZrnddlDiE=
+modernc.org/libc v1.11.28/go.mod h1:Ii4V0fTFcbq3qrv3CNn+OGHAvzqMBvC7dBNyC4vHZlg=
+modernc.org/libc v1.11.31/go.mod h1:FpBncUkEAtopRNJj8aRo29qUiyx5AvAlAxzlx9GNaVM=
+modernc.org/libc v1.11.34/go.mod h1:+Tzc4hnb1iaX/SKAutJmfzES6awxfU1BPvrrJO0pYLg=
+modernc.org/libc v1.11.37/go.mod h1:dCQebOwoO1046yTrfUE5nX1f3YpGZQKNcITUYWlrAWo=
+modernc.org/libc v1.11.39/go.mod h1:mV8lJMo2S5A31uD0k1cMu7vrJbSA3J3waQJxpV4iqx8=
+modernc.org/libc v1.11.42/go.mod h1:yzrLDU+sSjLE+D4bIhS7q1L5UwXDOw99PLSX0BlZvSQ=
+modernc.org/libc v1.11.44/go.mod h1:KFq33jsma7F5WXiYelU8quMJasCCTnHK0mkri4yPHgA=
+modernc.org/libc v1.11.45/go.mod h1:Y192orvfVQQYFzCNsn+Xt0Hxt4DiO4USpLNXBlXg/tM=
+modernc.org/libc v1.11.47/go.mod h1:tPkE4PzCTW27E6AIKIR5IwHAQKCAtudEIeAV1/SiyBg=
+modernc.org/libc v1.11.49/go.mod h1:9JrJuK5WTtoTWIFQ7QjX2Mb/bagYdZdscI3xrvHbXjE=
+modernc.org/libc v1.11.51/go.mod h1:R9I8u9TS+meaWLdbfQhq2kFknTW0O3aw3kEMqDDxMaM=
+modernc.org/libc v1.11.53/go.mod h1:5ip5vWYPAoMulkQ5XlSJTy12Sz5U6blOQiYasilVPsU=
+modernc.org/libc v1.11.54/go.mod h1:S/FVnskbzVUrjfBqlGFIPA5m7UwB3n9fojHhCNfSsnw=
+modernc.org/libc v1.11.55/go.mod h1:j2A5YBRm6HjNkoSs/fzZrSxCuwWqcMYTDPLNx0URn3M=
+modernc.org/libc v1.11.56/go.mod h1:pakHkg5JdMLt2OgRadpPOTnyRXm/uzu+Yyg/LSLdi18=
+modernc.org/libc v1.11.58/go.mod h1:ns94Rxv0OWyoQrDqMFfWwka2BcaF6/61CqJRK9LP7S8=
+modernc.org/libc v1.11.70/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
+modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
+modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0=
+modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI=
+modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE=
+modernc.org/libc v1.11.87 h1:PzIzOqtlzMDDcCzJ5cUP6h/Ku6Fa9iyflP2ccTY64aE=
+modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY=
+modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
+modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
+modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
+modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
+modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
+modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
+modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14=
+modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
+modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
+modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
+modernc.org/sqlite v1.14.2 h1:ohsW2+e+Qe2To1W6GNezzKGwjXwSax6R+CrhRxVaFbE=
+modernc.org/sqlite v1.14.2/go.mod h1:yqfn85u8wVOE6ub5UT8VI9JjhrwBUUCNyTACN0h6Sx8=
+modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs=
+modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
+modernc.org/tcl v1.8.13/go.mod h1:V+q/Ef0IJaNUSECieLU4o+8IScapxnMyFV6i/7uQlAY=
+modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
+modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
+modernc.org/z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
+sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
+xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 h1:bvLlAPW1ZMTWA32LuZMBEGHAUOcATZjzHcotf3SWweM=
+xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
+xorm.io/xorm v1.3.2 h1:uTRRKF2jYzbZ5nsofXVUx6ncMaek+SHjWYtCXyZo1oM=
+xorm.io/xorm v1.3.2/go.mod h1:9NbjqdnjX6eyjRRhh01GHm64r6N9shTb/8Ak3YRt8Nw=
diff --git a/lib/aes/aes.go b/lib/aes/aes.go
new file mode 100644
index 0000000..5ce2fb4
--- /dev/null
+++ b/lib/aes/aes.go
@@ -0,0 +1,64 @@
+package aes
+
+import (
+ "bytes"
+ "crypto/aes"
+ "crypto/cipher"
+ "encoding/base64"
+)
+
+func AesEncrypt(orig string, key string) string {
+ // 转成字节数组
+ origData := []byte(orig)
+ k := []byte(key)
+
+ // 分组秘钥
+ block, _ := aes.NewCipher(k)
+ // 获取秘钥块的长度
+ blockSize := block.BlockSize()
+ // 补全码
+ origData = PKCS7Padding(origData, blockSize)
+ // 加密模式
+ blockMode := cipher.NewCBCEncrypter(block, k[:blockSize])
+ // 创建数组
+ cryted := make([]byte, len(origData))
+ // 加密
+ blockMode.CryptBlocks(cryted, origData)
+
+ return base64.StdEncoding.EncodeToString(cryted)
+
+}
+
+func AesDecrypt(cryted string, key string) string {
+ // 转成字节数组
+ crytedByte, _ := base64.StdEncoding.DecodeString(cryted)
+ k := []byte(key)
+
+ // 分组秘钥
+ block, _ := aes.NewCipher(k)
+ // 获取秘钥块的长度
+ blockSize := block.BlockSize()
+ // 加密模式
+ blockMode := cipher.NewCBCDecrypter(block, k[:blockSize])
+ // 创建数组
+ orig := make([]byte, len(crytedByte))
+ // 解密
+ blockMode.CryptBlocks(orig, crytedByte)
+ // 去补全码
+ orig = PKCS7UnPadding(orig)
+ return string(orig)
+}
+
+// 补码
+func PKCS7Padding(ciphertext []byte, blocksize int) []byte {
+ padding := blocksize - len(ciphertext)%blocksize
+ padtext := bytes.Repeat([]byte{byte(padding)}, padding)
+ return append(ciphertext, padtext...)
+}
+
+// 去码
+func PKCS7UnPadding(origData []byte) []byte {
+ length := len(origData)
+ unpadding := int(origData[length-1])
+ return origData[:(length - unpadding)]
+}
diff --git a/lib/core/account/account.go b/lib/core/account/account.go
new file mode 100644
index 0000000..752b564
--- /dev/null
+++ b/lib/core/account/account.go
@@ -0,0 +1,54 @@
+package account
+
+import (
+ "fmt"
+ "strconv"
+ "time"
+
+ sysMenuService "nms_nbi/features/sys_menu/service"
+ sysRoleService "nms_nbi/features/sys_role/service"
+ "nms_nbi/lib/core/cache"
+ "nms_nbi/lib/core/vo"
+ "nms_nbi/lib/dborm"
+ srcConfig "nms_nbi/src/framework/config"
+)
+
+// 登录缓存用户信息
+func CacheLoginUser(user *dborm.User) {
+ // 过期时间
+ expiresStr, err := dborm.XormGetConfigValue("Security", "sessionExpires")
+ if err != nil {
+ expiresStr = "18000"
+ }
+ expiresValue, _ := strconv.Atoi(expiresStr)
+ expireTime := time.Duration(expiresValue) * time.Second
+
+ nowTime := time.Now().UnixMilli()
+
+ // 登录用户
+ loginUser := vo.LoginUser{
+ UserID: fmt.Sprint(user.Id),
+ UserName: user.Name,
+ ExpireTime: nowTime + expireTime.Milliseconds(),
+ LoginTime: nowTime,
+ User: *user,
+ }
+
+ // 是否管理员
+ if srcConfig.IsAdmin(loginUser.UserID) {
+ loginUser.Permissions = []string{"*:*:*"}
+ } else {
+ // 获取权限标识
+ loginUser.Permissions = sysMenuService.NewRepoSysMenu.SelectMenuPermsByUserId(loginUser.UserID)
+ // 获取角色信息
+ loginUser.User.Roles = sysRoleService.NewRepoSysRole.SelectRoleListByUserId(loginUser.UserID)
+ }
+
+ // 缓存时间
+ cache.SetLocalTTL(user.AccountId, loginUser, time.Duration(expireTime))
+}
+
+// 清除缓存用户信息
+func ClearLoginUser(accountId string) {
+ cache.DeleteLocalTTL(accountId)
+}
diff --git a/lib/core/cache/lcoal.go b/lib/core/cache/lcoal.go
new file mode 100644
index 0000000..f6f12af
--- /dev/null
+++ b/lib/core/cache/lcoal.go
@@ -0,0 +1,56 @@
+package cache
+
+import (
+ "strings"
+ "time"
+
+ "github.com/patrickmn/go-cache"
+)
+
+// 创建一个全局的不过期缓存对象
+var cNoExpiration = cache.New(cache.NoExpiration, cache.NoExpiration)
+
+// 将数据放入缓存,并设置永不过期
+func SetLocal(key string, value any) {
+ cNoExpiration.Set(key, value, cache.NoExpiration)
+}
+
+// 从缓存中获取数据
+func GetLocal(key string) (any, bool) {
+ return cNoExpiration.Get(key)
+}
+
+// 从缓存中删除数据
+func DeleteLocal(key string) {
+ cNoExpiration.Delete(key)
+}
+
+// 获取指定前缀的所有键
+func GetLocalKeys(prefix string) []string {
+ prefix = strings.TrimSuffix(prefix, "*")
+ var keys []string
+ for key := range cNoExpiration.Items() {
+ if strings.HasPrefix(key, prefix) {
+ keys = append(keys, key)
+ }
+ }
+ return keys
+}
+
+// 创建一个全局的过期缓存对象
+var cTTL = cache.New(6*time.Hour, 12*time.Hour)
+
+// 设置具有过期时间的缓存项
+func SetLocalTTL(key string, value any, expiration time.Duration) {
+ cTTL.Set(key, value, expiration)
+}
+
+// 从缓存中获取数据
+func GetLocalTTL(key string) (any, bool) {
+ return cTTL.Get(key)
+}
+
+// 从缓存中删除数据
+func DeleteLocalTTL(key string) {
+ cTTL.Delete(key)
+}
diff --git a/lib/core/conf/conf.go b/lib/core/conf/conf.go
new file mode 100644
index 0000000..c5e2586
--- /dev/null
+++ b/lib/core/conf/conf.go
@@ -0,0 +1,36 @@
+package conf
+
+import (
+ "fmt"
+
+ "github.com/spf13/viper"
+)
+
+var v *viper.Viper
+
+// 配置文件读取
+func InitConfig(configFile string) {
+ v = viper.New()
+
+ // 设置配置文件路径
+ v.SetConfigFile(configFile)
+
+ // 读取配置文件
+ err := v.ReadInConfig()
+ if err != nil {
+ fmt.Printf("读取配置文件失败: %v \n", err)
+ return
+ }
+}
+
+// Get 获取配置信息
+//
+// Get("framework.name")
+func Get(key string) any {
+ return v.Get(key)
+}
+
+// AllSettings 全部配置信息
+func AllSettings() map[string]interface{} {
+ return v.AllSettings()
+}
diff --git a/lib/core/constants/cachekey/cachekey.go b/lib/core/constants/cachekey/cachekey.go
new file mode 100644
index 0000000..0e69445
--- /dev/null
+++ b/lib/core/constants/cachekey/cachekey.go
@@ -0,0 +1,24 @@
+package cachekey
+
+// 缓存的key常量
+
+// 登录用户
+const LOGIN_TOKEN_KEY = "login_tokens:"
+
+// 验证码
+const CAPTCHA_CODE_KEY = "captcha_codes:"
+
+// 参数管理
+const SYS_CONFIG_KEY = "sys_config:"
+
+// 字典管理
+const SYS_DICT_KEY = "sys_dict:"
+
+// 防重提交
+const REPEAT_SUBMIT_KEY = "repeat_submit:"
+
+// 限流
+const RATE_LIMIT_KEY = "rate_limit:"
+
+// 登录账户密码错误次数
+const PWD_ERR_CNT_KEY = "pwd_err_cnt:"
diff --git a/lib/core/datasource/datasource.go b/lib/core/datasource/datasource.go
new file mode 100644
index 0000000..ac92847
--- /dev/null
+++ b/lib/core/datasource/datasource.go
@@ -0,0 +1,50 @@
+package datasource
+
+import (
+ "database/sql"
+ "regexp"
+
+ "nms_nbi/lib/dborm"
+
+ "xorm.io/xorm"
+)
+
+// 获取默认数据源
+func DefaultDB() *xorm.Engine {
+ return dborm.DbClient.XEngine
+}
+
+// RawDB 原生查询语句
+func RawDB(source string, sql string, parameters []any) ([]map[string]any, error) {
+ // 数据源
+ db := DefaultDB()
+
+ // 使用正则表达式替换连续的空白字符为单个空格
+ fmtSql := regexp.MustCompile(`\s+`).ReplaceAllString(sql, " ")
+
+ // log.Infof("sql=> %v", fmtSql)
+ // log.Infof("parameters=> %v", parameters)
+
+ // 查询结果
+ var rows []map[string]any
+ err := db.SQL(fmtSql, parameters...).Find(&rows)
+ if err != nil {
+ return nil, err
+ }
+ return rows, nil
+}
+
+// ExecDB 原生执行语句
+func ExecDB(source string, sql string, parameters []any) (sql.Result, error) {
+ // 数据源
+ db := DefaultDB()
+
+ // 使用正则表达式替换连续的空白字符为单个空格
+ fmtSql := regexp.MustCompile(`\s+`).ReplaceAllString(sql, " ")
+ // 执行结果
+ res, err := db.Exec(append([]any{fmtSql}, parameters...)...)
+ if err != nil {
+ return nil, err
+ }
+ return res, err
+}
diff --git a/lib/core/datasource/repo.go b/lib/core/datasource/repo.go
new file mode 100644
index 0000000..7b81a4e
--- /dev/null
+++ b/lib/core/datasource/repo.go
@@ -0,0 +1,126 @@
+package datasource
+
+import (
+ "fmt"
+ "reflect"
+ "strconv"
+ "strings"
+)
+
+// PageNumSize 分页页码记录数
+func PageNumSize(pageNum, pageSize any) (int, int) {
+ // 记录起始索引
+ pageNumStr := fmt.Sprintf("%v", pageNum)
+ num := 1
+ if v, err := strconv.Atoi(pageNumStr); err == nil && v > 0 {
+ num = v
+ }
+
+ // 显示记录数
+ pageSizeStr := fmt.Sprintf("%v", pageSize)
+ size := 10
+ if v, err := strconv.Atoi(pageSizeStr); err == nil && v > 0 {
+ size = v
+ }
+ return num - 1, size
+}
+
+// SetFieldValue 判断结构体内是否存在指定字段并设置值
+func SetFieldValue(obj any, fieldName string, value any) {
+ // 获取结构体的反射值
+ userValue := reflect.ValueOf(obj)
+
+ // 获取字段的反射值
+ fieldValue := userValue.Elem().FieldByName(fieldName)
+
+ // 检查字段是否存在
+ if fieldValue.IsValid() && fieldValue.CanSet() {
+ // 获取字段的类型
+ fieldType := fieldValue.Type()
+
+ // 转换传入的值类型为字段类型
+ switch fieldType.Kind() {
+ case reflect.String:
+ if value == nil {
+ fieldValue.SetString("")
+ } else {
+ fieldValue.SetString(fmt.Sprintf("%v", value))
+ }
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ intValue, err := strconv.ParseInt(fmt.Sprintf("%v", value), 10, 64)
+ if err != nil {
+ intValue = 0
+ }
+ fieldValue.SetInt(intValue)
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ uintValue, err := strconv.ParseUint(fmt.Sprintf("%v", value), 10, 64)
+ if err != nil {
+ uintValue = 0
+ }
+ fieldValue.SetUint(uintValue)
+ case reflect.Float32, reflect.Float64:
+ floatValue, err := strconv.ParseFloat(fmt.Sprintf("%v", value), 64)
+ if err != nil {
+ floatValue = 0
+ }
+ fieldValue.SetFloat(floatValue)
+ default:
+ // 设置字段的值
+ fieldValue.Set(reflect.ValueOf(value).Convert(fieldValue.Type()))
+ }
+ }
+}
+
+// ConvertIdsSlice 将 []string 转换为 []any
+func ConvertIdsSlice(ids []string) []any {
+ // 将 []string 转换为 []any
+ arr := make([]any, len(ids))
+ for i, v := range ids {
+ arr[i] = v
+ }
+ return arr
+}
+
+// 查询-参数值的占位符
+func KeyPlaceholderByQuery(sum int) string {
+ placeholders := make([]string, sum)
+ for i := 0; i < sum; i++ {
+ placeholders[i] = "?"
+ }
+ return strings.Join(placeholders, ",")
+}
+
+// 插入-参数映射键值占位符 keys, placeholder, values
+func KeyPlaceholderValueByInsert(params map[string]any) ([]string, string, []any) {
+ // 参数映射的键
+ keys := make([]string, len(params))
+ // 参数映射的值
+ values := make([]any, len(params))
+ sum := 0
+ for k, v := range params {
+ keys[sum] = k
+ values[sum] = v
+ sum++
+ }
+ // 参数值的占位符
+ placeholders := make([]string, sum)
+ for i := 0; i < sum; i++ {
+ placeholders[i] = "?"
+ }
+ return keys, strings.Join(placeholders, ","), values
+}
+
+// 更新-参数映射键值占位符 keys, values
+func KeyValueByUpdate(params map[string]any) ([]string, []any) {
+ // 参数映射的键
+ keys := make([]string, len(params))
+ // 参数映射的值
+ values := make([]any, len(params))
+ sum := 0
+ for k, v := range params {
+ keys[sum] = k + "=?"
+ values[sum] = v
+ sum++
+ }
+ return keys, values
+}
diff --git a/lib/core/mml_client/mml_client.go b/lib/core/mml_client/mml_client.go
new file mode 100644
index 0000000..c60a9a6
--- /dev/null
+++ b/lib/core/mml_client/mml_client.go
@@ -0,0 +1,87 @@
+package mmlclient
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "net"
+ "time"
+
+ "nms_nbi/lib/core/conf"
+)
+
+// 定义MMLClient结构体
+type MMLClient struct {
+ awaitTime time.Duration // 等待时间
+ conn net.Conn
+ reader *bufio.Reader
+ size int // 包含字符
+}
+
+// 封装NewMMLClient函数,用于创建MMLClient实例
+// 网元UDM的IP地址 "198.51.100.1"
+func NewMMLClient(ip string) (*MMLClient, error) {
+ // 创建TCP连接
+ portMML := conf.Get("mml.port").(int)
+ hostMML := fmt.Sprintf("%s:%d", ip, portMML)
+ conn, err := net.Dial("tcp", hostMML)
+ if err != nil {
+ return nil, err
+ }
+
+ // 进行登录
+ usernameMML := conf.Get("mml.user").(string)
+ passwordMML := conf.Get("mml.password").(string)
+ fmt.Fprintln(conn, usernameMML)
+ fmt.Fprintln(conn, passwordMML)
+
+ // 发送后等待
+ awaitTime := time.Duration(300) * time.Millisecond
+ time.Sleep(awaitTime)
+
+ // 读取内容
+ buf := make([]byte, 1024*1024*1)
+ n, err := conn.Read(buf)
+ if err != nil {
+ return nil, err
+ }
+
+ // 创建MMLClient实例
+ client := &MMLClient{
+ conn: conn,
+ reader: bufio.NewReader(conn),
+ awaitTime: awaitTime,
+ size: n,
+ }
+
+ return client, nil
+}
+
+// 封装Send函数,用于向TCP连接发送数据
+func (c *MMLClient) Send(msg string) error {
+ _, err := fmt.Fprintln(c.conn, msg)
+ if err != nil {
+ return err
+ }
+ time.Sleep(c.awaitTime)
+ return nil
+}
+
+// 封装Receive函数,用于从TCP连接中接收数据
+func (c *MMLClient) Receive() (string, error) {
+ buf := make([]byte, 1024*1024*1)
+ n, err := c.reader.Read(buf)
+ if err != nil {
+ if err == io.EOF {
+ return "", fmt.Errorf("server closed the connection")
+ }
+ return "", err
+ }
+ c.size += n
+ return string(buf[0:n]), nil
+}
+
+// 封装Close函数,用于关闭TCP连接
+func (c *MMLClient) Close() error {
+ return c.conn.Close()
+}
diff --git a/lib/core/mml_client/send.go b/lib/core/mml_client/send.go
new file mode 100644
index 0000000..943772c
--- /dev/null
+++ b/lib/core/mml_client/send.go
@@ -0,0 +1,105 @@
+package mmlclient
+
+import (
+ "fmt"
+ "strings"
+)
+
+// 发送MML原始消息
+// ip 网元IP地址
+// msg 指令
+func MMLSendMsg(ip, msg string) (string, error) {
+ // 创建MMLClient实例
+ client, err := NewMMLClient(ip)
+ if err != nil {
+ return "", fmt.Errorf("failed to create MMLClient instance: %v", err)
+ }
+ defer client.Close()
+
+ // 发送数据
+ err = client.Send(msg)
+ if err != nil {
+ return "", fmt.Errorf("sending data failed: %v", err)
+ }
+
+ // 接收数据
+ data, err := client.Receive()
+ if err != nil {
+ return "", fmt.Errorf("failed to receive data: %v", err)
+ }
+
+ return data, nil
+}
+
+// 发送MML
+// ip 网元IP地址
+// msg 指令
+func MMLSendMsgToString(ip, msg string) (string, error) {
+ // 发送获取数据
+ str, err := MMLSendMsg(ip, msg)
+ if err != nil {
+ return "", err
+ }
+
+ str = strings.ToLower(str)
+
+ // 截断
+ index := strings.Index(str, "\n")
+ if index != -1 {
+ str = str[:index]
+ }
+
+ // 命令成功
+ if strings.Contains(str, "ok") || strings.Contains(str, "success") {
+ return str, nil
+ }
+
+ return "", fmt.Errorf(str)
+}
+
+// 发送MML
+// ip 网元IP地址
+// msg 指令
+func MMLSendMsgToMap(ip, msg string) (map[string]string, error) {
+ // 发送获取数据
+ str, err := MMLSendMsg(ip, msg)
+ if err != nil {
+ return nil, err
+ }
+
+ // 无数据
+ if strings.HasPrefix(str, "No Auth Data") {
+ return nil, fmt.Errorf("no auth data")
+ }
+
+ // 初始化一个map用于存储拆分后的键值对
+ m := make(map[string]string)
+
+ var items []string
+ if strings.Contains(str, "\r\n") {
+ // 按照分隔符"\r\n"进行拆分
+ items = strings.Split(str, "\r\n")
+ } else if strings.Contains(str, "\n") {
+ // 按照分隔符"\n"进行拆分
+ items = strings.Split(str, "\n")
+ }
+
+ // 遍历拆分后的结果
+ for _, item := range items {
+ var pair []string
+
+ if strings.Contains(item, "=") {
+ // 按照分隔符"="进行拆分键值对
+ pair = strings.SplitN(item, "=", 2)
+ } else if strings.Contains(item, ":") {
+ // 按照分隔符":"进行拆分键值对
+ pair = strings.SplitN(item, ":", 2)
+ }
+
+ if len(pair) == 2 {
+ // 将键值对存入map中
+ m[pair[0]] = pair[1]
+ }
+ }
+ return m, err
+}
diff --git a/lib/core/utils/ctx/ctx.go b/lib/core/utils/ctx/ctx.go
new file mode 100644
index 0000000..af64a48
--- /dev/null
+++ b/lib/core/utils/ctx/ctx.go
@@ -0,0 +1,192 @@
+package ctx
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "nms_nbi/lib/core/vo"
+ commonConstants "nms_nbi/src/framework/constants/common"
+ tokenConst "nms_nbi/src/framework/constants/token"
+
+ "github.com/gorilla/mux"
+ "golang.org/x/text/language"
+)
+
+// Param 地址栏参数{id}
+func Param(r *http.Request, key string) string {
+ vars := mux.Vars(r)
+ v, ok := vars[key]
+ if ok {
+ return v
+ }
+ return ""
+}
+
+// GetQuery 查询参数
+func GetQuery(r *http.Request, key string) string {
+ return r.URL.Query().Get(key)
+}
+
+// GetHeader 请求头参数
+func GetHeader(r *http.Request, key string) string {
+ return r.Header.Get(key)
+}
+
+// QueryMap 查询参数转换Map
+func QueryMap(r *http.Request) map[string]any {
+ queryValues := r.URL.Query()
+ queryParams := make(map[string]any)
+ for key, values := range queryValues {
+ queryParams[key] = values[0]
+ }
+ return queryParams
+}
+
+// ShouldBindQuery 查询参数读取json请求结构团体
+func ShouldBindQuery(r *http.Request, args any) error {
+ queryParams := QueryMap(r)
+ body, err := json.Marshal(queryParams)
+ if err != nil {
+ return err
+ }
+ return json.Unmarshal(body, args)
+}
+
+// 读取json请求结构团体
+func ShouldBindJSON(r *http.Request, args any) error {
+ body, err := io.ReadAll(io.LimitReader(r.Body, 1<<20)) // 设置较大的长度,例如 1<<20 (1MB)
+ if err != nil {
+ return err
+ }
+ err = json.Unmarshal(body, args)
+ return err
+}
+
+// JSON 相应json数据
+func JSON(w http.ResponseWriter, code int, data any) {
+ w.Header().Set("Content-Type", "application/json;charset=UTF-8")
+
+ response, err := json.Marshal(data)
+ if err != nil {
+ w.WriteHeader(500)
+ w.Write([]byte(err.Error()))
+ } else {
+ w.WriteHeader(code)
+ w.Write(response)
+ }
+}
+
+// 将文件导出到外部下载
+func FileAttachment(w http.ResponseWriter, r *http.Request, filepath, filename string) {
+ w.Header().Set("Content-Disposition", `attachment; filename=`+url.QueryEscape(filename))
+ w.Header().Set("Content-Type", "application/octet-stream")
+ http.ServeFile(w, r, filepath)
+}
+
+// 将文件上传保存到指定目录
+// file, handler, err := r.FormFile("file")
+// SaveUploadedFile uploads the form file to specific dst.
+func SaveUploadedFile(r *http.Request, dst string) error {
+ // 解析请求中的文件
+ _, handler, err := r.FormFile("file")
+ if err != nil {
+ return err
+ }
+
+ src, err := handler.Open()
+ if err != nil {
+ return err
+ }
+ defer src.Close()
+
+ if err = os.MkdirAll(filepath.Dir(dst), 0750); err != nil {
+ return err
+ }
+
+ out, err := os.Create(dst)
+ if err != nil {
+ return err
+ }
+ defer out.Close()
+
+ _, err = io.Copy(out, src)
+ return err
+}
+
+/// ==== 登录用户信息, 通过中间件后预置入
+
+// Authorization 解析请求头
+func Authorization(r *http.Request) string {
+ authHeader := r.Header.Get(tokenConst.HEADER_KEY)
+ if authHeader == "" {
+ return ""
+ }
+ // 拆分 Authorization 请求头,提取 JWT 令牌部分
+ arr := strings.Split(authHeader, tokenConst.HEADER_PREFIX)
+ if len(arr) == 2 && arr[1] == "" {
+ return ""
+ }
+ return arr[1]
+}
+
+// AcceptLanguage 解析客户端接收语言 zh:中文 en: 英文
+func AcceptLanguage(r *http.Request) string {
+ preferredLanguage := language.English
+
+ // Query请求查询
+ if v := GetQuery(r, "language"); v != "" {
+ tags, _, _ := language.ParseAcceptLanguage(v)
+ if len(tags) > 0 {
+ preferredLanguage = tags[0]
+ }
+ }
+ // Header请求头
+ if v := GetHeader(r, "Accept-Language"); v != "" {
+ tags, _, _ := language.ParseAcceptLanguage(v)
+ if len(tags) > 0 {
+ preferredLanguage = tags[0]
+ }
+ }
+
+ // 只取前缀
+ lang := preferredLanguage.String()
+ arr := strings.Split(lang, "-")
+ return arr[0]
+}
+
+// ContextKey 定义自定义类型作为键
+type ContextKey string
+
+// LoginUser 登录用户信息需要Authorize中间件
+func LoginUser(r *http.Request) (vo.LoginUser, error) {
+ // 上下文
+ v := r.Context().Value(ContextKey(commonConstants.CTX_LOGIN_USER))
+ if v != nil {
+ return v.(vo.LoginUser), nil
+ }
+ return vo.LoginUser{}, fmt.Errorf("No user information")
+}
+
+// LoginUserToUserID 登录用户信息-用户ID
+func LoginUserToUserID(r *http.Request) string {
+ loginUser, err := LoginUser(r)
+ if err != nil {
+ return ""
+ }
+ return loginUser.UserID
+}
+
+// LoginUserToUserName 登录用户信息-用户名称
+func LoginUserToUserName(r *http.Request) string {
+ loginUser, err := LoginUser(r)
+ if err != nil {
+ return ""
+ }
+ return loginUser.UserName
+}
diff --git a/lib/core/utils/date/date.go b/lib/core/utils/date/date.go
new file mode 100644
index 0000000..865a499
--- /dev/null
+++ b/lib/core/utils/date/date.go
@@ -0,0 +1,70 @@
+package date
+
+import (
+ "fmt"
+ "time"
+
+ "nms_nbi/lib/log"
+)
+
+const (
+ // 年 列如:2022
+ YYYY = "2006"
+ // 年-月 列如:2022-12
+ YYYY_MM = "2006-01"
+ // 年-月-日 列如:2022-12-30
+ YYYY_MM_DD = "2006-01-02"
+ // 年月日时分秒 列如:20221230010159
+ YYYYMMDDHHMMSS = "20060102150405"
+ // 年-月-日 时:分:秒 列如:2022-12-30 01:01:59
+ YYYY_MM_DD_HH_MM_SS = "2006-01-02 15:04:05"
+)
+
+// 格式时间字符串
+//
+// dateStr 时间字符串
+//
+// formatStr 时间格式 默认YYYY-MM-DD HH:mm:ss
+func ParseStrToDate(dateStr, formatStr string) time.Time {
+ t, err := time.Parse(formatStr, dateStr)
+ if err != nil {
+ log.Infof("utils ParseStrToDate err %v", err)
+ return time.Time{}
+ }
+ return t
+}
+
+// 格式时间
+//
+// date 可转的Date对象
+//
+// formatStr 时间格式 默认YYYY-MM-DD HH:mm:ss
+func ParseDateToStr(date any, formatStr string) string {
+ t, ok := date.(time.Time)
+ if !ok {
+ switch v := date.(type) {
+ case int64:
+ if v == 0 {
+ return ""
+ }
+ t = time.UnixMilli(v)
+ case string:
+ parsedTime, err := time.Parse(formatStr, v)
+ if err != nil {
+ fmt.Printf("utils ParseDateToStr err %v \n", err)
+ return ""
+ }
+ t = parsedTime
+ default:
+ return ""
+ }
+ }
+ return t.Format(formatStr)
+}
+
+// 格式时间成日期路径
+//
+// 年/月 列如:2022/12
+func ParseDatePath(date time.Time) string {
+ return date.Format("2006/01")
+}
diff --git a/lib/core/utils/parse/parse.go b/lib/core/utils/parse/parse.go
new file mode 100644
index 0000000..ed2fd5d
--- /dev/null
+++ b/lib/core/utils/parse/parse.go
@@ -0,0 +1,139 @@
+package parse
+
+import (
+ "fmt"
+ "reflect"
+ "regexp"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/robfig/cron/v3"
+)
+
+// Number 解析数值型
+func Number(str any) int64 {
+ switch str := str.(type) {
+ case string:
+ if str == "" {
+ return 0
+ }
+ num, err := strconv.ParseInt(str, 10, 64)
+ if err != nil {
+ return 0
+ }
+ return num
+ case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
+ return reflect.ValueOf(str).Int()
+ case float32, float64:
+ return int64(reflect.ValueOf(str).Float())
+ default:
+ return 0
+ }
+}
+
+// Boolean 解析布尔型
+func Boolean(str any) bool {
+ switch str := str.(type) {
+ case string:
+ if str == "" || str == "false" || str == "0" {
+ return false
+ }
+ // 尝试将字符串解析为数字
+ if num, err := strconv.ParseFloat(str, 64); err == nil {
+ return num != 0
+ }
+ return true
+ case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
+ num := reflect.ValueOf(str).Int()
+ return num != 0
+ case float32, float64:
+ num := reflect.ValueOf(str).Float()
+ return num != 0
+ default:
+ return false
+ }
+}
+
+// FirstUpper 首字母转大写
+//
+// 字符串 abc_123!@# 结果 Abc_123
+func FirstUpper(str string) string {
+ if len(str) == 0 {
+ return str
+ }
+ reg := regexp.MustCompile(`[^_\w]+`)
+ str = reg.ReplaceAllString(str, "")
+ return strings.ToUpper(str[:1]) + str[1:]
+}
+
+// Bit 比特位为单位
+func Bit(bit float64) string {
+ var GB, MB, KB string
+
+ if bit > float64(1<<30) {
+ GB = fmt.Sprintf("%0.2f", bit/(1<<30))
+ }
+
+ if bit > float64(1<<20) && bit < (1<<30) {
+ MB = fmt.Sprintf("%.2f", bit/(1<<20))
+ }
+
+ if bit > float64(1<<10) && bit < (1<<20) {
+ KB = fmt.Sprintf("%.2f", bit/(1<<10))
+ }
+
+ if GB != "" {
+ return GB + "GB"
+ } else if MB != "" {
+ return MB + "MB"
+ } else if KB != "" {
+ return KB + "KB"
+ } else {
+ return fmt.Sprintf("%vB", bit)
+ }
+}
+
+// CronExpression 解析 Cron 表达式,返回下一次执行的时间戳(毫秒)
+//
+// 【*/5 * * * * ?】 6个参数
+func CronExpression(expression string) int64 {
+ specParser := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)
+ schedule, err := specParser.Parse(expression)
+ if err != nil {
+ fmt.Println(err)
+ return 0
+ }
+ return schedule.Next(time.Now()).UnixMilli()
+}
+
+// SafeContent 内容值进行安全掩码
+func SafeContent(value string) string {
+ if len(value) < 3 {
+ return strings.Repeat("*", len(value))
+ } else if len(value) < 6 {
+ return string(value[0]) + strings.Repeat("*", len(value)-1)
+ } else if len(value) < 10 {
+ return string(value[0]) + strings.Repeat("*", len(value)-2) + string(value[len(value)-1])
+ } else if len(value) < 15 {
+ return value[:2] + strings.Repeat("*", len(value)-4) + value[len(value)-2:]
+ } else {
+ return value[:3] + strings.Repeat("*", len(value)-6) + value[len(value)-3:]
+ }
+}
+
+// RemoveDuplicates 数组内字符串去重
+func RemoveDuplicates(ids []string) []string {
+ uniqueIDs := make(map[string]bool)
+ uniqueIDSlice := make([]string, 0)
+
+ for _, id := range ids {
+ _, ok := uniqueIDs[id]
+ if !ok && id != "" {
+ uniqueIDs[id] = true
+ uniqueIDSlice = append(uniqueIDSlice, id)
+ }
+ }
+
+ return uniqueIDSlice
+}
diff --git a/lib/core/utils/regular/regular.go b/lib/core/utils/regular/regular.go
new file mode 100644
index 0000000..e4b9cc6
--- /dev/null
+++ b/lib/core/utils/regular/regular.go
@@ -0,0 +1,54 @@
+package regular
+
+import (
+ "regexp"
+)
+
+// Replace 正则替换
+func Replace(originStr, pattern, repStr string) string {
+ regex := regexp.MustCompile(pattern)
+ return regex.ReplaceAllString(originStr, repStr)
+}
+
+// 判断是否为有效用户名格式
+//
+// 用户名不能以数字开头,可包含大写小写字母,数字,且不少于5位
+func ValidUsername(username string) bool {
+ if username == "" {
+ return false
+ }
+ pattern := `^[a-zA-Z][a-z0-9A-Z]{5,}`
+ match, err := regexp.MatchString(pattern, username)
+ if err != nil {
+ return false
+ }
+ return match
+}
+
+// 判断是否为有效手机号格式,1开头的11位手机号
+func ValidMobile(mobile string) bool {
+ if mobile == "" {
+ return false
+ }
+ pattern := `^1[3|4|5|6|7|8|9][0-9]\d{8}$`
+ match, err := regexp.MatchString(pattern, mobile)
+ if err != nil {
+ return false
+ }
+ return match
+}
+
+// 判断是否为http(s)://开头
+//
+// link 网络链接
+func ValidHttp(link string) bool {
+ if link == "" {
+ return false
+ }
+ pattern := `^http(s)?:\/\/+`
+ match, err := regexp.MatchString(pattern, link)
+ if err != nil {
+ return false
+ }
+ return match
+}
diff --git a/lib/core/utils/scan/scan.go b/lib/core/utils/scan/scan.go
new file mode 100644
index 0000000..6313763
--- /dev/null
+++ b/lib/core/utils/scan/scan.go
@@ -0,0 +1,31 @@
+package scan
+
+import (
+ "net"
+ "strconv"
+)
+
+func ScanPort(port int) bool {
+ ln, err := net.Listen("tcp", ":"+strconv.Itoa(port))
+ if err != nil {
+ return true
+ }
+ defer ln.Close()
+ return false
+}
+
+func ScanUDPPort(port int) bool {
+ ln, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: port})
+ if err != nil {
+ return true
+ }
+ defer ln.Close()
+ return false
+}
+
+func ScanPortWithProto(port int, proto string) bool {
+ if proto == "udp" {
+ return ScanUDPPort(port)
+ }
+ return ScanPort(port)
+}
diff --git a/lib/core/vo/login_user.go b/lib/core/vo/login_user.go
new file mode 100644
index 0000000..a171d2d
--- /dev/null
+++ b/lib/core/vo/login_user.go
@@ -0,0 +1,28 @@
+package vo
+
+import (
+ "nms_nbi/lib/dborm"
+)
+
+// LoginUser 登录用户身份权限信息对象
+type LoginUser struct {
+ // UserID 用户ID
+ UserID string `json:"userId"`
+
+ // UserName 用户名
+ UserName string `json:"userName"`
+
+ // LoginTime 登录时间时间戳
+ LoginTime int64 `json:"loginTime"`
+
+ // ExpireTime 过期时间时间戳
+ ExpireTime int64 `json:"expireTime"`
+
+ // Permissions 权限列表
+ Permissions []string `json:"permissions"`
+
+ // User 用户信息
+ User dborm.User `json:"user"`
+
+ Session dborm.Session `json:"-"`
+}
diff --git a/lib/core/vo/result/result.go b/lib/core/vo/result/result.go
new file mode 100644
index 0000000..51faf08
--- /dev/null
+++ b/lib/core/vo/result/result.go
@@ -0,0 +1,72 @@
+package result
+
+const CODE_ERROR = 0
+const MSG_ERROR = "error"
+const CODE_SUCCESS = 1
+const MSG_SUCCESS = "success"
+
+// CodeMsg 响应结果
+func CodeMsg(code int, msg string) map[string]any {
+ args := make(map[string]any)
+ args["code"] = code
+ args["msg"] = msg
+ return args
+}
+
+// 响应成功结果 map[string]any{}
+func Ok(v map[string]any) map[string]any {
+ args := make(map[string]any)
+ args["code"] = CODE_SUCCESS
+ args["msg"] = MSG_SUCCESS
+ // v合并到args
+ for key, value := range v {
+ args[key] = value
+ }
+ return args
+}
+
+// 响应成功结果信息
+func OkMsg(msg string) map[string]any {
+ args := make(map[string]any)
+ args["code"] = CODE_SUCCESS
+ args["msg"] = msg
+ return args
+}
+
+// 响应成功结果数据
+func OkData(data any) map[string]any {
+ args := make(map[string]any)
+ args["code"] = CODE_SUCCESS
+ args["msg"] = MSG_SUCCESS
+ args["data"] = data
+ return args
+}
+
+// 响应失败结果 map[string]any{}
+func Err(v map[string]any) map[string]any {
+ args := make(map[string]any)
+ args["code"] = CODE_ERROR
+ args["msg"] = MSG_ERROR
+ // v合并到args
+ for key, value := range v {
+ args[key] = value
+ }
+ return args
+}
+
+// 响应失败结果信息
+func ErrMsg(msg string) map[string]any {
+ args := make(map[string]any)
+ args["code"] = CODE_ERROR
+ args["msg"] = msg
+ return args
+}
+
+// 响应失败结果数据
+func ErrData(data any) map[string]any {
+ args := make(map[string]any)
+ args["code"] = CODE_ERROR
+ args["msg"] = MSG_ERROR
+ args["data"] = data
+ return args
+}
diff --git a/lib/core/vo/router.go b/lib/core/vo/router.go
new file mode 100644
index 0000000..a3cc408
--- /dev/null
+++ b/lib/core/vo/router.go
@@ -0,0 +1,17 @@
+package vo
+
+// Router 路由信息对象
+type Router struct {
+ // 路由名字 英文首字母大写
+ Name string `json:"name"`
+ // 路由地址
+ Path string `json:"path"`
+ // 其他元素
+ Meta RouterMeta `json:"meta"`
+ // 组件地址
+ Component string `json:"component"`
+ // 重定向地址
+ Redirect string `json:"redirect"`
+ // 子路由
+ Children []Router `json:"children,omitempty"`
+}
diff --git a/lib/core/vo/router_meta.go b/lib/core/vo/router_meta.go
new file mode 100644
index 0000000..b3447e0
--- /dev/null
+++ b/lib/core/vo/router_meta.go
@@ -0,0 +1,17 @@
+package vo
+
+// RouterMeta 路由元信息对象
+type RouterMeta struct {
+ // 设置该菜单在侧边栏和面包屑中展示的名字
+ Title string `json:"title"`
+ // 设置该菜单的图标
+ Icon string `json:"icon"`
+ // 设置为true,则不会被 缓存
+ Cache bool `json:"cache"`
+ // 内链地址(http(s)://开头), 打开目标位置 '_blank' | '_self' | ''
+ Target string `json:"target"`
+ // 在菜单中隐藏子节点
+ HideChildInMenu bool `json:"hideChildInMenu"`
+ // 在菜单中隐藏自己和子节点
+ HideInMenu bool `json:"hideInMenu"`
+}
diff --git a/lib/core/vo/treeselect.go b/lib/core/vo/treeselect.go
new file mode 100644
index 0000000..89fa19c
--- /dev/null
+++ b/lib/core/vo/treeselect.go
@@ -0,0 +1,36 @@
+package vo
+
+// import sysmenu "nms_nbi/features/sys_menu"
+
+// TreeSelect 树结构实体类
+type TreeSelect struct {
+ // ID 节点ID
+ ID string `json:"id"`
+
+ // Label 节点名称
+ Label string `json:"label"`
+
+ // Title 节点名称旧版本layui
+ Title string `json:"title"`
+
+ // Children 子节点
+ Children []TreeSelect `json:"children"`
+}
+
+// // SysMenuTreeSelect 使用给定的 SysMenu 对象解析为 TreeSelect 对象
+// func SysMenuTreeSelect(sysMenu sysmenu.SysMenu) TreeSelect {
+// t := TreeSelect{}
+// t.ID = sysMenu.MenuID
+// t.Label = sysMenu.MenuName
+
+// if len(sysMenu.Children) > 0 {
+// for _, menu := range sysMenu.Children {
+// child := SysMenuTreeSelect(menu)
+// t.Children = append(t.Children, child)
+// }
+// } else {
+// t.Children = []TreeSelect{}
+// }
+
+// return t
+// }
diff --git a/lib/dborm/dborm.go b/lib/dborm/dborm.go
new file mode 100644
index 0000000..646b5b3
--- /dev/null
+++ b/lib/dborm/dborm.go
@@ -0,0 +1,1962 @@
+package dborm
+
+import (
+ "database/sql"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "strconv"
+ "time"
+
+ "strings"
+
+ "nms_nbi/features/sys_role/model"
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/oauth"
+
+ _ "github.com/go-sql-driver/mysql"
+ "xorm.io/xorm"
+)
+
+const (
+ TableNameMeasureTask = "measure_task"
+ TableNameNeInfo = "ne_info"
+)
+
+type Menu struct {
+ Id int `json:"id"`
+ Title string `json:"title"`
+ Icon string `json:"icon"`
+ Href string `json:"href"`
+ ParentId int `json:"parent_id`
+ Remark int `json:"remark"`
+}
+
+type DatabaseClient struct {
+ dbType string
+ dbUrl string
+ dbConnMaxLifetime time.Duration
+ dbMaxIdleConns int
+ dbMaxOpenConns int
+ IsShowSQL bool
+
+ XEngine *xorm.Engine
+}
+
+var DbClient DatabaseClient
+
+func InitDbClient(dbType, dbUser, dbPassword, dbHost, dbPort, dbName string) error {
+ DbClient.dbUrl = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=true&loc=Local",
+ dbUser, dbPassword, dbHost, dbPort, dbName)
+ DbClient.dbType = dbType
+ DbClient.dbConnMaxLifetime = 0
+ DbClient.dbMaxIdleConns = 0
+ DbClient.dbMaxOpenConns = 0
+ if log.GetLevel() == log.LOG_TRACE {
+ DbClient.IsShowSQL = true
+ }
+ log.Debugf("dbType:%s dbUrl:%s:******@tcp(%s:%s)/%s??charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=true&loc=Local",
+ dbType, dbUser, dbHost, dbPort, dbName)
+
+ var err error
+ DbClient.XEngine, err = xorm.NewEngine(DbClient.dbType, DbClient.dbUrl)
+ if err != nil {
+ log.Error("Failed to connet database:", err)
+ return err
+ }
+ DbClient.XEngine.SetConnMaxLifetime(DbClient.dbConnMaxLifetime)
+ DbClient.XEngine.SetMaxIdleConns(DbClient.dbMaxIdleConns)
+ DbClient.XEngine.SetMaxOpenConns(DbClient.dbMaxOpenConns)
+ if DbClient.IsShowSQL {
+ DbClient.XEngine.ShowSQL(true)
+ }
+ xEngine = DbClient.XEngine
+
+ return nil
+}
+
+// func InitDbClient() error {
+// db := config.GetYamlConfig().Database
+// DbClient.dbUrl = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", db.User, db.Password, db.Host, db.Port, db.Name)
+// DbClient.dbType = db.Type
+// DbClient.dbConnMaxLifetime = 0
+// DbClient.dbMaxIdleConns = 0
+// DbClient.dbMaxOpenConns = 0
+// if log.GetLevel() == log.LOG_TRACE {
+// DbClient.IsShowSQL = true
+// }
+// log.Debugf("dbType:%s dbUrl:%s:******@tcp(%s:%s)/%s", DbClient.dbType, db.User, db.Host, db.Port, db.Name)
+// var err error
+// DbClient.XEngine, err = xorm.NewEngine(DbClient.dbType, DbClient.dbUrl)
+// if err != nil {
+// log.Error("Failed to connet database:", err)
+// return err
+// }
+// DbClient.XEngine.SetConnMaxLifetime(DbClient.dbConnMaxLifetime)
+// DbClient.XEngine.SetMaxIdleConns(DbClient.dbMaxIdleConns)
+// DbClient.XEngine.SetMaxOpenConns(DbClient.dbMaxOpenConns)
+// if DbClient.IsShowSQL {
+// DbClient.XEngine.ShowSQL(true)
+// }
+// xEngine = DbClient.XEngine
+
+// return nil
+// }
+
+var xEngine *xorm.Engine
+
+func XormConnectDatabase(dbType, dbUser, dbPassword, dbHost, dbPort, dbName string) (*xorm.Engine, error) {
+ sqlStr := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=true&loc=Local",
+ dbUser, dbPassword, dbHost, dbPort, dbName)
+ log.Debugf("dbType:%s Connect to:%s:******@tcp(%s:%s)/%s?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=true&loc=Local",
+ dbType, dbUser, dbHost, dbPort, dbName)
+ var err error
+ xEngine, err = xorm.NewEngine(dbType, sqlStr) //1、Create xorm engine
+ if err != nil {
+ log.Error("Failed to connect database:", err)
+ return nil, err
+ }
+ if log.GetLevel() == log.LOG_TRACE {
+ xEngine.ShowSQL(true)
+ }
+ return xEngine, nil
+}
+
+func ConstructInsertSQL(tableName string, insertData interface{}) (string, []string) {
+ log.Debug("ConstructInsertSQL processing... ")
+ log.Debug("Request insertData:", insertData)
+
+ var sql []string
+ var dataTag string
+ for t, d := range insertData.(map[string]interface{}) {
+ log.Tracef("t: %v d: %v", t, d)
+ dataTag = t
+
+ for i, r := range d.([]interface{}) {
+ var cl, vl string
+ log.Tracef("i: %v r: %v", i, r)
+ for c, v := range r.(map[string]interface{}) {
+ log.Tracef("c: %v v: %v", c, v)
+ if cl == "" {
+ cl = fmt.Sprintf("%s", c)
+ } else {
+ cl = fmt.Sprintf("%s, %s", cl, c)
+ }
+ if vl == "" {
+ vl = fmt.Sprintf("'%v'", v)
+ } else {
+ vl = fmt.Sprintf("%s, '%v'", vl, v)
+ }
+
+ log.Tracef("cl: %s vl: %s", cl, vl)
+ }
+ sql = append(sql, "insert into "+tableName+"("+cl+")"+" values("+vl+")")
+ }
+
+ log.Debug("sql:", sql)
+ }
+
+ return dataTag, sql
+}
+
+func InsertDataWithJson(insertData interface{}) (int64, error) {
+ log.Debug("InsertDataWithJson processing... ")
+
+ var data map[string]interface{}
+ var tableName string
+ for k, v := range insertData.(map[string]interface{}) {
+ log.Tracef("k: %v v: %v", k, v)
+ tableName = k
+ data = v.(map[string]interface{})
+ }
+
+ log.Tracef("data: %v", data)
+ xSession := xEngine.NewSession()
+ defer xSession.Close()
+ affected, err := xSession.Insert(data, tableName)
+ xSession.Commit()
+
+ return affected, err
+}
+
+type NeInfo struct {
+ Id int `json:"id" xorm:"pk 'id' autoincr"`
+ NeType string `json:"neType" xorm:"ne_type"`
+ NeId string `json:"neId" xorm:"ne_id"` // neUID/rmUID 网元唯一标识
+ RmUID string `json:"rmUid" xorm:"rm_uid"` // neUID/rmUID网元UID
+ NeName string `json:"neName" xorm:"ne_name"` // NeName/UserLabel 网元名称/网元设备友好名称
+ Ip string `json:"ip" xorm:"ip"`
+ Port string `json:"port" xorm:"port"`
+ PvFlag string `json:"pvFlag" xorm:"pv_flag"` // 网元虚实性标识 VNF/PNF: 虚拟/物理
+ NeAddress string `json:"neAddress" xorm:"ne_address"` // 只对PNF
+ Province string `json:"province" xorm:"province"` // 网元所在省份
+ VendorName string `json:"vendorName" xorm:"vendor_name"` // 厂商名称
+ Dn string `json:"dn" xorm:"dn"` // 网络标识
+ Status int `json:"status" xorm:"status"`
+ UpdateTime string `json:"-" xorm:"-"`
+}
+
+func XormGetMySQLVersion() (string, error) {
+ var versionInfo string = ""
+
+ var ver, verComment, verCompileOS, verCompile string
+ _, err := xEngine.SQL("SHOW VARIABLES LIKE 'version'").Get(&ver, &ver)
+ if err != nil {
+ return versionInfo, err
+ }
+
+ _, err = xEngine.SQL("SHOW VARIABLES LIKE 'version_comment'").Get(&verComment, &verComment)
+ if err != nil {
+ return versionInfo, err
+ }
+
+ _, err = xEngine.SQL("SHOW VARIABLES LIKE 'version_compile_os'").Get(&verCompileOS, &verCompileOS)
+ if err != nil {
+ return versionInfo, err
+ }
+
+ _, err = xEngine.SQL("SHOW VARIABLES LIKE 'version_compile_machine'").Get(&verCompile, &verCompile)
+ if err != nil {
+ return versionInfo, err
+ }
+
+ versionInfo = fmt.Sprintf("%s %s, for %s (%s)", ver, verComment, verCompileOS, verCompile)
+ return versionInfo, nil
+}
+
+func XormGetNeInfo(neType string, neId string) (*NeInfo, error) {
+ log.Debug("XormGetNeInfo processing... ")
+
+ neInfo := new(NeInfo)
+ has, err := xEngine.Where("ne_type=? and ne_id=?", strings.ToUpper(neType), neId).Get(neInfo)
+ if err != nil {
+ log.Error("Failed to get table ne_info from database:", err)
+ return nil, err
+ } else if !has {
+ log.Infof("Not found ne_info from database, neType=%s, neId=%s", neType, neId)
+ return nil, nil
+ }
+
+ log.Debug("NE Info:", neInfo)
+ return neInfo, nil
+}
+
+func XormGetNeInfoByRmUID(neType string, rmUID string) (*NeInfo, error) {
+ log.Debug("XormGetNeInfoByRmUID processing... ")
+
+ neInfo := new(NeInfo)
+ has, err := xEngine.Where("ne_type=? and rm_uid=?", strings.ToUpper(neType), rmUID).Get(neInfo)
+ if err != nil {
+ log.Error("Failed to get table ne_info from database:", err)
+ return nil, err
+ } else if !has {
+ log.Infof("Not found ne_info from database, neType=%s, neId=%s", neType, rmUID)
+ return nil, nil
+ }
+
+ log.Debug("NE Info:", neInfo)
+ return neInfo, nil
+}
+
+func XormGetAllNeInfo(nes *[]NeInfo) (*[]NeInfo, error) {
+ log.Debug("XormGetAllNeInfo processing... ")
+
+ ne := new(NeInfo)
+ rows, err := xEngine.Table("ne_info").Rows(ne)
+ if err != nil {
+ log.Error("Failed to get table ne_info from database:", err)
+ return nil, err
+ }
+ defer rows.Close()
+ for rows.Next() {
+ err := rows.Scan(ne)
+ if err != nil {
+ log.Error("Failed to get table ne_info from database:", err)
+ return nil, err
+ }
+ *nes = append(*nes, *ne)
+ }
+ log.Trace("nes:", nes)
+ return nes, nil
+}
+
+func XormGetNeInfoByNeType(neType string, nes *[]NeInfo) error {
+ log.Debug("XormGetNeInfoByNeType processing... ")
+
+ ne := new(NeInfo)
+ rows, err := xEngine.Table("ne_info").Where("ne_type=?", neType).Rows(ne)
+ if err != nil {
+ log.Error("Failed to get table ne_info from database:", err)
+ return err
+ }
+ defer rows.Close()
+ for rows.Next() {
+ err := rows.Scan(ne)
+ if err != nil {
+ log.Error("Failed to get table ne_info from database:", err)
+ return err
+ }
+ *nes = append(*nes, *ne)
+ }
+
+ return nil
+}
+
+func XormGetNeInfo2(neType string, neIDs []string, nes *[]NeInfo) error {
+ log.Info("XormGetNeInfo2 processing... ")
+
+ ne := new(NeInfo)
+ var rows *xorm.Rows
+ var err error
+ if len(neIDs) == 0 {
+ rows, err = xEngine.Table("ne_info").
+ Where("ne_type=?", neType).
+ Rows(ne)
+ } else {
+ rows, err = xEngine.Table("ne_info").
+ In("ne_id", neIDs).
+ And("ne_type=?", neType).
+ Rows(ne)
+ }
+ if err != nil {
+ log.Error("Failed to get table ne_info from database:", err)
+ return err
+ }
+ defer rows.Close()
+ for rows.Next() {
+ err := rows.Scan(ne)
+ if err != nil {
+ log.Error("Failed to get table ne_info from database:", err)
+ return err
+ }
+ *nes = append(*nes, *ne)
+ }
+
+ return nil
+}
+
+func XormInsertNeInfo(neInfo *NeInfo) (int64, error) {
+ log.Debug("XormInsertNeInfo processing... ")
+
+ var affected int64 = 0
+ var err error = nil
+
+ xSession := xEngine.NewSession()
+ defer xSession.Close()
+ ex, _ := xEngine.Table("ne_info").Where("status = '1' and ne_type = ? and ne_id = ?", neInfo.NeType, neInfo.NeId).Exist()
+ if ex {
+ neInfo.Status = 0
+ affected, err = xSession.Where("ne_type = ? and ne_id = ?", neInfo.NeType, neInfo.NeId).Update(neInfo)
+ } else {
+ affected, err = xSession.InsertOne(neInfo)
+ }
+ xSession.Commit()
+ return affected, err
+}
+
+func XormUpdateNeInfo(neInfo *NeInfo) (int64, error) {
+ log.Debug("XormUpdateNeInfo processing... ")
+
+ xSession := xEngine.NewSession()
+ defer xSession.Close()
+ affected, err := xSession.ID(neInfo.Id).MustCols("status").Update(neInfo)
+ xSession.Commit()
+ return affected, err
+}
+
+func XormDeleteNeInfo(neInfo *NeInfo) (int64, error) {
+ log.Debug("XormDeleteNeInfo processing... ")
+
+ xSession := xEngine.NewSession()
+ defer xSession.Close()
+ affected, err := xSession.Where("ne_type = ? and ne_id = ?", neInfo.NeType, neInfo.NeId).Delete(neInfo)
+ xSession.Commit()
+ return affected, err
+}
+
+func XormParseResult(body []byte) ([]NeInfo, error) {
+ log.Debug("XormParseResult processing... ")
+
+ data := make(map[string]interface{})
+ err := json.Unmarshal(body, &data)
+ if err != nil {
+ return nil, err
+ }
+ var neInfo []NeInfo
+ var re interface{}
+ if data["data"] == nil {
+ return nil, errors.New("The data is Not found")
+ }
+ for _, d := range data["data"].([]interface{}) {
+ if d == nil {
+ return nil, errors.New("The data is Not found")
+ }
+ for _, re = range d.(map[string]interface{}) {
+ if re == nil {
+ return nil, errors.New("The data is Not found")
+ }
+ for _, rc := range re.([]interface{}) {
+ if rc == nil {
+ return nil, errors.New("The data is Not found")
+ }
+ var s NeInfo
+ record := rc.(map[string]interface{})
+ s.NeName = fmt.Sprintf("%v", record["ne_name"])
+ s.NeId = fmt.Sprintf("%v", record["ne_id"])
+ s.NeType = fmt.Sprintf("%v", strings.ToLower(record["ne_type"].(string)))
+ s.Ip = fmt.Sprintf("%v", record["ip"])
+ s.Port = fmt.Sprintf("%v", record["port"])
+ s.PvFlag = fmt.Sprintf("%v", record["pv_flag"])
+
+ neInfo = append(neInfo, s)
+ }
+ }
+ }
+
+ log.Debug("neInfo:", neInfo)
+ return neInfo, nil
+}
+
+type ParamConfig struct {
+ // Id int `json:"id" xorm:"pk 'id' autoincr"`
+ NeType string `json:"neType"`
+ NeId string `json:"neId"`
+ TopTag string `json:"topTag"`
+ TopDisplay string `json:"topDisplay"`
+ ParamJson string `json:"paramJson"`
+}
+
+func XormInsertParamConfig(mapJson *map[string]interface{}) (int64, error) {
+ var affected, a int64
+ var err error
+ paramConfig := new(ParamConfig)
+ for n, d := range *mapJson {
+ if d == nil {
+ break
+ }
+ log.Debugf("n: %s", n)
+
+ for t, p := range d.(map[string]interface{}) {
+ if p == nil {
+ break
+ }
+ log.Debug("t:", t)
+ log.Debug("p:", p)
+ for k, v := range p.(map[string]interface{}) {
+ log.Debug("k, v: ", k, v)
+ if k == "display" {
+ paramConfig.TopDisplay = fmt.Sprintf("%v", v)
+
+ } else {
+ pc, _ := json.Marshal(v)
+ paramConfig.ParamJson = fmt.Sprintf("{\"%v\":%v}", k, string(pc))
+ }
+ }
+
+ paramConfig.NeType = strings.ToUpper(n)
+ paramConfig.NeId = ""
+ paramConfig.TopTag = t
+ // paramConfig.TopDisplay = p["display"]
+ // paramConfig.ParamJson = p.(string)
+
+ log.Debug("paramConfig:", paramConfig)
+
+ xSession := xEngine.NewSession()
+ defer xSession.Close()
+ _, err = xSession.Table("param_config").Where("ne_type = ? and top_tag = ?", paramConfig.NeType, paramConfig.TopTag).Delete()
+ if err != nil {
+ log.Error("Failed to insert param_config:", err)
+ }
+ a, err = xSession.Insert(paramConfig)
+ if err != nil {
+ log.Error("Failed to insert param_config:", err)
+ }
+ affected += a
+ xSession.Commit()
+ }
+ }
+
+ return affected, err
+}
+
+func ConstructUpdateSQLArray(tableName string, updateData interface{}, whereCondition string) (string, []string) {
+ log.Debug("ConstructUpdateSQL processing... ")
+ log.Debug("Request updateData:", updateData)
+
+ var sql []string
+ var tblName string
+ for t, d := range updateData.(map[string]interface{}) {
+ log.Tracef("t: %v d: %v", t, d)
+ tblName = t
+
+ for i, r := range d.([]interface{}) {
+ var cv string
+ log.Tracef("i: %v r: %v", i, r)
+ for c, v := range r.(map[string]interface{}) {
+ log.Tracef("c: %v v: %v", c, v)
+ if cv == "" {
+ cv = fmt.Sprintf("`%s`='%s'", c, v)
+ } else {
+ cv = fmt.Sprintf("%s,`%s`='%s'", cv, c, v)
+ }
+
+ log.Tracef("cv: %s", cv)
+ }
+ sql = append(sql, "UPDATE "+tableName+" SET "+cv+" WHERE "+whereCondition)
+ }
+
+ log.Debug("sql:", sql)
+ }
+
+ return tblName, sql
+}
+
+func ConstructUpdateSQL(tableName string, updateData interface{}, whereCondition string) (string, []string) {
+ log.Debug("ConstructUpdateSQL processing... ")
+ log.Debug("Request updateData:", updateData)
+
+ var sql []string
+ var tblName string
+ for t, d := range updateData.(map[string]interface{}) {
+ log.Tracef("t: %v d: %v", t, d)
+ tblName = t
+
+ var cv string
+ for c, v := range d.(map[string]interface{}) {
+ log.Tracef("c: %v v: %v", c, v)
+ if cv == "" {
+ //cv = fmt.Sprintf("`%s`=%s", c, v)
+ cv = fmt.Sprintf("`%s`='%s'", c, v)
+ } else {
+ //cv = fmt.Sprintf("%s,`%s`=%s", cv, c, v)
+ cv = fmt.Sprintf("%s,`%s`='%s'", cv, c, v)
+ }
+
+ log.Tracef("cv: %s", cv)
+ }
+ sql = append(sql, "UPDATE "+tableName+" SET "+cv+" WHERE "+whereCondition)
+
+ log.Debug("sql:", sql)
+ }
+
+ return tblName, sql
+}
+
+func ConstructDeleteSQL(tableName string, whereCondition string) string {
+ log.Debug("ConstructDeleteSQL processing... ")
+
+ var sql string
+
+ sql = "DELETE from " + tableName + " WHERE " + whereCondition
+ log.Debug("sql:", sql)
+ return sql
+}
+
+type TaskStatus string
+
+const (
+ MeasureTaskStatusInactive = "Inactive"
+ MeasureTaskStatusActive = "Active"
+ MeasureTaskStatusSuspend = "Suspend"
+ MeasureTaskStatusDeleted = "Deleted"
+)
+
+type MTask struct {
+ Id int `json:"id" xorm:"pk 'id' autoincr"`
+
+ NeSet struct {
+ NEs []string `json:"nes"`
+ } `json:"neSet" xorm:"ne_set"`
+ KpiSet struct {
+ Code string `json:"Code"`
+ KPIs []string `json:"KPIs`
+ } `json:"kpiSet" xorm:"kpi_set"`
+ StartTime string `json:"startTime" xorm:"start_time"`
+ EndTime string `json:"endTime" xorm:"end_time"`
+ Periods []struct {
+ Start string `json:"start"`
+ End string `json:"end"`
+ } `json:"Periods" xorm:"periods`
+ Schedule struct {
+ Type string `json:"type"`
+ Days []int `json:"days"`
+ } `json:"schedule" xorm:"schedule"`
+ GranulOption string `json:"granulOption" xorm:"granul_option"`
+ Status string `json:"status" xorm:"status"`
+ CreateTime string `json:"createTime" xorm:"create_time"`
+ UpdateTime string `json:"updateTime" xorm:"update_time"`
+ DeleteTime string `json:"deleteTime xorm:"delete_time"`
+}
+
+type ScheduleJ struct {
+ Type string `json:"Type"`
+ Days []int `json:"Days"`
+}
+
+type Period struct {
+ Start string `json:"Start"`
+ End string `json:"End"`
+}
+
+type KpiSetJ struct {
+ Code string `json:"Code"` // 统计编码 如:SMFHA01
+ KPIs []string `json:"KPIs` // 指标项集合 ["SMF.AttCreatePduSession", "SMF.AttCreatePduSession._Dnn"]
+}
+
+type MeasureTask struct {
+ Id int `json:"id" xorm:"pk 'id' autoincr"`
+ NeType string `json:"neType" xorm:"ne_type"`
+ NeIds []string `json:"neIds" xorm:"ne_ids"`
+ KpiSet []KpiSetJ `json:"KPISet" xorm:"kpi_set"`
+ StartTime string `json:"startTime" xorm:"start_time"`
+ EndTime string `json:"endTime" xorm:"end_time"`
+ Periods []Period `json:"Periods" xorm:"periods`
+ Schedule []ScheduleJ `json:"Schedule" xorm:"schedule"`
+ GranulOption string `json:"granulOption" xorm:"granul_option"`
+ Status string `json:"status" xorm:"status"`
+ AccountID string `json:"accountId" xorm:"account_id"`
+ Comment string `json:"comment" xorm:"comment"`
+ CreateTime string `json:"createTime" xorm:"create_time"`
+ UpdateTime string `json:"updateTime" xorm:"update_time"`
+ DeleteTime string `json:"deleteTime xorm:"delete_time"`
+}
+
+func GetMeasureTask(taskId int) (*MeasureTask, error) {
+ log.Debug("GetMeasureTask processing... ")
+
+ measureTask := new(MeasureTask)
+ has, err := xEngine.Table("measure_task").Where("id=?", taskId).Get(measureTask)
+ if err != nil || !has {
+ log.Error("Failed to get table measure_task from database:", err)
+
+ return nil, err
+ }
+
+ log.Debug("Measure Task:", measureTask)
+ return measureTask, nil
+}
+
+func XormGetActiveMeasureTask(measureTasks *[]MeasureTask) (*[]MeasureTask, error) {
+ log.Debug("XormGetActiveMeasureTask processing... ")
+
+ measureTask := new(MeasureTask)
+ rows, err := xEngine.Table("measure_task").Where("status='Active'").Rows(measureTask)
+ if err != nil {
+ log.Error("Failed to get table measure_task:", err)
+ return nil, err
+ }
+ defer rows.Close()
+ for rows.Next() {
+ err := rows.Scan(measureTask)
+ if err != nil {
+ log.Error("Failed to get table measure_task from database:", err)
+ return nil, err
+ }
+ *measureTasks = append(*measureTasks, *measureTask)
+ }
+ log.Debug("measureTasks:", measureTasks)
+ return measureTasks, nil
+}
+
+func GetTableByWhere(whereCondition string, tableName string) (*[]interface{}, error) {
+ log.Debug("GetTableByWhere processing... ")
+
+ rows := new([]interface{})
+ has, err := xEngine.Table(tableName).Where(whereCondition).Get(rows)
+ if err != nil {
+ log.Errorf("Failed to get table %s from database:%v", tableName, err)
+ return nil, err
+ } else if has == false {
+ log.Infof("Not found table %s from database:where=%d", tableName, whereCondition)
+ return nil, nil
+ }
+
+ log.Debugf("%s:%v", tableName, rows)
+ return rows, nil
+}
+
+func GetTableById(id int, tableName string) (*[]interface{}, error) {
+ log.Debug("GetTableById processing... ")
+
+ rows := new([]interface{})
+ has, err := xEngine.Table(tableName).ID(id).Get(rows)
+ if err != nil {
+ log.Errorf("Failed to get table %s from database:id=%d, %v", tableName, id, err)
+ return nil, err
+ } else if has == false {
+ log.Infof("Not found table %s from database:id=%d", tableName, id)
+ return nil, nil
+ }
+
+ log.Debugf("%s:%v", tableName, rows)
+ return rows, nil
+}
+
+func XormUpdateTableById(id int, tableName string, tbInfo interface{}) (int64, error) {
+ log.Debug("XormUpdateTableById processing...id: ", id)
+
+ affected, err := xEngine.Table(tableName).ID(id).Update(tbInfo)
+ if err != nil {
+ log.Errorf("Failed to update table %s from database:%v", tableName, err)
+ return 0, err
+ }
+
+ return affected, nil
+}
+
+func XormUpdateTableByWhere(whereCondition string, tableName string, tbInfo interface{}) (int64, error) {
+ log.Debug("UpdateTableByWhere processing... ")
+
+ affected, err := xEngine.Table(tableName).Where(whereCondition).Update(tbInfo)
+ if err != nil {
+ log.Errorf("Failed to update table %s from database:%v", tableName, err)
+ return 0, err
+ }
+
+ return affected, nil
+}
+
+type User struct {
+ Id int `json:"id" xorm:"pk 'id' autoincr"`
+ AccountId string `json:"accountId"`
+ Name string `json:"name" xorm:"name"`
+ Sn string `json:"sn"`
+ Gender string `json:"gender"`
+ Description string `json:"description"`
+ TelephoneNumber string `json:"telephoneNumber" xorm:"telephone_number"`
+ Mobile string `json:"mobile"`
+ Email string `json:"email" xorm:"email"`
+ StartTime string `json:"startTime" xorm:"start_time"`
+ EndTime string `json:"endTime" xorm:"end_time"`
+ IdCardNumber string `json:"idCardNumber"`
+ EmployeeNumber string `json:"employeeNumber"`
+ Organize string `json:"organize"`
+ EmployeeType string `json:"employeeType"`
+ SupporterCorpName string `json:"supporterCorpName"`
+ RealName string `json:"realName" xorm:"real_name"`
+ Password string `json:"password" xorm:"password"`
+ PasswordSha512 string `json:"passwordSha512"`
+ ChangePasswordFlag int `json:"changePasswordFlag"`
+ PasswordExpiration string `json:"passwordExpiration"`
+ Status string `json:"status"`
+ UserExpiration string `json:"userExpiration"`
+ GroupName string `json:"groupId" xorm:"group_name"`
+ Profile string `json:"profile" xorm:"profile"`
+ Phone string `json:"phone" xorm:"phone"`
+ CreateTime string `json:"createTime" xorm:"create_time"`
+ UpdateTime string `json:"updateTime" xorm:"update_time"`
+
+ // 角色对象组
+ Roles []model.SysRole `json:"roles" xorm:"-"`
+}
+
+// 记录密码登录错误次数
+func pwdErrCountAdd(accountId, profileStr string, reset bool) int {
+ if profileStr == "" {
+ profileStr = "{}"
+ }
+ profile := make(map[string]any)
+ count := 0
+ timeMlli := time.Now().UnixMilli()
+
+ // 反序列去值
+ err := json.Unmarshal([]byte(profileStr), &profile)
+ if err != nil {
+ log.Error("json Unmarshal:%s", err.Error())
+ return 0
+ }
+
+ // 读取配置信息 登录策略设置
+ result, err := XormGetConfig("Security", "loginSecurity")
+ if err != nil {
+ return 0
+ }
+ data := make(map[string]any)
+ err = json.Unmarshal([]byte(result["value_json"].(string)), &data)
+ if err != nil {
+ return 0
+ }
+ limitNum := data["limit_num"].(string)
+ passwordLimitTime := data["password_limit_time"].(string)
+
+ // 重置
+ if reset {
+ // xEngine.Exec("UPDATE user SET status = 'Active' WHERE account_id = ?", accountId)
+ profile["pwdErrCount"] = float64(0)
+ profile["pwdErrTime"] = 0
+ } else {
+ if v, ok := profile["pwdErrTime"]; ok && v != nil {
+ // 获取当前时间
+ currentTime := time.Now()
+
+ // 获取给定的时间戳(毫秒)
+ timestamp := int64(v.(float64)) // 要比较的时间戳(毫秒)
+ // 将时间戳转换为时间类型
+ tm := time.Unix(timestamp/1000, (timestamp%1000)*int64(time.Millisecond))
+
+ // 计算当前时间与给定时间之间的差值
+ duration := currentTime.Sub(tm)
+
+ // // 比较差值是否超过30分钟
+ // if duration.Minutes() > 30 {
+ // xEngine.Exec("UPDATE user SET status = 'Active' WHERE account_id = ?", accountId)
+ // profile["pwdErrCount"] = float64(0)
+ // profile["pwdErrTime"] = 0
+ // }
+
+ // 比较差值是否小于限定时间s
+ passwordLimitTimeInt, err := strconv.Atoi(passwordLimitTime)
+ if err != nil {
+ passwordLimitTimeInt = 0
+ }
+ if duration.Seconds() > float64(passwordLimitTimeInt) {
+ // xEngine.Exec("UPDATE user SET status = 'Active' WHERE account_id = ?", accountId)
+ profile["pwdErrCount"] = float64(0)
+ profile["pwdErrTime"] = 0
+ }
+ }
+
+ if v, ok := profile["pwdErrCount"]; ok && v != nil {
+ count = int(v.(float64)) + 1
+ profile["pwdErrCount"] = count
+ profile["pwdErrTime"] = timeMlli
+ // 错误最大后锁定
+ limitNumInt, err := strconv.Atoi(limitNum)
+ if err != nil {
+ limitNumInt = 0
+ }
+ // if count == limitNumInt {
+ // _, err := xEngine.Exec("UPDATE user SET status = 'Locked' WHERE account_id = ?", accountId)
+ // if err != nil {
+ // return count
+ // }
+ // }
+ if count >= limitNumInt {
+ return count
+ }
+ } else {
+ count = 1
+ profile["pwdErrCount"] = count
+ profile["pwdErrTime"] = timeMlli
+ }
+ }
+
+ // 序列后记录
+ strByte, err := json.Marshal(profile)
+ if err != nil {
+ log.Error("json Marshal:%s", err.Error())
+ return count
+ }
+ _, err = xEngine.Exec("UPDATE user SET profile = ? WHERE account_id = ?", string(strByte), accountId)
+ if err != nil {
+ return count
+ }
+ return count
+}
+
+func XormCheckLoginUser(name, password, cryptArgo string) (bool, *User, error) {
+ log.Info("XormCheckLoginUser processing... ")
+
+ user := new(User)
+ // has, err := xEngine.Table("user").Where("name='%s' and password=PASSWORD('%s')", name, password).Get(user)
+ switch cryptArgo {
+ case "mysql":
+ has, err := xEngine.SQL("select * from user where account_id=? and password=PASSWORD(?)", name, password).Exist()
+ if err != nil || has == false {
+ log.Error("Failed to check user from database:", err)
+
+ return false, nil, err
+ }
+ case "md5":
+ has, err := xEngine.
+ SQL("select * from user where account_id=? and password=MD5(?)", name, password).Exist()
+ if err != nil || has == false {
+ log.Error("Failed to check user from database:", err)
+ return false, nil, err
+ }
+ case "bcrypt":
+ has, err := xEngine.Table("user").Where("account_id=?", name).Get(user)
+ if err != nil || !has {
+ log.Error("Failed to get user from database:", err)
+ return false, nil, err
+ }
+ if oauth.BcryptCompare(user.Password, password) != nil {
+ err := errors.New("Incorrect user name or password")
+ log.Error(err)
+ // 记录错误
+ errCoutn := pwdErrCountAdd(user.AccountId, user.Profile, false)
+ if errCoutn > 3 {
+ // 登录失败次数过多,请30分钟后重试
+ return false, nil, errors.New("Login failed too many times, please retry after 30 minutes")
+ }
+ return false, nil, err
+ }
+ // 重置错误次数
+ pwdErrCountAdd(user.AccountId, user.Profile, true)
+ default:
+ errMsg := "Incorrect crypt algoritmo"
+ log.Error("crypt:%s", errMsg)
+ return false, nil, errors.New(errMsg)
+ }
+
+ // enum('Active','Closed','Locked','Pending')
+ errMsg := ""
+ switch user.Status {
+ case "Closed":
+ errMsg = "Account disabled" // 账户已禁用
+ case "Locked":
+ errMsg = "Account locked" // 账户已锁定
+ case "Pending":
+ // errMsg = "账户已挂起"
+ _, err := xEngine.Exec("UPDATE user SET status = 'Active' WHERE account_id = ?", user.AccountId)
+ if err != nil {
+ return false, nil, err
+ }
+ }
+ if errMsg != "" {
+ log.Error("user Status:%s", errMsg)
+ return false, nil, errors.New(errMsg)
+ }
+
+ // 密码到期时间
+ if user.PasswordExpiration != "" {
+ arr := strings.Split(user.PasswordExpiration, " ")
+ if len(arr) > 0 {
+ t, err := time.Parse("2006-01-02", arr[0])
+ if err != nil {
+ return false, nil, err
+ }
+ if t.Before(time.Now()) {
+ errMsg := "Password expiration time" // 密码到期时间
+ // 读取配置信息
+ result, err := XormGetConfig("Security", "pwdStrong")
+ if err != nil {
+ return false, nil, err
+ }
+ data := make(map[string]any)
+ err = json.Unmarshal([]byte(result["value_json"].(string)), &data)
+ if err != nil {
+ log.Error("json Unmarshal:%s", errMsg)
+ return false, nil, err
+ }
+ errMsg = data["outTimeMsg"].(string)
+ log.Error("PasswordExpiration:%s", errMsg)
+ return false, nil, errors.New(errMsg)
+ }
+ }
+ }
+
+ // 用户到期时间
+ if user.UserExpiration != "" {
+ arr := strings.Split(user.UserExpiration, " ")
+ if len(arr) > 0 {
+ t, err := time.Parse("2006-01-02", arr[0])
+ if err != nil {
+ return false, nil, err
+ }
+ if t.Before(time.Now()) {
+ errMsg := "User account expiration" // 用户账户到期
+ log.Error("UserExpiration:%s", errMsg)
+ return false, nil, errors.New(errMsg)
+ }
+ }
+ }
+
+ return true, user, nil
+}
+
+func XormIsExistUser(accid string) (bool, error) {
+ log.Info("XormIsExistUser processing... ")
+
+ exist, err := xEngine.Table("user").
+ Where("account_id=?", accid).
+ Exist()
+ if err != nil {
+ log.Error("Failed to exist user:", err)
+
+ return false, err
+ }
+
+ return exist, nil
+}
+
+func XormGetConfig(moduleName, configTag string) (map[string]any, error) {
+ result, err := DbClient.XEngine.QueryInterface("select * from config where module_name=? and config_tag=?", moduleName, configTag)
+ if err != nil {
+ log.Error("Failed to get config:", err)
+ return nil, err
+ }
+ if len(result) > 0 {
+ return result[0], nil
+ }
+ return map[string]any{}, nil
+}
+
+func XormGetConfigValue(moduleName, configTag string) (string, error) {
+ var value string
+ _, err := xEngine.Table("config").
+ Where("module_name=? and config_tag=?", moduleName, configTag).
+ Cols("value").
+ Get(&value)
+ if err != nil {
+ log.Error("Failed to get config:", err)
+ return "", err
+ }
+ return value, nil
+}
+
+type Session struct {
+ Id int `json:"id" xorm:"pk 'id' autoincr"`
+ AccountId string `json:"accountId" xorm:"account_id"`
+ Name string `json:"name" xorm:"name"`
+ Host string `json:"host" xorm:"host"`
+ AccessToken string `json:"accessToken" xorm:"access_token"`
+ Expires uint32 `json:"expires" xorm:"expires"`
+ Status string `json:"status" xorm:"status"`
+ LoginTime string `json:"loginTime" xorm:"-"`
+ ShakeTime sql.NullTime `son:"shakeTime" xorm:"shake_time"`
+ LogoutTime sql.NullTime `json:"logoutTime" xorm:"logout_time"`
+}
+
+// XormInsertSession create session
+func XormInsertSession(name, host, token string, expires uint32, sessionFlag string) (int64, error) {
+ log.Info("XormInsertSession processing... ")
+
+ var affected int64 = 0
+ var err error = nil
+ currentTime := time.Now()
+ timeValue := currentTime.Local()
+ nullTime := sql.NullTime{Valid: true, Time: timeValue}
+ value, err := XormGetConfigValue("Security", "sessionExpires")
+ if err != nil {
+ return affected, err
+ }
+ if value != "" {
+ intValue, _ := strconv.Atoi(value)
+ expires = uint32(intValue)
+ log.Debugf("intValue=%d, expires=%d", intValue, expires)
+ }
+
+ session := Session{AccountId: name,
+ Name: name, Host: host, AccessToken: token,
+ Status: "online", Expires: expires,
+ ShakeTime: nullTime,
+ }
+
+ //session.ShakeTime.Time
+ xSession := xEngine.NewSession()
+ defer xSession.Close()
+ if strings.ToLower(sessionFlag) == "multiple" {
+ exist, err := xEngine.Table("session").Where("status = 'online' and account_id = ? and host = ?", name, host).Exist()
+ if err != nil {
+ return affected, err
+ }
+ if exist == true {
+ affected, err = xSession.Table("session").Where("account_id = ? and host = ?", name, host).Update(session)
+ } else {
+ affected, err = xSession.InsertOne(session)
+ }
+ } else { // single session for a user
+ exist, err := xEngine.Table("session").Where("status = 'online' and account_id = ?", name).Exist()
+ if err != nil {
+ return affected, err
+ }
+ if exist == true {
+ // todo...
+ err := errors.New("user is logged in")
+ return -1, err
+ } else {
+ affected, err = xSession.InsertOne(session)
+ }
+ }
+ xSession.Commit()
+ return affected, err
+}
+
+// XormUpdateSession update session
+func XormLogoutUpdateSession(token string) (Session, error) {
+ log.Info("XormLogoutUpdateSession processing... ")
+
+ session := Session{Status: "offline", AccessToken: token}
+ session.LogoutTime.Valid = true
+ session.LogoutTime.Time = time.Now()
+
+ xSession := xEngine.NewSession()
+ defer xSession.Close()
+ _, err := xSession.Table("session").Where("access_token = ?", token).Update(session)
+ xSession.Commit()
+ // 查询记录返回
+ if err == nil {
+ session := Session{}
+ _, err = xSession.Table("session").Where("access_token = ?", token).Get(&session)
+ return session, err
+ }
+ return session, err
+}
+
+// XormUpdateSessionShakeTime create session
+func XormUpdateSessionShakeTime(token string) (Session, error) {
+ log.Debug("XormUpdateSessionShakeTime processing... ")
+
+ session := Session{AccessToken: token}
+ session.ShakeTime.Valid = true
+ session.ShakeTime.Time = time.Now()
+ xSession := xEngine.NewSession()
+ defer xSession.Close()
+ _, err := xSession.Table("session").Where("access_token = ?", token).Update(session)
+ xSession.Commit()
+ // 查询记录返回
+ if err == nil {
+ session := Session{}
+ _, err = xSession.Table("session").Where("access_token = ?", token).Get(&session)
+ return session, err
+ }
+ return session, err
+}
+
+func XormExistValidToken(token string, expires uint32) bool {
+ log.Info("XormExistValidToken processing... ")
+
+ exist, err := xEngine.Table("session").
+ Where("status = 'online' and access_token = ? and DATE_ADD(shake_time, INTERVAL expires SECOND) > NOW()", token).
+ Exist()
+ if err != nil {
+ return false
+ }
+
+ return exist
+}
+
+/*
+ type NorthboundPm struct {
+ Id int `json:"id" xorm:"pk 'id' autoincr"`
+ Date string `json:"date" xorm:"date"`
+ Index int `json:"index"`
+ StartTime string `json:"startTime" xorm:"start_time"`
+ TimeZone string `json:"timeZone" xorm:"time_zone"`
+ NEName string `json:"neName" xorm:"ne_name"`
+ PmVersion string `json:"pmVersion" xorm:"pm_version"`
+ Period string `json:"period" xorm:"pevalue"
+ RmUid string `json:"rmUid" xorm:"rm_uid"`
+ NEType string `json:"neType" xorm:"ne_type"`
+ Dn string `json:"neType" xorm:"ne_type"`
+ ObjectType string `json:"objectType" xorm:"object_type"`
+ PmName string `json:"pmName" xorm:"pm_name"`
+ PmExt string `json:"pmExt" xorm:"pm_ext"`
+ Value int `json:"value" xorm:"value"`
+ Timestamp string `json:"timestamp" xorm:"timestamp"`
+ }
+*/
+type NorthboundPm struct {
+ Id int `json:"-" xorm:"pk 'id' autoincr"`
+ Date string `json:"Date" xorm:"date"`
+ Index int `json:"Index" xorm:"index"` // 1天中测量时间粒度(如15分钟)的切片索引: 0~95
+ Timestamp string `json:"-" xorm:"-"`
+ NeName string `json:"NeName" xorm:"ne_name"` // UserLabel
+ RmUID string `json:"RmUID" xorm:"rm_uid"`
+ NeType string `json:"NeType" xorm:"ne_type"` // 网元类型
+ PmVersion string `json:"PmVersion" xorm:"pm_version"` // 性能数据版本号
+ Dn string `json:"Dn" xorm:"dn"` // (???)网元标识, 如:RJN-CMZJ-TZ,SubNetwork=5GC88,ManagedElement=SMF53456,SmfFunction=53456
+ Period string `json:"Period" xorm:"period"` // 测量时间粒度选项:5/15/30/60
+ TimeZone string `json:"TimeZone" xorm:"time_zone"`
+ StartTime string `json:"StartTime" xorm:"start_time"`
+
+ Datas []struct {
+ ObjectType string `json:"ObjectType" xorm:"object_type"` // 网络资源类别名称, Pm指标项列表中为空间粒度 如:SmfFunction
+ PmDatas []struct {
+ PmName string `json:"KPIID" xorm:"pm_name"` // 指标项, 如: SMF.AttCreatePduSession._Dnn
+ SubDatas []struct {
+ SN string `json:"Name" xorm:"sn"` // 单个的写"Total", 或者指标项有多个测量项,如Dnn的名称写对应的Dnn"cmnet"/"ims"
+ SV int64 `json:"Value" xorm:"sv"`
+ } `json:"KPIValues" xorm:"sub_datas"`
+ } `json:"KPIs" xorm:"pm_datas"`
+ } `json:"Datas" xorm:"datas"`
+}
+
+type Alarm struct {
+ AlarmSeq int `json:"alarmSeq"`
+ AlarmId string `json:"alarmId" xorm:"alarm_id"`
+ NeId string `json:"neId"`
+ AlarmCode int `json:"alarmCode"`
+ AlarmTitle string `json:"alarmTitle"`
+ EventTime string `json:"eventTime"`
+ AlarmType string `json:"alarmType"`
+ OrigSeverity string `json:"origSeverity"`
+ PVFlag string `json:"pvFlag" xorm:"pv_flag"`
+ NeName string `json:"neName"`
+ NeType string `json:"neType"`
+ ObjectUid string `json:"objectUid" xorm:"object_uid"`
+ ObjectName string `json:"objectName" xorm:"object_name"`
+ ObjectType string `json:"objectType" xorm:"object_type"`
+ LocationInfo string `json:"locationInfo"`
+ Province string `json:"province"`
+ AlarmStatus int `json:"alarmStatus"`
+ SpecificProblem string `json:"specificProblem"`
+ SpecificProblemID string `json:"specificProblemID" xorm:"specific_problem_id"`
+ AddInfo string `json:"addInfo"`
+
+ // AckState int `json:"ackState" xorm:"-"`
+ // AckTime string `json:"ackTime" xorm:"-"`
+ ClearType int `json:"clearType" xorm:"-"` // 0: Unclear, 1: Auto clear, 2: Manual clear
+ ClearTime string `json:"clearTime" xorm:"-"`
+}
+
+func XormGetAlarmByAlarmId(alarmId string, alarms *[]Alarm) (*[]Alarm, error) {
+ log.Debug("XormGetAlarmByAlarmId processing... ")
+
+ alarm := new(Alarm)
+ rows, err := xEngine.Table("alarm").Where("alarm_id=?", alarmId).Rows(alarm)
+ if err != nil {
+ log.Error("Failed to get table alarm from database:", err)
+ return nil, err
+ }
+
+ defer rows.Close()
+ for rows.Next() {
+ err := rows.Scan(alarm)
+ if err != nil {
+ log.Error("Failed to get table alarm from database:", err)
+ return nil, err
+ }
+ *alarms = append(*alarms, *alarm)
+ }
+
+ log.Trace("alarms:", alarms)
+ return alarms, nil
+}
+
+func XormSQLGetStringValue(querySQL string) (string, error) {
+ log.Debug("XormSQLGetStringValue processing... ")
+
+ row := make(map[string]interface{})
+ _, err := xEngine.SQL(querySQL).Get(&row)
+ if err != nil {
+ log.Errorf("Failed to get by SQL=%s:%v", querySQL, err)
+ return "", err
+ }
+
+ for _, v := range row {
+ return fmt.Sprintf("%v", v), nil
+ }
+
+ return "", nil
+}
+
+func XormGetSingleCol(table, col, where string) (string, error) {
+ log.Debug("XormGetSingleCol processing... ")
+
+ row := make(map[string]interface{})
+ _, err := xEngine.Table(table).Where(where).Cols(col).Get(&row)
+ if err != nil {
+ log.Errorf("Failed to get %s from table %s:%v", col, table, err)
+ return "", err
+ }
+
+ for _, v := range row {
+ return fmt.Sprintf("%v", v), nil
+ }
+
+ return "", nil
+}
+
+func XormGetSingleColStringArrayByIn(table, col, incol string, conditions []string, cols *[]string) error {
+ log.Debug("XormGetSingleColStringArrayByIn processing... ")
+
+ err := xEngine.Table(table).In(incol, conditions).Cols(col).Distinct().Find(cols)
+ if err != nil {
+ log.Errorf("Failed to get %s from table %s:%v", col, table, err)
+ return err
+ }
+ return nil
+}
+
+func XormGetColStringArrayByWhere(table, coln, where string, colv *[]string) error {
+ log.Debug("XormGetColStringArrayByWhere processing... ")
+
+ _, err := xEngine.Table(table).Where(where).Cols(coln).Get(colv)
+ if err != nil {
+ log.Errorf("Failed to Get %s from table %s:%v", coln, table, err)
+ return err
+ }
+ return nil
+}
+
+func XormFindColStringArrayByWhere(table, col, where string, cols *[]string) error {
+ log.Debug("XormFindColStringArrayByWhere processing... ")
+
+ err := xEngine.Table(table).Where(where).Cols(col).Distinct().Find(cols)
+ if err != nil {
+ log.Errorf("Failed to Find %s from table %s:%v", col, table, err)
+ return err
+ }
+ return nil
+}
+
+func XormGetSingleColStringByWhere(table, col, where string) (string, error) {
+ log.Info("XormFindSingleColStringByWhere processing... ")
+
+ var colv string
+ _, err := xEngine.Table(table).Where(where).Cols(col).Get(&colv)
+ if err != nil {
+ log.Errorf("Failed to Find %s from table %s:%v", col, table, err)
+ return colv, err
+ }
+ return colv, nil
+}
+
+func XormGetSingleColStringArrayByID(table, col string, id int, cols *[]string) error {
+ log.Debug("XormGetSingleColStringArrayByID processing... ")
+
+ err := xEngine.Table(table).ID(id).Cols(col).Find(cols)
+ if err != nil {
+ log.Errorf("Failed to Find %s from table %s:%v", col, table, err)
+ return err
+ }
+ return nil
+}
+
+func XormGetStringValue(table, col string, id int) (string, error) {
+ log.Debug("XormGetStringValue processing... ")
+
+ row := make(map[string]interface{})
+ _, err := xEngine.ID(id).Table(table).Cols(col).Get(&row)
+ if err != nil {
+ log.Errorf("Failed to get %s from table %s:%v", col, table, err)
+ return "", err
+ }
+
+ for _, v := range row {
+ return fmt.Sprintf("%v", v), nil
+ }
+
+ return "", nil
+}
+
+type NeBackup struct {
+ Id int `json:"id" xorm:"pk 'id' autoincr"`
+ NeType string `json:"neType" xorm:"ne_type"`
+ NeId string `json:"neId" xorm:"ne_id"`
+ FileName string `json:"neName" xorm:"file_name"`
+ Path string `json:"path"`
+ Md5Sum string `json:"md5Sum" xorm:"md5_sum"`
+ CreateTime string `json:"createTime" xorm:"-" `
+}
+
+// XormInsertSession create session
+func XormInsertTableOne(tableName string, tbInfo interface{}) (int64, error) {
+ log.Debug("XormInsertTableOne processing... ")
+
+ var affected int64 = 0
+ var err error = nil
+
+ xSession := xEngine.NewSession()
+ defer xSession.Close()
+ affected, err = xSession.Table(tableName).InsertOne(tbInfo)
+ xSession.Commit()
+ return affected, err
+}
+
+// XormExistTableOne create session
+func XormExistTableOne(tableName string, where string) (bool, error) {
+ log.Debug("XormExistTableOne processing... ")
+
+ has, err := xEngine.Table(tableName).Where(where).Exist()
+
+ return has, err
+}
+
+func XormGetTableRows(tableName string, where string, tbInfo *[]interface{}) (*[]interface{}, error) {
+ log.Debug("XormGetTableRows processing... ")
+
+ row := make(map[string]interface{})
+ rows, err := xEngine.Table(tableName).Where(where).Rows(row)
+ if err != nil {
+ log.Errorf("Failed to Rows table %s from database: %v", tableName, err)
+ return nil, err
+ }
+ defer rows.Close()
+ for rows.Next() {
+ err := rows.Scan(row)
+ if err != nil {
+ log.Errorf("Failed to Scan table %s from database: %v", tableName, err)
+ return nil, err
+ }
+ *tbInfo = append(*tbInfo, row)
+ }
+ log.Trace("tbInfo:", tbInfo)
+ return tbInfo, nil
+}
+
+type MeasureThreshold struct {
+ Id int `json:"id" xorm:"pk 'id' autoincr"`
+ NeType string `json:"neType" xorm:"ne_type"`
+ KpiSet []string `json:"kpiSet" xorm:"kpi_set"`
+ Threshold int64 `json:"threshold"`
+ Status string `json:"md5Sum" xorm:"md5_sum"`
+ OrigSeverity string `json:"createTime" xorm:"orig_severity"`
+ AlarmId string `json:"alarmId" xorm:"alarm_id"`
+}
+
+type NeSoftware struct {
+ Id int `json:"id" xorm:"pk 'id' autoincr"`
+ NeType string `json:"neType" xorm:"ne_type"`
+ FileName string `json:"neName" xorm:"file_name"`
+ Path string `json:"path"`
+ Version string `json:"version"`
+ Md5Sum string `json:"md5Sum" xorm:"md5_sum"`
+ Comment string `json:"comment"`
+ // Status string `json:"status"`
+ UpdateTime string `json:"createTime" xorm:"-" `
+}
+
+type NeVersion struct {
+ Id int `json:"id" xorm:"pk 'id' autoincr"`
+ NeType string `json:"neType" xorm:"ne_type"`
+ NeId string `json:"neId" xorm:"ne_id"`
+ Version string `json:"version"`
+ FilePath string `json:"filePath" xorm:"file_path"`
+ NewVersion string `json:"newVersion" xorm:"new_version"`
+ NewFile string `json:"newFile" xorm:"new_file"`
+ PreVersion string `json:"preVersion" xorm:"pre_version"`
+ PreFile string `json:"preFile" xorm:"pre_file"`
+ Status string `json:"status"`
+ UpdateTime string `json:"createTime" xorm:"-" `
+}
+
+func XormGetDataBySQL(sql string) (*[]map[string]string, error) {
+ log.Debug("XormGetDataBySQL processing... ")
+
+ rows := make([]map[string]string, 0)
+ rows, err := DbClient.XEngine.QueryString(sql)
+ if err != nil {
+ log.Error("Failed to QueryString:", err)
+ return nil, err
+ }
+
+ return &rows, nil
+}
+
+func XormDeleteDataByWhere(where, table string) (int64, error) {
+ log.Debug("XormDeleteDataByWhere processing... ")
+
+ xSession := DbClient.XEngine.NewSession()
+ defer xSession.Close()
+ affected, err := xSession.Table(table).Where(where).Delete()
+ if err != nil {
+ log.Error("Failed to Delete:", err)
+ return 0, err
+ }
+ xSession.Commit()
+
+ return affected, nil
+}
+
+func XormDeleteDataById(id int, table string) (int64, error) {
+ log.Debug("XormDeleteDataByWhere processing... ")
+
+ xSession := DbClient.XEngine.NewSession()
+ defer xSession.Close()
+ affected, err := xSession.Table(table).ID(id).Delete()
+ if err != nil {
+ log.Error("Failed to Delete:", err)
+ return 0, err
+ }
+ xSession.Commit()
+
+ return affected, nil
+}
+
+type ColOutput struct {
+ Name string `json:"name"`
+ Display string `json:"display"`
+ Length int `json:"length"`
+ Alias []string `json:"Alias"`
+}
+
+type ColInput struct {
+ Name string `json:"name"`
+ Alias string `json:"Alias"`
+ Type string `json:"type"`
+ Length int `json:"length"`
+ Value any `json:"value"`
+}
+
+type MmlInput struct {
+ BodyFmt string `json:"bodyFmt"`
+ BodyKey string `json:"bodyKey"`
+ CallFunc string `json:"callFunc"`
+ Cols []ColInput `json:"cols"`
+}
+
+type MmlOutput struct {
+ RetFmt string `json:"retFmt"`
+ RetMsg string `json:"retMsg"`
+ ErrMsg string `json:"errMsg"`
+ Title string `json:"title"`
+ SingleList bool `json:"singleList"`
+ SepSpaceNum int `json:"sepSpaceNum"`
+ AlignmentM string `json:"alignmentM"`
+ AlignmentSN string `json:"alignmentSN"`
+ AlignmentSV string `json:"alignmentSV"`
+ Cols []ColOutput `json:"cols"`
+ End string `json:"end"`
+}
+
+type MmlHttpMap struct {
+ ID int `json:"-" xorm:"id"`
+ NeType string `json:"neType" xorm:"ne_type"`
+ Operation string `json:"operation"`
+ Object string `json:"object"`
+ Method string `json:"method"`
+ URI string `json:"uri" xorm:"uri"`
+ ExtUri string `json:"extUri"`
+ ParamTag string `json:"paramTag"`
+ Params string `json:"params"`
+ Input string `json:"input"`
+ Output string `json:"output"`
+}
+
+func XormGetMmlHttpMap(table, where string) (*MmlHttpMap, error) {
+ mmlMap := new(MmlHttpMap)
+ _, err := DbClient.XEngine.Table(table).Where(where).Get(mmlMap)
+ if err != nil {
+ log.Error("Failed to Get:", err)
+ return nil, err
+ }
+ return mmlMap, nil
+}
+
+type MmlCommand struct {
+ ID int `json:"-" xorm:"id"`
+ NeType string `json:"neType" xorm:"ne_type"`
+ Category string `json:"category"`
+ CatDisplay string `json:"catDisplay"`
+ Operation string `json:"operation"`
+ Object string `json:"object" xorm:"object"`
+ MmlDisplay string `json:"mmlDisplay"`
+ ParamJson []struct {
+ Name string `json:"name"`
+ Alias string `json:"alias"`
+ Type string `json:"type"`
+ Optional string `json:"optional"`
+ Apostr string `json:"apostr"`
+ Loc string `json:"loc"`
+ Filter string `json:"filter"`
+ Display string `json:"display"`
+ Comment string `json:"comment"`
+ } `json:"paramJson"`
+}
+
+func XormGetMmlCommand(table, where string) (*MmlCommand, error) {
+ mmlCmd := new(MmlCommand)
+ _, err := DbClient.XEngine.Table(table).Where(where).Get(mmlCmd)
+ if err != nil {
+ log.Error("Failed to Get:", err)
+ return nil, err
+ }
+ return mmlCmd, nil
+}
+
+type AlarmOMCConfig struct {
+ ID int `json:"-" xorm:"id"`
+ ModuleName string `json:"moduleName"`
+ ConfigTag string `json:"configTag"`
+ Value string `json:"value"`
+ ValueJson string `json:"valueJson"`
+}
+
+type ValueJson struct {
+ AlarmStatus string `json:"alarm_status"`
+ AlarmType string `json:"alarm_type"`
+ OrigSeverity string `json:"orig_severity"`
+ AckUser string `json:"ack_user"`
+}
+
+type AlarmForwardToUsers struct {
+ Interface string `json:"interface"`
+ ToUser []string `json:"to_user"`
+}
+
+func XormGetAAConfig() (*ValueJson, error) {
+ aaConfig := new(AlarmOMCConfig)
+ _, err := DbClient.XEngine.Table("config").
+ Where("module_name=? and config_tag=?", "Alarm", "autoAlarmAck").
+ Cols("value", "value_json").
+ Get(aaConfig)
+ if err != nil {
+ log.Error("Failed to Get:", err)
+ return nil, err
+ }
+ log.Debug("aaConfig:", aaConfig)
+ valueJson := new(ValueJson)
+ err = json.Unmarshal([]byte(aaConfig.ValueJson), valueJson)
+ if err != nil {
+ log.Error("Failed to Unmarshal:", err)
+ return nil, err
+ }
+ log.Debug("valueJson:", valueJson)
+ return valueJson, nil
+}
+
+func XormGetAlarmForward(interfaceName string) (*[]string, error) {
+ alarmForwardConfig := new(AlarmOMCConfig)
+ _, err := DbClient.XEngine.Table("config").
+ Where("module_name=? and config_tag=?", "Alarm", "forwardAlarm").
+ Cols("value", "value_json").
+ Get(alarmForwardConfig)
+ if err != nil {
+ log.Error("Failed to Get:", err)
+ return nil, err
+ }
+ log.Debug("alarmForwardConfig:", alarmForwardConfig)
+ alarmForwardToUsers := new([]AlarmForwardToUsers)
+ err = json.Unmarshal([]byte(alarmForwardConfig.ValueJson), alarmForwardToUsers)
+ if err != nil {
+ log.Error("Failed to Unmarshal:", err)
+ return nil, err
+ }
+ log.Debug("alarmForwardToUsers:", alarmForwardToUsers)
+ for _, a := range *alarmForwardToUsers {
+ if a.Interface == interfaceName {
+ return &(a.ToUser), nil
+ }
+ }
+ return nil, nil
+}
+
+type AlarmForwardLog struct {
+ ID int `json:"-" xorm:"pk 'id' autoincr"`
+ NeType string `json:"neType" xorm:"ne_type"`
+ NeID string `json:"neID" xorm:"ne_id"`
+ AlarmID string `json:"alarmID" xorm:"alarm_id"`
+ AlarmTitle string `json:"alarmTitle" xorm:"alarm_title"`
+ AlarmSeq int `json:"alarmSeq" xorm:"alarm_seq"`
+ EventTime string `json:"eventTime" xorm:"event_time"`
+ ToUser string `json:"toUser" xorm:"to_user"`
+ OperResult string `json:"operResult" xorm:"oper_result"`
+ LogTime string `json:"-" xorm:"-"`
+}
+
+func XormInsertAlarmForwardLog(logData *AlarmForwardLog) (int64, error) {
+ log.Debug("XormInsertAlarmForwardLog processing... ")
+
+ xSession := xEngine.NewSession()
+ defer xSession.Close()
+ affected, err := xSession.Insert(logData)
+ xSession.Commit()
+
+ return affected, err
+}
+
+type SystemLog struct {
+ ID int `json:"-" xorm:"pk 'id' autoincr"`
+ UserName string `json:"user_name" xorm:"user_name"`
+ ProcessName string `json:"process_name" xorm:"process_name"`
+ ProcessID int32 `json:"process_id" xorm:"process_id"`
+ Operation string `json:"operation" xorm:"operation"`
+ StartTime string `json:"start_time" `
+ LogTime string `json:"-" xorm:"-"`
+}
+
+func XormInsertSystemLog(logData *SystemLog) (int64, error) {
+ log.Info("XormInsertSystemLog processing... ")
+
+ xSession := xEngine.NewSession()
+ defer xSession.Close()
+ affected, err := xSession.Insert(logData)
+ xSession.Commit()
+
+ return affected, err
+}
+
+type permission struct {
+ ID int `json:"-" xorm:"pk 'id' autoincr"`
+ PermissionName string `json:"permissionName"`
+ Method string `json:"method"`
+ Element string `json:"element"`
+ Object string `json:"object"`
+}
+
+func IsPermissionAllowed(token, method, module, dbname, tbname, pack string) (bool, error) {
+ log.Info("IsPermissionAllowed processing... ")
+
+ exist, err := xEngine.Table("permission").
+ Join("INNER", "role_permission", "permission.permission_name = role_permission.p_name").
+ Join("INNER", "user_role", "role_permission.r_name = user_role.r_name").
+ Join("INNER", "session", "user_role.u_name = session.account_id and session.access_token=?", token).
+ Where("method in ('*',?) and module in ('*',?) and management in ('*',?) and element in ('*',?) and object in ('*',?)", method, pack, module, dbname, tbname).
+ Exist()
+ if err != nil {
+ return false, err
+ }
+
+ return exist, nil
+}
+
+type NeLicense struct {
+ NeType string `json:"neType" xorm:"ne_type"`
+ NeID string `json:"neID" xorm:"ne_id"`
+ SerialNo string `json:"serialNo" xorm:"serial_no"`
+ Capcity int `json:"capcity" xorm:"capcity"`
+ Used int `json:"used" xorm:"used"`
+ FeatureEnabled string `json:"featureEnabled" xorm:"feature_enabled"`
+ ExpirationDate string `json:"expirationDate" xorm:"expiration_date"`
+ Status string `json:"status" xorm:"status"`
+ Path string `json:"path" xorm:"path"`
+ FileName string `json:"file_name" xorm:"file_name"`
+ Comment string `json:"comment" xorm:"comment"`
+ CreatedAt string `json:"createdAt" xorm:"-"`
+ UpdatedAt string `json:"updatedAt" xorm:"-"`
+ DeletedAt string `json:"deletedAt" xorm:"-"`
+}
+
+func XormAdjustmentNeLicense(neType, neID string, value int) (int64, error) {
+ //neLicense := NeLicense{NeType: neType, NeID: neID, Capability: value}
+ // session.LogoutTime.Valid = true
+ // session.LogoutTime.Time = time.Now()
+ res, err := xEngine.Exec("update ne_license set capcity=capcity+? where IFNULL(ne_type, '')=? and IFNULL(ne_id, '')=?", value, neType, neID)
+ // defer xSession.Close()
+
+ //affected, err := xSession.Table("ne_license").Where("ne_type=? and ne_id=?", neType, neID).Update(&neLicense)
+
+ // //affected, err := xSession.Table("ne_license").SQL("ne_tye=? and ne_id=?", neType, neID).Update(session)
+ // err := xSession.SQL("update ne_license set capability=capability+? where ne_type=? and ne_id=?", value, neType, neID)
+ //xSession.Commit()
+ affected, err := res.RowsAffected()
+ return affected, err
+}
+
+func XormUpdateNeLicense(neType, neID string, capcity int) (int64, error) {
+ var err error
+ var res sql.Result
+ if neType != "" && neID != "" {
+ res, err = xEngine.Exec("update ne_license set capcity=? where ne_type=? and ne_id=?", capcity, neType, neID)
+ } else if neType != "" && neID == "" {
+ res, err = xEngine.Exec("update ne_license set capcity=? where ne_type=?", capcity, neType)
+ } else if neType == "" && neID != "" {
+ res, err = xEngine.Exec("update ne_license set capcity=? where ne_id=?", capcity, neID)
+ } else {
+ res, err = xEngine.Exec("update ne_license set capcity=?", capcity)
+ }
+
+ affected, err := res.RowsAffected()
+ return affected, err
+}
+
+type NorthboundCm struct {
+ ID int `json:"-" xorm:"pk '-' autoincr"`
+ Timestamp string `json:"timestamp" xorm:"timestamp"`
+ TimeZone string `json:"timeZone" xorm:"time_zone"`
+ VendorName string `json:"vendorName" xorm:"vendor_name"`
+ NeType string `json:"neType" xorm:"ne_type"`
+ CmVersion string `json:"cmVersion" xorm:"cm_version"`
+ RmUID string `json:"rmUID" xorm:"rm_uid"`
+ NeID string `json:"neID" xorm:"ne_id"`
+ UserLabel string `json:"userLabel" xorm:"user_label"`
+ ObjectType string `json:"objectType" xorm:"object_type"`
+ PvFlag string `json:"pvFlag" xorm:"pv_flag"`
+ VMID string `json:"vmID" xorm:"vm_id"`
+ VnfInstanceID string `json:"vnf_instance_id"`
+ ValueJSON string `json:"valueJson" xorm:"value_json"`
+ Status string `json:"status" xorm:"status"`
+}
+
+func XormGetNorthboundCm(neType string, cmResults *[]NorthboundCm) error {
+ log.Info("XormGetNorthboundCm processing... ")
+
+ cmResult := new(NorthboundCm)
+ rows, err := xEngine.Table("northbound_cm").
+ Distinct("object_type").
+ Where("`ne_type` = ?", neType).
+ Desc("timestamp").
+ Cols("*").
+ Rows(cmResult)
+ if err != nil {
+ log.Error("Failed to get table northbound_cm:", err)
+ return err
+ }
+ defer rows.Close()
+ for rows.Next() {
+ err := rows.Scan(cmResult)
+ if err != nil {
+ log.Error("Failed to get table northbound_cm:", err)
+ return err
+ }
+ *cmResults = append(*cmResults, *cmResult)
+ }
+ return nil
+}
+
+func XormGetNorthboundCmLatestObject(neType, neID, objectType string) (*NorthboundCm, error) {
+ log.Info("XormGetNorthboundCmLatestObject processing... ")
+
+ cmResult := new(NorthboundCm)
+ _, err := xEngine.Table("northbound_cm").
+ Where("`ne_type`=? and `ne_id`=? and `object_type`=?", neType, neID, objectType).
+ Desc("timestamp").
+ Cols("*").
+ Limit(1).
+ Get(cmResult)
+ if err != nil {
+ log.Error("Failed to get table northbound_cm:", err)
+ return nil, err
+ }
+
+ return cmResult, nil
+}
+
+type TraceData struct {
+ ID int `json:"-" xorm:"pk 'id' autoincr"`
+ TaskID int `json:"taskID" xorm:"task_id"`
+ Imsi string `json:"imsi" xorm:"imsi"`
+ Msisdn string `json:"msisdn" xorm:"msisdn"`
+ SrcAddr string `json:"srcAddr" xorm:"src_addr"`
+ DstAddr string `json:"dstAddr" xorm:"dst_addr"`
+ IfType int `json:"ifType" xorm:"if_type"`
+ MsgType int `json:"msgType" xorm:"msg_type"`
+ MsgDirect int `json:"msgDirect" xorm:"msg_direct"`
+ Length int `json:"length" xorm:"length"`
+ Timestamp int64 `json:"timestamp" xorm:"timestamp"`
+ RawMsg []byte `json:"rawMsg" xorm:"raw_msg"`
+ DecMsg string `json:"decMsg" xorm:"dec_msg"`
+}
+
+func XormGetTraceData(id int) (*TraceData, error) {
+ result := new(TraceData)
+ _, err := xEngine.Table("trace_data").
+ Where("id=?", id).
+ Get(result)
+ if err != nil {
+ log.Error("Failed to get table trace_data:", err)
+ return nil, err
+ }
+
+ return result, nil
+}
+
+func XormUpdateTraceData(id int, data *TraceData) (int64, error) {
+ affected, err := xEngine.Table("trace_data").
+ Where("id=?", id).
+ Update(data)
+ if err != nil {
+ log.Error("Failed to update table trace_data:", err)
+ return 0, err
+ }
+
+ return affected, nil
+}
+
+func XormInsertTraceData(data *TraceData) (int64, error) {
+ affected, err := xEngine.Table("trace_data").
+ InsertOne(data)
+ if err != nil {
+ log.Error("Failed to insert table trace_data:", err)
+ return 0, err
+ }
+
+ return affected, nil
+}
+
+func XormDeleteTraceData(id int) (int64, error) {
+ affected, err := xEngine.Table("trace_data").
+ Where("id=?", id).
+ Delete()
+ if err != nil {
+ log.Error("Failed to delete table trace_data:", err)
+ return 0, err
+ }
+
+ return affected, nil
+}
+
+func XormGetTraceRawMsg(id int) (int64, []byte, error) {
+ var rawMsg []byte
+ var timestamp int64
+ _, err := xEngine.Table("trace_data").
+ Where("id=?", id).
+ Cols("timestamp", "raw_msg").
+ Get(×tamp, &rawMsg)
+ if err != nil {
+ log.Error("Failed to get table trace_data:", err)
+ return timestamp, rawMsg, err
+ }
+
+ return timestamp, rawMsg, nil
+}
+
+func XormGetNEStateInfo(neType, neID string) (string, string, error) {
+ SN := "-"
+ Version := "-"
+ _, err := xEngine.Table("ne_state").
+ Where("ne_type=? and ne_id=?", neType, neID).
+ Desc("timestamp").
+ Cols("serial_num", "version").
+ Limit(1).
+ Get(&SN, &Version)
+ return SN, Version, err
+}
+
+type NeState struct {
+ Id int `json:"id" xorm:"pk 'id' autoincr"`
+ NeType string `json:"neType" xorm:"ne_type"`
+ NeId string `json:"neId" xorm:"ne_id"`
+ Version string `json:"version" xorm:"column 'version' VARCHAR(16)"`
+ Capability uint32 `json:"capability" xorm:"capability"`
+ SerialNum string `json:"serialNum" xorm:"serial_num"`
+ ExpiryDate string `json:"expiryDate" xorm:"expiry_date"`
+ CpuUsage string `json:"cpuUsage" xorm:"cpu_usage"`
+ MemUsage string `json:"memUsage" xorm:"mem_usage"`
+ DiskSpace string `json:"diskSpace" xorm:"disk_space"`
+ Timestamp string `json:"timestamp" xorm:"-" `
+}
+
+func XormInsertNeState(neState *NeState) (int64, error) {
+ log.Debug("XormInsertNeState processing... ")
+
+ var affected int64 = 0
+
+ session := xEngine.NewSession()
+ defer session.Close()
+ affected, err := session.InsertOne(neState)
+ if err != nil {
+ return 0, err
+ }
+ err = session.Commit()
+ if err != nil {
+ return 0, err
+ }
+ return affected, err
+}
+
+type AlarmDefine struct {
+ AlarmId string `json:"alarmId" xorm:"alarm_id"`
+ AlarmCode int `json:"alarmCode" xorm:"alarm_code"`
+ AlarmTitle string `json:"alarmTitle" xorm:"alarm_title"`
+ NeType string `json:"neType" xorm:"ne_type"`
+ AlarmType string `json:"alarmType" xorm:"alarm_type"`
+ OrigSeverity string `json:"origSeverity" xorm:"orig_severity"`
+ ObjectUid string `json:"objectUid" xorm:"object_uid"`
+ ObjectName string `json:"objectName" xorm:"object_name"`
+ ObjectType string `json:"objectType" xorm:"object_type"`
+ LocationInfo string `json:"locationInfo"`
+ SpecificProblem string `json:"specificProblem"`
+ SpecificProblemId string `json:"specificProblemId" xorm:"specific_problem_id"`
+ AddInfo string `json:"addInfo" xorm:"add_info"`
+ Threshold int64 `json:"threshold" xorm:"threshold"`
+ Status string `json:"status" xorm:"status"`
+}
+
+func XormGetAlarmDefine(alarmCode string) (*AlarmDefine, error) {
+ log.Debug("XormGetAlarmDefine processing... ")
+
+ alarmDefine := new(AlarmDefine)
+ _, err := xEngine.
+ Where("alarm_code=? and status='Active'", alarmCode).
+ Get(alarmDefine)
+ if err != nil {
+ log.Error("Failed to get table alarm_define from database:", err)
+ return nil, err
+ }
+
+ return alarmDefine, nil
+}
diff --git a/lib/file/file.go b/lib/file/file.go
new file mode 100644
index 0000000..a03e354
--- /dev/null
+++ b/lib/file/file.go
@@ -0,0 +1,161 @@
+package file
+
+import (
+ "fmt"
+ "net/http"
+ "os"
+)
+
+// const (
+// //经过测试,linux下,延时需要大于100ms
+// TIME_DELAY_AFTER_WRITE = 200
+// )
+
+// type Response struct {
+// Data []string `json:"data"`
+// }
+
+// type MMLRequest struct {
+// MML []string `json:"mml"`
+// }
+
+// func GetFile(w http.ResponseWriter, r *http.Request) {
+// log.Debug("PostMMLToNF processing... ")
+
+// vars := mux.Vars(r)
+// neType := vars["elementTypeValue"]
+// params := r.URL.Query()
+// neId := params["ne_id"]
+// log.Debug("neType:", neType, "neId", neId)
+
+// neInfo := new(dborm.NeInfo)
+// var err error
+// if len(neId) == 0 {
+// log.Error("ne_id NOT FOUND")
+// services.ResponseBadRequest400WrongParamValue(w)
+// return
+// }
+// neInfo, err = dborm.XormGetNeInfo(neType, neId[0])
+// if err != nil {
+// log.Error("dborm.XormGetNeInfo is failed:", err)
+// services.ResponseInternalServerError500DatabaseOperationFailed(w)
+// return
+// }
+
+// var buf [8192]byte
+// var n int
+// var mmlResult []string
+
+// if neInfo != nil {
+// hostMML := fmt.Sprintf("%s:%d", neInfo.Ip, config.GetYamlConfig().MML.Port)
+// conn, err := net.Dial("tcp", hostMML)
+// if err != nil {
+// errMsg := fmt.Sprintf("Failed to dial %s: %v", hostMML, err)
+// log.Error(errMsg)
+// mmlResult = append(mmlResult, errMsg)
+// response := Response{mmlResult}
+// services.ResponseWithJson(w, http.StatusOK, response)
+// return
+// }
+
+// loginStr := fmt.Sprintf("%s\n%s\n", config.GetYamlConfig().MML.User, config.GetYamlConfig().MML.Password)
+// n, err = conn.Write([]byte(loginStr))
+// if err != nil {
+// log.Errorf("Error: %s", err.Error())
+// return
+// }
+
+// time.Sleep(time.Millisecond * TIME_DELAY_AFTER_WRITE)
+
+// n, err = conn.Read(buf[0:])
+// if err != nil {
+// log.Errorf("Error: %s", err.Error())
+// return
+// }
+// log.Debug(string(buf[0:n]))
+
+// body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen))
+// if err != nil {
+// log.Error("io.ReadAll is failed:", err)
+// services.ResponseNotFound404UriNotExist(w, r)
+// return
+// }
+// log.Debug("Body:", string(body))
+
+// mmlRequest := new(MMLRequest)
+// _ = json.Unmarshal(body, mmlRequest)
+
+// for _, mml := range mmlRequest.MML {
+// mmlCommand := fmt.Sprintf("%s\n", mml)
+// log.Debug("mml command:", mmlCommand)
+// n, err = conn.Write([]byte(mmlCommand))
+// if err != nil {
+// log.Errorf("Error: %s", err.Error())
+// return
+// }
+// time.Sleep(time.Millisecond * TIME_DELAY_AFTER_WRITE)
+
+// n, err = conn.Read(buf[0:])
+// if err != nil {
+// log.Errorf("Error: %s", err.Error())
+// return
+// }
+// log.Debug(string(buf[0 : n-len(neType)-2]))
+// mmlResult = append(mmlResult, string(buf[0:n-len(neType)-2]))
+// }
+// }
+
+// response := Response{mmlResult}
+// services.ResponseWithJson(w, http.StatusOK, response)
+// }
+
+// 格式文件大小单位
+func FormatFileSize(fileSize float64) (size string) {
+ if fileSize < 1024 {
+ return fmt.Sprintf("%.2fB", fileSize/float64(1))
+ } else if fileSize < (1024 * 1024) {
+ return fmt.Sprintf("%.2fKB", fileSize/float64(1024))
+ } else if fileSize < (1024 * 1024 * 1024) {
+ return fmt.Sprintf("%.2fMB", fileSize/float64(1024*1024))
+ } else if fileSize < (1024 * 1024 * 1024 * 1024) {
+ return fmt.Sprintf("%.2fGB", fileSize/float64(1024*1024*1024))
+ } else if fileSize < (1024 * 1024 * 1024 * 1024 * 1024) {
+ return fmt.Sprintf("%.2fTB", fileSize/float64(1024*1024*1024*1024))
+ } else {
+ return fmt.Sprintf("%.2fEB", fileSize/float64(1024*1024*1024*1024*1024))
+ }
+}
+
+func IsSymlink(mode os.FileMode) bool {
+ return mode&os.ModeSymlink != 0
+}
+
+const dotCharacter = 46
+
+func IsHidden(path string) bool {
+ return path[0] == dotCharacter
+}
+
+func GetMimeType(path string) string {
+ file, err := os.Open(path)
+ if err != nil {
+ return ""
+ }
+ defer file.Close()
+
+ buffer := make([]byte, 512)
+ _, err = file.Read(buffer)
+ if err != nil {
+ return ""
+ }
+ mimeType := http.DetectContentType(buffer)
+ return mimeType
+}
+
+func GetSymlink(path string) string {
+ linkPath, err := os.Readlink(path)
+ if err != nil {
+ return ""
+ }
+ return linkPath
+}
diff --git a/lib/global/exec_linux.go b/lib/global/exec_linux.go
new file mode 100644
index 0000000..e4aa858
--- /dev/null
+++ b/lib/global/exec_linux.go
@@ -0,0 +1,47 @@
+//go:build linux
+// +build linux
+
+package global
+
+import (
+ "bytes"
+ "os/exec"
+)
+
+func ExecCmd(command string) ([]byte, error) {
+ cmd := exec.Command("/bin/bash", "-c", command)
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ return nil, err
+ }
+
+ return out, nil
+}
+
+func ExecShell(command string) error {
+ in := bytes.NewBuffer(nil)
+ cmd := exec.Command("sh")
+ cmd.Stdin = in
+ in.WriteString(command)
+ in.WriteString("exit\n")
+ if err := cmd.Start(); err != nil {
+ return err
+ }
+ return nil
+}
+
+func ExecOsCmd(command, os string) ([]byte, error) {
+ var cmd *exec.Cmd
+ switch os {
+ case "Linux":
+ cmd = exec.Command(command)
+ case "Windows":
+ cmd = exec.Command("cmd", "/C", command)
+ }
+
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
diff --git a/lib/global/exec_windows.go b/lib/global/exec_windows.go
new file mode 100644
index 0000000..285005c
--- /dev/null
+++ b/lib/global/exec_windows.go
@@ -0,0 +1,34 @@
+//go:build windows
+// +build windows
+
+package global
+
+import (
+ "os/exec"
+)
+
+func ExecCmd(command string) ([]byte, error) {
+ cmd := exec.Command("cmd", "/C", command)
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ return nil, err
+ }
+
+ return out, nil
+}
+
+func ExecOsCmd(command, os string) ([]byte, error) {
+ var cmd *exec.Cmd
+ switch os {
+ case "Linux":
+ cmd = exec.Command(command)
+ case "Windows":
+ cmd = exec.Command("cmd", "/C", command)
+ }
+
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
diff --git a/lib/global/global.go b/lib/global/global.go
new file mode 100644
index 0000000..228fdf2
--- /dev/null
+++ b/lib/global/global.go
@@ -0,0 +1,65 @@
+package global
+
+import "errors"
+
+// 跨package引用的首字母大写
+const (
+ RequestBodyMaxLen = 2000000
+ ApiVersionV1 = "v1"
+ ApiVersionV2 = "v2"
+ LineBreak = "\n"
+)
+
+const (
+ DateTime = "2006-01-02 15:04:05"
+ DateData = "20060102150405"
+ DateHour = "2006010215"
+ DateZone = "2006-01-02 15:04:05 +0000 UTC"
+)
+
+const (
+ MaxInt32Number = 2147483647
+)
+
+const (
+ MaxLimitData = 1000
+)
+
+var (
+ Version string = "-"
+ BuildTime string = "-"
+ GoVer string = "-"
+)
+
+var (
+ DefaultUriPrefix = "/api/rest"
+)
+var (
+ ErrParamsNotAdapted = errors.New("the number of params is not adapted")
+
+ // PM module error message
+ ErrPMNotFoundData = errors.New("not found PM data")
+
+ // CM module error message
+ ErrCMNotFoundTargetNE = errors.New("not found target NE")
+ ErrCMCannotDeleteActiveNE = errors.New("can not delete an active NE")
+ ErrCMInvalidBackupFile = errors.New("invalid backup file")
+ ErrCMNotMatchMD5File = errors.New("md5 not match between file and url")
+ ErrCMNotMatchSignFile = errors.New("digests signatures not match in the file")
+ ErrCMExistSoftwareFile = errors.New("exist the same software package file")
+ ErrCMNotFoundTargetSoftware = errors.New("not found the target software package")
+ ErrCMNotFoundTargetNeVersion = errors.New("not found the target NE version")
+ ErrCMNotFoundRollbackNeVersion = errors.New("not found the rollback NE version")
+ ErrCMUnknownServiceAction = errors.New("unknown service action")
+ ErrCMUnknownInstanceAction = errors.New("unknown instance action")
+
+ ErrCMNotFoundTargetBackupFile = errors.New("not found the target NE backup")
+ ErrCMUnknownSoftwareFormat = errors.New("unknown software package format") // 未知软件包格式
+
+ // TRACE module error message
+ ErrTraceFailedDistributeToNEs = errors.New("failed to distribute trace task to target NEs")
+ ErrTraceNotCarriedTaskID = errors.New("not carried task id in request url")
+
+ // MML module error define
+ ErrMmlInvalidCommandFormat = errors.New("invalid mml command format")
+)
diff --git a/lib/global/kits.go b/lib/global/kits.go
new file mode 100644
index 0000000..593f18c
--- /dev/null
+++ b/lib/global/kits.go
@@ -0,0 +1,698 @@
+package global
+
+import (
+ "archive/zip"
+ "bytes"
+ "crypto/md5"
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net"
+ "net/http"
+ "os"
+ "path/filepath"
+ "reflect"
+ "regexp"
+ "sort"
+ "strings"
+ "time"
+)
+
+const (
+ IsIPv4 = "IPv4"
+ IsIPv6 = "IPv6"
+ NonIP = "NonIp"
+)
+
+type em struct{}
+
+func GetPkgName() string {
+ return reflect.TypeOf(em{}).PkgPath()
+}
+
+// interface{} change to map[string]interface{}
+// interface{} data is []interface{}
+func ListToMap(list interface{}, key string) map[string]interface{} {
+ res := make(map[string]interface{})
+ arr := ToSlice(list)
+ for _, row := range arr {
+ immutable := reflect.ValueOf(row)
+ val := immutable.FieldByName(key).String()
+ res[val] = row
+ }
+ return res
+}
+
+// interface{} change to []interface{}
+func ToSlice(arr interface{}) []interface{} {
+ ret := make([]interface{}, 0)
+ v := reflect.ValueOf(arr)
+ if v.Kind() != reflect.Slice {
+ ret = append(ret, arr)
+ return ret
+ }
+ l := v.Len()
+ for i := 0; i < l; i++ {
+ ret = append(ret, v.Index(i).Interface())
+ }
+ return ret
+}
+
+var TodoList []Todo
+
+type Todo struct {
+ Id int64
+ Item string
+}
+
+// JSON序列化方式
+func jsonStructToMap(TodoList Todo) (map[string]interface{}, error) {
+ // 结构体转json
+ strRet, err := json.Marshal(TodoList)
+ if err != nil {
+ return nil, err
+ }
+ // json转map
+ var mRet map[string]interface{}
+ err1 := json.Unmarshal(strRet, &mRet)
+ if err1 != nil {
+ return nil, err1
+ }
+ return mRet, nil
+}
+
+func IsContain(item string, items []string) bool {
+ for _, e := range items {
+ if e == item {
+ return true
+ }
+ }
+ return false
+}
+
+func IsContainP(item string, items *[]string, size int) bool {
+ for i := 0; i < size; i++ {
+ if (*items)[i] == item {
+ return true
+ }
+ }
+ return false
+}
+
+// 将字符串 分割成 字符串数组
+// @s:分割符
+func SplitString(str string, s string) []string {
+ sa := strings.Split(str, s)
+ return sa
+}
+
+// 合并字符串数组
+func MergeStringArr(a, b []string) []string {
+ var arr []string
+ for _, i := range a {
+ arr = append(arr, i)
+ }
+ for _, j := range b {
+ arr = append(arr, j)
+ }
+ return arr
+}
+
+// 数组去重
+func UniqueStringArr(m []string) []string {
+ d := make([]string, 0)
+ tempMap := make(map[string]bool, len(m))
+ for _, v := range m { // 以值作为键名
+ if tempMap[v] == false {
+ tempMap[v] = true
+ d = append(d, v)
+ }
+ }
+ return d
+}
+
+// 合并整型数组
+func MergeArr(a, b []int) []int {
+ var arr []int
+ for _, i := range a {
+ arr = append(arr, i)
+ }
+ for _, j := range b {
+ arr = append(arr, j)
+ }
+ return arr
+}
+
+// 数组去重
+func UniqueArr(m []int) []int {
+ d := make([]int, 0)
+ tempMap := make(map[int]bool, len(m))
+ for _, v := range m { // 以值作为键名
+ if tempMap[v] == false {
+ tempMap[v] = true
+ d = append(d, v)
+ }
+ }
+ return d
+}
+
+// 升序
+func AscArr(e []int) []int {
+ sort.Ints(e[:])
+ return e
+}
+
+// 降序
+func DescArr(e []int) []int {
+ sort.Sort(sort.Reverse(sort.IntSlice(e)))
+ return e
+}
+
+func MatchRmUID(p string, s string) bool {
+ match, _ := regexp.MatchString(p, s)
+ return match
+}
+
+type OrderedMap struct {
+ Order []string
+ Map map[string]interface{}
+}
+
+func (om *OrderedMap) UnmarshalJson(b []byte) error {
+ json.Unmarshal(b, &om.Map)
+
+ index := make(map[string]int)
+ for key := range om.Map {
+ om.Order = append(om.Order, key)
+ esc, _ := json.Marshal(key) //Escape the key
+ index[key] = bytes.Index(b, esc)
+ }
+
+ sort.Slice(om.Order, func(i, j int) bool { return index[om.Order[i]] < index[om.Order[j]] })
+ return nil
+}
+
+func (om OrderedMap) MarshalJson() ([]byte, error) {
+ var b []byte
+ buf := bytes.NewBuffer(b)
+ buf.WriteRune('{')
+ l := len(om.Order)
+ for i, key := range om.Order {
+ km, err := json.Marshal(key)
+ if err != nil {
+ return nil, err
+ }
+ buf.Write(km)
+ buf.WriteRune(':')
+ vm, err := json.Marshal(om.Map[key])
+ if err != nil {
+ return nil, err
+ }
+ buf.Write(vm)
+ if i != l-1 {
+ buf.WriteRune(',')
+ }
+ fmt.Println(buf.String())
+ }
+ buf.WriteRune('}')
+ fmt.Println(buf.String())
+ return buf.Bytes(), nil
+}
+
+func GetBodyCopy(r *http.Request) (*bytes.Buffer, error) {
+ // If r.bodyBuf present, return the copy
+ // if r.bodyBuf != nil {
+ // return bytes.NewBuffer(r.bodyBuf.Bytes()), nil
+ // }
+
+ // Maybe body is `io.Reader`.
+ // Note: Resty user have to watchout for large body size of `io.Reader`
+ if r.Body != nil {
+ b, err := io.ReadAll(r.Body)
+ if err != nil {
+ return nil, err
+ }
+
+ // Restore the Body
+ // close(r.Body)
+ r.Body = io.NopCloser(bytes.NewReader(b))
+
+ // Return the Body bytes
+ return bytes.NewBuffer(b), nil
+ }
+ return nil, nil
+}
+
+func UnmarshalBody(r *http.Request, v *interface{}, maxLen int64) error {
+ body, err := io.ReadAll(io.LimitReader(r.Body, maxLen))
+ if err != nil {
+ return err
+ }
+
+ return json.Unmarshal(body, v)
+}
+
+func SetNotifyUrl(ip string, port uint16, uri string) string {
+ return fmt.Sprintf("http://%s:%d%s", ip, port, uri)
+}
+
+func GetIps() (ips []string, err error) {
+ interfaceAddr, err := net.InterfaceAddrs()
+ if err != nil {
+ return ips, err
+ }
+
+ for _, address := range interfaceAddr {
+ ipNet, isVailIpNet := address.(*net.IPNet)
+ // 检查ip地址判断是否回环地址
+ if isVailIpNet && !ipNet.IP.IsLoopback() {
+ if ipNet.IP.To4() != nil {
+ ips = append(ips, ipNet.IP.String())
+ }
+ }
+ }
+ return ips, nil
+}
+
+func GetCurrentTimeSliceIndexByPeriod(t time.Time, period int) int {
+ index := int((t.Hour()*60+t.Minute())/period) - 1
+ if index < 0 {
+ return int(24*60/period) - 1
+ }
+ return index
+}
+
+var (
+ cst *time.Location
+)
+
+// RFC3339ToDateTime convert rfc3339 value to china standard time layout
+func RFC3339ToDateTime(value string) (string, error) {
+ ts, err := time.Parse(time.RFC3339, value)
+ if err != nil {
+ return "", err
+ }
+
+ return ts.In(cst).Format("2006-01-02 15:04:05"), nil
+}
+
+// CreateTimeDir 根据当前时间格式来创建文件夹
+func CreateTimeDir(fmt string, path string) string {
+ folderName := time.Now().Format(fmt)
+ folderPath := filepath.Join(path, folderName)
+ if _, err := os.Stat(folderPath); os.IsNotExist(err) {
+ // 必须分成两步:先创建文件夹、再修改权限
+ os.Mkdir(folderPath, 0664) //0644也可以os.ModePerm
+ os.Chmod(folderPath, 0664)
+ }
+ return folderPath
+}
+
+// CreateDir 根据传入的目录名和路径来创建文件夹
+func CreateDir(folderName string, path string) string {
+ folderPath := filepath.Join(path, folderName)
+ if _, err := os.Stat(folderPath); os.IsNotExist(err) {
+ // 必须分成两步:先创建文件夹、再修改权限
+ os.MkdirAll(folderPath, 0664) //0644也可以os.ModePerm
+ os.Chmod(folderPath, 0664)
+ }
+ return folderPath
+}
+
+func GetFmtTimeString(srcFmt string, timeString string, dstFmt string) string {
+ t, _ := time.ParseInLocation(srcFmt, timeString, time.Local)
+ return t.Format(dstFmt)
+}
+
+func GetFileMD5Sum(filePath string) (string, error) {
+ file, err := os.Open(filePath)
+ if err != nil {
+ return "", err
+ }
+ defer file.Close()
+ md5 := md5.New()
+ _, err = io.Copy(md5, file)
+ if err != nil {
+ return "", err
+ }
+
+ md5str := hex.EncodeToString(md5.Sum(nil))
+
+ return md5str, nil
+}
+
+// PathExists check path is exist or no
+func PathExists(path string) (bool, error) {
+ _, err := os.Stat(path)
+ if err == nil { //文件或者目录存在
+ return true, nil
+ }
+ if os.IsNotExist(err) {
+ return false, nil
+ }
+ return false, err
+}
+
+// PathExists check path is exist or no
+func FilePathExists(filePath string) (bool, error) {
+ _, err := os.Stat(filePath)
+ if err == nil { //文件或者目录存在
+ return true, nil
+ }
+ if os.IsNotExist(err) {
+ return false, nil
+ }
+ return false, err
+}
+
+func GetDayDuration(d1, d2 string) int64 {
+ a, _ := time.Parse("2006-01-02", d1)
+ b, _ := time.Parse("2006-01-02", d2)
+ d := a.Sub(b)
+
+ return (int64)(d.Hours() / 24)
+}
+
+func GetSecondsSinceDatetime(datetimeStr string) (int64, error) {
+ loc1, _ := time.LoadLocation("Local")
+ // 解析日期时间字符串为时间对象
+ datetime, err := time.ParseInLocation(time.DateTime, datetimeStr, loc1)
+ if err != nil {
+ return 0, err
+ }
+
+ // 计算时间差
+ duration := time.Since(datetime)
+
+ // 获取时间差的秒数
+ seconds := int64(duration.Seconds())
+
+ return seconds, nil
+}
+
+func GetSecondDuration(time1, time2 string) (int64, error) {
+ loc1, _ := time.LoadLocation("Local")
+ // 解析日期时间字符串为时间对象
+ t1, err := time.ParseInLocation(time.DateTime, time1, loc1)
+ if err != nil {
+ return 0, err
+ }
+ t2, err := time.ParseInLocation(time.DateTime, time2, loc1)
+ if err != nil {
+ return 0, err
+ }
+
+ // 计算时间差
+ duration := t2.Sub(t1)
+
+ // 获取时间差的秒数
+ seconds := int64(duration.Seconds())
+
+ return seconds, nil
+}
+
+// 0: invalid ip
+// 4: IPv4
+// 6: IPv6
+func ParseIP(s string) (net.IP, int) {
+ ip := net.ParseIP(s)
+ if ip == nil {
+ return nil, 0
+ }
+ for i := 0; i < len(s); i++ {
+ switch s[i] {
+ case '.':
+ return ip, 4
+ case ':':
+ return ip, 6
+ }
+ }
+ return nil, 0
+}
+
+func BytesCombine1(pBytes ...[]byte) []byte {
+ return bytes.Join(pBytes, []byte(""))
+}
+
+func BytesCombine(pBytes ...[]byte) []byte {
+ length := len(pBytes)
+ s := make([][]byte, length)
+ for index := 0; index < length; index++ {
+ s[index] = pBytes[index]
+ }
+ sep := []byte("")
+ return bytes.Join(s, sep)
+}
+
+func ParseIPAddr(ip string) string {
+ ipAddr := net.ParseIP(ip)
+
+ if ipAddr != nil {
+ if ipAddr.To4() != nil {
+ return IsIPv4
+ } else {
+ return IsIPv6
+ }
+ }
+
+ return NonIP
+}
+
+func CombineHostUri(ip string, port string) string {
+ var hostUri string = ""
+ ipType := ParseIPAddr(ip)
+ if ipType == IsIPv4 {
+ hostUri = fmt.Sprintf("http://%s:%v", ip, port)
+ } else {
+ hostUri = fmt.Sprintf("http://[%s]:%v", ip, port)
+ }
+
+ return hostUri
+}
+
+func StructToMap(obj interface{}) map[string]interface{} {
+ objValue := reflect.ValueOf(obj)
+ objType := objValue.Type()
+
+ m := make(map[string]interface{})
+
+ for i := 0; i < objValue.NumField(); i++ {
+ field := objValue.Field(i)
+ fieldName := objType.Field(i).Name
+
+ m[fieldName] = field.Interface()
+ }
+
+ return m
+}
+
+// ToMap 结构体转为Map[string]interface{}
+func ToMap(in interface{}, tagName string) (map[string]interface{}, error) {
+ out := make(map[string]interface{})
+
+ v := reflect.ValueOf(in)
+ if v.Kind() == reflect.Ptr {
+ v = v.Elem()
+ }
+
+ if v.Kind() != reflect.Struct { // 非结构体返回错误提示
+ return nil, fmt.Errorf("ToMap only accepts struct or struct pointer; got %T", v)
+ }
+
+ t := v.Type()
+ // 遍历结构体字段
+ // 指定tagName值为map中key;字段值为map中value
+ for i := 0; i < v.NumField(); i++ {
+ fi := t.Field(i)
+ if tagValue := fi.Tag.Get(tagName); tagValue != "" {
+ out[tagValue] = v.Field(i).Interface()
+ }
+ }
+ return out, nil
+}
+
+func ZipOneFile(srcFile, dstZip string, pathFlag bool) error {
+ zipFile, err := os.Create(dstZip)
+ if err != nil {
+ return err
+ }
+ defer zipFile.Close()
+
+ zipWriter := zip.NewWriter(zipFile)
+ defer zipWriter.Close()
+
+ fileToCompress, err := os.Open(srcFile)
+ if err != nil {
+ return err
+ }
+ defer fileToCompress.Close()
+
+ var fileInZip io.Writer
+ if pathFlag {
+ fileInZip, err = zipWriter.Create(srcFile)
+ if err != nil {
+ return err
+ }
+ } else {
+ // 获取文件的基本名称
+ fileName := filepath.Base(fileToCompress.Name())
+ fileInZip, err = zipWriter.Create(fileName)
+ if err != nil {
+ return err
+ }
+ }
+
+ _, err = io.Copy(fileInZip, fileToCompress)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func ZipDirectoryFile(srcDir, dstZip string) error {
+ // Create a new zip file
+ zipfileWriter, err := os.Create(dstZip)
+ if err != nil {
+ return err
+ }
+ defer zipfileWriter.Close()
+
+ // Create a new zip archive
+ zipWriter := zip.NewWriter(zipfileWriter)
+ defer zipWriter.Close()
+
+ // Walk through the directory and add files to the zip archive
+ err = filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ // Create a new file header for the current file
+ header, err := zip.FileInfoHeader(info)
+ if err != nil {
+ return err
+ }
+
+ // Set the name of the file within the zip archive
+ header.Name = filepath.Join(filepath.Base(srcDir), path[len(srcDir):])
+
+ // If the current file is a directory, skip it
+ if info.IsDir() {
+ return nil
+ }
+
+ // Create a new file in the zip archive
+ fileWriter, err := zipWriter.CreateHeader(header)
+ if err != nil {
+ return err
+ }
+
+ // Open the current file
+ file, err := os.Open(path)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ // Copy the contents of the current file to the zip archive
+ _, err = io.Copy(fileWriter, file)
+ if err != nil {
+ return err
+ }
+
+ return nil
+ })
+
+ return err
+}
+
+// 判断软件包是rpm或者deb, 1:rpm, 2:deb, 0:unknown format
+func JudgeRpmOrDebPackage(filePath string) (int, error) {
+ var fileType int = 0
+ file, err := os.Open(filePath)
+ if err != nil {
+ return fileType, err
+ }
+ defer file.Close()
+
+ // Read the first 6 bytes of the file
+ header := make([]byte, 6)
+ _, err = file.Read(header)
+ if err != nil {
+ return fileType, err
+ }
+
+ // Check the magic numbers to determine the package format
+ if string(header) == "!" {
+ fileType = 1
+ } else if string(header) == "!\n"
+ magic := []byte("!\n")
+ buffer := make([]byte, len(magic))
+
+ _, err := file.Read(buffer)
+ if err != nil && err != io.EOF {
+ return false
+ }
+
+ return string(buffer) == string(magic)
+}
+
+func CheckRpmOrDebPackage(filePath string) (int, error) {
+ var fileType int = 0
+ file, err := os.Open(filePath)
+ if err != nil {
+ return fileType, err
+ }
+ defer file.Close()
+
+ isRpm := isRpmPackage(file)
+ isDeb := isDebPackage(file)
+
+ if isRpm {
+ fileType = 1
+ } else if isDeb {
+ fileType = 2
+ } else {
+ fileType = 0
+ }
+
+ return fileType, nil
+}
+
+func IsRpmOrDebPackage(filePath string) int {
+ var fileType int = 0
+
+ if strings.Contains(filePath, ".rpm") {
+ fileType = 1
+ } else if strings.Contains(filePath, ".deb") {
+ fileType = 2
+ } else {
+ fileType = 0
+ }
+
+ return fileType
+}
diff --git a/lib/log/logger.go b/lib/log/logger.go
new file mode 100644
index 0000000..1df7062
--- /dev/null
+++ b/lib/log/logger.go
@@ -0,0 +1,338 @@
+// logger for omc/ems
+
+package log
+
+import (
+ "fmt"
+ "io"
+ "log"
+)
+
+// LogLevel defines a log level
+type LogLevel int
+
+// enum all LogLevels
+const (
+ // following level also match syslog.Priority value
+ LOG_TRACE LogLevel = iota
+ LOG_DEBUG
+ LOG_INFO
+ LOG_WARN
+ LOG_ERROR
+ LOG_FATAL
+ LOG_OFF
+ LOG_NODEF
+)
+
+// default log options
+const (
+ DEFAULT_LOG_PREFIX = "omc"
+ DEFAULT_LOG_FLAG = log.Lshortfile | log.Ldate | log.Lmicroseconds
+ DEFAULT_LOG_LEVEL = LOG_DEBUG
+ DEFAULT_CALL_DEPTH = 3
+)
+
+// Logger is a logger interface
+type Logger interface {
+ Fatal(v ...interface{})
+ Fatalf(format string, v ...interface{})
+ Error(v ...interface{})
+ Errorf(format string, v ...interface{})
+ Warn(v ...interface{})
+ Warnf(format string, v ...interface{})
+ Info(v ...interface{})
+ Infof(format string, v ...interface{})
+ Debug(v ...interface{})
+ Debugf(format string, v ...interface{})
+ Trace(v ...interface{})
+ Tracef(format string, v ...interface{})
+
+ Level() LogLevel
+ LevelString() string
+ SetLevel(l LogLevel)
+}
+
+var _ Logger = DiscardLogger{}
+
+// DiscardLogger don't log implementation for ILogger
+type DiscardLogger struct{}
+
+// Trace empty implementation
+func (DiscardLogger) Trace(v ...interface{}) {}
+
+// Tracef empty implementation
+func (DiscardLogger) Tracef(format string, v ...interface{}) {}
+
+// Debug empty implementation
+func (DiscardLogger) Debug(v ...interface{}) {}
+
+// Debugf empty implementation
+func (DiscardLogger) Debugf(format string, v ...interface{}) {}
+
+// Info empty implementation
+func (DiscardLogger) Info(v ...interface{}) {}
+
+// Infof empty implementation
+func (DiscardLogger) Infof(format string, v ...interface{}) {}
+
+// Warn empty implementation
+func (DiscardLogger) Warn(v ...interface{}) {}
+
+// Warnf empty implementation
+func (DiscardLogger) Warnf(format string, v ...interface{}) {}
+
+// Error empty implementation
+func (DiscardLogger) Error(v ...interface{}) {}
+
+// Errorf empty implementation
+func (DiscardLogger) Errorf(format string, v ...interface{}) {}
+
+// Fatal empty implementation
+func (DiscardLogger) Fatal(v ...interface{}) {}
+
+// Fatalf empty implementation
+func (DiscardLogger) Fatalf(format string, v ...interface{}) {}
+
+// Level empty implementation
+func (DiscardLogger) Level() LogLevel {
+ return LOG_NODEF
+}
+
+// Level empty implementation
+func (DiscardLogger) LevelString() string {
+ return ""
+}
+
+// SetLevel empty implementation
+func (DiscardLogger) SetLevel(l LogLevel) {}
+
+// EmsLogger is the default implment of ILogger
+type EmsLogger struct {
+ TRACE *log.Logger
+ DEBUG *log.Logger
+ INFO *log.Logger
+ WARN *log.Logger
+ ERROR *log.Logger
+ FATAL *log.Logger
+ level LogLevel
+ levelString []string
+ depth int
+}
+
+var _ Logger = &EmsLogger{}
+
+// NewEmsLogger use a special io.Writer as logger output
+func NewEmsLogger(out io.Writer) *EmsLogger {
+ return NewEmsLogger2(out, DEFAULT_LOG_PREFIX, DEFAULT_LOG_FLAG)
+}
+
+// NewEmsLogger2 let you customrize your logger prefix and flag
+func NewEmsLogger2(out io.Writer, prefix string, flag int) *EmsLogger {
+ return NewEmsLogger3(out, prefix, flag, DEFAULT_LOG_LEVEL)
+}
+
+// NewEmsLogger3 let you customrize your logger prefix and flag and logLevel
+func NewEmsLogger3(out io.Writer, prefix string, flag int, l LogLevel) *EmsLogger {
+ return &EmsLogger{
+ TRACE: log.New(out, fmt.Sprintf("[%s] [trace] ", prefix), flag),
+ DEBUG: log.New(out, fmt.Sprintf("[%s] [debug] ", prefix), flag),
+ INFO: log.New(out, fmt.Sprintf("[%s] [info ] ", prefix), flag),
+ WARN: log.New(out, fmt.Sprintf("[%s] [warn ] ", prefix), flag),
+ ERROR: log.New(out, fmt.Sprintf("[%s] [error] ", prefix), flag),
+ FATAL: log.New(out, fmt.Sprintf("[%s] [fatal] ", prefix), flag),
+ level: l,
+ levelString: []string{"trace", "debug", "info", "warn", "error", "fatal"},
+ depth: DEFAULT_CALL_DEPTH,
+ }
+}
+
+// Trace implement ILogger
+func (s *EmsLogger) Trace(v ...interface{}) {
+ if s.level <= LOG_TRACE {
+ _ = s.TRACE.Output(s.depth, fmt.Sprintln(v...))
+ }
+}
+
+// Tracef implement ILogger
+func (s *EmsLogger) Tracef(format string, v ...interface{}) {
+ if s.level <= LOG_TRACE {
+ _ = s.TRACE.Output(s.depth, fmt.Sprintf(format, v...))
+ }
+}
+
+// Debug implement ILogger
+func (s *EmsLogger) Debug(v ...interface{}) {
+ if s.level <= LOG_DEBUG {
+ _ = s.DEBUG.Output(s.depth, fmt.Sprintln(v...))
+ }
+}
+
+// Debugf implement ILogger
+func (s *EmsLogger) Debugf(format string, v ...interface{}) {
+ if s.level <= LOG_DEBUG {
+ _ = s.DEBUG.Output(s.depth, fmt.Sprintf(format, v...))
+ }
+}
+
+// Info implement ILogger
+func (s *EmsLogger) Info(v ...interface{}) {
+ if s.level <= LOG_INFO {
+ _ = s.INFO.Output(s.depth, fmt.Sprintln(v...))
+ }
+}
+
+// Infof implement ILogger
+func (s *EmsLogger) Infof(format string, v ...interface{}) {
+ if s.level <= LOG_INFO {
+ _ = s.INFO.Output(s.depth, fmt.Sprintf(format, v...))
+ }
+}
+
+// Warn implement ILogger
+func (s *EmsLogger) Warn(v ...interface{}) {
+ if s.level <= LOG_WARN {
+ _ = s.WARN.Output(s.depth, fmt.Sprintln(v...))
+ }
+}
+
+// Warnf implement ILogger
+func (s *EmsLogger) Warnf(format string, v ...interface{}) {
+ if s.level <= LOG_WARN {
+ _ = s.WARN.Output(s.depth, fmt.Sprintf(format, v...))
+ }
+}
+
+// Error implement ILogger
+func (s *EmsLogger) Error(v ...interface{}) {
+ if s.level <= LOG_ERROR {
+ _ = s.ERROR.Output(s.depth, fmt.Sprintln(v...))
+ }
+}
+
+// Errorf implement ILogger
+func (s *EmsLogger) Errorf(format string, v ...interface{}) {
+ if s.level <= LOG_ERROR {
+ _ = s.ERROR.Output(s.depth, fmt.Sprintf(format, v...))
+ }
+}
+
+// Warn implement ILogger
+func (s *EmsLogger) Fatal(v ...interface{}) {
+ if s.level <= LOG_FATAL {
+ _ = s.FATAL.Output(s.depth, fmt.Sprintln(v...))
+ }
+}
+
+// Warnf implement ILogger
+func (s *EmsLogger) Fatalf(format string, v ...interface{}) {
+ if s.level <= LOG_FATAL {
+ _ = s.FATAL.Output(s.depth, fmt.Sprintf(format, v...))
+ }
+}
+
+// Level implement ILogger
+func (s *EmsLogger) Level() LogLevel {
+ return s.level
+}
+
+// Level implement ILogger
+func (s *EmsLogger) LevelString() string {
+ return s.levelString[s.level]
+}
+
+// SetLevel implement ILogger
+func (s *EmsLogger) SetLevel(l LogLevel) {
+ s.level = l
+}
+
+var Elogger Logger
+
+func InitLogger(logFile string, period int, count int, prefix string, logLevel LogLevel) io.Writer {
+
+ /*
+ logFile, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0766)
+ if err != nil {
+ panic(err)
+ }
+ */
+
+ logWriter := getLogWriter(logFile, period, count)
+ Elogger = NewEmsLogger3(logWriter, prefix, DEFAULT_LOG_FLAG, logLevel)
+ fmt.Printf("logFile=%s, period=%d, count=%d, prefix=%s, logLevel=%s\n", logFile, period, count, prefix, GetLevelString())
+ return logWriter
+}
+
+// Trace implement ILogger
+func Trace(v ...interface{}) {
+ Elogger.Trace(v...)
+}
+
+// Tracef implement ILogger
+func Tracef(format string, v ...interface{}) {
+ Elogger.Tracef(format, v...)
+}
+
+// Debug implement ILogger
+func Debug(v ...interface{}) {
+ Elogger.Debug(v...)
+}
+
+// Debugf implement ILogger
+func Debugf(format string, v ...interface{}) {
+ Elogger.Debugf(format, v...)
+}
+
+// Info implement ILogger
+func Info(v ...interface{}) {
+ Elogger.Info(v...)
+}
+
+// Infof implement ILogger
+func Infof(format string, v ...interface{}) {
+ Elogger.Infof(format, v...)
+}
+
+// Warn implement ILogger
+func Warn(v ...interface{}) {
+ Elogger.Warn(v...)
+}
+
+// Warnf implement ILogger
+func Warnf(format string, v ...interface{}) {
+ Elogger.Warnf(format, v...)
+}
+
+// Error implement ILogger
+func Error(v ...interface{}) {
+ Elogger.Error(v...)
+}
+
+// Errorf implement ILogger
+func Errorf(format string, v ...interface{}) {
+ Elogger.Errorf(format, v...)
+}
+
+// Warn implement ILogger
+func Fatal(v ...interface{}) {
+ Elogger.Fatal(v...)
+}
+
+// Warnf implement ILogger
+func Fatalf(format string, v ...interface{}) {
+ Elogger.Fatalf(format, v...)
+}
+
+// Level implement ILogger
+func GetLevel() LogLevel {
+ return Elogger.Level()
+}
+
+// Level implement ILogger
+func GetLevelString() string {
+ return Elogger.LevelString()
+}
+
+// SetLevel implement ILogger
+func SetLevel(l LogLevel) {
+ Elogger.SetLevel(l)
+}
diff --git a/lib/log/partition.go b/lib/log/partition.go
new file mode 100644
index 0000000..5d32e58
--- /dev/null
+++ b/lib/log/partition.go
@@ -0,0 +1,71 @@
+package log
+
+import (
+ "io"
+ "time"
+
+ rotatelogs "github.com/lestrrat/go-file-rotatelogs"
+)
+
+type WriteSyncer interface {
+ io.Writer
+ Sync() error
+}
+
+// 得到LogWriter
+func getLogWriter(filePath string, period, count int) WriteSyncer {
+ warnIoWriter := getWriter(filePath, period, count)
+ return addSync(warnIoWriter)
+}
+
+// 日志文件切割
+func getWriter(filename string, period, count int) io.Writer {
+ // 保存日志count天,每period小时分割一次日志
+ duration := time.Hour * time.Duration(period)
+ var logfile string
+ if period >= 24 {
+ logfile = filename + "-%Y%m%d"
+ } else {
+ logfile = filename + "-%Y%m%d%H"
+ }
+ hook, err := rotatelogs.New(
+
+ logfile,
+ rotatelogs.WithLinkName(filename),
+ // rotatelogs.WithMaxAge(duration),
+ rotatelogs.WithRotationCount(count),
+ rotatelogs.WithRotationTime(duration),
+ rotatelogs.WithLocation(time.Local),
+ )
+
+ //保存日志30天,每1分钟分割一次日志
+ /*
+ hook, err := rotatelogs.New(
+ filename+"_%Y%m%d%H%M.log",
+ rotatelogs.WithLinkName(filename),
+ rotatelogs.WithMaxAge(time.Hour*24*30),
+ rotatelogs.WithRotationTime(time.Minute*1),
+ )
+ */
+ if err != nil {
+ panic(err)
+ }
+ return hook
+}
+
+func addSync(w io.Writer) WriteSyncer {
+ switch w := w.(type) {
+ case WriteSyncer:
+ return w
+ default:
+ return writerWrapper{w}
+ }
+}
+
+type writerWrapper struct {
+ io.Writer
+}
+
+func (w writerWrapper) Sync() error {
+ return nil
+}
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/lib/midware/arrow_ip_addr.go b/lib/midware/arrow_ip_addr.go
new file mode 100644
index 0000000..bba209a
--- /dev/null
+++ b/lib/midware/arrow_ip_addr.go
@@ -0,0 +1,77 @@
+package midware
+
+import (
+ "encoding/json"
+ "net/http"
+ "strings"
+ "time"
+
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/services"
+)
+
+// 登录策略限制登录时间和访问ip范围
+func ArrowIPAddr(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ ipAddr := strings.Split(r.RemoteAddr, ":")[0]
+
+ // 读取配置信息 登录策略设置
+ result, err := dborm.XormGetConfig("Security", "loginSecurity")
+ if err != nil {
+ next.ServeHTTP(w, r)
+ return
+ }
+ data := make(map[string]any)
+ err = json.Unmarshal([]byte(result["value_json"].(string)), &data)
+ if err != nil {
+ next.ServeHTTP(w, r)
+ return
+ }
+
+ // 开关
+ switchStr := data["switch"].(string)
+ if switchStr == "0" {
+ next.ServeHTTP(w, r)
+ return
+ }
+
+ ipRange := data["ipRange"].(string)
+ logintimeRange := data["logintime_range"].(string)
+
+ // 检查ip
+ ips := strings.Split(ipRange, "/")
+ hasIP := false
+ for _, ip := range ips {
+ if ipAddr == ip {
+ hasIP = true
+ }
+ }
+ if !hasIP {
+ services.ResponseErrorWithJson(w, 502, "网关登录策略-IP限制: "+ipAddr)
+ return
+ }
+
+ // 检查开放时间
+ logintimeRangeArr := strings.Split(logintimeRange, " - ")
+
+ // 加载中国时区
+ loc, _ := time.LoadLocation("Asia/Shanghai")
+ // 获取当前时间
+ currentTime := time.Now().In(loc)
+
+ // 获取当前日期
+ currentDate := time.Date(currentTime.Year(), currentTime.Month(), currentTime.Day(), 0, 0, 0, 0, currentTime.Location())
+ ymd := currentDate.Format("2006-01-02")
+
+ // 定义开始时间和结束时间
+ startTime, _ := time.ParseInLocation("2006-01-02 15:04:05", ymd+" "+logintimeRangeArr[0], loc)
+ endTime, _ := time.ParseInLocation("2006-01-02 15:04:05", ymd+" "+logintimeRangeArr[1], loc)
+
+ // 判断当前时间是否在范围内
+ if currentTime.After(startTime) && currentTime.Before(endTime) {
+ next.ServeHTTP(w, r)
+ } else {
+ services.ResponseErrorWithJson(w, 502, "网关登录策略-不在开放时间范围内")
+ }
+ })
+}
diff --git a/lib/midware/authorize.go b/lib/midware/authorize.go
new file mode 100644
index 0000000..43b7031
--- /dev/null
+++ b/lib/midware/authorize.go
@@ -0,0 +1,228 @@
+package midware
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+
+ "nms_nbi/lib/core/cache"
+ "nms_nbi/lib/core/utils/ctx"
+ "nms_nbi/lib/core/vo"
+ "nms_nbi/lib/core/vo/result"
+ "nms_nbi/lib/dborm"
+ commonConstants "nms_nbi/src/framework/constants/common"
+ tokenUtils "nms_nbi/src/framework/utils/token"
+)
+
+// Authorize 用户身份授权认证校验
+//
+// 只需含有其中角色 "hasRoles": {"xxx"},
+//
+// 只需含有其中权限 "hasPerms": {"xxx"},
+//
+// 同时匹配其中角色 "matchRoles": {"xxx"},
+//
+// 同时匹配其中权限 "matchPerms": {"xxx"},
+func Authorize(options map[string][]string) func(http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // 获取请求头标识信息
+ tokenStr := ctx.Authorization(r)
+ // 获取请求头标识信息-旧头
+ accessToken := r.Header.Get("AccessToken")
+ if tokenStr == "" && accessToken != "" {
+ // 验证令牌 == 这里直接查数据库session
+ if !dborm.XormExistValidToken(accessToken, 0) {
+ ctx.JSON(w, 401, result.CodeMsg(401, "Invalid identity authorization valid error"))
+ return
+ }
+ se, err := dborm.XormUpdateSessionShakeTime(accessToken)
+ if err != nil {
+ ctx.JSON(w, 401, result.CodeMsg(401, "Invalid identity authorization shake error"))
+ return
+ }
+
+ // 获取缓存的用户信息
+ data, ok := cache.GetLocalTTL(se.AccountId)
+ if data == nil || !ok {
+ ctx.JSON(w, 401, result.CodeMsg(401, "Invalid identity authorization info error"))
+ return
+ }
+ loginUser := data.(vo.LoginUser)
+
+ // 登录用户角色权限校验
+ if options != nil {
+ var roles []string
+ for _, item := range loginUser.User.Roles {
+ roles = append(roles, item.RoleKey)
+ }
+ perms := loginUser.Permissions
+ verifyOk := verifyRolePermission(roles, perms, options)
+ if !verifyOk {
+ msg := fmt.Sprintf("Unauthorized access %s %s", r.Method, r.RequestURI)
+ ctx.JSON(w, 403, result.CodeMsg(403, msg))
+ return
+ }
+ }
+
+ // 在请求的 Context 中存储数据
+ rContext := r.Context()
+ rContext = context.WithValue(rContext, ctx.ContextKey(commonConstants.CTX_LOGIN_USER), loginUser)
+ // 继续处理请求
+ next.ServeHTTP(w, r.WithContext(rContext))
+ return
+ }
+
+ // 获取请求头标识信息
+ if tokenStr == "" {
+ ctx.JSON(w, 401, result.CodeMsg(401, "Invalid identity authorization token error"))
+ return
+ }
+
+ // 验证令牌
+ claims, err := tokenUtils.Verify(tokenStr)
+ if err != nil {
+ ctx.JSON(w, 401, result.CodeMsg(401, "Invalid identity authorization valid error"))
+ return
+ }
+
+ // 获取缓存的用户信息
+ loginUser := tokenUtils.LoginUser(claims)
+ if loginUser.UserID == "" {
+ ctx.JSON(w, 401, result.CodeMsg(401, "Invalid identity authorization shake error"))
+ return
+ }
+
+ // 检查刷新有效期后存入上下文
+ tokenUtils.RefreshIn(&loginUser)
+
+ // 登录用户角色权限校验
+ if options != nil {
+ var roles []string
+ for _, item := range loginUser.User.Roles {
+ roles = append(roles, item.RoleKey)
+ }
+ perms := loginUser.Permissions
+ verifyOk := verifyRolePermission(roles, perms, options)
+ if !verifyOk {
+ msg := fmt.Sprintf("Unauthorized access %s %s", r.Method, r.RequestURI)
+ ctx.JSON(w, 403, result.CodeMsg(403, msg))
+ return
+ }
+ }
+
+ // 在请求的 Context 中存储数据
+ rContext := r.Context()
+ rContext = context.WithValue(rContext, ctx.ContextKey(commonConstants.CTX_LOGIN_USER), loginUser)
+ // 继续处理请求
+ next.ServeHTTP(w, r.WithContext(rContext))
+ })
+ }
+}
+
+// verifyRolePermission 校验角色权限是否满足
+//
+// roles 角色字符数组
+//
+// perms 权限字符数组
+//
+// options 参数
+func verifyRolePermission(roles, perms []string, options map[string][]string) bool {
+ // 直接放行 管理员角色或任意权限
+ if contains(roles, "admin") || contains(perms, "*:*:*") {
+ return true
+ }
+ opts := make([]bool, 4)
+
+ // 只需含有其中角色
+ hasRole := false
+ if arr, ok := options["hasRoles"]; ok && len(arr) > 0 {
+ hasRole = some(roles, arr)
+ opts[0] = true
+ }
+
+ // 只需含有其中权限
+ hasPerms := false
+ if arr, ok := options["hasPerms"]; ok && len(arr) > 0 {
+ hasPerms = some(perms, arr)
+ opts[1] = true
+ }
+
+ // 同时匹配其中角色
+ matchRoles := false
+ if arr, ok := options["matchRoles"]; ok && len(arr) > 0 {
+ matchRoles = every(roles, arr)
+ opts[2] = true
+ }
+
+ // 同时匹配其中权限
+ matchPerms := false
+ if arr, ok := options["matchPerms"]; ok && len(arr) > 0 {
+ matchPerms = every(perms, arr)
+ opts[3] = true
+ }
+
+ // 同时判断 含有其中
+ if opts[0] && opts[1] {
+ return hasRole || hasPerms
+ }
+ // 同时判断 匹配其中
+ if opts[2] && opts[3] {
+ return matchRoles && matchPerms
+ }
+ // 同时判断 含有其中且匹配其中
+ if opts[0] && opts[3] {
+ return hasRole && matchPerms
+ }
+ if opts[1] && opts[2] {
+ return hasPerms && matchRoles
+ }
+
+ return hasRole || hasPerms || matchRoles || matchPerms
+}
+
+// contains 检查字符串数组中是否包含指定的字符串
+func contains(arr []string, target string) bool {
+ for _, str := range arr {
+ if str == target {
+ return true
+ }
+ }
+ return false
+}
+
+// some 检查字符串数组中含有其中一项
+func some(origin []string, target []string) bool {
+ has := false
+ for _, t := range target {
+ for _, o := range origin {
+ if t == o {
+ has = true
+ break
+ }
+ }
+ if has {
+ break
+ }
+ }
+ return has
+}
+
+// every 检查字符串数组中同时包含所有项
+func every(origin []string, target []string) bool {
+ match := true
+ for _, t := range target {
+ found := false
+ for _, o := range origin {
+ if t == o {
+ found = true
+ break
+ }
+ }
+ if !found {
+ match = false
+ break
+ }
+ }
+ return match
+}
diff --git a/lib/midware/cors.go b/lib/midware/cors.go
new file mode 100644
index 0000000..b0a73ad
--- /dev/null
+++ b/lib/midware/cors.go
@@ -0,0 +1,66 @@
+package midware
+
+import (
+ "net/http"
+ "strings"
+)
+
+// Cors 跨域
+func Cors(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // 设置Vary头部
+ w.Header().Set("Vary", "Origin")
+ w.Header().Set("Keep-Alive", "timeout=5")
+
+ requestOrigin := r.Header.Get("Origin")
+ if requestOrigin == "" {
+ next.ServeHTTP(w, r)
+ return
+ }
+
+ w.Header().Set("Access-Control-Allow-Origin", "*")
+
+ w.Header().Set("Access-Control-Allow-Credentials", "true")
+
+ // OPTIONS
+ if r.Method == "OPTIONS" {
+ requestMethod := r.Header.Get("Access-Control-Request-Method")
+ if requestMethod == "" {
+ next.ServeHTTP(w, r)
+ return
+ }
+
+ // 响应最大时间值
+ w.Header().Set("Access-Control-Max-Age", "31536000")
+
+ // 允许方法
+ allowMethods := []string{
+ "OPTIONS",
+ "HEAD",
+ "GET",
+ "POST",
+ "PUT",
+ "DELETE",
+ "PATCH",
+ }
+ w.Header().Set("Access-Control-Allow-Methods", strings.Join(allowMethods, ","))
+
+ // 允许请求头
+ allowHeaders := []string{
+ "Accesstoken",
+ "Content-Type",
+ "operationtype",
+ }
+ w.Header().Set("Access-Control-Allow-Headers", strings.Join(allowHeaders, ","))
+
+ w.WriteHeader(204)
+ return
+ }
+
+ // 暴露请求头
+ exposeHeaders := []string{"X-RepeatSubmit-Rest", "AccessToken"}
+ w.Header().Set("Access-Control-Expose-Headers", strings.Join(exposeHeaders, ","))
+
+ next.ServeHTTP(w, r)
+ })
+}
diff --git a/lib/midware/midhandle.go b/lib/midware/midhandle.go
new file mode 100644
index 0000000..f4932c0
--- /dev/null
+++ b/lib/midware/midhandle.go
@@ -0,0 +1,80 @@
+package midware
+
+import (
+ "net/http"
+ "strings"
+
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/services"
+ tokenConst "nms_nbi/src/framework/constants/token"
+
+ "github.com/gorilla/mux"
+)
+
+func LoggerTrace(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // Do stuff here
+ log.Trace("Http Trace Info:")
+ log.Trace(" From Host:", r.RemoteAddr)
+ log.Trace(" To Host:", r.Host)
+ log.Debug(" RequestUri:", r.RequestURI)
+ log.Trace(" Method:", r.Method)
+ log.Trace(" Proto:", r.Proto)
+ log.Trace(" ContentLength:", r.ContentLength)
+ log.Trace(" User-Agent:", r.Header.Get("User-Agent"))
+ log.Trace(" Content-Type:", r.Header.Get("Content-Type"))
+ log.Trace(" AccessToken:", r.Header.Get("AccessToken"))
+ log.Trace(" Authorization:", r.Header.Get(tokenConst.HEADER_KEY))
+ log.Trace("Trace End=====")
+ //body, _ := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen))
+ // nop-close to ready r.Body !!!
+ //r.Body = ioutil.NopCloser(bytes.NewReader(body))
+ //log.Trace("Body:", string(body))
+ // Call the next handler, which can be another middleware in the chain, or the final handler.
+ // if r.Method == "OPTIONS" {
+ // services.ResponseStatusOK201Accepted(w)
+ // return
+ // }
+
+ next.ServeHTTP(w, r)
+ })
+}
+
+// 已禁用
+func OptionProcess(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == "OPTIONS" {
+ services.ResponseStatusOK201Accepted(w)
+ return
+ }
+
+ next.ServeHTTP(w, r)
+ })
+}
+
+// 已禁用
+func CheckPermission(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ token := r.Header.Get("AccessToken")
+ vars := mux.Vars(r)
+ management := vars["managedType"]
+ element := vars["elementTypeValue"]
+ object := vars["objectTypeValue"]
+ pack := "*"
+ if token != "" && element != "oauth" {
+ log.Debugf("token:%s, method:%s, management:%s, element:%s, object:%s, pack:%s", token, r.Method, management, element, object, pack)
+ exist, err := services.CheckUserPermission(token, strings.ToLower(r.Method), management, element, object, pack)
+ if err != nil {
+ log.Error("Failed to get permission:", err)
+ services.ResponseForbidden403NotPermission(w)
+ return
+ }
+ if !exist {
+ log.Error("Not permission!")
+ services.ResponseForbidden403NotPermission(w)
+ return
+ }
+ }
+ next.ServeHTTP(w, r)
+ })
+}
diff --git a/lib/midware/mml_log.go b/lib/midware/mml_log.go
new file mode 100644
index 0000000..804adbd
--- /dev/null
+++ b/lib/midware/mml_log.go
@@ -0,0 +1,68 @@
+package midware
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "strings"
+ "time"
+
+ "nms_nbi/lib/core/datasource"
+ "nms_nbi/lib/core/utils/ctx"
+ "nms_nbi/lib/core/utils/date"
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/log"
+)
+
+// LogMML mml操作日志搜集
+func LogMML(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // 读取请求体内容
+ body, err := io.ReadAll(r.Body)
+ if err != nil {
+ http.Error(w, "Internal Server Error", http.StatusInternalServerError)
+ return
+ }
+ // 解析json内参数
+ var bodyArgs map[string]any
+ _ = json.Unmarshal(bytes.Clone(body), &bodyArgs)
+ // 将请求体内容存储在临时缓冲区中
+ buffer := bytes.NewBuffer(body)
+ r.Body = io.NopCloser(buffer)
+
+ next.ServeHTTP(w, r)
+
+ // 收尾存入数据库的参数
+ mmlCmd := bodyArgs["mml"].([]any)[0]
+ ipAddr := strings.Split(r.RemoteAddr, ":")[0]
+ neType := ctx.Param(r, "elementTypeValue")
+ neId := ctx.GetQuery(r, "ne_id")
+ timeStr := date.ParseDateToStr(time.Now(), date.YYYY_MM_DD_HH_MM_SS)
+
+ // 响应内容长度和状态码作为结果
+ str := strings.TrimSuffix(fmt.Sprintf("%v", w), "}")
+ strArr := strings.Split(str, " ")
+ size := strArr[1]
+ status := strArr[2]
+ contentType := w.Header().Get("Content-Type")
+ resultStr := fmt.Sprintf(`{"status":"%s","size":"%s","content-type":"%s"}`, status, size, contentType)
+
+ // 用户名
+ username := "-"
+ accessToken := r.Header.Get("AccessToken")
+ if accessToken != "" {
+ // 验证令牌 == 这里直接查数据库session
+ se, _ := dborm.XormUpdateSessionShakeTime(accessToken)
+ username = se.AccountId
+ }
+
+ // 执行插入
+ sql := "insert into mml_log (user,ip,ne_type,ne_id,mml,result,log_time)values(?,?,?,?,?,?,?)"
+ _, sqlerr := datasource.ExecDB("", sql, []any{username, ipAddr, neType, neId, mmlCmd, resultStr, timeStr})
+ if sqlerr != nil {
+ log.Errorf("insert row : %v", err.Error())
+ }
+ })
+}
diff --git a/lib/midware/operate_log.go b/lib/midware/operate_log.go
new file mode 100644
index 0000000..5e1cae0
--- /dev/null
+++ b/lib/midware/operate_log.go
@@ -0,0 +1,143 @@
+package midware
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "reflect"
+ "runtime"
+ "strings"
+ "time"
+
+ "nms_nbi/lib/core/utils/parse"
+ "nms_nbi/lib/dborm"
+ "nms_nbi/src/framework/constants/common"
+ "nms_nbi/src/framework/middleware/collectlogs"
+ "nms_nbi/src/framework/utils/ip2region"
+ "nms_nbi/src/modules/system/model"
+ "nms_nbi/src/modules/system/service"
+)
+
+// 敏感属性字段进行掩码
+var maskProperties []string = []string{
+ "password",
+ "oldPassword",
+ "newPassword",
+ "confirmPassword",
+}
+
+// LogOperate 操作日志搜集
+func LogOperate(options collectlogs.Options) func(http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ startTime := time.Now()
+
+ // 只对前端发送的数据进行记录
+ appCodeStr := r.Header.Get("X-App-Code")
+ if appCodeStr == "" {
+ next.ServeHTTP(w, r)
+ return
+ }
+
+ // 函数名
+ funcName := runtime.FuncForPC(reflect.ValueOf(next).Pointer()).Name()
+ lastDotIndex := strings.LastIndex(funcName, "/")
+ funcName = funcName[lastDotIndex+1:]
+
+ // 用户名
+ username := "-"
+ accessToken := r.Header.Get("AccessToken")
+ if accessToken != "" {
+ // 验证令牌 == 这里直接查数据库session
+ se, _ := dborm.XormUpdateSessionShakeTime(accessToken)
+ username = se.AccountId
+ }
+
+ // 解析ip地址
+ ip := strings.Split(r.RemoteAddr, ":")[0]
+ ipaddr := ip2region.ClientIP(ip)
+ location := ip2region.RealAddressByIp(ipaddr)
+ // 操作日志记录
+ operLog := model.SysLogOperate{
+ Title: options.Title,
+ BusinessType: options.BusinessType,
+ OperatorType: collectlogs.OPERATOR_TYPE_MANAGE,
+ Method: funcName,
+ OperURL: r.RequestURI,
+ RequestMethod: r.Method,
+ OperIP: ipaddr,
+ OperLocation: location,
+ OperName: username,
+ DeptName: "",
+ }
+
+ // 是否需要保存request,参数和值
+ if options.IsSaveRequestData && (r.Method == "POST" || r.Method == "PUT") {
+ // 读取请求体内容
+ body, err := io.ReadAll(r.Body)
+ if err != nil {
+ http.Error(w, "Internal Server Error", http.StatusInternalServerError)
+ return
+ }
+ // 解析json内参数
+ var bodyArgs map[string]any
+ _ = json.Unmarshal(bytes.Clone(body), &bodyArgs)
+ // 将请求体内容存储在临时缓冲区中
+ buffer := bytes.NewBuffer(body)
+ r.Body = io.NopCloser(buffer)
+
+ params := bodyArgs
+ for k, v := range params {
+ // 敏感属性字段进行掩码
+ for _, s := range maskProperties {
+ if s == k {
+ params[k] = parse.SafeContent(v.(string))
+ break
+ }
+ }
+ }
+ jsonStr, _ := json.Marshal(params)
+ paramsStr := string(jsonStr)
+ if len(paramsStr) > 2000 {
+ paramsStr = paramsStr[:2000]
+ }
+ operLog.OperParam = paramsStr
+ }
+
+ next.ServeHTTP(w, r)
+
+ // 响应内容长度和状态码作为结果
+ str := strings.TrimSuffix(fmt.Sprintf("%v", w), "}")
+ strArr := strings.Split(str, " ")
+ size := strArr[1]
+ status := strArr[2]
+
+ // 响应状态
+ if status == "200" || status == "204" {
+ operLog.Status = common.STATUS_YES
+ } else {
+ operLog.Status = common.STATUS_NO
+ }
+
+ // 是否需要保存response,参数和值
+ if options.IsSaveResponseData {
+ contentDisposition := w.Header().Get("Content-Disposition")
+ contentType := w.Header().Get("Content-Type")
+ content := contentType + contentDisposition
+ msg := fmt.Sprintf(`{"status":"%s","size":"%s","content-type":"%s"}`, status, size, content)
+ operLog.OperMsg = msg
+ }
+
+ // 日志记录时间
+ duration := time.Since(startTime)
+ operLog.CostTime = duration.Milliseconds()
+ operLog.OperTime = time.Now().UnixMilli()
+
+ // 保存操作记录到数据库
+ service.NewSysLogOperateImpl.InsertSysLogOperate(operLog)
+
+ })
+ }
+}
diff --git a/lib/mmlp/parse.go b/lib/mmlp/parse.go
new file mode 100644
index 0000000..adb99e4
--- /dev/null
+++ b/lib/mmlp/parse.go
@@ -0,0 +1,1058 @@
+package mmlp
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "math"
+ "net/http"
+ "regexp"
+ "strconv"
+ "strings"
+
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/global"
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/run"
+ tokenConst "nms_nbi/src/framework/constants/token"
+
+ "github.com/go-resty/resty/v2"
+)
+
+type Param struct {
+ Name string `json:"name"`
+ Value string `json:"value"`
+}
+
+type MmlCommand struct {
+ Operation string `json:"operation"`
+ Object string `json:"object"`
+ Params []Param `json:"params"`
+ PaList []string `json:"paList"`
+ AaList []string `json:"aaList"`
+ AaMap map[string]interface{} `json:"aaMap"`
+ NaMap map[string]interface{} `json:"naMap"`
+ AaUri []string `json:"aaUri"`
+ AaLoc []string `json:"aaLoc"` // for loc parameter
+}
+
+type MmlVar struct {
+ Version string `json:"version"`
+ Output string `json:"output"`
+ MmlHome string `json:"mmlHome"`
+ Limit int `json:"limit"`
+ User string `json:"user"`
+ SessionToken string `josn:"sessionToken"`
+ Authorization string `josn:"authorization"`
+ HttpUri string `json:"httpUri"`
+ UserAgent string `json:"userAgent"`
+}
+
+// func init() {
+// OmcMmlVar = &MmlVar{
+// Version: "16.1.1",
+// Output: DefaultFormatType,
+// Limit: 50,
+// }
+// }
+
+// func SetOmcMmlVarOutput(output string) {
+// OmcMmlVar.Output = output
+// }
+
+// func SetOmcMmlVarLimit(limit int) {
+// OmcMmlVar.Limit = limit
+// }
+
+func splitByColon(str string) []string {
+ return splitBy(str, ':')
+}
+
+func splitByComma(str string) []string {
+ return splitBy(str, ',')
+}
+
+func splitBy(str string, sep rune) []string {
+ var result []string
+ var stack []string
+ var current []rune
+ var quotes, apoFlag bool = false, false
+
+ for _, c := range str {
+ if c == '{' || c == '[' || (c == '\'' && apoFlag == false) || (c == '"' && quotes == false) { // "'"
+ apoFlag = true
+ quotes = true
+ stack = append(stack, string(c))
+ } else if c == '}' || c == ']' || (c == '\'' && apoFlag == true) || (c == '"' && quotes == true) {
+ apoFlag = false
+ quotes = false
+ if len(stack) > 0 {
+ stack = stack[:len(stack)-1]
+ }
+ }
+
+ if c == sep && len(stack) == 0 {
+ result = append(result, string(current))
+ current = []rune{}
+ } else {
+ current = append(current, c)
+ }
+ }
+
+ result = append(result, string(current))
+ return result
+}
+
+func ParseMMLCommand(mmlStr string, mmlComms *[]MmlCommand) error {
+ log.Info("ParseMMLCommand processing ...")
+ log.Debug("mmlStr: ", mmlStr)
+
+ mc := new(MmlCommand)
+ reg := regexp.MustCompile(`\s*;\s*`)
+ mmls := reg.Split(mmlStr, -1)
+ for _, mml := range mmls {
+ log.Trace("mml:", mml)
+ if len(mml) == 0 {
+ continue
+ }
+ //reg := regexp.MustCompile(`\s*:\s*`)
+ //ms := reg.Split(mml, -1)
+ ms := splitByColon(mml)
+ if len(ms) < 1 || len(ms) > 2 {
+ err := global.ErrMmlInvalidCommandFormat
+ log.Error(err)
+ return err
+ }
+ if len(ms) == 2 {
+ cmd := strings.Trim(ms[0], " ")
+ reg = regexp.MustCompile(`\s+`)
+ cs := reg.Split(cmd, -1)
+ //cs := strings.Split(cmd, " ")
+ if len(cs) == 2 {
+ mc.Operation = cs[0]
+ mc.Object = cs[1]
+ } else {
+ err := global.ErrMmlInvalidCommandFormat
+ log.Error(err)
+ return err
+ }
+ //reg = regexp.MustCompile(`\s*,\s*`)
+ //reg = regexp.MustCompile(`(?U)(? 0 {
+ extUri := strings.Join(mml.AaUri, "/")
+ requestURI = requestURI + fmt.Sprintf(mmlMap.ExtUri, extUri)
+ }
+ if mmlMap.Params != "" {
+ params := strings.Join(mml.AaLoc, "+and+")
+ params = strings.ReplaceAll(params, " ", "+") // replace " " to "+"
+ log.Trace("params:", params)
+ if mmlMap.ParamTag == "SQL" && strings.TrimSpace(params) != "" {
+ params = "+where+" + params
+ }
+ requestURI = fmt.Sprintf("%s%s%s", requestURI, mmlMap.Params, params)
+ }
+ return requestURI
+}
+
+func DeploymentLicense(mml *MmlCommand, omcMmlVar *MmlVar, outputJson *dborm.MmlOutput) *[]byte {
+ var output []byte
+ log.Debug("mml:", mml)
+ var srcNeType, srcNeid, dstNeType, dstNeId, value string
+ for _, Param := range mml.Params {
+ switch Param.Name {
+ case "srcnetype":
+ srcNeType = Param.Value
+ case "srcneid":
+ srcNeid = Param.Value
+ case "dstnetype":
+ dstNeType = Param.Value
+ case "dstneid":
+ dstNeId = Param.Value
+ case "number":
+ value = Param.Value
+ }
+ }
+ intValue, _ := strconv.Atoi(value)
+ log.Debugf("srcNeType:%s, srcNeid:%s dstNeType:%s dstNeId:%s intValue:%d", srcNeType, srcNeid, dstNeType, dstNeId, intValue)
+ a1, err := dborm.XormAdjustmentNeLicense(srcNeType, srcNeid, intValue)
+ if err != nil {
+ log.Error("Failed to Put:", err)
+ }
+
+ a2, err := dborm.XormAdjustmentNeLicense(dstNeType, dstNeId, -intValue)
+ if err != nil {
+ log.Error("Failed to Put:", err)
+ output = *ParseErrorOutput(err)
+ } else {
+ //response := &resty.Response{StatusCode: http.StatusOK}
+ //output = ParseOutputResponse(omcMmlVar, outputJson, response.)
+ str := fmt.Sprintf("RetCode = 0 operation succeeded\n\nAffected rows = %d \n\n", a1+a2)
+ output = []byte(str)
+ }
+
+ return &output
+}
+
+func AdjustmentLicense(mml *MmlCommand, omcMmlVar *MmlVar, outputJson *dborm.MmlOutput) *[]byte {
+ var output []byte
+ log.Debug("mml:", mml)
+ var neType, neid, number string
+ for _, Param := range mml.Params {
+ switch Param.Name {
+ case "netype":
+ neType = Param.Value
+ case "neid":
+ neid = Param.Value
+ case "number":
+ number = Param.Value
+ }
+ }
+ intValue, _ := strconv.Atoi(number)
+ log.Debugf("neType:%s, neid:%s intValue:%d", neType, neid, intValue)
+ affected, err := dborm.XormAdjustmentNeLicense(neType, neid, intValue)
+ if err != nil {
+ log.Error("Failed to XormAdjustmentNeLicense:", err)
+ output = *ParseErrorOutput(err)
+ } else {
+ str := fmt.Sprintf("RetCode = 0 operation succeeded\n\nAffected rows = %d \n\n", affected)
+ output = []byte(str)
+ }
+
+ return &output
+}
+
+func InstallLicense(mml *MmlCommand, omcMmlVar *MmlVar, outputJson *dborm.MmlOutput) *[]byte {
+ var output []byte
+ log.Debug("mml:", mml)
+ var neType, neid, number string
+ for _, Param := range mml.Params {
+ switch Param.Name {
+ case "netype":
+ neType = Param.Value
+ case "neid":
+ neid = Param.Value
+ case "number":
+ number = Param.Value
+ }
+ }
+ intValue, _ := strconv.Atoi(number)
+ log.Debugf("neType:%s, neid:%s intValue:%d", neType, neid, intValue)
+ affected, err := dborm.XormUpdateNeLicense(neType, neid, intValue)
+ if err != nil {
+ log.Error("Failed to XormUpdateNeLicense:", err)
+ output = *ParseErrorOutput(err)
+ } else {
+ str := fmt.Sprintf("RetCode = 0 operation succeeded\n\nAffected rows = %d \n\n", affected)
+ output = []byte(str)
+ }
+
+ return &output
+}
+
+func RunShellCommand(mml *MmlCommand, omcMmlVar *MmlVar, outputJson *dborm.MmlOutput) *[]byte {
+ var output []byte
+ log.Debug("mml:", mml)
+ var command string
+ for _, Param := range mml.Params {
+ switch Param.Name {
+ case "cmd":
+ command = Param.Value
+ default:
+ }
+ }
+ out, err := run.ExecCmd(command, omcMmlVar.MmlHome)
+ //cmd := exec.Command("/bin/bash", "-c", command)
+
+ //out, err := cmd.CombinedOutput()
+ log.Tracef("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("exe cmd error: ", err)
+ str := fmt.Sprintf("Command: %s output:\n\n%v\n", command, string(out))
+ //output = *ParseErrorOutput(err)
+ output = []byte(str)
+ //return &output
+ } else {
+ str := fmt.Sprintf("Command: %s output:\n\n%v\n", command, string(out))
+ output = []byte(str)
+ }
+
+ return &output
+}
+
+func TransMml2HttpReq(omcMmlVar *MmlVar, mml *MmlCommand) (*[]byte, error) {
+ log.Info("TransMml2HttpReq processing ...")
+ log.Debug("mml: ", mml)
+
+ where := fmt.Sprintf("operation='%s' AND object='%s'", mml.Operation, mml.Object)
+ mmlMap, err := dborm.XormGetMmlHttpMap("mml_http_map", where)
+ if err != nil {
+ log.Error("Failed to XormGetMmlHttpMap: ", err)
+ return ParseErrorOutput(err), err
+ }
+ if mmlMap == nil {
+ err := errors.New("Not found mml map")
+ log.Error(err)
+ return ParseErrorOutput(err), err
+ }
+ log.Trace("mmlMap: ", mmlMap)
+ if mmlMap.Output == "" {
+ mmlMap.Output = "{}"
+ }
+ outputJson := new(dborm.MmlOutput)
+ err = json.Unmarshal([]byte(mmlMap.Output), outputJson)
+ if err != nil {
+ log.Error("Failed to Unmarshal:", err)
+ return ParseErrorOutput(err), err
+ }
+ log.Trace("outputJson: ", outputJson)
+ inputJson := new(dborm.MmlInput)
+ log.Trace("mmlMap.Input: ", mmlMap.Input)
+ if mmlMap.Input == "" {
+ mmlMap.Input = "{}"
+ }
+ err = json.Unmarshal([]byte(mmlMap.Input), inputJson)
+ if err != nil {
+ log.Error("Failed to Unmarshal:", err)
+ return ParseErrorOutput(err), err
+ }
+ log.Trace("inputJson: ", inputJson)
+
+ var requestURI string
+ var output *[]byte
+ client := resty.New()
+ switch strings.ToLower(mmlMap.Method) {
+ case "get":
+ requestURI = parseRequestUri(omcMmlVar.HttpUri, mmlMap, mml)
+ log.Debugf("method: Get requestURI: %s", requestURI)
+ response, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{tokenConst.HEADER_KEY: omcMmlVar.Authorization}).
+ SetHeaders(map[string]string{"accessToken": omcMmlVar.SessionToken}).
+ SetHeaders(map[string]string{"User-Agent": omcMmlVar.UserAgent}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ Get(requestURI)
+ if err != nil {
+ log.Error("Failed to Get:", err)
+ output = ParseErrorOutput(err)
+ } else {
+ output = ParseOutputResponse(omcMmlVar, outputJson, response)
+ }
+ case "post":
+ requestURI = parseRequestUri(omcMmlVar.HttpUri, mmlMap, mml)
+ body := ParseInputBody(inputJson, mml)
+ log.Debugf("method: Post requestURI: %s", requestURI)
+ response, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{tokenConst.HEADER_KEY: omcMmlVar.Authorization}).
+ SetHeaders(map[string]string{"accessToken": omcMmlVar.SessionToken}).
+ SetHeaders(map[string]string{"User-Agent": omcMmlVar.UserAgent}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ SetBody(*body).
+ Post(requestURI)
+ if err != nil {
+ log.Error("Failed to Post:", err)
+ output = ParseErrorOutput(err)
+ } else {
+ output = ParseOutputResponse(omcMmlVar, outputJson, response)
+ }
+ case "put":
+ switch inputJson.CallFunc {
+ case "DeploymentLicense":
+ output = DeploymentLicense(mml, omcMmlVar, outputJson)
+ return output, nil
+ case "AdjustmentLicense":
+ output = AdjustmentLicense(mml, omcMmlVar, outputJson)
+ return output, nil
+ case "InstallLicense":
+ output = InstallLicense(mml, omcMmlVar, outputJson)
+ return output, nil
+ case "RunShellCommand":
+ output = RunShellCommand(mml, omcMmlVar, outputJson)
+ return output, nil
+ default:
+ }
+
+ requestURI = parseRequestUri(omcMmlVar.HttpUri, mmlMap, mml)
+ log.Debugf("method: Put requestURI: %s", requestURI)
+ body := ParseInputBody(inputJson, mml)
+ response, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{tokenConst.HEADER_KEY: omcMmlVar.Authorization}).
+ SetHeaders(map[string]string{"accessToken": omcMmlVar.SessionToken}).
+ SetHeaders(map[string]string{"User-Agent": omcMmlVar.UserAgent}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ SetBody(*body).
+ Put(requestURI)
+ if err != nil {
+ log.Error("Failed to Put:", err)
+ output = ParseErrorOutput(err)
+ } else {
+ output = ParseOutputResponse(omcMmlVar, outputJson, response)
+ }
+ case "delete":
+ requestURI = parseRequestUri(omcMmlVar.HttpUri, mmlMap, mml)
+ log.Debugf("method: Delete requestURI: %s", requestURI)
+ response, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{tokenConst.HEADER_KEY: omcMmlVar.Authorization}).
+ SetHeaders(map[string]string{"accessToken": omcMmlVar.SessionToken}).
+ SetHeaders(map[string]string{"User-Agent": omcMmlVar.UserAgent}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ Delete(requestURI)
+ if err != nil {
+ log.Error("Failed to Delete:", err)
+ output = ParseErrorOutput(err)
+ } else {
+ output = ParseOutputResponse(omcMmlVar, outputJson, response)
+ }
+ case "patch":
+ requestURI = parseRequestUri(omcMmlVar.HttpUri, mmlMap, mml)
+ log.Debugf("method: patch requestURI: %s", requestURI)
+ response, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{tokenConst.HEADER_KEY: omcMmlVar.Authorization}).
+ SetHeaders(map[string]string{"accessToken": omcMmlVar.SessionToken}).
+ SetHeaders(map[string]string{"User-Agent": omcMmlVar.UserAgent}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ Patch(requestURI)
+ if err != nil {
+ log.Error("Failed to Patch:", err)
+ output = ParseErrorOutput(err)
+ } else {
+ output = ParseOutputResponse(omcMmlVar, outputJson, response)
+ }
+ default:
+ err := errors.New("not found mml command")
+ log.Error(err)
+ output = ParseErrorOutput(err)
+ }
+ return output, nil
+}
+
+const (
+ MaxMmlOutputBufferSize = 1000000
+ FormatTypeJson = "json"
+ FormatTypeTable = "table"
+ DefaultFormatType = FormatTypeTable
+)
+
+const (
+ RetCodeSucceeded = 0
+ RetCodeFailed = 0
+)
+
+func ParseInputBody(inputJson *dborm.MmlInput, mml *MmlCommand) *[]byte {
+ inputBody := make(map[string]interface{})
+ log.Trace("mml.NaMap:", mml.NaMap)
+ log.Trace("mml.AaMap:", mml.AaMap)
+ if strings.ToLower(inputJson.BodyFmt) == "putdb" {
+ for _, icol := range inputJson.Cols {
+ log.Trace("icol:", icol)
+ mml.NaMap[icol.Name] = icol.Value
+ }
+ inputBody[inputJson.BodyKey] = mml.NaMap
+ } else {
+ inputParams := make([]map[string]interface{}, 0)
+ inputParams = append(inputParams, mml.NaMap)
+ inputBody[inputJson.BodyKey] = inputParams
+ }
+
+ body, err := json.Marshal(inputBody)
+ if err != nil {
+ log.Error("Failed to marshal:", err)
+ }
+
+ log.Trace("inputBody:", inputBody)
+ log.Trace("body:", string(body))
+ return &body
+}
+
+func ParseOutputResponse(omcMmlVar *MmlVar, outputJson *dborm.MmlOutput, response *resty.Response) *[]byte {
+ var output []byte
+ var str bytes.Buffer
+
+ switch response.StatusCode() {
+ case http.StatusOK, http.StatusCreated, http.StatusNoContent, http.StatusAccepted:
+ if omcMmlVar.Output == FormatTypeJson {
+ code := fmt.Sprintf("StatusCode = %d status %s\n\n", response.StatusCode(), response.Status())
+ title := formatTitle(outputJson.Title)
+
+ json.Indent(&str, response.Body(), "", " ")
+ log.Trace(str.String())
+
+ output = global.BytesCombine1([]byte(code), []byte(title), str.Bytes(), []byte("\n"))
+ } else {
+ log.Trace("Body:", string(response.Body()))
+ mapDatas := make(map[string]interface{}, 0)
+
+ err := json.Unmarshal(response.Body(), &mapDatas)
+ if err != nil {
+ log.Error("Failed to json.Unmarshal:", err)
+ //output = *ParseErrorOutput(err)
+ output = *ParseErrorOutput(string(response.Body()))
+ return &output
+ }
+ log.Trace("mapDatas:", mapDatas)
+ switch strings.ToLower(outputJson.RetFmt) {
+ case "getdb":
+ if len(mapDatas) > 0 {
+ var data interface{}
+ for _, data = range mapDatas {
+ log.Trace("data:", data)
+ break
+ }
+ if len(data.([]interface{})) > 0 {
+ table := (data.([]interface{}))[0]
+ log.Trace("table:", table)
+
+ code := fmt.Sprintf(outputJson.RetMsg, RetCodeSucceeded)
+ title := formatTitle(outputJson.Title)
+ fmtResults := ParseTableOutput(outputJson, table)
+ output = global.BytesCombine1([]byte(code), []byte(title), []byte(fmtResults))
+ }
+ }
+ case "deletedb":
+ var data interface{}
+ for _, data = range mapDatas {
+ log.Trace("data:", data)
+ break
+ }
+
+ if len(data.(map[string]interface{})) > 0 {
+ table := data.(map[string]interface{})
+ code := fmt.Sprintf(outputJson.RetMsg, RetCodeSucceeded)
+ fmtResults := ParseDBOperOutput(outputJson, table)
+ output = global.BytesCombine1([]byte(code), []byte(fmtResults))
+ }
+ case "postdb":
+ var data interface{}
+ for _, data = range mapDatas {
+ log.Trace("data:", data)
+ break
+ }
+
+ if len(data.(map[string]interface{})) > 0 {
+ table := data.(map[string]interface{})
+ code := fmt.Sprintf(outputJson.RetMsg, RetCodeSucceeded)
+ fmtResults := ParseDBOperOutput(outputJson, table)
+ output = global.BytesCombine1([]byte(code), []byte(fmtResults))
+ }
+ case "putdb":
+ var data interface{}
+ for _, data = range mapDatas {
+ log.Trace("data:", data)
+ break
+ }
+ if len(data.(map[string]interface{})) > 0 {
+ table := data.(map[string]interface{})
+ code := fmt.Sprintf(outputJson.RetMsg, RetCodeSucceeded)
+ fmtResults := ParseDBOperOutput(outputJson, table)
+ output = global.BytesCombine1([]byte(code), []byte(fmtResults))
+ }
+ case "getnf":
+ if len(mapDatas) > 0 {
+ var data interface{}
+ for _, data = range mapDatas {
+ log.Trace("data:", data)
+ break
+ }
+ if len(data.([]interface{})) > 0 {
+ //table := (data.([]interface{}))[0]
+ //log.Trace("table:", table)
+
+ code := fmt.Sprintf(outputJson.RetMsg, RetCodeSucceeded)
+ title := formatTitle(outputJson.Title)
+ fmtResults := ParseNFTableOutput(outputJson, data)
+ output = global.BytesCombine1([]byte(code), []byte(title), []byte(fmtResults))
+ }
+ }
+ default:
+ code := fmt.Sprintf(outputJson.RetMsg, RetCodeSucceeded)
+ output = global.BytesCombine1([]byte(code))
+ }
+
+ }
+ default:
+ if omcMmlVar.Output == FormatTypeJson {
+ code := fmt.Sprintf("StatusCode = %d status %s\n\n", response.StatusCode(), response.Status())
+ //title := formatTitle("Network Element Information")
+
+ json.Indent(&str, response.Body(), "", " ")
+ log.Trace(str.String())
+
+ output = global.BytesCombine1([]byte(code), str.Bytes(), []byte("\n"))
+ } else {
+ log.Trace("Body:", string(response.Body()))
+ mapResults := make(map[string]interface{}, 0)
+
+ err := json.Unmarshal(response.Body(), &mapResults)
+ if err != nil {
+ log.Error("Failed to json.Unmarshal:", err)
+ output = *ParseErrorOutput(string(response.Body()))
+ } else {
+ log.Trace("mapResults:", mapResults)
+ if v, ok := mapResults["error"]; ok {
+ vMap := v.(map[string]interface{})
+ if len(vMap) > 0 {
+ errCode, _ := strconv.Atoi(fmt.Sprintf("%v", vMap["errorCode"]))
+ errorInfo := vMap["errorInfo"]
+ output = []byte(fmt.Sprintf(outputJson.ErrMsg, errCode, errorInfo))
+ }
+ } else if v, ok := mapResults["code"]; ok {
+ errCode, _ := strconv.Atoi(fmt.Sprintf("%v", v))
+ errorInfo := mapResults["msg"]
+ output = []byte(fmt.Sprintf(outputJson.ErrMsg, errCode, errorInfo))
+ } else {
+ output = []byte(fmt.Sprintf("%v", mapResults))
+ }
+ }
+ }
+ }
+ return &output
+}
+
+func ParseDBOperOutput(outputJson *dborm.MmlOutput, cols any) string {
+ var colOutput []dborm.ColOutput = outputJson.Cols
+ var value, retFmtCols string
+ if len(cols.(map[string]interface{})) > 0 {
+ if len(colOutput) > 0 {
+ coln := colOutput[0].Name
+ value = fmt.Sprintf("%v", cols.(map[string]interface{})[coln])
+ log.Tracef("coln:%s value:%s", coln, value)
+ retFmtCols = colOutput[0].Display + " = " + value + "\n\n"
+ }
+ }
+
+ return retFmtCols
+}
+
+func ParseNFTableOutput(outputJson *dborm.MmlOutput, cols any) string {
+ var colOutput []dborm.ColOutput
+ var fmtColName string
+ var colName []string
+ var spaceNum int = 1
+ var alignmentM, alignmentSN, alignmentSV string = "Left", "Right", "Left"
+
+ if outputJson.SepSpaceNum != 0 {
+ spaceNum = outputJson.SepSpaceNum
+ }
+ if outputJson.AlignmentM != "" {
+ alignmentM = outputJson.AlignmentM
+ }
+ if outputJson.AlignmentSN != "" {
+ alignmentSN = outputJson.AlignmentSN
+ }
+ if outputJson.AlignmentSV != "" {
+ alignmentSV = outputJson.AlignmentSV
+ }
+
+ maxLength := math.MinInt64
+ for _, coln := range outputJson.Cols {
+ log.Trace("coln:", coln)
+
+ if len(coln.Display) > maxLength {
+ maxLength = len(coln.Display)
+ }
+ if coln.Length < len(coln.Display) {
+ coln.Length = len(coln.Display)
+ }
+
+ colName = append(colName, ParseAlignmentOutput(coln.Length, alignmentM, coln.Display))
+ colOutput = append(colOutput, coln)
+ }
+ fmtColName = formatLineBySpace(&colName, spaceNum)
+ log.Trace("fmtColName:", fmtColName)
+
+ var retFmtCols string
+ var fmtColValues []string
+ var numberResult int
+ // for _, colnvs := range cols.([]interface{}) {
+ // log.Trace("colnvs:", colnvs)
+ // if colnvs == nil {
+ // break
+ // }
+ numberResult = len(cols.([]interface{}))
+
+ if numberResult == 1 && outputJson.SingleList == true {
+ colnv := cols.([]interface{})[0]
+ log.Trace("colnv:", colnv)
+
+ var fmtNV []string
+ for _, coln := range colOutput {
+ fmtName := ParseAlignmentOutput(maxLength, alignmentSN, coln.Display)
+ log.Tracef("alignmentSN:%s fmtName:%s", alignmentSN, fmtName)
+ value := fmt.Sprintf("%v", colnv.(map[string]interface{})[coln.Name])
+ fmtValue := ParseAlignmentOutput(coln.Length, alignmentSV, value)
+ fmtNV = append(fmtNV, fmtName+": "+fmtValue)
+ }
+
+ fmtResults := strings.Join(fmtNV, "\n")
+ log.Tracef("fmtResults:\n%s", fmtResults)
+ fmtEnd := fmt.Sprintf(outputJson.End, numberResult)
+ retFmtCols = fmtResults + "\n\n" + fmtEnd
+ log.Tracef("retFmtCols:\n%s", retFmtCols)
+ return retFmtCols
+ } else {
+ for i := 0; i < numberResult; i++ {
+ colnv := cols.([]interface{})[i]
+ log.Trace("colnv:", colnv)
+ var colValues []string
+ var newVal []string
+ for _, coln := range colOutput {
+ value := fmt.Sprintf("%v", colnv.(map[string]interface{})[coln.Name])
+ if len(coln.Alias) != 0 {
+ enumVal, _ := strconv.Atoi(value)
+ value = parseEnumAlias(&(coln.Alias), enumVal)
+ }
+ newVal = append(newVal, ParseAlignmentOutput(coln.Length, alignmentM, value))
+ }
+ colValues = append(colValues, formatLineBySpace(&newVal, spaceNum))
+ log.Trace("colValues:", colValues)
+ fmtColValues = append(fmtColValues, strings.Join(colValues, "\n"))
+ log.Trace("fmtColValues:", fmtColValues)
+ }
+ fmtEnd := fmt.Sprintf(outputJson.End, numberResult)
+ retFmtCols = fmtColName + "\n\n" + strings.Join(fmtColValues, "\n") + "\n\n" + fmtEnd
+ log.Tracef("retFmtCols:\n%s", retFmtCols)
+ return retFmtCols
+ }
+}
+
+func ParseTableOutput(outputJson *dborm.MmlOutput, cols any) string {
+ var colOutput []dborm.ColOutput
+ var fmtColName string
+ var colName []string
+ var spaceNum int = 1
+ var alignmentM, alignmentSN, alignmentSV string = "Left", "Right", "Left"
+
+ if outputJson.SepSpaceNum != 0 {
+ spaceNum = outputJson.SepSpaceNum
+ }
+ if outputJson.AlignmentM != "" {
+ alignmentM = outputJson.AlignmentM
+ }
+ if outputJson.AlignmentSN != "" {
+ alignmentSN = outputJson.AlignmentSN
+ }
+ if outputJson.AlignmentSV != "" {
+ alignmentSV = outputJson.AlignmentSV
+ }
+
+ maxLength := math.MinInt64
+ for _, coln := range outputJson.Cols {
+ log.Trace("coln:", coln)
+
+ if len(coln.Display) > maxLength {
+ maxLength = len(coln.Display)
+ }
+ if coln.Length < len(coln.Display) {
+ coln.Length = len(coln.Display)
+ }
+
+ colName = append(colName, ParseAlignmentOutput(coln.Length, alignmentM, coln.Display))
+ colOutput = append(colOutput, coln)
+ }
+ fmtColName = formatLineBySpace(&colName, spaceNum)
+ log.Trace("fmtColName:", fmtColName)
+
+ var retFmtCols string
+ var fmtColValues []string
+ var numberResult int
+ for _, colnvs := range cols.(map[string]interface{}) {
+ log.Trace("colnvs:", colnvs)
+ if colnvs == nil {
+ break
+ }
+ numberResult = len(colnvs.([]interface{}))
+
+ if numberResult == 1 && outputJson.SingleList == true {
+ colnv := colnvs.([]interface{})[0]
+ log.Trace("colnv:", colnv)
+
+ var fmtNV []string
+ for _, coln := range colOutput {
+ fmtName := ParseAlignmentOutput(maxLength, alignmentSN, coln.Display)
+ log.Tracef("alignmentSN:%s fmtName:%s", alignmentSN, fmtName)
+ value := fmt.Sprintf("%v", colnv.(map[string]interface{})[coln.Name])
+ fmtValue := ParseAlignmentOutput(coln.Length, alignmentSV, value)
+ fmtNV = append(fmtNV, fmtName+": "+fmtValue)
+ }
+
+ fmtResults := strings.Join(fmtNV, "\n")
+ log.Tracef("fmtResults:\n%s", fmtResults)
+ fmtEnd := fmt.Sprintf(outputJson.End, numberResult)
+ retFmtCols = fmtResults + "\n\n" + fmtEnd
+ log.Tracef("retFmtCols:\n%s", retFmtCols)
+ return retFmtCols
+ } else {
+ for i := 0; i < numberResult; i++ {
+ colnv := colnvs.([]interface{})[i]
+ log.Trace("colnv:", colnv)
+ var colValues []string
+ var newVal []string
+ for _, coln := range colOutput {
+ value := fmt.Sprintf("%v", colnv.(map[string]interface{})[coln.Name])
+ if len(coln.Alias) != 0 {
+ enumVal, _ := strconv.Atoi(value)
+ value = parseEnumAlias(&(coln.Alias), enumVal)
+ }
+ newVal = append(newVal, ParseAlignmentOutput(coln.Length, alignmentM, value))
+ }
+ colValues = append(colValues, formatLineBySpace(&newVal, spaceNum))
+ log.Trace("colValues:", colValues)
+ fmtColValues = append(fmtColValues, strings.Join(colValues, "\n"))
+ log.Trace("fmtColValues:", fmtColValues)
+ }
+ fmtEnd := fmt.Sprintf(outputJson.End, numberResult)
+ retFmtCols = fmtColName + "\n\n" + strings.Join(fmtColValues, "\n") + "\n\n" + fmtEnd
+ log.Tracef("retFmtCols:\n%s", retFmtCols)
+ return retFmtCols
+ }
+ }
+ fmtEnd := fmt.Sprintf(outputJson.End, numberResult)
+ retFmtCols = fmtColName + "\n" + strings.Join(fmtColValues, "\n") + "\n\n" + fmtEnd
+ log.Tracef("retFmtCols:\n%s", retFmtCols)
+ return retFmtCols
+}
+
+func parseEnumAlias(alias *[]string, enumVal int) string {
+ return (*alias)[enumVal]
+}
+
+func formatLineBySpace(strArray *[]string, spaceNum int) string {
+ space := strings.Repeat(" ", spaceNum)
+ return strings.Join(*strArray, space)
+}
+
+func ParseAlignmentOutput(length int, alignment string, str string) string {
+ spaceLen := length - len(str)
+ if spaceLen < 0 {
+ log.Warnf("len(str=%s)=%d more length=%d", str, len(str), length)
+ spaceLen = 0
+ }
+ var retStr string
+ switch alignment {
+ case "Left":
+ suffix := strings.Repeat(" ", spaceLen)
+ retStr = str + suffix
+ case "Right":
+ prefix := strings.Repeat(" ", spaceLen)
+ retStr = prefix + str
+ log.Tracef("retStr:%s", retStr)
+ case "Middle":
+ prefix := strings.Repeat(" ", int(math.Ceil(float64(spaceLen)/2)))
+ suffix := strings.Repeat(" ", int(math.Floor(float64(spaceLen)/2)))
+ retStr = prefix + str + suffix
+ }
+ log.Tracef("length=%d, spaceLne=%d, alignment=%s, str=%s, retStr=%s", length, spaceLen, alignment, str, retStr)
+ return retStr
+}
+
+func ParseErrorOutput(err any) *[]byte {
+ var output []byte
+
+ var formatType string = DefaultFormatType
+ if formatType == FormatTypeJson {
+ output = []byte(fmt.Sprintf("ErrorCode = 1 Error message: %v\n\n", err))
+ } else {
+ output = []byte(fmt.Sprintf("RetCode = -1 operation failed: %v\n\n", err))
+ }
+
+ return &output
+}
+
+func formatTitle(title string) string {
+ var builder strings.Builder
+ builder.WriteString(title)
+ builder.WriteString("\n")
+ for i := 0; i < len(title); i++ {
+ builder.WriteString("-")
+ }
+ builder.WriteString("\n")
+ return builder.String()
+}
+
+func formatTableOutput() {
+
+}
diff --git a/lib/oauth/oauth.go b/lib/oauth/oauth.go
new file mode 100644
index 0000000..376abf4
--- /dev/null
+++ b/lib/oauth/oauth.go
@@ -0,0 +1,184 @@
+package oauth
+
+import (
+ "crypto/sha256"
+ "crypto/sha512"
+ "encoding/hex"
+ "fmt"
+ "math/rand"
+ "net/http"
+ "strings"
+ "time"
+
+ "nms_nbi/lib/log"
+
+ "github.com/dgrijalva/jwt-go"
+ "golang.org/x/crypto/bcrypt"
+)
+
+// GenToken 生成Token值
+func GenToken(mapClaims jwt.MapClaims) (string, error) {
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, mapClaims)
+ var nowDate = time.Now()
+ var secret = fmt.Sprintf("%v%v", nowDate, "xxxx")
+ return token.SignedString([]byte(secret))
+}
+
+// GenerateToken 生成Token值
+func GenerateToken(mapClaims jwt.MapClaims, key string) (string, error) {
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, mapClaims)
+ return token.SignedString([]byte(key))
+}
+
+// ParseToken: "解析token"
+func ParseToken(token string, secret string) (string, error) {
+ claim, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
+ return []byte(secret), nil
+ })
+ if err != nil {
+ return "", err
+ }
+ return claim.Claims.(jwt.MapClaims)["cmd"].(string), nil
+}
+
+func RandAccessToken(n int) (ret string) {
+ allString := "52661fbd-6b84-4fc2-aa1e-17879a5c6c9b"
+ ret = ""
+ for i := 0; i < n; i++ {
+ r := rand.Intn(len(allString))
+ ret = ret + allString[r:r+1]
+ }
+ return ret
+}
+
+const letterBytes = "abcdef0123456789"
+const (
+ letterIdxBits = 6 // 6 bits to represent a letter index
+ letterIdxMask = 1<= 0; {
+ if remain == 0 {
+ cache, remain = src.Int63(), letterIdxMax
+ }
+ if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
+ b[i] = letterBytes[idx]
+ i--
+ }
+ cache >>= letterIdxBits
+ remain--
+ }
+
+ return string(b)
+}
+
+func GenRandToken(prefix string) string {
+ if prefix == "" {
+ return RandStringBytes(8) + "-" + RandStringBytes(4) + "-" +
+ RandStringBytes(4) + "-" + RandStringBytes(4) + "-" + RandStringBytes(12)
+ } else {
+ return prefix + "-" + RandStringBytes(8) + "-" + RandStringBytes(4) + "-" +
+ RandStringBytes(4) + "-" + RandStringBytes(4) + "-" + RandStringBytes(12)
+ }
+}
+
+type OAuthBody struct {
+ GrantType string
+ UserName string
+ Value string
+}
+
+/*
+func IsValidOAuthInfo(oAuthBody OAuthBody) bool {
+ log.Debug("IsValidOAuthInfo processing... ")
+
+ conf := config.GetYamlConfig()
+ for _, o := range conf.Auth {
+ if oAuthBody.GrantType == o.Type && oAuthBody.UserName == o.User && oAuthBody.Value == o.Password {
+ return true
+ }
+ }
+
+ return false
+}
+*/
+
+func IsWrongOAuthInfo(oAuthBody OAuthBody) bool {
+ log.Debug("IsWrongOAuthInfo processing... ")
+
+ if oAuthBody.GrantType == "" || strings.ToLower(oAuthBody.GrantType) != "password" ||
+ oAuthBody.UserName == "" || oAuthBody.Value == "" {
+ return true
+ }
+
+ return false
+}
+
+func GetTokenFromHttpRequest(r *http.Request) string {
+ for k, v := range r.Header {
+ log.Tracef("k:%s, v:%s", k, v)
+ if strings.ToLower(k) == "accesstoken" && len(v) != 0 {
+ log.Trace("AccessToken:", v[0])
+ return v[0]
+ }
+ }
+
+ return ""
+}
+
+// IsCarriedToken check token is carried
+func IsCarriedToken(r *http.Request) (string, bool) {
+
+ token := GetTokenFromHttpRequest(r)
+ if token == "" {
+ return "", false
+ }
+ return token, true
+}
+
+// Bcrypt Encrypt 加密明文密码
+func BcryptEncrypt(password string) (string, error) {
+ hashedBytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
+ return string(hashedBytes), err
+}
+
+// Bcrypt Compare 密文校验
+func BcryptCompare(hashedPassword, password string) error {
+ return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
+}
+
+// sha256 crypt
+func GetSHA256HashCode(stringMessage string) string {
+ message := []byte(stringMessage) //字符串转化字节数组
+ //创建一个基于SHA256算法的hash.Hash接口的对象
+ hash := sha256.New() //sha-256加密
+ //输入数据
+ hash.Write(message)
+ //计算哈希值
+ bytes := hash.Sum(nil)
+ //将字符串编码为16进制格式,返回字符串
+ hashCode := hex.EncodeToString(bytes)
+ //返回哈希值
+ return hashCode
+}
+
+// sha512 crypt
+func GetSHA512HashCode(stringMessage string) string {
+ message := []byte(stringMessage) //字符串转化字节数组
+ //创建一个基于SHA256算法的hash.Hash接口的对象
+ hash := sha512.New() //SHA-512加密
+ //输入数据
+ hash.Write(message)
+ //计算哈希值
+ bytes := hash.Sum(nil)
+ //将字符串编码为16进制格式,返回字符串
+ hashCode := hex.EncodeToString(bytes)
+ //返回哈希值
+ return hashCode
+}
diff --git a/lib/pair/pair.go b/lib/pair/pair.go
new file mode 100644
index 0000000..f779424
--- /dev/null
+++ b/lib/pair/pair.go
@@ -0,0 +1,37 @@
+package pair
+
+type Pair struct {
+ Key int
+ Value int
+}
+
+type PairList []Pair
+
+type Interface interface {
+ // Len is the number of elements in the collection.
+ Len() int
+
+ // Less reports whether the element with index i
+ // must sort before the element with index j.
+ //
+ // If both Less(i, j) and Less(j, i) are false,
+ // then the elements at index i and j are considered equal.
+ // Sort may place equal elements in any order in the final result,
+ // while Stable preserves the original input order of equal elements.
+ //
+ // Less must describe a transitive ordering:
+ // - if both Less(i, j) and Less(j, k) are true, then Less(i, k) must be true as well.
+ // - if both Less(i, j) and Less(j, k) are false, then Less(i, k) must be false as well.
+ //
+ // Note that floating-point comparison (the < operator on float32 or float64 values)
+ // is not a transitive ordering when not-a-number (NaN) values are involved.
+ // See Float64Slice.Less for a correct implementation for floating-point values.
+ Less(i, j int) bool
+
+ // Swap swaps the elements with indexes i and j.
+ Swap(i, j int)
+}
+
+func (p PairList) Len() int { return len(p) }
+func (p PairList) Less(i, j int) bool { return p[i].Value < p[j].Value }
+func (p PairList) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
diff --git a/lib/routes/routes.go b/lib/routes/routes.go
new file mode 100644
index 0000000..8750dd3
--- /dev/null
+++ b/lib/routes/routes.go
@@ -0,0 +1,423 @@
+package routes
+
+import (
+ "net/http"
+
+ // "log"
+
+ "nms_nbi/features/aaaa"
+ "nms_nbi/features/cdr"
+ "nms_nbi/features/cm"
+ "nms_nbi/features/dbrest"
+ "nms_nbi/features/file"
+ "nms_nbi/features/fm"
+ "nms_nbi/features/lm"
+ "nms_nbi/features/mml"
+ "nms_nbi/features/monitor/monitor"
+ "nms_nbi/features/monitor/psnet"
+ "nms_nbi/features/nbi"
+ "nms_nbi/features/pm"
+ "nms_nbi/features/security"
+ "nms_nbi/features/sm"
+ "nms_nbi/features/state"
+ sysconfig "nms_nbi/features/sys_config"
+ sysdictdata "nms_nbi/features/sys_dict_data"
+ sysdicttype "nms_nbi/features/sys_dict_type"
+ sysmenu "nms_nbi/features/sys_menu"
+ sysrole "nms_nbi/features/sys_role"
+ sysuser "nms_nbi/features/sys_user"
+ "nms_nbi/features/trace"
+ udmuser "nms_nbi/features/udm_user"
+ "nms_nbi/features/ue"
+ "nms_nbi/lib/midware"
+ "nms_nbi/lib/services"
+ "nms_nbi/src/framework/middleware/collectlogs"
+
+ "github.com/gorilla/mux"
+)
+
+type Router struct {
+ Method string
+ Pattern string
+ Handler http.HandlerFunc
+ Middleware mux.MiddlewareFunc
+}
+
+var routers []Router
+
+func init() {
+ Register("POST", security.UriOauthToken, security.LoginFromOMC, nil)
+ Register("POST", security.UriOauthHandshake, security.HandshakeFromOMC, nil)
+ Register("DELETE", security.UriOauthToken, security.LogoutFromOMC, nil)
+
+ Register("POST", security.CustomUriOauthToken, security.LoginFromOMC, nil)
+ Register("DELETE", security.CustomUriOauthToken, security.LogoutFromOMC, nil)
+ Register("POST", security.CustomUriOauthHandshake, security.HandshakeFromOMC, nil)
+
+ // System state
+ Register("GET", state.UriSysState, state.GetStateFromNF, nil)
+ Register("GET", state.UriSysState2, state.GetStateFromNF, nil)
+ Register("GET", state.UriSysInfoAll, state.GetAllSysinfoFromNF, nil)
+ Register("GET", state.UriSysInfoOne, state.GetOneSysinfoFromNF, nil)
+ Register("GET", state.UriLicenseInfoAll, state.GetAllLicenseInfoFromNF, nil)
+ Register("GET", state.UriLicenseInfoOne, state.GetOneLicenseInfoFromNF, nil)
+
+ Register("GET", state.CustomUriSysState, state.GetStateFromNF, nil)
+ Register("GET", state.CustomUriSysState2, state.GetStateFromNF, nil)
+ Register("GET", state.CustomUriSysInfoAll, state.GetAllSysinfoFromNF, nil)
+ Register("GET", state.CustomUriSysInfoOne, state.GetOneSysinfoFromNF, nil)
+ Register("GET", state.CustomUriLicenseInfoAll, state.GetAllLicenseInfoFromNF, nil)
+ Register("GET", state.CustomUriLicenseInfoOne, state.GetOneLicenseInfoFromNF, nil)
+
+ Register("GET", sm.UriOMCLocalTime, sm.GetOMCLocalTime, nil)
+ Register("GET", sm.CustomUriOMCLocalTime, sm.GetOMCLocalTime, nil)
+
+ // database management
+ Register("GET", dbrest.XormGetDataUri, dbrest.DatabaseGetData, nil)
+ Register("GET", dbrest.XormSelectDataUri, dbrest.DatabaseGetData, nil)
+ Register("POST", dbrest.XormInsertDataUri, dbrest.DatabaseInsertData, nil)
+ Register("PUT", dbrest.XormUpdateDataUri, dbrest.DatabaseUpdateData, nil)
+ Register("DELETE", dbrest.XormDeleteDataUri, dbrest.DatabaseDeleteData, nil)
+
+ Register("GET", dbrest.CustomXormGetDataUri, dbrest.DatabaseGetData, nil)
+ Register("GET", dbrest.CustomXormSelectDataUri, dbrest.DatabaseGetData, nil)
+ Register("POST", dbrest.CustomXormInsertDataUri, dbrest.DatabaseInsertData, nil)
+ Register("PUT", dbrest.CustomXormUpdateDataUri, dbrest.DatabaseUpdateData, nil)
+ Register("DELETE", dbrest.CustomXormDeleteDataUri, dbrest.DatabaseDeleteData, nil)
+
+ Register("GET", dbrest.XormCommonUri, dbrest.DatabaseGetData, nil)
+ Register("POST", dbrest.XormCommonUri, dbrest.DatabaseInsertData, nil)
+ Register("PUT", dbrest.XormCommonUri, dbrest.DatabaseUpdateData, nil)
+ Register("DELETE", dbrest.XormCommonUri, dbrest.DatabaseDeleteData, nil)
+
+ Register("GET", dbrest.XormDatabaseUri, dbrest.TaskDatabaseGetData, nil)
+ Register("POST", dbrest.XormDatabaseUri, dbrest.TaskDatabaseInsertData, nil)
+ Register("PUT", dbrest.XormDatabaseUri, dbrest.TaskDatabaseUpdateData, nil)
+ Register("DELETE", dbrest.XormDatabaseUri, dbrest.TaskDatabaseDeleteData, nil)
+
+ Register("GET", dbrest.CustomXormCommonUri, dbrest.DatabaseGetData, nil)
+ Register("POST", dbrest.CustomXormCommonUri, dbrest.DatabaseInsertData, nil)
+ Register("PUT", dbrest.CustomXormCommonUri, dbrest.DatabaseUpdateData, nil)
+ Register("DELETE", dbrest.CustomXormCommonUri, dbrest.DatabaseDeleteData, nil)
+
+ Register("GET", dbrest.XormDataRestUri, dbrest.ExtDatabaseGetData, nil)
+ Register("POST", dbrest.XormDataRestUri, dbrest.ExtDatabaseInsertData, nil)
+ Register("PUT", dbrest.XormDataRestUri, dbrest.ExtDatabaseUpdateData, nil)
+ Register("DELETE", dbrest.XormDataRestUri, dbrest.ExtDatabaseDeleteData, nil)
+
+ Register("GET", dbrest.XormExtDataUri, dbrest.ExtDatabaseGetData, nil)
+ Register("POST", dbrest.XormExtDataUri, dbrest.ExtDatabaseInsertData, nil)
+ Register("PUT", dbrest.XormExtDataUri, dbrest.ExtDatabaseUpdateData, nil)
+ Register("DELETE", dbrest.XormExtDataUri, dbrest.ExtDatabaseDeleteData, nil)
+
+ Register("GET", dbrest.CustomXormExtDataUri, dbrest.ExtDatabaseGetData, nil)
+ Register("POST", dbrest.CustomXormExtDataUri, dbrest.ExtDatabaseInsertData, nil)
+ Register("PUT", dbrest.CustomXormExtDataUri, dbrest.ExtDatabaseUpdateData, nil)
+ Register("DELETE", dbrest.CustomXormExtDataUri, dbrest.ExtDatabaseDeleteData, nil)
+
+ // alarm restful Register
+ Register("POST", fm.UriAlarms, fm.PostAlarmFromNF, nil)
+ Register("Get", fm.UriAlarms, fm.GetAlarmFromNF, nil)
+
+ Register("POST", fm.CustomUriAlarms, fm.PostAlarmFromNF, nil)
+ Register("Get", fm.CustomUriAlarms, fm.GetAlarmFromNF, nil)
+
+ // performance restful Register
+ Register("POST", pm.PerformanceUri, pm.PostKPIReportFromNF, nil)
+ Register("POST", pm.MeasureTaskUri, pm.PostMeasureTaskToNF, midware.LogOperate(collectlogs.OptionNew("MeasureTask", collectlogs.BUSINESS_TYPE_INSERT)))
+ Register("PUT", pm.MeasureTaskUri, pm.PutMeasureTaskToNF, midware.LogOperate(collectlogs.OptionNew("MeasureTask", collectlogs.BUSINESS_TYPE_UPDATE)))
+ Register("DELETE", pm.MeasureTaskUri, pm.DeleteMeasureTaskToNF, midware.LogOperate(collectlogs.OptionNew("MeasureTask", collectlogs.BUSINESS_TYPE_DELETE)))
+ Register("PATCH", pm.MeasureTaskUri, pm.PatchMeasureTaskToNF, midware.LogOperate(collectlogs.OptionNew("MeasureTask", collectlogs.BUSINESS_TYPE_UPDATE)))
+ Register("POST", pm.MeasureReportUri, pm.PostMeasureReportFromNF, midware.LogOperate(collectlogs.OptionNew("MeasureTask", collectlogs.BUSINESS_TYPE_UPDATE)))
+
+ Register("POST", pm.MeasurementUri, pm.PostMeasurementFromNF, nil)
+ Register("GET", pm.MeasurementUri, pm.GetMeasurementFromNF, nil)
+
+ Register("POST", pm.CustomPerformanceUri, pm.PostKPIReportFromNF, nil)
+ Register("POST", pm.CustomMeasureTaskUri, pm.PostMeasureTaskToNF, nil)
+ Register("PUT", pm.CustomMeasureTaskUri, pm.PutMeasureTaskToNF, nil)
+ Register("DELETE", pm.CustomMeasureTaskUri, pm.DeleteMeasureTaskToNF, nil)
+ Register("PATCH", pm.CustomMeasureTaskUri, pm.PatchMeasureTaskToNF, nil)
+ Register("POST", pm.CustomMeasureReportUri, pm.PostMeasureReportFromNF, nil)
+
+ Register("POST", pm.CustomMeasurementUri, pm.PostMeasurementFromNF, nil)
+ Register("GET", pm.CustomMeasurementUri, pm.GetMeasurementFromNF, nil)
+
+ // parameter config management
+ Register("GET", cm.ParamConfigUri, cm.GetParamConfigFromNF, nil)
+ Register("POST", cm.ParamConfigUri, cm.PostParamConfigToNF, midware.LogOperate(collectlogs.OptionNew("Parameter Config", collectlogs.BUSINESS_TYPE_INSERT)))
+ Register("PUT", cm.ParamConfigUri, cm.PutParamConfigToNF, midware.LogOperate(collectlogs.OptionNew("Parameter Config", collectlogs.BUSINESS_TYPE_UPDATE)))
+ Register("DELETE", cm.ParamConfigUri, cm.DeleteParamConfigToNF, midware.LogOperate(collectlogs.OptionNew("Parameter Config", collectlogs.BUSINESS_TYPE_INSERT)))
+
+ Register("GET", cm.CustomParamConfigUri, cm.GetParamConfigFromNF, nil)
+ Register("POST", cm.CustomParamConfigUri, cm.PostParamConfigToNF, nil)
+ Register("PUT", cm.CustomParamConfigUri, cm.PutParamConfigToNF, nil)
+ Register("DELETE", cm.CustomParamConfigUri, cm.DeleteParamConfigToNF, nil)
+
+ // Get/Create/Modify/Delete NE info
+ Register("GET", cm.UriNeInfo, cm.GetNeInfo, nil)
+ Register("POST", cm.UriNeInfo, cm.PostNeInfo, midware.LogOperate(collectlogs.OptionNew("NE Info", collectlogs.BUSINESS_TYPE_INSERT)))
+ Register("PUT", cm.UriNeInfo, cm.PutNeInfo, midware.LogOperate(collectlogs.OptionNew("NE Info", collectlogs.BUSINESS_TYPE_UPDATE)))
+ Register("DELETE", cm.UriNeInfo, cm.DeleteNeInfo, midware.LogOperate(collectlogs.OptionNew("NE Info", collectlogs.BUSINESS_TYPE_DELETE)))
+
+ // Get/Create/Modify/Delete NE info
+ Register("GET", cm.CustomUriNeInfo, cm.GetNeInfo, nil)
+ Register("POST", cm.CustomUriNeInfo, cm.PostNeInfo, nil)
+ Register("PUT", cm.CustomUriNeInfo, cm.PutNeInfo, nil)
+ Register("DELETE", cm.CustomUriNeInfo, cm.DeleteNeInfo, nil)
+
+ //ne service action handle
+ Register("POST", cm.UriNeService, cm.PostNeServiceAction, nil)
+ //ne service action handle
+ Register("POST", cm.UriNeInstance, cm.PostNeInstanceAction, nil)
+ // Post MML command to NF
+ Register("POST", mml.UriMML, mml.PostMMLToNF, midware.LogMML)
+ Register("POST", mml.UriMMLDiscard, mml.PostMMLToNF, nil)
+ Register("POST", mml.UriOmMmlExt, mml.PostMMLToOMC, midware.LogMML)
+ Register("POST", mml.CustomUriMML, mml.PostMMLToNF, midware.LogMML)
+ Register("POST", mml.CustomUriOmMmlExt, mml.PostMMLToOMC, midware.LogMML)
+ // post mml2 (standard upf port=5002)
+ Register("POST", mml.UriMML2, mml.PostMML2ToNF, midware.LogMML)
+ Register("POST", mml.CustomUriMML2, mml.PostMML2ToNF, midware.LogMML)
+
+ // Northbound Get NRM
+ Register("GET", nbi.GetNRMUri, nbi.NBIGetNRMFromNF, nil)
+
+ Register("GET", nbi.CustomGetNRMUri, nbi.NBIGetNRMFromNF, nil)
+
+ // Import/Export NF CM
+ Register("GET", cm.NeCmUri, cm.ExportCmFromNF, nil)
+ Register("POST", cm.NeCmUri, cm.ImportCmToNF, midware.LogOperate(collectlogs.OptionNew("Import NF", collectlogs.BUSINESS_TYPE_INSERT)))
+
+ Register("GET", cm.UriNeCmFile, cm.DownloadNeBackupFile, nil)
+ Register("DELETE", cm.UriNeCmFile, cm.DeleteNeBackupFile, midware.LogOperate(collectlogs.OptionNew("Delete NF Backup", collectlogs.BUSINESS_TYPE_DELETE)))
+
+ Register("GET", cm.CustomNeCmUri, cm.ExportCmFromNF, nil)
+ Register("POST", cm.CustomNeCmUri, cm.ImportCmToNF, nil)
+
+ Register("GET", cm.CustomUriNeCmFile, cm.DownloadNeBackupFile, nil)
+ Register("DELETE", cm.CustomUriNeCmFile, cm.DeleteNeBackupFile, nil)
+
+ // Software management
+ Register("GET", cm.UriSoftware, cm.DownloadSoftwareFile, nil)
+ //Register("POST", cm.UriSoftware, cm.UploadSoftwareFile, nil)
+ Register("POST", cm.UriSoftware, cm.UploadSoftwareMultiFile, midware.LogOperate(collectlogs.OptionNew("Software", collectlogs.BUSINESS_TYPE_UPDATE)))
+ Register("DELETE", cm.UriSoftware, cm.DeleteSoftwareFile, midware.LogOperate(collectlogs.OptionNew("Software", collectlogs.BUSINESS_TYPE_DELETE)))
+
+ Register("POST", cm.UriSoftwareNE, cm.DistributeSoftwareToNF, midware.LogOperate(collectlogs.OptionNew("Software", collectlogs.BUSINESS_TYPE_OTHER)))
+ Register("PUT", cm.UriSoftwareNE, cm.ActiveSoftwareToNF, midware.LogOperate(collectlogs.OptionNew("Software", collectlogs.BUSINESS_TYPE_OTHER)))
+ Register("PATCH", cm.UriSoftwareNE, cm.RollBackSoftwareToNF, midware.LogOperate(collectlogs.OptionNew("Software", collectlogs.BUSINESS_TYPE_OTHER)))
+
+ Register("GET", cm.CustomUriSoftware, cm.DownloadSoftwareFile, nil)
+ Register("POST", cm.CustomUriSoftware, cm.UploadSoftwareFile, nil)
+ Register("DELETE", cm.CustomUriSoftware, cm.DeleteSoftwareFile, nil)
+
+ Register("POST", cm.CustomUriSoftwareNE, cm.DistributeSoftwareToNF, nil)
+ Register("PUT", cm.CustomUriSoftwareNE, cm.ActiveSoftwareToNF, nil)
+ Register("PATCH", cm.CustomUriSoftwareNE, cm.RollBackSoftwareToNF, nil)
+
+ // License management
+ Register("POST", cm.UriLicense, cm.UploadLicenseFileData, midware.LogOperate(collectlogs.OptionNew("License", collectlogs.BUSINESS_TYPE_INSERT)))
+ Register("POST", cm.UriLicenseExt, cm.UploadLicenseFileData, midware.LogOperate(collectlogs.OptionNew("License", collectlogs.BUSINESS_TYPE_INSERT)))
+
+ Register("POST", cm.CustomUriLicense, cm.UploadLicenseFileData, nil)
+ Register("POST", cm.CustomUriLicenseExt, cm.UploadLicenseFileData, nil)
+
+ // Trace management 跟踪任务
+ Register("POST", trace.UriTraceTask, trace.PostTraceTaskToNF, midware.LogOperate(collectlogs.OptionNew("Trace Task", collectlogs.BUSINESS_TYPE_INSERT)))
+ Register("PUT", trace.UriTraceTask, trace.PutTraceTaskToNF, midware.LogOperate(collectlogs.OptionNew("Trace Task", collectlogs.BUSINESS_TYPE_UPDATE)))
+ Register("DELETE", trace.UriTraceTask, trace.DeleteTraceTaskToNF, midware.LogOperate(collectlogs.OptionNew("Trace Task", collectlogs.BUSINESS_TYPE_DELETE)))
+ Register("GET", trace.UriTraceDecMsg, trace.ParseRawMsg2Html, nil)
+
+ Register("POST", trace.CustomUriTraceTask, trace.PostTraceTaskToNF, nil)
+ Register("PUT", trace.CustomUriTraceTask, trace.PutTraceTaskToNF, nil)
+ Register("DELETE", trace.CustomUriTraceTask, trace.DeleteTraceTaskToNF, nil)
+
+ // 网元发送执行 pcap抓包任务
+ Register("POST", trace.UriTcpdumpTask, trace.TcpdumpNeTask, midware.LogOperate(collectlogs.OptionNew("Packet Capturing Task", collectlogs.BUSINESS_TYPE_INSERT)))
+ Register("POST", trace.CustomUriTcpdumpTask, trace.TcpdumpNeTask, midware.LogOperate(collectlogs.OptionNew("Packet Capturing Task", collectlogs.BUSINESS_TYPE_INSERT)))
+ // 网元发送执行 抓包下载pcap文件
+ Register("POST", trace.UriTcpdumpPcapDownload, trace.TcpdumpPcapDownload, midware.LogOperate(collectlogs.OptionNew("Packet Capturing Task", collectlogs.BUSINESS_TYPE_EXPORT)))
+ Register("POST", trace.CustomUriTcpdumpPcapDownload, trace.TcpdumpPcapDownload, midware.LogOperate(collectlogs.OptionNew("Packet Capturing Task", collectlogs.BUSINESS_TYPE_EXPORT)))
+ // 网元发送执行UPF pcap抓包
+ Register("POST", trace.UriTcpdumpNeUPFTask, trace.TcpdumpNeUPFTask, midware.LogOperate(collectlogs.OptionNew("Packet Capturing Task", collectlogs.BUSINESS_TYPE_INSERT)))
+ Register("POST", trace.CustomUriTcpdumpNeUPFTask, trace.TcpdumpNeUPFTask, midware.LogOperate(collectlogs.OptionNew("Packet Capturing Task", collectlogs.BUSINESS_TYPE_INSERT)))
+
+ // file management
+ Register("POST", file.UriFile, file.UploadFile, midware.LogOperate(collectlogs.OptionNew("File", collectlogs.BUSINESS_TYPE_INSERT)))
+ Register("GET", file.UriFile, file.DownloadFile, midware.LogOperate(collectlogs.OptionNew("File", collectlogs.BUSINESS_TYPE_OTHER)))
+ Register("DELETE", file.UriFile, file.DeleteFile, midware.LogOperate(collectlogs.OptionNew("File", collectlogs.BUSINESS_TYPE_DELETE)))
+
+ Register("POST", file.CustomUriFile, file.UploadFile, nil)
+ Register("GET", file.CustomUriFile, file.DownloadFile, nil)
+ Register("DELETE", file.CustomUriFile, file.DeleteFile, nil)
+
+ // AAAA
+ Register("GET", aaaa.UriAAAASSO, aaaa.GetSSOFromAAAA, nil)
+ // AAAA
+ Register("GET", aaaa.CustomUriAAAASSO, aaaa.GetSSOFromAAAA, nil)
+
+ // UEInfo: SMF
+ Register("GET", ue.UriUEInfo, ue.GetUEInfoFromNF, nil)
+ Register("GET", ue.CustomUriUEInfo, ue.GetUEInfoFromNF, nil)
+
+ // UEInfo: PCF
+ Register("GET", ue.UriPCFUser, ue.GetUEInfoFromNF, nil)
+ Register("GET", ue.CustomUriPCFUser, ue.GetUEInfoFromNF, nil)
+ Register("POST", ue.UriPCFUser, ue.PostPCFUserInfo, nil)
+ Register("POST", ue.CustomUriPCFUser, ue.PostPCFUserInfo, nil)
+ Register("PUT", ue.UriPCFUser, ue.PutPCFUserInfo, nil)
+ Register("PUT", ue.CustomUriPCFUser, ue.PutPCFUserInfo, nil)
+ Register("DELETE", ue.UriPCFUser, ue.DeletePCFUserInfo, nil)
+ Register("DELETE", ue.CustomUriPCFUser, ue.DeletePCFUserInfo, nil)
+
+ // PCFUEInfo: batch add/modify/delete
+ Register("POST", ue.UriPCFUserM, ue.PostPCFUserInfo, nil)
+ Register("POST", ue.CustomUriPCFUserM, ue.PostPCFUserInfo, nil)
+ Register("PUT", ue.UriPCFUserM, ue.PutPCFUserInfo, nil)
+ Register("PUT", ue.CustomUriPCFUserM, ue.PutPCFUserInfo, nil)
+ Register("DELETE", ue.UriPCFUserM, ue.DeletePCFUserInfo, nil)
+ Register("DELETE", ue.CustomUriPCFUserM, ue.DeletePCFUserInfo, nil)
+ //PCF User file
+ Register("GET", ue.UriPCFUserFileExport, ue.GetUEInfoFileExportNF, nil)
+ Register("GET", ue.CustomUriPCFUserFileExport, ue.GetUEInfoFromNF, nil)
+ Register("PUT", ue.UriPCFUserFileImport, ue.PutPCFUserInfo, nil)
+ Register("PUT", ue.CustomUriPCFUserFileImport, ue.PutPCFUserInfo, nil)
+
+ // UE Number
+ Register("GET", ue.UriUENum, ue.GetUENumFromNF, nil)
+ Register("GET", ue.CustomUriUENum, ue.GetUENumFromNF, nil)
+
+ // NBInfo
+ Register("GET", ue.UriNBInfo, ue.GetNBInfoFromNF, nil)
+ Register("GET", ue.CustomUriNBInfo, ue.GetNBInfoFromNF, nil)
+
+ // NSSF AvailableAMFs
+ Register("GET", ue.UriNSSFAvailableAMFs, ue.GetAvailableAMFsFromNSSF, nil)
+ Register("GET", ue.CustomUriNSSFAvailableAMFs, ue.GetAvailableAMFsFromNSSF, nil)
+
+ // NSSF Subscriptions
+ Register("GET", ue.UriNSSFSubscriptions, ue.GetSubscriptionsFromNSSF, nil)
+ Register("GET", ue.CustomUriNSSFSubscriptions, ue.GetSubscriptionsFromNSSF, nil)
+
+ Register("POST", cdr.UriCDREvent, cdr.PostCDREventFromNF, nil)
+ Register("POST", cdr.CustomUriCDREvent, cdr.PostCDREventFromNF, nil)
+
+ // UE event
+ //Register("POST", event.UriUEEvent, event.PostUEEventFromAMF, nil)
+
+ // 进程网络
+ Register("GET", psnet.UriWs, psnet.ProcessWs, nil)
+ Register("POST", psnet.UriStop, psnet.StopProcess, nil)
+ Register("POST", psnet.UriPing, psnet.Ping, nil)
+
+ // 主机CPU内存监控
+ Register("POST", monitor.UriLoad, monitor.LoadMonitor, nil)
+ Register("GET", monitor.UriNetOpt, monitor.Netoptions, nil)
+ Register("GET", monitor.UriIPAddr, monitor.IPAddr, nil)
+ Register("GET", monitor.UriIoOpt, monitor.Iooptions, nil)
+
+ // 文件资源
+ Register("GET", file.UriDiskList, file.DiskList, nil)
+ Register("POST", file.UriListFiles, file.ListFiles, nil)
+
+ // 数据库连接情况
+ Register("GET", dbrest.UriDbConnection, dbrest.DbConnection, nil)
+ Register("GET", dbrest.CustomUriDbConnection, dbrest.DbConnection, nil)
+ Register("POST", dbrest.UriDbStop, dbrest.DbStop, nil)
+ Register("POST", dbrest.CustomUriDbStop, dbrest.DbStop, nil)
+
+ // 系统备份
+ Register("POST", dbrest.UriDbBackup, dbrest.DbBackup, nil)
+ Register("POST", dbrest.CustomUriDbBackup, dbrest.DbBackup, nil)
+ Register("POST", dbrest.UriConfBackup, dbrest.ConfBackup, nil)
+ Register("POST", dbrest.CustomUriConfBackup, dbrest.ConfBackup, nil)
+
+ // 日志表备份
+ Register("POST", lm.ExtBackupDataUri, lm.ExtDatabaseBackupData, nil)
+ Register("POST", lm.CustomExtBackupDataUri, lm.ExtDatabaseBackupData, nil)
+
+ // 系统登录
+ Register("POST", security.UriLogin, security.LoginOMC, nil)
+ Register("POST", security.CustomUriLogin, security.LoginOMC, nil)
+
+ // 获取验证码
+ Register("GET", security.UriCaptchaImage, security.CaptchaImage, nil)
+ Register("GET", security.CustomUriCaptchaImage, security.CaptchaImage, nil)
+
+ // 登录用户信息
+ Register("GET", security.UriUserInfo, security.UserInfo, midware.Authorize(nil))
+ Register("GET", security.CustomUriUserInfo, security.UserInfo, midware.Authorize(nil))
+
+ // 登录用户路由信息
+ Register("GET", security.UriRouters, security.Routers, midware.Authorize(nil))
+ Register("GET", security.CustomUriRouters, security.Routers, midware.Authorize(nil))
+
+ // 参数配置信息接口添加到路由
+ for _, v := range sysconfig.Routers() {
+ Register(v.Method, v.Pattern, v.Handler, v.Middleware)
+ }
+
+ // 字典类型信息接口添加到路由
+ for _, v := range sysdicttype.Routers() {
+ Register(v.Method, v.Pattern, v.Handler, v.Middleware)
+ }
+
+ // 字典类型对应的字典数据信息接口添加到路由
+ for _, v := range sysdictdata.Routers() {
+ Register(v.Method, v.Pattern, v.Handler, v.Middleware)
+ }
+
+ // 菜单接口添加到路由
+ for _, v := range sysmenu.Routers() {
+ Register(v.Method, v.Pattern, v.Handler, v.Middleware)
+ }
+
+ // 角色接口添加到路由
+ for _, v := range sysrole.Routers() {
+ Register(v.Method, v.Pattern, v.Handler, v.Middleware)
+ }
+
+ // 用户接口添加到路由
+ for _, v := range sysuser.Routers() {
+ Register(v.Method, v.Pattern, v.Handler, v.Middleware)
+ }
+
+ // UDM 用户信息接口添加到路由
+ for _, v := range udmuser.Routers() {
+ Register(v.Method, v.Pattern, v.Handler, v.Middleware)
+ }
+}
+
+// To resolv rest POST/PUT/DELETE/PATCH cross domain
+func OptionsProc(w http.ResponseWriter, r *http.Request) {
+ services.ResponseStatusOK204NoContent(w)
+}
+
+func NewRouter() *mux.Router {
+ r := mux.NewRouter()
+
+ // set custom handle for status 404/405
+ // r.NotFoundHandler = services.CustomResponseNotFound404Handler()
+ // r.MethodNotAllowedHandler = services.CustomResponseMethodNotAllowed405Handler()
+
+ r.Use(midware.LoggerTrace)
+ // r.Use(midware.Cors)
+ // r.Use(midware.OptionProcess)
+ // r.Use(midware.ArrowIPAddr)
+
+ for _, router := range routers {
+ rt := r.Methods(router.Method).Subrouter()
+ rt.HandleFunc(router.Pattern, router.Handler)
+ if router.Middleware != nil {
+ rt.Use(router.Middleware)
+ }
+
+ }
+
+ return r
+}
+
+func Register(method, pattern string, handler http.HandlerFunc, middleware mux.MiddlewareFunc) {
+ routers = append(routers, Router{method, pattern, handler, middleware})
+}
diff --git a/lib/run/exec_linux.go b/lib/run/exec_linux.go
new file mode 100644
index 0000000..00b5d72
--- /dev/null
+++ b/lib/run/exec_linux.go
@@ -0,0 +1,56 @@
+//go:build linux
+// +build linux
+
+package run
+
+import (
+ "bytes"
+ "os/exec"
+
+ "nms_nbi/lib/log"
+)
+
+func ExecCmd(command, path string) ([]byte, error) {
+ log.Debug("Exec command:", command)
+
+ cmd := exec.Command("/bin/bash", "-c", command)
+ cmd.Dir = path
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ return out, err
+ }
+
+ return out, nil
+}
+
+func ExecShell(command string) error {
+ in := bytes.NewBuffer(nil)
+ cmd := exec.Command("sh")
+ cmd.Stdin = in
+ in.WriteString(command)
+ in.WriteString("exit\n")
+ if err := cmd.Start(); err != nil {
+ return err
+ }
+ return nil
+}
+
+func ExecOsCmd(command, os string) error {
+ log.Debugf("Exec %s command:%s", os, command)
+
+ var cmd *exec.Cmd
+ switch os {
+ case "Linux":
+ cmd = exec.Command(command)
+ case "Windows":
+ cmd = exec.Command("cmd", "/C", command)
+ }
+
+ out, err := cmd.CombinedOutput()
+ log.Tracef("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("exe cmd error: ", err)
+ return err
+ }
+ return nil
+}
diff --git a/lib/run/exec_wasm.go b/lib/run/exec_wasm.go
new file mode 100644
index 0000000..985b0f3
--- /dev/null
+++ b/lib/run/exec_wasm.go
@@ -0,0 +1,45 @@
+//go:build wasm
+// +build wasm
+
+package run
+
+import (
+ "os/exec"
+
+ "nms_nbi/lib/log"
+)
+
+func ExecCmd(command, path string) ([]byte, error) {
+ log.Debug("Exec command:", command)
+
+ cmd := exec.Command("cmd", "/C", command)
+ cmd.Dir = path
+ out, err := cmd.CombinedOutput()
+ log.Tracef("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("exe cmd error: ", err)
+ return out, err
+ }
+
+ return out, nil
+}
+
+func ExecOsCmd(command, os string) error {
+ log.Debugf("Exec %s command:%s", os, command)
+
+ var cmd *exec.Cmd
+ switch os {
+ case "Linux":
+ cmd = exec.Command(command)
+ case "Windows":
+ cmd = exec.Command("cmd", "/C", command)
+ }
+
+ out, err := cmd.CombinedOutput()
+ log.Tracef("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("exe cmd error: ", err)
+ return err
+ }
+ return nil
+}
diff --git a/lib/run/exec_windows.go b/lib/run/exec_windows.go
new file mode 100644
index 0000000..645a05b
--- /dev/null
+++ b/lib/run/exec_windows.go
@@ -0,0 +1,45 @@
+//go:build windows
+// +build windows
+
+package run
+
+import (
+ "os/exec"
+
+ "nms_nbi/lib/log"
+)
+
+func ExecCmd(command, path string) ([]byte, error) {
+ log.Debug("Exec command:", command)
+
+ cmd := exec.Command("cmd", "/C", command)
+ cmd.Dir = path
+ out, err := cmd.CombinedOutput()
+ log.Tracef("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("exe cmd error: ", err)
+ return out, err
+ }
+
+ return out, nil
+}
+
+func ExecOsCmd(command, os string) error {
+ log.Debugf("Exec %s command:%s", os, command)
+
+ var cmd *exec.Cmd
+ switch os {
+ case "Linux":
+ cmd = exec.Command(command)
+ case "Windows":
+ cmd = exec.Command("cmd", "/C", command)
+ }
+
+ out, err := cmd.CombinedOutput()
+ log.Tracef("Exec output: %v", string(out))
+ if err != nil {
+ log.Error("exe cmd error: ", err)
+ return err
+ }
+ return nil
+}
diff --git a/lib/services/file.go b/lib/services/file.go
new file mode 100644
index 0000000..47215fb
--- /dev/null
+++ b/lib/services/file.go
@@ -0,0 +1,426 @@
+package services
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "io"
+ "mime/multipart"
+ "net/http"
+ "os"
+ "strconv"
+ "strings"
+
+ "nms_nbi/lib/log"
+)
+
+const (
+ RootPath = "uploads/"
+ ChunkRootPath = "uploads_tmp/"
+)
+
+var (
+ // FilesMax 限制上传文件的大小为7 MB
+ FilesMax int64 = 32 << 20
+ // ValuesMax 限制POST字段内容的大小
+ ValuesMax int64 = 512
+)
+
+func GetPostFile(w http.ResponseWriter, r *http.Request) {
+ //获取文件流,第三个返回值是错误对象
+ file, header, _ := r.FormFile("file")
+ //读取文件流为[]byte
+ b, err := io.ReadAll(file)
+ if err != nil {
+ log.Error("Failed to ReadAll:", err)
+ ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ //把文件保存到指定位置
+ err = os.WriteFile("./upload/test.zip", b, 0644)
+ if err != nil {
+ log.Error("Failed to WriteFile:", err)
+ ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ //输出上传时文件名
+ log.Debug("filename:", header.Filename)
+}
+
+func GetUploadFile(w http.ResponseWriter, r *http.Request) {
+ log.Debug("GetUploadFile processing...")
+
+ file, err := os.Create("./test.zip")
+ if err != nil {
+ log.Error("Failed to Create:", err)
+ ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ _, err = io.Copy(file, r.Body)
+ if err != nil {
+ log.Error("Failed to Copy:", err)
+ ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+}
+
+func GetUploadFormFile(w http.ResponseWriter, r *http.Request) {
+ // 设置最大的内存限制为32MB
+ r.ParseMultipartForm(32 << 20)
+ file, handler, err := r.FormFile("file")
+ if err != nil {
+ log.Error("Failed to FormFile:", err)
+ ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ defer file.Close()
+ log.Debug("Header:%v", handler.Header)
+ f, err := os.OpenFile("./"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
+ if err != nil {
+ log.Error("Failed to OpenFile:", err)
+ ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ defer f.Close()
+ _, err = io.Copy(f, file)
+ if err != nil {
+ log.Error("Failed to Copy:", err)
+ ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ log.Debug("File uploaded successfully:", handler.Filename)
+}
+
+func HandleUploadFile(r *http.Request, path, newFileName string) (string, error) {
+ var filePath, fileName string
+ reader, err := r.MultipartReader()
+ if err != nil {
+ log.Error("Failed to MultipartReader:", err)
+ return "", err
+ }
+
+ for {
+ part, err := reader.NextPart()
+ if err == io.EOF {
+ break
+ } else if err != nil {
+ log.Error("Failed to NextPart:", err)
+ return "", err
+ }
+
+ log.Debugf("FileName=[%s], FormName=[%s]", part.FileName(), part.FormName())
+ if part.FileName() == "" { // this is FormData
+ data, _ := io.ReadAll(part)
+ log.Debugf("FormData=[%s]", string(data))
+ } else { // This is FileData
+
+ if newFileName != "" {
+ fileName = newFileName
+ } else {
+ fileName = part.FileName()
+ }
+
+ err := os.MkdirAll(path, os.ModePerm)
+ if err != nil {
+ log.Error("Failed to Mkdir:", err)
+ return "", err
+ }
+
+ filePath = path + "/" + fileName
+
+ file, err := os.Create(filePath)
+ if err != nil {
+ log.Error("Failed to Create:", err)
+ return "", err
+ }
+ defer file.Close()
+ _, err = io.Copy(file, part)
+ if err != nil {
+ log.Error("Failed to Copy:", err)
+ return "", err
+ }
+ }
+ }
+ return fileName, nil
+}
+
+type UploadMultiFileData struct {
+ SoftwareFileName string `json:"softwareFileName"`
+ CmsFileName string `json:"cmsFileName"`
+ Datas map[string][]string `json:"datas"`
+}
+
+func HandleUploadMultiFile(r *http.Request, path, newFileName string) (*UploadMultiFileData, error) {
+ fileData := new(UploadMultiFileData)
+ // 解析multipart/form-data请求
+ err := r.ParseMultipartForm(100 << 20) // 100MB
+ if err != nil {
+ return fileData, err
+ }
+
+ // 获取文件和数据
+ softwareFile := r.MultipartForm.File["file"]
+ cmsFile := r.MultipartForm.File["cms"]
+ fileData.Datas = r.MultipartForm.Value
+
+ // 处理文件
+ if len(softwareFile) > 0 {
+ file := softwareFile[0]
+ // 打开文件
+ f, err := file.Open()
+ if err != nil {
+ return fileData, err
+ }
+ defer f.Close()
+
+ // 创建本地文件
+ dst, err := os.Create(path + "/" + file.Filename)
+ if err != nil {
+ return fileData, err
+ }
+ defer dst.Close()
+
+ fileData.SoftwareFileName = file.Filename
+ // 将文件内容拷贝到本地文件
+ _, err = io.Copy(dst, f)
+ if err != nil {
+ return fileData, err
+ }
+ }
+ // 处理文件
+ if len(cmsFile) > 0 {
+ file := cmsFile[0]
+ // 打开文件
+ f, err := file.Open()
+ if err != nil {
+ return fileData, err
+ }
+ defer f.Close()
+
+ // 创建本地文件
+ dst, err := os.Create(path + "/" + file.Filename)
+ if err != nil {
+ return fileData, err
+ }
+ defer dst.Close()
+
+ fileData.CmsFileName = file.Filename
+ // 将文件内容拷贝到本地文件
+ _, err = io.Copy(dst, f)
+ if err != nil {
+ return fileData, err
+ }
+ }
+
+ return fileData, nil
+}
+
+func HandleUploadFormFile(w http.ResponseWriter, r *http.Request) {
+ r.ParseMultipartForm(32 << 20)
+ //mForm := r.MultipartForm
+
+ for k := range r.MultipartForm.File {
+ // k is the key of file part
+ file, fileHeader, err := r.FormFile(k)
+ if err != nil {
+ fmt.Println("inovke FormFile error:", err)
+ return
+ }
+ defer file.Close()
+ fmt.Printf("the uploaded file: name[%s], size[%d], header[%#v]\n",
+ fileHeader.Filename, fileHeader.Size, fileHeader.Header)
+
+ // store uploaded file into local path
+ localFileName := "./upload/" + fileHeader.Filename
+ out, err := os.Create(localFileName)
+ if err != nil {
+ fmt.Printf("failed to open the file %s for writing", localFileName)
+ return
+ }
+ defer out.Close()
+ _, err = io.Copy(out, file)
+ if err != nil {
+ fmt.Printf("copy file err:%s\n", err)
+ return
+ }
+ fmt.Printf("file %s uploaded ok\n", fileHeader.Filename)
+ }
+}
+
+func PostFileHandler(w http.ResponseWriter, r *http.Request) {
+ fmt.Println("PostFileHandler processing... ")
+ if !strings.Contains(r.Header.Get("Content-Type"), "multipart/form-data") {
+ // 不支持的 Content-Type 类型
+ fmt.Println("Invalid Content-Type: ", r.Header.Get("Content-Type"))
+ http.Error(w, " Unsupported Content-Type Types", http.StatusBadRequest)
+ return
+ }
+
+ // 整个请求的主体大小设置为7.5Mb
+ r.Body = http.MaxBytesReader(w, r.Body, FilesMax+ValuesMax)
+ reader, err := r.MultipartReader()
+ if err != nil {
+ fmt.Println(err)
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ for {
+ // A Part represents a single part in a multipart body.
+ part, err := reader.NextPart()
+ if err != nil {
+ if err == io.EOF {
+
+ break
+ }
+
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ fileName := part.FileName()
+ formName := part.FormName()
+ var buf = &bytes.Buffer{}
+ // 非文件字段部分大小限制验证(非文件字段,go中filename会是空)
+ if fileName == "" {
+ // "请求主体中非文件字段" + formName + "超出大小限制"
+ var limitError = fmt.Sprintf("Non-file field %s in request body exceeds size limit", formName)
+ err = uploadSizeLimit(buf, part, ValuesMax, limitError)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ continue
+ }
+
+ // 文件字段部分大小限制验证
+ // "请求主体中文件字段" + fileName + "超出大小限制"
+ var limitError = fmt.Sprintf("File field %s in request body exceeds size limit", fileName)
+ err = uploadSizeLimit(buf, part, FilesMax, limitError)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ // 文件创建部分
+ if err := uploadFileHandle(r.Header, fileName, buf); err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ // 非逻辑内容,仅为测试使用
+ var chunkNumber = r.Header.Get("chunk-number")
+ if chunkNumber == "" {
+ // "文件"+fileName+"上传成功"
+ http.Error(w, fmt.Sprintf("File %s uploaded successfully", fileName), http.StatusOK)
+ } else {
+ // "分片文件 %s %s 上传成功"
+ http.Error(w, fmt.Sprintf("Sliced file %s %s uploaded successfully.", fileName, chunkNumber), http.StatusOK)
+ }
+ }
+}
+
+// 上传内容大小限制
+func uploadSizeLimit(buf *bytes.Buffer, part *multipart.Part, maxLimit int64, limitError string) error {
+ n, err := io.CopyN(buf, part, maxLimit+1)
+ if err != nil && err != io.EOF {
+ fmt.Println("PostFileHandler:", err)
+ return err
+ }
+ maxLimit -= n
+ if maxLimit < 0 {
+ return fmt.Errorf(limitError)
+ }
+ return nil
+}
+
+// uploadFileHandle handle upload file
+func uploadFileHandle(header http.Header, fileName string, buf *bytes.Buffer) error {
+ var chunkNumberStr = header.Get("chunk-number")
+ // 1.普通文件上传处理
+ if chunkNumberStr == "" {
+ //创建文件并写入文件内容
+ return createFile(RootPath+fileName, buf.Bytes())
+ }
+ // 2.分片文件上传处理
+ //2.1读取分片编号
+ chunkNumber, err := strconv.Atoi(chunkNumberStr)
+ if err != nil {
+ return err
+ }
+ //2.2创建分片文件并写入分片内容
+ if err := createFile(fmt.Sprintf(ChunkRootPath+fileName+"%d.chunk", chunkNumber), buf.Bytes()); err != nil {
+ return err
+ }
+ //2.3确认是否上传完毕
+ if header.Get("chunk-final") == "true" {
+ //2.4合并文件
+ if err := mergeChunkFiles(fileName); err != nil {
+ return err
+ }
+ //2.5删除分片
+ for i := 0; ; i++ {
+ chunFileName := fmt.Sprintf(ChunkRootPath+fileName+"%d.chunk", i)
+ err := os.Remove(chunFileName)
+ if err != nil {
+ if os.IsNotExist(err) {
+ break
+ }
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+// 创建文件并写入内容
+func createFile(fileName string, res []byte) error {
+ newFile, err := os.Create(fileName)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ _ = newFile.Close()
+ }()
+ bufferedWriter := bufio.NewWriter(newFile)
+ _, err = bufferedWriter.Write(res)
+ if err != nil && err != io.EOF {
+ return err
+ }
+ return bufferedWriter.Flush()
+}
+
+// 合并分片文件
+func mergeChunkFiles(fileName string) error {
+ var (
+ n int64
+ err error
+ )
+ finalFile, err := os.Create(RootPath + fileName)
+ if err != nil {
+ return err
+ }
+ defer finalFile.Close()
+ // 将分片内容写入最终文件
+ for i := 0; ; i++ {
+ chunFile, err := os.Open(fmt.Sprintf(ChunkRootPath+fileName+"%d.chunk", i))
+ if err != nil {
+ if os.IsNotExist(err) {
+ break
+ }
+ return err
+ }
+ n, err = io.Copy(finalFile, chunFile)
+ if err != nil {
+ return err
+ }
+ err = chunFile.Close()
+ if err != nil {
+ return err
+ }
+ if n < 1 {
+ break
+ }
+ }
+ return nil
+}
diff --git a/lib/services/services.go b/lib/services/services.go
new file mode 100644
index 0000000..8c4d722
--- /dev/null
+++ b/lib/services/services.go
@@ -0,0 +1,997 @@
+package services
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "mime/multipart"
+ "os"
+ "path/filepath"
+
+ // "log"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/global"
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/oauth"
+ "nms_nbi/restagent/config"
+
+ "github.com/gorilla/mux"
+)
+
+type NameValue struct {
+ Name string
+ Value string
+}
+
+type NameOid struct {
+ Name string
+ Oid string
+}
+
+type DataResponse struct {
+ Data interface{} `json:"data"`
+}
+
+type MapResponse map[string]any
+
+type NullResponse struct {
+ nil interface{}
+}
+
+type ErrorResponse struct {
+ Error interface{} `json:"error"`
+}
+
+type ErrorMessage struct {
+ ErrorCode string `json:"errorCode"`
+ ErrorInfo string `json:"errorInfo"`
+}
+
+type SucceedOAuthResponse struct {
+ AccessToken string `json:"accessToken"`
+ Expires string `json:"expires"`
+ // Enum: "0": 不需要修改密码, "1": "FirstLogin",首次登录, "2": PasswordAboutToExpire, 密码即将过期
+ ChangePasswordFlag int `json:"changePasswordFlag"`
+ GroupName string `json:"groupName"`
+ Roles []string `json:"roles"`
+ Perms []string `json:"perms"`
+}
+
+type ServiceResponse struct {
+}
+
+func GetUriParamString(r *http.Request, paramName string, sep string, brackets bool, apostr bool) string {
+ vars := r.URL.Query()
+ s, ok := vars[paramName]
+ if !ok {
+ log.Infof("Parameter Name is not exist, %s", paramName)
+ return ""
+ }
+ if apostr {
+ for i := 0; i < len(s); i++ {
+ s[i] = "'" + s[i] + "'"
+ }
+ }
+ pn := strings.Join(s, sep)
+ if brackets {
+ pn = fmt.Sprintf("(%s)", pn)
+ }
+
+ return pn
+}
+
+func GetUriWhereString(r *http.Request) string {
+ return GetUriParamString(r, "WHERE", " and ", false, false)
+}
+
+func GetUriLocString(r *http.Request) string {
+ return GetUriParamString(r, "loc", " and ", false, false)
+}
+
+func GetUriPageLimitString(r *http.Request) string {
+ vars := r.URL.Query()
+ p, ok := vars["PAGE"]
+ if !ok {
+ log.Info("page param is not exist")
+ return ""
+ }
+
+ l, ok := vars["LIMIT"]
+ if !ok {
+ log.Info("limit param is not exist")
+ return ""
+ }
+ li, _ := strconv.Atoi(l[0])
+ pi, _ := strconv.Atoi(p[0])
+ ls := fmt.Sprintf("limit %d, %d", li*(pi-1), li)
+
+ log.Debug("Limit array:", ls)
+ return ls
+}
+
+func ExtGetUriPageLimitString(r *http.Request) string {
+ vars := r.URL.Query()
+ p, ok := vars["page"]
+ if !ok {
+ log.Info("page param is not exist")
+ p = append(p, "1")
+ }
+
+ l, ok := vars["limit"]
+ if !ok {
+ log.Info("limit param is not exist")
+ l = append(l, strconv.Itoa(global.MaxLimitData))
+ }
+ limit, _ := strconv.Atoi(l[0])
+ if limit > global.MaxLimitData {
+ limit = global.MaxLimitData
+ }
+ page, _ := strconv.Atoi(p[0])
+ limitStr := fmt.Sprintf("limit %d, %d", limit*(page-1), limit)
+
+ log.Debug("limitStr:", limitStr)
+ return limitStr
+}
+
+func IsJsonContentType(r *http.Request) bool {
+ hType := r.Header.Get("Content-Type")
+ return strings.Contains(hType, "application/json")
+}
+
+func IsValidOAuthUri(r *http.Request) bool {
+ vars := mux.Vars(r)
+ apiVer := vars["apiVersion"] // 获取Uri
+ return apiVer == "v1"
+}
+
+func IsVallidContentType(r *http.Request, checkFlag bool) bool {
+ log.Debug("IsVallidContentType processing ...")
+ /*
+ ctype := r.Header["Content-Type"]
+ log.Debug("ctype:", ctype)
+ if len(ctype) != 0 && !strings.Contains(ctype[0], "application/json") {
+ return false
+ }
+ */
+ if strings.Contains(r.Header.Get("Content-Type"), "application/json") || !checkFlag {
+ return true
+ }
+ return false
+}
+
+func CheckParameterName(r *http.Request) []string {
+ var errorParams []string
+ vars := r.URL.Query()
+ for k, v := range vars {
+ log.Debug("vars:", k, v)
+ if k != "rmUIDs" && k != "fields" {
+ errorParams = append(errorParams, k)
+ }
+ }
+
+ return errorParams
+}
+
+func GetRmUIDArr(r *http.Request) []string {
+ vars := r.URL.Query()
+ rmUIDs, ok := vars["rmUIDs"]
+ if !ok {
+ log.Info("rmUIDs is not exist")
+ return nil
+ }
+
+ var rmUIDValues []string
+ for _, r := range rmUIDs {
+ if r != "" {
+ rmUIDValues = global.MergeStringArr(rmUIDValues, strings.Split(r, `,`))
+ }
+ }
+
+ return rmUIDValues
+}
+
+func GetAttrNameArr(r *http.Request) []string {
+ vars := r.URL.Query()
+ fields, ok := vars["fields"]
+ if !ok {
+ log.Info("attributeNames does not exist")
+ return nil
+ }
+ var attrNames []string
+ for _, a := range fields {
+ if a != "" {
+ attrNames = global.MergeStringArr(attrNames, strings.Split(a, `,`))
+ }
+ }
+
+ return attrNames
+}
+
+func CheckValidRmUID(rmUIDs []string) []string {
+ log.Debug("CheckValidRmUID processing... ")
+
+ var invalidRmUIDs []string
+ for _, r := range rmUIDs {
+ if !global.MatchRmUID(config.GetRmUIDRegexpFromConfig(), r) {
+ invalidRmUIDs = append(invalidRmUIDs, r)
+ }
+ }
+
+ return invalidRmUIDs
+}
+
+func CheckLocalRmUID(rmUIDs []string) string {
+ log.Debug("GetLocalRmUID processing... ")
+
+ rmUID := config.GetRmUIDFromConfig()
+ for _, r := range rmUIDs {
+ if r == rmUID {
+ return rmUID
+ }
+ }
+
+ return ""
+}
+
+func GetParamsArrByName(paramName string, r *http.Request) []string {
+ vars := r.URL.Query()
+ pns, ok := vars[paramName]
+ if !ok {
+ log.Infof("%s is not exist", paramName)
+ return nil
+ }
+
+ var pnArr []string
+ for _, pn := range pns {
+ if pn != "" {
+ pnArr = global.MergeStringArr(pnArr, strings.Split(pn, `,`))
+ }
+ }
+
+ return pnArr
+}
+
+func GetOperationTypeFromHttpRequest(r *http.Request) string {
+ for k, v := range r.Header {
+ log.Tracef("k:%s, v:%s", k, v)
+ if strings.ToLower(k) == "operationtype" && len(v) != 0 {
+ log.Trace("OperationType:", v[0])
+ return v[0]
+ }
+ }
+
+ return ""
+}
+
+func CheckNorthboundValidRequest(w http.ResponseWriter, r *http.Request) (string, error) {
+ log.Debug("CheckNorthboundValidRequest processing... ")
+
+ var token string = ""
+ var err error
+ var ret bool
+ // response 414-4 uri too long ? (optional)
+ // todo ... ?
+ if bytes.Count([]byte(r.RequestURI), nil) > config.GetUriMaxLenFromConfig() {
+ log.Error("Request Uri too long:", bytes.Count([]byte(r.RequestURI), nil), config.GetUriMaxLenFromConfig())
+ ResponseRequestURITooLong414UriTooLong(w)
+ return token, err
+ }
+
+ // check media type(content type) only support "application/json"
+ // response 415-1
+ if !IsJsonContentType(r) {
+ log.Error("invalid Content-Type")
+ ResponseUnsupportedMediaType415(w)
+ return token, err
+ }
+
+ // error processing ...
+ // 401-1 response
+ token, ret = oauth.IsCarriedToken(r)
+ if !ret {
+ log.Error("accessToken is not carried")
+ ResponseUnauthorized401AccessTokenNotCarried(w)
+ return token, err
+ }
+
+ // 401-2 response
+ if !dborm.XormExistValidToken(token, config.GetExpiresFromConfig()) {
+ log.Error("accessToken fails or does not exist")
+ ResponseUnauthorized401AccessTokenNotExist(w)
+ return token, err
+ }
+
+ if operType := GetOperationTypeFromHttpRequest(r); operType != "auto" {
+ _, err = dborm.XormUpdateSessionShakeTime(token)
+ if err != nil {
+ log.Error("Failed to update session table:", err)
+ ResponseUnauthorized401AccessTokenNotExist(w)
+ return token, err
+ }
+ }
+
+ vars := mux.Vars(r)
+ apiVer := vars["apiVersion"]
+ if apiVer != global.ApiVersionV1 {
+ log.Error("Uri is invalid")
+ ResponseNotFound404UriNotExist(w, r)
+ return token, err
+ }
+
+ // response 406-1
+ rmUIDValues := GetRmUIDArr(r)
+ if rmUIDValues == nil {
+ log.Error("missing parameter: rmUIDs")
+ ResponseNotAcceptable406MissingParam(w)
+ return token, err
+ }
+
+ // response 406-2
+ errorParams := CheckParameterName(r)
+ if errorParams != nil {
+ log.Error("parameter name error: ", errorParams)
+ ResponseNotAcceptable406ParamError(w, errorParams)
+ return token, err
+ }
+
+ // response 400-5
+ if len(rmUIDValues) == 0 {
+ log.Error("rmUIDs is wrong or NULL")
+ ResponseBadRequest400WrongParamValue(w)
+ return token, err
+ }
+
+ // response 414-1
+ if len(rmUIDValues) > config.GetYamlConfig().Params.RmUIDMaxNum {
+ log.Error("rmUID greater than", config.GetYamlConfig().Params.RmUIDMaxNum)
+ ResponseRequestURITooLong414NRMNumExceed(w, config.GetYamlConfig().Params.RmUIDMaxNum)
+ return token, err
+ }
+
+ return token, nil
+}
+
+func CheckCommonValidRequest(w http.ResponseWriter, r *http.Request) (string, error) {
+ log.Debug("CheckCommonValidRequest processing... ")
+
+ var token string = ""
+ var err error
+ var ret bool
+ // response 414-4 uri too long ? (optional)
+ // todo ... ?
+ if bytes.Count([]byte(r.RequestURI), nil) > config.GetUriMaxLenFromConfig() {
+ log.Error("Request Uri too long:", bytes.Count([]byte(r.RequestURI), nil), config.GetUriMaxLenFromConfig())
+ ResponseRequestURITooLong414UriTooLong(w)
+ return token, err
+ }
+
+ // check media type(content type) only support "application/json"
+ // response 415-1
+ if !IsJsonContentType(r) {
+ log.Error("Invalid Content-Type")
+ ResponseUnsupportedMediaType415(w)
+ return token, err
+ }
+
+ // error processing ...
+ // 401-1 response
+ token, ret = oauth.IsCarriedToken(r)
+ if !ret {
+ log.Error("accessToken is not carried")
+ ResponseUnauthorized401AccessTokenNotCarried(w)
+ return token, err
+ }
+
+ // 401-2 response
+ if !dborm.XormExistValidToken(token, config.GetExpiresFromConfig()) {
+ log.Error("accessToken fails or does not exist")
+ ResponseUnauthorized401AccessTokenNotExist(w)
+ return token, err
+ }
+
+ if operType := GetOperationTypeFromHttpRequest(r); operType != "auto" {
+ _, err = dborm.XormUpdateSessionShakeTime(token)
+ if err != nil {
+ log.Error("Failed to update session table:", err)
+ ResponseUnauthorized401AccessTokenNotExist(w)
+ return token, err
+ }
+ }
+
+ vars := mux.Vars(r)
+ apiVer := vars["apiVersion"]
+ if apiVer != global.ApiVersionV1 {
+ log.Error("Uri is invalid")
+ ResponseNotFound404UriNotExist(w, r)
+ return token, err
+ }
+
+ return token, nil
+}
+
+func CheckUserPermission(token, method, module, dbname, tbname, pack string) (bool, error) {
+ if config.GetYamlConfig().OMC.RBACMode {
+ if module == "" {
+ module = "*"
+ }
+ if dbname == "" {
+ dbname = "*"
+ }
+ if tbname == "" {
+ tbname = "*"
+ }
+ exist, err := dborm.IsPermissionAllowed(token, method, module, dbname, tbname, pack)
+ if err != nil {
+ return false, err
+ }
+ if !exist {
+ return false, nil
+ }
+ }
+
+ return true, nil
+}
+
+func IsLocalhost(host string) bool {
+ if strings.Contains(host, "127.0.0.1") || strings.Contains(host, "::1") {
+ return true
+ }
+ return false
+}
+
+func CheckFrontValidRequest(w http.ResponseWriter, r *http.Request) (string, error) {
+ log.Debug("CheckFrontValidRequest processing... ")
+
+ var token string = ""
+ var err error
+ var ret bool
+ // response 414-4 uri too long ? (optional)
+ // todo ... ?
+ if bytes.Count([]byte(r.RequestURI), nil) > config.GetUriMaxLenFromConfig() {
+ err = errors.New("request Uri too long")
+ log.Errorf("Request Uri too long: bytes=%d, MaxLen=%d", bytes.Count([]byte(r.RequestURI), nil), config.GetUriMaxLenFromConfig())
+ ResponseRequestURITooLong414UriTooLong(w)
+ return token, err
+ }
+
+ /*
+ // check media type(content type) only support "application/json"
+ // response 415-1
+ if !IsVallidContentType(r) {
+ err := errors.New("Invalid Content-Type")
+ log.Error(err)
+ ResponseUnsupportedMediaType415(w)
+ return err
+ }
+ */
+
+ // error processing ...
+ // 401-1 response
+ if config.GetYamlConfig().Auth.Token && !IsLocalhost(r.RemoteAddr) {
+ token, ret = oauth.IsCarriedToken(r)
+ if !ret {
+ err = errors.New("accessToken is not carried")
+ log.Error(err)
+ ResponseUnauthorized401AccessTokenNotCarried(w)
+ return token, err
+ }
+
+ // 401-2 response
+ if !dborm.XormExistValidToken(token, config.GetExpiresFromConfig()) {
+ err = errors.New("accessToken fails or does not exist")
+ log.Error(err)
+ ResponseUnauthorized401AccessTokenNotExist(w)
+ return token, err
+ }
+
+ if operType := GetOperationTypeFromHttpRequest(r); operType != "auto" {
+ _, err = dborm.XormUpdateSessionShakeTime(token)
+ if err != nil {
+ log.Error("Failed to update session table:", err)
+ ResponseUnauthorized401AccessTokenNotExist(w)
+ return token, err
+ }
+ }
+
+ }
+
+ vars := mux.Vars(r)
+ apiVer := vars["apiVersion"]
+ if apiVer != global.ApiVersionV1 {
+ err = errors.New("uri is invalid")
+ log.Error(err)
+ ResponseNotFound404UriNotExist(w, r)
+ return token, err
+ }
+
+ return token, nil
+}
+
+func CheckExtValidRequest(w http.ResponseWriter, r *http.Request) (string, error) {
+ log.Debug("CheckExtValidRequest processing... ")
+
+ var token string = ""
+ var err error
+ var ret bool
+ // error processing ...
+ // 401-1 response
+ if config.GetYamlConfig().Auth.Token {
+ token, ret = oauth.IsCarriedToken(r)
+ if !ret {
+ err = errors.New("accessToken is not carried")
+ log.Error(err)
+ ResponseUnauthorized401AccessTokenNotCarried(w)
+ return token, err
+ }
+
+ // 401-2 response
+ if !dborm.XormExistValidToken(token, config.GetExpiresFromConfig()) {
+ err = errors.New("accessToken fails or does not exist")
+ log.Error(err)
+ ResponseUnauthorized401AccessTokenNotExist(w)
+ return token, err
+ }
+
+ if operType := GetOperationTypeFromHttpRequest(r); operType != "auto" {
+ _, err = dborm.XormUpdateSessionShakeTime(token)
+ if err != nil {
+ log.Error("Failed to update session table:", err)
+ ResponseUnauthorized401AccessTokenNotExist(w)
+ return token, err
+ }
+ }
+ }
+
+ vars := mux.Vars(r)
+ apiVer := vars["apiVersion"]
+ if apiVer != global.ApiVersionV1 {
+ err = errors.New("uri is invalid")
+ log.Error(err)
+ ResponseNotFound404UriNotExist(w, r)
+ return token, err
+ }
+
+ return token, nil
+}
+
+func ResponseStatusOK200Login(w http.ResponseWriter, token string, user *dborm.User) {
+ var oAuthResponse SucceedOAuthResponse
+ oAuthResponse.AccessToken = token
+ oAuthResponse.Expires = strconv.Itoa((int)(config.GetExpiresFromConfig()))
+ oAuthResponse.ChangePasswordFlag = user.ChangePasswordFlag
+ oAuthResponse.GroupName = user.GroupName
+ ResponseWithJson(w, http.StatusOK, oAuthResponse)
+}
+
+func ResponseStatusOK200LoginWhitRP(w http.ResponseWriter, token string, user *dborm.User, roles, perms []string) {
+ var oAuthResponse SucceedOAuthResponse
+ oAuthResponse.AccessToken = token
+ oAuthResponse.Expires = strconv.Itoa((int)(config.GetExpiresFromConfig()))
+ oAuthResponse.ChangePasswordFlag = user.ChangePasswordFlag
+ oAuthResponse.GroupName = user.GroupName
+ oAuthResponse.Roles = roles
+ oAuthResponse.Perms = perms
+ ResponseWithJson(w, http.StatusOK, oAuthResponse)
+}
+
+func ResponseStatusOK200Null(w http.ResponseWriter) {
+ response := NullResponse{""}
+ ResponseWithJson(w, http.StatusOK, response)
+}
+
+func ResponseStatusOK204NoContent(w http.ResponseWriter) {
+ ResponseWithJson(w, http.StatusNoContent, "")
+}
+
+func ResponseStatusOK201Accepted(w http.ResponseWriter) {
+ ResponseWithJson(w, http.StatusAccepted, "")
+}
+
+type SSORedirect struct {
+ User string `json:"user"`
+ Token string `json:"token"`
+}
+
+func ResponseRedirect(w http.ResponseWriter, redirectUrl, user, token string) {
+ w.Header().Set("Cache-Control", "must-revalidate, no-store")
+ w.Header().Set("Content-Type", " text/html;charset=UTF-8")
+ w.Header().Set("Location", redirectUrl) //跳转地址设置
+ //w.WriteHeader(http.StatusTemporaryRedirect) //重定向!
+ ssoRedirect := &SSORedirect{user, token}
+ ResponseWithJson(w, http.StatusTemporaryRedirect, *ssoRedirect)
+}
+
+func ResponseBadRequest400RmUIDsIsInvalid(w http.ResponseWriter, rmUIDs []string) {
+ errorMessage := ErrorMessage{"1", "rmUIDs is invalid:" + strings.Join(rmUIDs, ",")}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusBadRequest, errorResponse)
+}
+
+func ResponseBadRequest400DuplicateSubId(w http.ResponseWriter, SubIds string) {
+ errorMessage := ErrorMessage{"2", "Duplicate with resource subscription id:" + SubIds}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusBadRequest, errorResponse)
+}
+
+func ResponseBadRequest400DuplicateAlarmId(w http.ResponseWriter, AlarmIds string) {
+ errorMessage := ErrorMessage{"3", "Duplicate with alarm subscription id: " + AlarmIds}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusBadRequest, errorResponse)
+}
+
+func ResponseBadRequest400IncorrectLogin(w http.ResponseWriter) {
+ errorMessage := ErrorMessage{"4", "incorrect username and password"}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusBadRequest, errorResponse)
+}
+
+func ResponseBadRequest400WrongParamValue(w http.ResponseWriter) {
+ errorMessage := ErrorMessage{"5", "wrong parameter value"}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusBadRequest, errorResponse)
+}
+
+func ResponseBadRequest400CMCALoginError(w http.ResponseWriter) {
+ errorMessage := ErrorMessage{"6", "CMCA centralized authentication login error"}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusBadRequest, errorResponse)
+}
+
+func ResponseBadRequest400InvalidJson(w http.ResponseWriter) {
+ errorMessage := ErrorMessage{"7", "invalid json format"}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusUnauthorized, errorResponse)
+}
+
+func ResponseUnauthorized401AccessTokenNotCarried(w http.ResponseWriter) {
+ errorMessage := ErrorMessage{"1", "accessToken is not carried"}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusUnauthorized, errorResponse)
+}
+
+func ResponseUnauthorized401AccessTokenNotExist(w http.ResponseWriter) {
+ errorMessage := ErrorMessage{"2", "accessToken fails or does not exist"}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusUnauthorized, errorResponse)
+}
+
+func ResponseForbidden403NotPermission(w http.ResponseWriter) {
+ errorMessage := ErrorMessage{"1", "do not have the operation permissions"}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusForbidden, errorResponse)
+}
+
+func ResponseForbidden403MultiLoginNotAllowed(w http.ResponseWriter) {
+ errorMessage := ErrorMessage{"2", "multiple logins are not allowed"}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusForbidden, errorResponse)
+}
+
+func ResponseNotFound404UriNotExist(w http.ResponseWriter, r *http.Request) {
+ if r.Method == "OPTIONS" {
+ ResponseStatusOK204NoContent(w)
+ return
+ }
+ errorMessage := ErrorMessage{"1", "the requested URI does not exist"}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusNotFound, errorResponse)
+}
+
+func ResponseNotFound404UriNotExistExt(w http.ResponseWriter) {
+ errorMessage := ErrorMessage{"1", "the requested URI does not exist"}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusNotFound, errorResponse)
+}
+
+func CustomResponseNotFound404Handler() http.Handler {
+ return http.HandlerFunc(ResponseNotFound404UriNotExist)
+}
+
+func ResponseNotFound404NRMNotExist(w http.ResponseWriter, rmUIDs []string) {
+ errorMessage := ErrorMessage{"2", "rmUIDs does not exist: " + strings.Join(rmUIDs, ",")}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusNotFound, errorResponse)
+}
+
+func ResponseNotFound404PMNotExist(w http.ResponseWriter, rmUIDs []string) {
+ errorMessage := ErrorMessage{"3", "rmUIDs does not exist: " + strings.Join(rmUIDs, ",")}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusNotFound, errorResponse)
+}
+
+func ResponseNotFound404AlarmNotExist(w http.ResponseWriter, AlarmIds []string) {
+ errorMessage := ErrorMessage{"4", "alarmIds does not exist: " + strings.Join(AlarmIds, ",")}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusNotFound, errorResponse)
+}
+
+func ResponseNotFound404GetSubscriptionNotExist(w http.ResponseWriter, SubIds []string) {
+ errorMessage := ErrorMessage{"5", "subscription id does not exist: " + strings.Join(SubIds, ",")}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusNotFound, errorResponse)
+}
+
+func ResponseNotFound404DeleteSubscriptionNotExist(w http.ResponseWriter, SubIds []string) {
+ errorMessage := ErrorMessage{"6", "subscription id does not exist: " + strings.Join(SubIds, ",")}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusNotFound, errorResponse)
+}
+
+func ResponseNotFound404GetAlarmSubscriptionNotExist(w http.ResponseWriter, SubIds []string) {
+ errorMessage := ErrorMessage{"7", "subscription id does not exist: " + strings.Join(SubIds, ",")}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusNotFound, errorResponse)
+}
+
+func ResponseNotFound404DeleteAlarmSubscriptionNotExist(w http.ResponseWriter, SubIds []string) {
+ errorMessage := ErrorMessage{"8", "subscription id does not exist: " + strings.Join(SubIds, ",")}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusNotFound, errorResponse)
+}
+
+func ResponseMethodNotAllowed405(w http.ResponseWriter, r *http.Request) {
+ if r.Method == "OPTIONS" {
+ ResponseStatusOK204NoContent(w)
+ return
+ }
+ errorMessage := ErrorMessage{"1", "method not allowed"}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusMethodNotAllowed, errorResponse)
+}
+
+func CustomResponseMethodNotAllowed405Handler() http.Handler {
+ return http.HandlerFunc(ResponseMethodNotAllowed405)
+}
+
+func ResponseNotAcceptable406MissingParam(w http.ResponseWriter) {
+ errorMessage := ErrorMessage{"1", "missing parameter: rmUIDs"}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusNotAcceptable, errorResponse)
+}
+
+func ResponseNotAcceptable406ParamError(w http.ResponseWriter, errorParamsName []string) {
+ errorMessage := ErrorMessage{"2", "parameter name error: " + strings.Join(errorParamsName, ",")}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusNotAcceptable, errorResponse)
+}
+
+func ResponseNotAcceptable406QuerySQLError(w http.ResponseWriter) {
+ errorMessage := ErrorMessage{"3", "wrong or non-query SQL statement"}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusNotAcceptable, errorResponse)
+}
+
+func ResponseRequestEntityTooLarge413SubscriptionExceed(w http.ResponseWriter, num int) {
+ errorMessage := ErrorMessage{"1", "the number of subscriptions greater than " + strconv.Itoa(num)}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusRequestEntityTooLarge, errorResponse)
+}
+
+func ResponseRequestEntityTooLarge413BodyToLarge(w http.ResponseWriter) {
+ errorMessage := ErrorMessage{"2", "the request entity too large"}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusRequestEntityTooLarge, errorResponse)
+}
+
+func ResponseRequestURITooLong414NRMNumExceed(w http.ResponseWriter, num int) {
+ errorMessage := ErrorMessage{"1", "the number of NRM rmUIDs greater than " + strconv.Itoa(num)}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusRequestURITooLong, errorResponse)
+}
+
+func ResponseRequestURITooLong414AlarmNumExceed(w http.ResponseWriter, num int) {
+ errorMessage := ErrorMessage{"2", "the number of alarmIds greater than " + strconv.Itoa(num)}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusRequestURITooLong, errorResponse)
+}
+
+func ResponseRequestURITooLong414PMNumExceed(w http.ResponseWriter, num int) {
+ errorMessage := ErrorMessage{"3", "the number of PM rmUIDs greater than " + strconv.Itoa(num)}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusRequestURITooLong, errorResponse)
+}
+
+func ResponseRequestURITooLong414UriTooLong(w http.ResponseWriter) {
+ errorMessage := ErrorMessage{"3", "request URI too long"}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusRequestURITooLong, errorResponse)
+}
+
+func ResponseUnsupportedMediaType415(w http.ResponseWriter) {
+ errorMessage := ErrorMessage{"1", "unsupported media type"}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusUnsupportedMediaType, errorResponse)
+}
+
+func ResponseInternalServerError500NFConnectRefused(w http.ResponseWriter) {
+ errorMessage := ErrorMessage{"1", "internal server error, NF connnect refused"}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusInternalServerError, errorResponse)
+}
+
+func ResponseInternalServerError500DatabaseOperationFailed(w http.ResponseWriter) {
+ errorMessage := ErrorMessage{"2", "internal server error, database opration failed"}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusInternalServerError, errorResponse)
+}
+
+func ResponseInternalServerError500ProcessError(w http.ResponseWriter, err error) {
+ em := fmt.Sprintf("internal server error: %v", err)
+ errorMessage := ErrorMessage{"3", em}
+ errorResponse := ErrorResponse{errorMessage}
+ ResponseWithJson(w, http.StatusInternalServerError, errorResponse)
+}
+
+func ResponseWithJson(w http.ResponseWriter, code int, payload interface{}) {
+ log.Trace("payload: ", payload)
+
+ response, _ := json.Marshal(payload)
+ SetResponseHeader(w)
+ w.WriteHeader(code)
+ w.Write(response)
+ log.Trace("Response Code:", code)
+ log.Trace("Response Body:", string(response))
+}
+
+func ResponseWithZip(w http.ResponseWriter, payload interface{}) {
+ response, _ := json.Marshal(payload)
+ SetResponseHeader(w)
+ w.WriteHeader(http.StatusOK)
+ w.Write(response)
+ log.Trace("Response Body:", string(response))
+}
+
+func TransportResponse(w http.ResponseWriter, code int, payload []byte) {
+ var tempBody, transBody interface{}
+ switch code {
+ case http.StatusOK, http.StatusCreated, http.StatusNoContent, http.StatusAccepted:
+ json.Unmarshal(payload, &tempBody)
+ transBody = DataResponse{tempBody}
+ default:
+ json.Unmarshal(payload, &tempBody)
+ transBody = ErrorResponse{tempBody}
+ }
+ response, _ := json.Marshal(transBody)
+ log.Trace("transBody: ", transBody)
+ SetResponseHeader(w)
+ w.WriteHeader(code)
+ w.Write(response)
+ log.Trace("response: ", string(response))
+}
+
+func ResponseWithUnsortJson(w http.ResponseWriter, code int, payload map[string]interface{}) {
+ var om global.OrderedMap
+ om.Map = payload
+ response, _ := om.MarshalJson()
+ log.Trace("payload: ", payload)
+ SetResponseHeader(w)
+ w.WriteHeader(code)
+ w.Write(response)
+ log.Trace("response: ", string(response))
+}
+
+func ResponseErrorWithJson(w http.ResponseWriter, code int, nameValue interface{}) {
+ response := make(map[string]interface{})
+ response["error"] = nameValue
+ ResponseWithJson(w, code, response)
+}
+
+func SetCommonResponseHeader(w http.ResponseWriter) {
+ // 设置Vary头部
+ w.Header().Set("Vary", "Origin")
+ w.Header().Set("Keep-Alive", "timeout=5")
+ // To solve cross domain issue
+ w.Header().Set("Access-Control-Allow-Origin", "*")
+ w.Header().Set("Access-Control-Allow-Methods", "*")
+ w.Header().Set("Access-Control-Allow-Headers", "*")
+ w.Header().Set("Access-Control-Allow-Credentials", "true")
+ // 响应最大时间值
+ w.Header().Set("Access-Control-Max-Age", "31536000")
+}
+
+func SetResponseHeader(w http.ResponseWriter) {
+ w.Header().Set("Content-Type", "application/json;charset=UTF-8")
+ SetCommonResponseHeader(w)
+}
+
+// Creates a new file upload http request with optional extra params
+func ResponseUploadFile(w http.ResponseWriter, code int, params map[string]string, paramName, path string) {
+ file, err := os.Open(path)
+ if err != nil {
+ log.Errorf("Failed to open: %v", err)
+ ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ defer file.Close()
+
+ body := &bytes.Buffer{}
+ writer := multipart.NewWriter(body)
+ part, err := writer.CreateFormFile(paramName, filepath.Base(path))
+ if err != nil {
+ log.Error("Failed to CreateFormFile:", err)
+ ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ _, err = io.Copy(part, file)
+ if err != nil {
+ log.Error("Failed to Copy:", err)
+ ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ for key, val := range params {
+ _ = writer.WriteField(key, val)
+ }
+
+ err = writer.Close()
+ if err != nil {
+ log.Error("Failed to Close:", err)
+ ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ SetCommonResponseHeader(w)
+ w.Header().Set("Content-Type", writer.FormDataContentType())
+ w.WriteHeader(code)
+ w.Write(body.Bytes())
+}
+
+func ResponseFile(w http.ResponseWriter, code int, filePath string) {
+ fileBytes, err := os.ReadFile(filePath)
+ if err != nil {
+ log.Error("Failed to ReadFile:", err)
+ ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ SetCommonResponseHeader(w)
+ w.Header().Set("Content-Type", "application/octet-stream")
+ w.WriteHeader(code)
+ w.Write(fileBytes)
+}
+
+func ResponseFileWithNameAndMD5(w http.ResponseWriter, code int, fileName, path, md5Sum string) {
+ filePath := path + "/" + fileName
+ fileBytes, err := os.ReadFile(filePath)
+ if err != nil {
+ log.Error("Failed to ReadFile:", err)
+ ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+ SetCommonResponseHeader(w)
+ encodedFileName := url.PathEscape(fileName)
+ w.Header().Set("Content-Disposition", `attachment; filename="`+encodedFileName+`"`)
+ w.Header().Set("Content-Type", "application/octet-stream")
+ w.Header().Set("User-File", fileName)
+ w.Header().Set("MD5-Sum", md5Sum)
+ w.WriteHeader(code)
+ w.Write(fileBytes)
+}
+
+func ResponseHtmlContent(w http.ResponseWriter, code int, filePath string) {
+ htmlContent, err := os.ReadFile(filePath)
+ if err != nil {
+ log.Error("Failed to ReadFile:", err)
+ ResponseInternalServerError500ProcessError(w, err)
+ return
+ }
+
+ SetCommonResponseHeader(w)
+ w.Header().Set("Content-Type", "text/html")
+ w.WriteHeader(code)
+ w.Write(htmlContent)
+}
+
+// RouterItem 路由项
+type RouterItem struct {
+ Method string
+ Pattern string
+ Handler http.HandlerFunc
+ Middleware mux.MiddlewareFunc
+}
diff --git a/lib/session/session.go b/lib/session/session.go
new file mode 100644
index 0000000..26cb03c
--- /dev/null
+++ b/lib/session/session.go
@@ -0,0 +1,168 @@
+package session
+
+import (
+ "crypto/rand"
+ "encoding/base64"
+ "fmt"
+ "io"
+ "net/http"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "nms_nbi/lib/log"
+ "nms_nbi/lib/oauth"
+ "nms_nbi/restagent/config"
+)
+
+// SessionMgr session manager
+type SessManager struct {
+ name string
+ expires int64
+ lock sync.RWMutex
+ sessions map[string]*Session
+}
+
+// Session
+type Session struct {
+ token string
+ time time.Time
+ permission []bool
+ values map[interface{}]interface{}
+}
+
+// NewSessionMgr create session manager
+func NewSessManager(name string) *SessManager {
+ smgr := &SessManager{name: name, expires: (int64)(config.GetExpiresFromConfig()), sessions: make(map[string]*Session)}
+ go smgr.SessionGC()
+ return smgr
+}
+
+// NewSession create session
+func (smgr *SessManager) NewSession(w http.ResponseWriter, r *http.Request, plist []bool) string {
+ smgr.lock.Lock()
+ defer smgr.lock.Unlock()
+ token := oauth.GenRandToken("omc") // Generate new token to session ID
+ session := &Session{token: token, time: time.Now(), permission: plist, values: make(map[interface{}]interface{})}
+ smgr.sessions[token] = session
+
+ return token
+}
+
+// EndSession
+func (smgr *SessManager) EndSession(w http.ResponseWriter, r *http.Request) {
+ token := smgr.GetTokenFromHttpRequest(r)
+
+ smgr.lock.Lock()
+ defer smgr.lock.Unlock()
+ delete(smgr.sessions, token)
+}
+
+// Handshake session, restart session
+func (smgr *SessManager) ShakeSession(token string) bool {
+
+ smgr.lock.Lock()
+ defer smgr.lock.Unlock()
+ for _, s := range smgr.sessions {
+ if token == s.token {
+ log.Debug("session time:", s.time)
+ s.time = time.Now()
+ return true
+ }
+ }
+ return false
+}
+
+// EndSessionByID end the session by session ID
+func (smgr *SessManager) DeleteSession(token string) {
+ smgr.lock.Lock()
+ defer smgr.lock.Unlock()
+ delete(smgr.sessions, token)
+}
+
+// SetSessionValue set value fo session
+func (smgr *SessManager) SetSessionValue(token string, key interface{}, value interface{}) error {
+ smgr.lock.Lock()
+ defer smgr.lock.Unlock()
+ if session, ok := smgr.sessions[token]; ok {
+ session.values[key] = value
+ return nil
+ }
+ return fmt.Errorf("invalid session ID")
+}
+
+// GetSessionValue get value fo session
+func (smgr *SessManager) GetSessionValue(token string, key interface{}) (interface{}, error) {
+ smgr.lock.RLock()
+ defer smgr.lock.RUnlock()
+ if session, ok := smgr.sessions[token]; ok {
+ if val, ok := session.values[key]; ok {
+ return val, nil
+ }
+ }
+ return nil, fmt.Errorf("invalid session ID")
+}
+
+func (smgr *SessManager) GetTokenFromHttpRequest(r *http.Request) string {
+ for k, v := range r.Header {
+ if strings.ToLower(k) == "accesstoken" && len(v) != 0 {
+ log.Debug("AccessToken:", v[0])
+ return v[0]
+ }
+ }
+ return ""
+}
+
+// IsValidToken check token is valid or not
+func (smgr *SessManager) IsValidToken(token string) bool {
+
+ smgr.lock.Lock()
+ defer smgr.lock.Unlock()
+ if _, ok := smgr.sessions[token]; ok {
+ return true
+ }
+ return false
+}
+
+// IsCarriedToken check token is carried
+func (smgr *SessManager) IsCarriedToken(r *http.Request) (string, bool) {
+
+ token := smgr.GetTokenFromHttpRequest(r)
+ if token == "" {
+ return "", false
+ }
+ return token, true
+}
+
+// GetPermissionFromSession get permission from session by token
+func (smgr *SessManager) GetPermissionFromSession(token string) []bool {
+
+ if s, ok := smgr.sessions[token]; ok {
+ return s.permission
+ }
+ return nil
+}
+
+// SessionGC maintain session
+func (smgr *SessManager) SessionGC() {
+ smgr.lock.Lock()
+ defer smgr.lock.Unlock()
+ for token, session := range smgr.sessions {
+ if session.time.Unix()+smgr.expires < time.Now().Unix() {
+ delete(smgr.sessions, token)
+ }
+ }
+
+ time.AfterFunc(time.Duration(smgr.expires)*time.Second, func() { smgr.SessionGC() })
+}
+
+// NewSessionID generate unique ID
+func (smgr *SessManager) NewSessionID() string {
+ b := make([]byte, 32)
+ if _, err := io.ReadFull(rand.Reader, b); err != nil {
+ nano := time.Now().UnixNano()
+ return strconv.FormatInt(nano, 10)
+ }
+ return base64.URLEncoding.EncodeToString(b)
+}
diff --git a/lib/wsinfo/client.go b/lib/wsinfo/client.go
new file mode 100644
index 0000000..36201cd
--- /dev/null
+++ b/lib/wsinfo/client.go
@@ -0,0 +1,45 @@
+package wsinfo
+
+import (
+ "github.com/gorilla/websocket"
+)
+
+type Client struct {
+ ID string
+ Socket *websocket.Conn
+ Msg chan []byte
+}
+
+func NewWsClient(ID string, socket *websocket.Conn) *Client {
+ return &Client{
+ ID: ID,
+ Socket: socket,
+ Msg: make(chan []byte, 100),
+ }
+}
+
+func (c *Client) Read() {
+ defer func() {
+ close(c.Msg)
+ }()
+ for {
+ _, message, err := c.Socket.ReadMessage()
+ if err != nil {
+ return
+ }
+ ProcessData(c, message)
+ }
+}
+
+func (c *Client) Write() {
+ defer func() {
+ c.Socket.Close()
+ }()
+ for {
+ message, ok := <-c.Msg
+ if !ok {
+ return
+ }
+ _ = c.Socket.WriteMessage(websocket.TextMessage, message)
+ }
+}
diff --git a/lib/wsinfo/process_data.go b/lib/wsinfo/process_data.go
new file mode 100644
index 0000000..d501acc
--- /dev/null
+++ b/lib/wsinfo/process_data.go
@@ -0,0 +1,383 @@
+package wsinfo
+
+import (
+ "encoding/json"
+ "fmt"
+ "sort"
+ "strings"
+ "sync"
+ "time"
+
+ "nms_nbi/lib/log"
+
+ "github.com/shirou/gopsutil/v3/host"
+ "github.com/shirou/gopsutil/v3/net"
+ "github.com/shirou/gopsutil/v3/process"
+)
+
+type WsInput struct {
+ Type string `json:"type"`
+ DownloadProgress
+ PsProcessConfig
+ SSHSessionConfig
+ NetConfig
+}
+
+type DownloadProgress struct {
+ Keys []string `json:"keys"`
+}
+
+type PsProcessConfig struct {
+ Pid int32 `json:"pid"`
+ Name string `json:"name"`
+ Username string `json:"username"`
+}
+
+type SSHSessionConfig struct {
+ LoginUser string `json:"loginUser"`
+ LoginIP string `json:"loginIP"`
+}
+
+type NetConfig struct {
+ Port uint32 `json:"port"`
+ ProcessName string `json:"processName"`
+ ProcessID int32 `json:"processID"`
+}
+
+type PsProcessData struct {
+ PID int32 `json:"PID"`
+ Name string `json:"name"`
+ PPID int32 `json:"PPID"`
+ Username string `json:"username"`
+ Status string `json:"status"`
+ StartTime string `json:"startTime"`
+ NumThreads int32 `json:"numThreads"`
+ NumConnections int `json:"numConnections"`
+ CpuPercent string `json:"cpuPercent"`
+
+ DiskRead string `json:"diskRead"`
+ DiskWrite string `json:"diskWrite"`
+ CmdLine string `json:"cmdLine"`
+
+ Rss string `json:"rss"`
+ VMS string `json:"vms"`
+ HWM string `json:"hwm"`
+ Data string `json:"data"`
+ Stack string `json:"stack"`
+ Locked string `json:"locked"`
+ Swap string `json:"swap"`
+
+ CpuValue float64 `json:"cpuValue"`
+ RssValue uint64 `json:"rssValue"`
+
+ Envs []string `json:"envs"`
+
+ OpenFiles []process.OpenFilesStat `json:"openFiles"`
+ Connects []processConnect `json:"connects"`
+}
+
+type processConnect struct {
+ Type string `json:"type"`
+ Status string `json:"status"`
+ Laddr net.Addr `json:"localaddr"`
+ Raddr net.Addr `json:"remoteaddr"`
+ PID int32 `json:"PID"`
+ Name string `json:"name"`
+}
+
+type ProcessConnects []processConnect
+
+func (p ProcessConnects) Len() int {
+ return len(p)
+}
+
+func (p ProcessConnects) Less(i, j int) bool {
+ return p[i].PID < p[j].PID
+}
+
+func (p ProcessConnects) Swap(i, j int) {
+ p[i], p[j] = p[j], p[i]
+}
+
+type sshSession struct {
+ Username string `json:"username"`
+ PID int32 `json:"PID"`
+ Terminal string `json:"terminal"`
+ Host string `json:"host"`
+ LoginTime string `json:"loginTime"`
+}
+
+func ProcessData(c *Client, inputMsg []byte) {
+ wsInput := &WsInput{}
+ err := json.Unmarshal(inputMsg, wsInput)
+ if err != nil {
+ log.Errorf("unmarshal wsInput error,err %s", err.Error())
+ return
+ }
+ switch wsInput.Type {
+ case "ps":
+ res, err := getProcessData(wsInput.PsProcessConfig)
+ if err != nil {
+ return
+ }
+ c.Msg <- res
+ case "ssh":
+ res, err := getSSHSessions(wsInput.SSHSessionConfig)
+ if err != nil {
+ return
+ }
+ c.Msg <- res
+ case "net":
+ res, err := getNetConnections(wsInput.NetConfig)
+ if err != nil {
+ return
+ }
+ c.Msg <- res
+ }
+
+}
+
+type Process struct {
+ Total uint64 `json:"total"`
+ Written uint64 `json:"written"`
+ Percent float64 `json:"percent"`
+ Name string `json:"name"`
+}
+
+const (
+ b = uint64(1)
+ kb = 1024 * b
+ mb = 1024 * kb
+ gb = 1024 * mb
+)
+
+func formatBytes(bytes uint64) string {
+ switch {
+ case bytes < kb:
+ return fmt.Sprintf("%dB", bytes)
+ case bytes < mb:
+ return fmt.Sprintf("%.2fKB", float64(bytes)/float64(kb))
+ case bytes < gb:
+ return fmt.Sprintf("%.2fMB", float64(bytes)/float64(mb))
+ default:
+ return fmt.Sprintf("%.2fGB", float64(bytes)/float64(gb))
+ }
+}
+
+func getProcessData(processConfig PsProcessConfig) (res []byte, err error) {
+ var processes []*process.Process
+ processes, err = process.Processes()
+ if err != nil {
+ return
+ }
+
+ var (
+ result []PsProcessData
+ resultMutex sync.Mutex
+ wg sync.WaitGroup
+ numWorkers = 4
+ )
+
+ handleData := func(proc *process.Process) {
+ procData := PsProcessData{
+ PID: proc.Pid,
+ }
+ if processConfig.Pid > 0 && processConfig.Pid != proc.Pid {
+ return
+ }
+ if procName, err := proc.Name(); err == nil {
+ procData.Name = procName
+ } else {
+ procData.Name = ""
+ }
+ if processConfig.Name != "" && !strings.Contains(procData.Name, processConfig.Name) {
+ return
+ }
+ if username, err := proc.Username(); err == nil {
+ procData.Username = username
+ }
+ if processConfig.Username != "" && !strings.Contains(procData.Username, processConfig.Username) {
+ return
+ }
+ procData.PPID, _ = proc.Ppid()
+ statusArray, _ := proc.Status()
+ if len(statusArray) > 0 {
+ procData.Status = strings.Join(statusArray, ",")
+ }
+ createTime, procErr := proc.CreateTime()
+ if procErr == nil {
+ t := time.Unix(createTime/1000, 0)
+ procData.StartTime = t.Format("2006-1-2 15:04:05")
+ }
+ procData.NumThreads, _ = proc.NumThreads()
+ connections, procErr := proc.Connections()
+ if procErr == nil {
+ procData.NumConnections = len(connections)
+ for _, conn := range connections {
+ if conn.Laddr.IP != "" || conn.Raddr.IP != "" {
+ procData.Connects = append(procData.Connects, processConnect{
+ Status: conn.Status,
+ Laddr: conn.Laddr,
+ Raddr: conn.Raddr,
+ })
+ }
+ }
+ }
+ procData.CpuValue, _ = proc.CPUPercent()
+ procData.CpuPercent = fmt.Sprintf("%.2f", procData.CpuValue) + "%"
+ menInfo, procErr := proc.MemoryInfo()
+ if procErr == nil {
+ procData.Rss = formatBytes(menInfo.RSS)
+ procData.RssValue = menInfo.RSS
+ procData.Data = formatBytes(menInfo.Data)
+ procData.VMS = formatBytes(menInfo.VMS)
+ procData.HWM = formatBytes(menInfo.HWM)
+ procData.Stack = formatBytes(menInfo.Stack)
+ procData.Locked = formatBytes(menInfo.Locked)
+ procData.Swap = formatBytes(menInfo.Swap)
+ } else {
+ procData.Rss = "--"
+ procData.Data = "--"
+ procData.VMS = "--"
+ procData.HWM = "--"
+ procData.Stack = "--"
+ procData.Locked = "--"
+ procData.Swap = "--"
+
+ procData.RssValue = 0
+ }
+ ioStat, procErr := proc.IOCounters()
+ if procErr == nil {
+ procData.DiskWrite = formatBytes(ioStat.WriteBytes)
+ procData.DiskRead = formatBytes(ioStat.ReadBytes)
+ } else {
+ procData.DiskWrite = "--"
+ procData.DiskRead = "--"
+ }
+ procData.CmdLine, _ = proc.Cmdline()
+ procData.OpenFiles, _ = proc.OpenFiles()
+ procData.Envs, _ = proc.Environ()
+
+ resultMutex.Lock()
+ result = append(result, procData)
+ resultMutex.Unlock()
+ }
+
+ chunkSize := (len(processes) + numWorkers - 1) / numWorkers
+ for i := 0; i < numWorkers; i++ {
+ wg.Add(1)
+ start := i * chunkSize
+ end := (i + 1) * chunkSize
+ if end > len(processes) {
+ end = len(processes)
+ }
+
+ go func(start, end int) {
+ defer wg.Done()
+ for j := start; j < end; j++ {
+ handleData(processes[j])
+ }
+ }(start, end)
+ }
+
+ wg.Wait()
+
+ sort.Slice(result, func(i, j int) bool {
+ return result[i].PID < result[j].PID
+ })
+ res, err = json.Marshal(result)
+ return
+}
+
+func getSSHSessions(config SSHSessionConfig) (res []byte, err error) {
+ var (
+ result []sshSession
+ users []host.UserStat
+ processes []*process.Process
+ )
+ processes, err = process.Processes()
+ if err != nil {
+ return
+ }
+ users, err = host.Users()
+ if err != nil {
+ return
+ }
+ for _, proc := range processes {
+ name, _ := proc.Name()
+ if name != "sshd" || proc.Pid == 0 {
+ continue
+ }
+ connections, _ := proc.Connections()
+ for _, conn := range connections {
+ for _, user := range users {
+ if user.Host == "" {
+ continue
+ }
+ if conn.Raddr.IP == user.Host {
+ if config.LoginUser != "" && !strings.Contains(user.User, config.LoginUser) {
+ continue
+ }
+ if config.LoginIP != "" && !strings.Contains(user.Host, config.LoginIP) {
+ continue
+ }
+ if terminal, err := proc.Cmdline(); err == nil {
+ if strings.Contains(terminal, user.Terminal) {
+ session := sshSession{
+ Username: user.User,
+ Host: user.Host,
+ Terminal: user.Terminal,
+ PID: proc.Pid,
+ }
+ t := time.Unix(int64(user.Started), 0)
+ session.LoginTime = t.Format("2006-1-2 15:04:05")
+ result = append(result, session)
+ }
+ }
+ }
+ }
+ }
+ }
+ res, err = json.Marshal(result)
+ return
+}
+
+var netTypes = [...]string{"tcp", "udp"}
+
+func getNetConnections(config NetConfig) (res []byte, err error) {
+ var (
+ result []processConnect
+ proc *process.Process
+ )
+ for _, netType := range netTypes {
+ connections, _ := net.Connections(netType)
+ if err == nil {
+ for _, conn := range connections {
+ if config.ProcessID > 0 && config.ProcessID != conn.Pid {
+ continue
+ }
+ proc, err = process.NewProcess(conn.Pid)
+ if err == nil {
+ name, _ := proc.Name()
+ if name != "" && config.ProcessName != "" && !strings.Contains(name, config.ProcessName) {
+ continue
+ }
+ if config.Port > 0 && config.Port != conn.Laddr.Port && config.Port != conn.Raddr.Port {
+ continue
+ }
+ result = append(result, processConnect{
+ Type: netType,
+ Status: conn.Status,
+ Laddr: conn.Laddr,
+ Raddr: conn.Raddr,
+ PID: conn.Pid,
+ Name: name,
+ })
+ }
+
+ }
+ }
+ }
+ res, err = json.Marshal(result)
+ return
+}
diff --git a/makefile b/makefile
new file mode 100644
index 0000000..77c8324
--- /dev/null
+++ b/makefile
@@ -0,0 +1,166 @@
+# Makefile for AGrandTech EMS project
+
+ProjectL = omc
+ProjectU = OMC
+PROJECT = $(ProjectL)
+VERSION = 2.2403.1
+RelDate = `date +%Y%m%d`
+Release = $(RelDate)
+RelVer = $(VERSION)-$(RelDate)
+PLATFORM = amd64
+ARMPLATFORM = aarch64
+GitLocalRoot = $(HOME)/local.git
+EmsBuildRoot=$(GitLocalRoot)/build.ems
+BuildDir = $(EmsBuildRoot)/build
+BuildOMCDir = $(BuildDir)/usr/local/omc
+BuildOMCBinDir = $(BuildOMCDir)/bin
+BuildOMCEtcDir = $(BuildOMCDir)/etc
+BuildLibDir = $(BuildDir)/lib
+BuildLibSystemDir = $(BuildLibDir)/systemd/system
+DebBuildDir = $(EmsBuildRoot)/debbuild
+DebFEBuildDir = $(EmsBuildRoot)/debbuild/usr/local/omc/htdocs
+DebBuild2204Dir = $(EmsBuildRoot)/debbuild/22.04
+DebFEBuild2204Dir = $(EmsBuildRoot)/debbuild/22.04/usr/local/omc/htdocs
+RpmBuildDir = $(EmsBuildRoot)/rpmbuild
+RpmFEBuildDir = $(RpmBuildDir)/BUILD/usr/local/omc/htdocs
+OmcInstallDir = /usr/local/omc
+ReleaseDir = $(EmsBuildRoot)/release
+EmsBEDir = $(GitLocalRoot)/nms_nbi
+ConfigEtcDir = $(EmsBEDir)/config/etc
+ConfigSystemdDir = $(EmsBEDir)/config/systemd
+EmsFEDir = $(GitLocalRoot)/fe.ems
+EmsFEVue3Dir = $(GitLocalRoot)/fe.ems.vue3/dist
+LibDir = $(EmsBEDir)/lib
+RESTAGENT = restagent
+CRONTASK = crontask
+SshSvcBin = sshsvc
+CapTraceBin = captrace
+Data2htmlBin = data2html
+NBI_ALARM = nbi_alarm
+NBI_AGENT = nbi_agent
+4A_AGENT = 4a_agent
+RestagentDir = $(EmsBEDir)/$(RESTAGENT)
+CrontaskDir = $(EmsBEDir)/$(CRONTASK)
+SshSvcDir = $(EmsBEDir)/$(SshSvcBin)
+CapTraceDir = $(EmsBEDir)/$(CapTraceBin)
+Data2htmlDir = $(EmsBEDir)/$(Data2htmlBin)
+DBSQLSrcDir = $(EmsBEDir)/database
+MiscDir = $(EmsBEDir)/misc
+FrontBuildDir = $(BuildOMCDir)/htdocs
+FrontSrcDir = $(EmsBEDir)/front
+ReleaseDebs = $(ReleaseDir)/debs/$(PLATFORM)
+CrontaskSize = 27788951
+RestagentSize = 29525312
+BinWriterDir = $(HOME)/bin
+ifeq ($(shell grep VERSION_ID /etc/os-release), VERSION_ID="22.04")
+DebBuildDir := $(DebBuild2204Dir)
+DebFEBuildDir := $(DebFEBuild2204Dir)
+#DebPkgFile := $(PROJECT)-$(VERSION)-22.04-$(Release).$(PLATFORM).deb
+DebPkgFile := $(PROJECT)-r$(VERSION)-$(Release)-ub22.deb
+else ifeq ($(shell grep VERSION_ID /etc/os-release), VERSION_ID="18.04")
+DebPkgFile := $(PROJECT)-r$(VERSION)-$(Release)-ub18.deb
+endif
+
+.PHONY: all $(RESTAGENT) $(CRONTASK) $(SshSvcBin) $(CapTraceBin) $(Data2htmlBin)
+all: $(RESTAGENT) $(CRONTASK) $(SshSvcBin) $(CapTraceBin) $(Data2htmlBin)
+ cd $(RestagentDir)
+ go build -o $(RESTAGENT) -v -ldflags "-X 'nms_nbi/lib/conifg.Version=$(RelVer)' \
+ -X '$(LibDir)/conifg.BuildTime=`date`' \
+ -X '$(LibDir)/conifg.GoVer=`go version`'"
+ cd $(CrontaskDir)
+ go build -o $(CRONTASK) -v -ldflags "-X '$(LibDir)/conifg.Version=$(RelVer)' \
+ -X '$(LibDir)/conifg.BuildTime=`date`' \
+ -X '$(LibDir)/conifg.GoVer=`go version`'"
+ cd $(SshSvcDir)
+ go build -o $(SshSvcBin) -v -ldflags "-X '$(LibDir)/conifg.Version=$(RelVer)' \
+ -X '$(LibDir)/conifg.BuildTime=`date`' \
+ -X '$(LibDir)/conifg.GoVer=`go version`'"
+ cd $(CapTraceDir)
+ go build -o $(CapTraceBin) -v -ldflags "-X '$(LibDir)/conifg.Version=$(RelVer)' \
+ -X '$(LibDir)/conifg.BuildTime=`date`' \
+ -X '$(LibDir)/conifg.GoVer=`go version`'"
+ cd $(Data2htmlDir)
+ go build -o $(Data2htmlBin) -v -ldflags "-X '$(LibDir)/conifg.Version=$(RelVer)' \
+ -X '$(LibDir)/conifg.BuildTime=`date`' \
+ -X '$(LibDir)/conifg.GoVer=`go version`'"
+
+clean:
+ rm ./restagent/$(RESTAGENT) ./crontask/$(CRONTASK) $(SshSvcDir)/$(SshSvcBin) $(CapTraceDir)/$(CapTraceBin) $(Data2htmlDir)/$(Data2htmlBin)
+
+dist:
+ tar -zvcPf $(ReleaseDir)/$(PROJECT)-src-$(VERSION).tar.gz \
+ ../lib \
+ ../restagent \
+ ../crontask \
+ ../initems \
+ ../database \
+ ../docs \
+ ../misc \
+ ../config \
+ --exclude=../restagent/restagent \
+ --exclude=../crontask/crontask \
+ --exclude=../initems/initems
+
+deb: $(BINNAME)
+ # clear build cache
+ rm -rf $(FrontBuildDir)/front/*
+ rm -rf $(DebFEBuildDir)/front/*
+ rm -rf $(DebBuildDir)/usr/local/omc/etc/db/*
+
+ cp -rf $(RestagentDir)/$(RESTAGENT) $(BuildOMCBinDir)
+ cp -rf $(CrontaskDir)/$(CRONTASK) $(BuildOMCBinDir)
+ cp -rf $(SshSvcDir)/$(SshSvcBin) $(BuildOMCBinDir)
+ cp -rf $(CapTraceDir)/$(CapTraceBin) $(BuildOMCBinDir)
+ cp -rf $(Data2htmlDir)/$(Data2htmlBin) $(BuildOMCBinDir)
+ cp -rf $(MiscDir)/* $(BuildOMCBinDir)
+ cp -rf $(ConfigEtcDir)/* $(BuildOMCEtcDir)
+ rm -rf $(BuildOMCEtcDir)/db/*
+ cp -rf $(DBSQLSrcDir)/* $(BuildOMCEtcDir)/db/
+ cp -rf $(ConfigSystemdDir)/* $(BuildLibSystemDir)
+ cp -rf $(EmsFEVue3Dir)/* $(FrontBuildDir)/front >/dev/null
+ chmod 755 $(BuildOMCBinDir)/*
+ chmod 755 $(DebBuildDir)/DEBIAN/preinst
+ chmod 755 $(DebBuildDir)/DEBIAN/postinst
+ chmod 755 $(DebBuildDir)/DEBIAN/postrm
+ cp -rf $(BuildDir)/* $(DebBuildDir)/
+ #cp -rf $(BuildDir)/nginx/* $(DebBuildDir)/etc/nginx/conf.d
+ #cp -rf $(BuildDir)/systemd/*.service $(DebBuildDir)/lib/systemd/system/
+ chmod +x $(DebBuildDir)/usr/local/omc/bin/*
+ #sed -i.bak 's/YYYYMMDD/$(shell date +"%Y%m%d")/g' $(DebBuildDir)/DEBIAN/control
+ sed -i 's/YYYYMMDD/$(shell date +"%Y%m%d")/g' $(DebBuildDir)/DEBIAN/control
+ perl -0777 -i -pe 's/ne:\n user: root/ne:\n user: agtuser/g' $(DebBuildDir)/usr/local/omc/etc/default/restconf.yaml
+ dpkg -b $(DebBuildDir) $(ReleaseDebs)/$(DebPkgFile)
+
+rpm: $(BINNAME)
+ # clear build cache
+ rm -rf $(FrontBuildDir)/front/*
+ rm -rf $(RpmFEBuildDir)/front/*
+ rm -rf $(RpmBuildDir)/BUILD/usr/local/omc/etc/db/*
+
+ cp -rf $(RestagentDir)/$(RESTAGENT) $(BuildOMCBinDir)
+ cp -rf $(CrontaskDir)/$(CRONTASK) $(BuildOMCBinDir)
+ cp -rf $(SshSvcDir)/$(SshSvcBin) $(BuildOMCBinDir)
+ cp -rf $(CapTraceDir)/$(CapTraceBin) $(BuildOMCBinDir)
+ cp -rf $(Data2htmlDir)/$(Data2htmlBin) $(BuildOMCBinDir)
+# $(BinWriterDir)/binWriter $(BuildOMCBinDir)/$(RESTAGENT) $(RestagentSize)
+# $(BinWriterDir)/binWriter $(BuildOMCBinDir)/$(CRONTASK) $(CrontaskSize)
+# cp -rf $(MiscDir)/ne-hosts $(BuildOMCBinDir)
+# cp -rf ./nbi/$(NBI_ALARM)/bin/$(NBI_ALARM) $(BinDir2)
+# cp -rf ./nbi/$(NBI_AGENT)/bin/$(NBI_AGENT) $(BinDir2)
+# cp -rf ./$(4A_AGENT)/bin/$(4A_AGENT) $(BinDir2)
+ cp -rf $(MiscDir)/* $(BuildOMCBinDir)
+# rm -rf $(BinDir2)/ne-hosts
+ cp -rf $(ConfigEtcDir)/* $(BuildOMCEtcDir)
+ rm -rf $(BuildOMCEtcDir)/db/*
+ cp -rf $(DBSQLSrcDir)/* $(BuildOMCEtcDir)/db/
+ cp -rf $(ConfigSystemdDir)/* $(BuildLibSystemDir)
+ #unzip $(FrontSrcDir)/front.zip -d $(FrontBuildDir) >/dev/null
+ mkdir -p $(FrontBuildDir)/front
+ cp -rf $(EmsFEVue3Dir)/* $(FrontBuildDir)/front >/dev/null
+ chmod 755 $(BuildOMCBinDir)/*
+# chmod 755 $(BinDir2)/*
+ cp -rf $(BuildDir)/* $(RpmBuildDir)/BUILD/
+ #cp -rf $(BuildDir)/nginx $(RpmBuildDir)/BUILD/etc/
+ #cp -rf $(BuildDir)/systemd $(RpmBuildDir)/BUILD/lib/
+ cd $(RpmBuildDir)
+ rpmbuild -bb -D "_topdir $(RpmBuildDir)" $(RpmBuildDir)/SPECS/omc.spec
diff --git a/mkpkg.sh b/mkpkg.sh
new file mode 100644
index 0000000..41f6519
--- /dev/null
+++ b/mkpkg.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+
+ProcList="restagent crontask sshsvc captrace data2html"
+ProjectL=omc
+VERSION=2.2403.1
+RelDate=`date +%Y%m%d`
+Release=${RelDate}
+RelVer=${VERSION}-${RelDate}
+Ky10Arch=ky10.aarch64
+RpmPkgName=${ProjectL}-${RelVer}.ky10.aarch64.rpm
+RpmPkgRename=${ProjectL}-r${RelVer}-ky10.rpm
+DebPkgName=${ProjectL}-r${RelVer}-ub*.deb
+GitLocalRoot=${HOME}/local.git
+EmsBEDir=${GitLocalRoot}/nms_nbi
+EmsBuildRoot=${GitLocalRoot}/build.ems
+RpmArch=`arch`
+RpmsDir=${EmsBuildRoot}/rpmbuild/RPMS/
+ReleaseDir=${EmsBuildRoot}/release
+DumpToolDir=${EmsBEDir}/tools/misc
+
+if [[ ${RpmArch} =~ "x86_64" ]];then
+ RelArch=amd64
+elif [[ $RpmArch =~ "aarch64" ]];then
+ RelArch=arm64
+fi
+
+cd $EmsBEDir
+for procName in $ProcList;do
+ cd $EmsBEDir/$procName
+ echo "Make $procName ..."
+ make
+done
+
+#Get SQL from database
+if [ "$2" = "db" ]; then
+ cd ${DumpToolDir}
+ chmod +x ${DumpToolDir}/dumpdb.sh
+ ${DumpToolDir}/dumpdb.sh
+fi
+
+case "$1" in
+ rpm)
+ cd $EmsBEDir
+ make rpm
+ mv -f $RpmsDir/$RpmArch/$RpmPkgName $ReleaseDir/"$1"s/$RelArch/$RpmPkgRename
+ cd $ReleaseDir/"$1"s/$RelArch
+ rm -f omc-md5sum.txt
+# rpm --addsign *.rpm
+ md5sum $RpmPkgRename >omc-md5sum.txt
+ ;;
+ deb)
+ cd $EmsBEDir
+ make deb
+ cd $ReleaseDir/"$1"s/$RelArch
+ rm -f omc-md5sum.txt
+# rpm --addsign *.rpm
+ md5sum $DebPkgName >omc-md5sum.txt
+ ;;
+ *)
+ echo "mkpkg"
+ echo "Usage: $0 rpm|deb"
+ ;;
+esac
\ No newline at end of file
diff --git a/src/app.go b/src/app.go
new file mode 100644
index 0000000..23c845e
--- /dev/null
+++ b/src/app.go
@@ -0,0 +1,136 @@
+package src
+
+import (
+ "embed"
+ "fmt"
+
+ "nms_nbi/src/framework/config"
+ "nms_nbi/src/framework/errorcatch"
+ "nms_nbi/src/framework/middleware"
+ "nms_nbi/src/framework/middleware/security"
+ "nms_nbi/src/modules/chart"
+ "nms_nbi/src/modules/common"
+ "nms_nbi/src/modules/crontask"
+ "nms_nbi/src/modules/monitor"
+ networkdata "nms_nbi/src/modules/network_data"
+ networkelement "nms_nbi/src/modules/network_element"
+ "nms_nbi/src/modules/system"
+ "nms_nbi/src/modules/trace"
+ "nms_nbi/src/modules/ws"
+
+ "github.com/gin-gonic/gin"
+)
+
+//go:embed assets/*
+var assetsDir embed.FS
+
+// 路由函数句柄,交给由 http.ListenAndServe(addr, router)
+func AppEngine() *gin.Engine {
+ app := initAppEngine()
+
+ // 初始全局默认
+ initDefeat(app)
+
+ // 初始模块路由
+ initModulesRoute(app)
+
+ // 设置程序内全局资源访问
+ config.SetAssetsDirFS(assetsDir)
+
+ // 读取服务配置
+ app.ForwardedByClientIP = config.Get("server.proxy").(bool)
+ return app
+}
+
+// 运行服务程序 main.go
+//
+// func main() {
+// src.ConfigurationInit()
+// if err := src.RunServer(); err != nil {
+// src.ConfigurationClose()
+// }
+// }
+func RunServer() error {
+ app := AppEngine()
+
+ // 读取服务配置
+ 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.Run(addr)
+}
+
+// 初始应用引擎
+func initAppEngine() *gin.Engine {
+ var app *gin.Engine
+
+ // 禁止控制台日志输出的颜色
+ gin.DisableConsoleColor()
+
+ // 根据运行环境注册引擎
+ if config.Env() == "prod" {
+ gin.SetMode(gin.ReleaseMode)
+ app = gin.New()
+ app.Use(gin.Recovery())
+ } else {
+ app = gin.Default()
+ }
+
+ return app
+}
+
+// 初始全局默认
+func initDefeat(app *gin.Engine) {
+ // 全局中间件
+ app.Use(errorcatch.ErrorCatch(), middleware.Report(), middleware.Cors(), security.Security())
+
+ // 静态目录-静态资源
+ if v := config.Get("staticFile.default"); v != nil {
+ fsMap := v.(map[string]any)
+ prefix, dir := fsMap["prefix"], fsMap["dir"]
+ if prefix != nil && dir != nil {
+ app.StaticFS(prefix.(string), gin.Dir(dir.(string), true))
+ }
+ }
+
+ // 静态目录-上传资源
+ if v := config.Get("staticFile.upload"); v != nil {
+ fsMap := v.(map[string]any)
+ prefix, dir := fsMap["prefix"], fsMap["dir"]
+ if prefix != nil && dir != nil {
+ app.StaticFS(prefix.(string), gin.Dir(dir.(string), true))
+ }
+ }
+
+ // 路由未找到时
+ app.NoRoute(func(c *gin.Context) {
+ c.JSON(404, gin.H{
+ "code": 404,
+ "msg": fmt.Sprintf("%s Not Found", c.Request.RequestURI),
+ })
+ })
+}
+
+// 初始模块路由
+func initModulesRoute(app *gin.Engine) {
+ // 通用模块
+ common.Setup(app)
+ // 系统模块
+ system.Setup(app)
+ // 网元功能模块
+ networkelement.Setup(app)
+ // 网元数据模块
+ networkdata.Setup(app)
+ // 跟踪模块
+ trace.Setup(app)
+ // 图表模块
+ chart.Setup(app)
+ // ws 模块
+ ws.Setup(app)
+ // 调度任务模块--暂无接口
+ crontask.Setup(app)
+ // 监控模块 - 含调度处理加入队列,放最后
+ monitor.Setup(app)
+}
diff --git a/src/assets/template/excel/user_import_template_en.xlsx b/src/assets/template/excel/user_import_template_en.xlsx
new file mode 100644
index 0000000..90f88c4
Binary files /dev/null and b/src/assets/template/excel/user_import_template_en.xlsx differ
diff --git a/src/assets/template/excel/user_import_template_zh.xlsx b/src/assets/template/excel/user_import_template_zh.xlsx
new file mode 100644
index 0000000..0f034a3
Binary files /dev/null and b/src/assets/template/excel/user_import_template_zh.xlsx differ
diff --git a/src/configuration.go b/src/configuration.go
new file mode 100644
index 0000000..c01adb7
--- /dev/null
+++ b/src/configuration.go
@@ -0,0 +1,35 @@
+package src
+
+import (
+ "nms_nbi/src/framework/config"
+ "nms_nbi/src/framework/cron"
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/redis"
+)
+
+// 配置中心初始加载
+func ConfigurationInit() {
+ // 初始配置参数
+ config.InitConfig()
+ // 初始程序日志
+ logger.InitLogger()
+ // 连接数据库实例
+ datasource.Connect()
+ // 连接Redis实例
+ redis.Connect()
+ // 启动调度任务实例
+ cron.StartCron()
+}
+
+// 配置中心相关配置关闭连接
+func ConfigurationClose() {
+ // 停止调度任务实例
+ cron.StopCron()
+ // 关闭Redis实例
+ redis.Close()
+ // 关闭数据库实例
+ datasource.Close()
+ // 关闭程序日志
+ logger.Close()
+}
diff --git a/src/framework/config/config.go b/src/framework/config/config.go
new file mode 100644
index 0000000..a7461bd
--- /dev/null
+++ b/src/framework/config/config.go
@@ -0,0 +1,174 @@
+package config
+
+import (
+ "bytes"
+ "embed"
+ "fmt"
+ "log"
+ "os"
+ "time"
+
+ libConfig "nms_nbi/src/lib_features/config"
+
+ "github.com/spf13/pflag"
+ "github.com/spf13/viper"
+)
+
+//go:embed config/*.yaml
+var configFiles embed.FS
+
+// 初始化程序配置
+func InitConfig() {
+ initFlag()
+ initViper()
+}
+
+// 指定参数绑定
+func initFlag() {
+ // --env prod
+ pflag.String("env", "prod", "Specify Run Environment Configuration local or prod")
+ // --c /etc/restconf.yaml
+ // -c /etc/restconf.yaml
+ pConfig := pflag.StringP("config", "c", "./etc/restconf.yaml", "Specify Configuration File")
+ // --version
+ // -V
+ pVersion := pflag.BoolP("version", "V", false, "Output program version")
+ // --help
+ pHelp := pflag.Bool("help", false, "Viewing Help Commands")
+
+ pflag.Parse()
+
+ // 参数固定输出
+ if *pVersion {
+ buildInfo := libConfig.BuildInfo()
+ fmt.Println(buildInfo)
+ os.Exit(1)
+ }
+ if *pHelp {
+ pflag.Usage()
+ os.Exit(1)
+ }
+
+ // 外层lib和features使用的配置
+ libConfig.ConfigRead(*pConfig)
+
+ viper.BindPFlags(pflag.CommandLine)
+}
+
+// 配置文件读取
+func initViper() {
+ // 在当前工作目录中寻找配置
+ // viper.AddConfigPath("config")
+ // viper.AddConfigPath("src/config")
+ // 如果配置文件名中没有扩展名,则需要设置Type
+ viper.SetConfigType("yaml")
+
+ // 从 embed.FS 中读取默认配置文件内容
+ configDefault, err := configFiles.ReadFile("config/config.default.yaml")
+ if err != nil {
+ log.Fatalf("ReadFile config default file: %s", err)
+ return
+ }
+ // 设置默认配置文件内容到 viper
+ err = viper.ReadConfig(bytes.NewReader(configDefault))
+ if err != nil {
+ log.Fatalf("NewReader config default file: %s", err)
+ return
+ }
+
+ // // 配置文件的名称(无扩展名)
+ // viper.SetConfigName("config.default")
+ // // 读取默认配置文件
+ // if err := viper.ReadInConfig(); err != nil {
+ // log.Fatalf("fatal error config default file: %s", err)
+ // }
+
+ env := viper.GetString("env")
+ if env != "local" && env != "prod" {
+ log.Fatalf("fatal error config env for local or prod : %s", env)
+ }
+ log.Printf("Current service environment operation configuration => %s \n", env)
+
+ // 加载运行配置文件合并相同配置
+ if env == "prod" {
+ // viper.SetConfigName("config.prod")
+ // 从 embed.FS 中读取默认配置文件内容
+ configProd, err := configFiles.ReadFile("config/config.prod.yaml")
+ if err != nil {
+ log.Fatalf("ReadFile config prod file: %s", err)
+ return
+ }
+ // 设置默认配置文件内容到 viper
+ err = viper.MergeConfig(bytes.NewReader(configProd))
+ if err != nil {
+ log.Fatalf("NewReader config prod file: %s", err)
+ return
+ }
+ } else {
+ // viper.SetConfigName("config.local")
+ // 从 embed.FS 中读取默认配置文件内容
+ configLocal, err := configFiles.ReadFile("config/config.local.yaml")
+ if err != nil {
+ log.Fatalf("ReadFile config local file: %s", err)
+ return
+ }
+ // 设置默认配置文件内容到 viper
+ err = viper.MergeConfig(bytes.NewReader(configLocal))
+ if err != nil {
+ log.Fatalf("NewReader config local file: %s", err)
+ return
+ }
+ }
+ // if err := viper.MergeInConfig(); err != nil {
+ // log.Fatalf("fatal error config MergeInConfig: %s", err)
+ // }
+
+ // 合并外层lib和features使用配置
+ libConfig.ConfigInMerge()
+
+ // 记录程序开始运行的时间点
+ viper.Set("runTime", time.Now())
+}
+
+// Env 获取运行服务环境
+// local prod
+func Env() string {
+ return viper.GetString("env")
+}
+
+// RunTime 程序开始运行的时间
+func RunTime() time.Time {
+ return viper.GetTime("runTime")
+}
+
+// Get 获取配置信息
+//
+// Get("framework.name")
+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 == "" {
+ return false
+ }
+ // 从本地配置获取user信息
+ admins := Get("user.adminList").([]any)
+ for _, s := range admins {
+ if s.(string) == userID {
+ return true
+ }
+ }
+ return false
+}
diff --git a/src/framework/config/config/config.default.yaml b/src/framework/config/config/config.default.yaml
new file mode 100644
index 0000000..40ab8cc
--- /dev/null
+++ b/src/framework/config/config/config.default.yaml
@@ -0,0 +1,211 @@
+# 项目信息
+framework:
+ name: "CN EMS"
+ version: "2.2401.1"
+
+# 应用服务配置
+server:
+ # 服务端口
+ port: 3030
+ # 是否开启代理
+ proxy: false
+
+# 日志
+logger:
+ fileDir: "/var/log"
+ fileName: "omc.log"
+ level: 2 # 日志记录的等级 0:silent<1:info<2:warn<3:error
+ maxDay: 180 # 日志会保留 180 天
+ maxSize: 10 # 调整按 10MB 大小的切割
+
+# 静态文件配置, 相对项目根路径或填绝对路径
+staticFile:
+ # 默认资源,dir目录需要预先创建
+ default:
+ prefix: "/static"
+ dir: "/usr/local/omc/static"
+ # 文件上传资源目录映射,与项目目录同级
+ upload:
+ prefix: "/upload"
+ dir: "/usr/local/omc/upload"
+
+# 文件上传
+upload:
+ # 最大上传文件大小,默认为 10mb
+ fileSize: 10
+ # 文件扩展名白名单
+ whitelist:
+ # 图片
+ - ".bmp"
+ - ".webp"
+ - ".gif"
+ - ".jpg"
+ - ".jpeg"
+ - ".png"
+ # word excel powerpoint
+ - ".doc"
+ - ".docx"
+ - ".xls"
+ - ".xlsx"
+ - ".ppt"
+ - ".pptx"
+ # 文本文件
+ - ".html"
+ - ".htm"
+ - ".txt"
+ # pdf
+ - ".pdf"
+ # 压缩文件
+ - ".zip"
+ - ".gz"
+ - ".tgz"
+ - ".gzip"
+ # 音视频格式
+ - ".mp3"
+ - ".mp4"
+ - ".avi"
+ - ".rmvb"
+ # 软件包
+ - ".deb"
+ - ".rpm"
+
+# cors 跨域
+cors:
+ # 设置 Access-Control-Allow-Origin 的值,【默认值】会获取请求头上的 origin
+ # 例如:http://mask-api.org
+ # 如果请求设置了 credentials,则 origin 不能设置为 *
+ origin: "*"
+ # 设置 Access-Control-Allow-Credentials,【默认值】false
+ credentials: true
+ # 设置 Access-Control-Max-Age
+ maxAge: 31536000
+ # 允许跨域的方法,【默认值】为 GET,HEAD,PUT,POST,DELETE,PATCH
+ allowMethods:
+ - "OPTIONS"
+ - "HEAD"
+ - "GET"
+ - "POST"
+ - "PUT"
+ - "DELETE"
+ - "PATCH"
+ # 设置 Access-Control-Allow-Headers 的值,【默认值】会获取请求头上的 Access-Control-Request-Headers
+ allowHeaders:
+ - "X-App-Code"
+ - "X-App-Version"
+ - "Authorization"
+ - "Origin"
+ - "X-Requested-With"
+ - "Content-Type"
+ - "Content-Language"
+ - "Accept-Language"
+ - "Accept"
+ - "Range"
+ - "Accesstoken"
+ - "Operationtype"
+ # 设置 Access-Control-Expose-Headers 的值
+ exposeHeaders:
+ - "X-RepeatSubmit-Rest"
+
+# security 安全
+security:
+ csrf:
+ enable: false
+ type: "referer"
+ # 允许调用的域名地址的,例如:http:///mask-api
+ refererWhiteList:
+ - "127.0.0.1:3030"
+ xframe:
+ enable: false
+ value: "SAMEORIGIN"
+ csp:
+ enable: true
+ hsts:
+ enable: false
+ maxAge: 31536000
+ includeSubdomains: false
+ noopen:
+ enable: false
+ nosniff:
+ enable: false
+ xssProtection:
+ enable: true
+ value: "1; mode=block"
+
+# JWT 令牌配置
+jwt:
+ # 令牌算法 HS256 HS384 HS512
+ algorithm: "HS512"
+ # 令牌密钥
+ secret: "217a0481c7f9cfe1cb547d32ee012b0f"
+ # 令牌有效期(默认120分钟)
+ expiresIn: 120
+ # 验证令牌有效期,相差不足xx分钟,自动刷新缓存
+ refreshIn: 20
+
+# GORM 数据源
+gorm:
+ dataSource:
+ # 默认数据库实例
+ default:
+ type: "mysql"
+ host: "127.0.0.1"
+ port: 3306
+ username: "<用户名>"
+ password: "<密码>"
+ database: "<数据库>"
+ logging: false
+ # 多个数据源时可以用这个指定默认的数据源
+ defaultDataSourceName: "default"
+
+# Redis 缓存数据
+redis:
+ dataSource:
+ default:
+ port: 6379 # Redis port
+ host: "127.0.0.1" # Redis host
+ password: "<密码>"
+ db: 0 # Redis db_num
+ # 多个数据源时可以用这个指定默认的数据源
+ defaultDataSourceName: "default"
+
+# 用户配置
+user:
+ # 密码
+ password:
+ # 密码最大错误次数
+ maxRetryCount: 5
+ # 密码锁定时间,单位分钟(默认10分钟)
+ lockTime: 10
+ # 管理员列表
+ adminList:
+ - "1"
+
+# char 字符验证码配置
+charCaptcha:
+ # 宽度
+ width: 120
+ # 高度
+ height: 40
+ # 干扰线条的数量
+ noise: 4
+ # 验证码的字符是否有颜色,默认没有,如果设定了背景,则默认有
+ color: true
+ # 验证码图片背景颜色
+ background: "#fafafa"
+ # 验证码长度
+ size: 4
+ # 验证码字符
+ chars: "023456789abcdefghjkmnprstuvwxyz"
+
+# math 数值计算码配置
+mathCaptcha:
+ # 宽度
+ width: 120
+ # 高度
+ height: 40
+ # 干扰线条的数量
+ noise: 4
+ # 验证码的字符是否有颜色,默认没有,如果设定了背景,则默认有
+ color: true
+ # 验证码图片背景颜色
+ background: "#fafafa"
diff --git a/src/framework/config/config/config.local.yaml b/src/framework/config/config/config.local.yaml
new file mode 100644
index 0000000..e5af417
--- /dev/null
+++ b/src/framework/config/config/config.local.yaml
@@ -0,0 +1,54 @@
+# 应用服务配置
+server:
+ port: 3040
+ proxy: true
+
+# 日志
+logger:
+ fileDir: "C:/usr/local/omc/log"
+ level: 0 # 输出最低等级
+
+# 静态文件配置, 相对项目根路径或填绝对路径
+staticFile:
+ default:
+ dir: "C:/usr/local/omc/static"
+ # 文件上传资源目录映射,与项目目录同级
+ upload:
+ dir: "C:/usr/local/omc/upload"
+
+# security 安全
+security:
+ csrf:
+ refererWhiteList:
+ - "localhost:3131"
+ - "127.0.0.1:3131"
+
+# GORM 数据源
+gorm:
+ dataSource:
+ default:
+ type: "mysql"
+ host: "192.168.0.229"
+ port: 33066
+ username: "root"
+ password: "1000omc@kp!"
+ database: "omc_db_dev"
+ logging: true
+
+# Redis 缓存数据,数据源声明全小写
+redis:
+ dataSource:
+ # OMC系统使用库
+ default:
+ port: 6379 # Redis port
+ host: "192.168.0.229" # Redis host
+ password: ""
+ db: 10 # Redis db_num
+ # UDM网元用户库
+ udmuser:
+ port: 6379 # Redis port
+ host: "192.168.0.229"
+ password: ""
+ db: 0 # Redis db_num
+ # 多个数据源时可以用这个指定默认的数据源
+ defaultDataSourceName: "default"
diff --git a/src/framework/config/config/config.prod.yaml b/src/framework/config/config/config.prod.yaml
new file mode 100644
index 0000000..36cccb2
--- /dev/null
+++ b/src/framework/config/config/config.prod.yaml
@@ -0,0 +1,32 @@
+# 应用服务配置
+server:
+ port: 3030
+ proxy: true
+
+# security 安全
+security:
+ csrf:
+ # 允许调用的域名地址的,例如:http:///
+ refererWhiteList:
+ - "127.0.0.1"
+ - ""
+
+# GORM 数据源
+gorm:
+ dataSource:
+ default:
+ type: "mysql"
+ host: ""
+ port: 3306
+ username: "<用户名>"
+ password: "<密码>"
+ database: "<数据库>"
+
+# Redis 缓存数据
+redis:
+ dataSource:
+ default:
+ port: 6379 # Redis port
+ host: ""
+ password: "<密码>"
+ db: 0 # Redis db_num
diff --git a/src/framework/constants/admin/admin.go b/src/framework/constants/admin/admin.go
new file mode 100644
index 0000000..7ac32c5
--- /dev/null
+++ b/src/framework/constants/admin/admin.go
@@ -0,0 +1,12 @@
+package admin
+
+// 管理员常量信息
+
+// 管理员-系统指定角色ID
+const ROLE_ID = "1"
+
+// 管理员-系统指定角色KEY
+const ROLE_KEY = "admin"
+
+// 管理员-系统指定权限
+const PERMISSION = "*:*:*"
diff --git a/src/framework/constants/cachekey/cachekey.go b/src/framework/constants/cachekey/cachekey.go
new file mode 100644
index 0000000..dd7c11f
--- /dev/null
+++ b/src/framework/constants/cachekey/cachekey.go
@@ -0,0 +1,30 @@
+package cachekey
+
+// 缓存的key常量
+
+// 登录用户
+const LOGIN_TOKEN_KEY = "login_tokens:"
+
+// 验证码
+const CAPTCHA_CODE_KEY = "captcha_codes:"
+
+// 参数管理
+const SYS_CONFIG_KEY = "sys_config:"
+
+// 字典管理
+const SYS_DICT_KEY = "sys_dict:"
+
+// 防重提交
+const REPEAT_SUBMIT_KEY = "repeat_submit:"
+
+// 限流
+const RATE_LIMIT_KEY = "rate_limit:"
+
+// 登录账户密码错误次数
+const PWD_ERR_CNT_KEY = "pwd_err_cnt:"
+
+// 网元信息管理
+const NE_KEY = "ne_info:"
+
+// 网元数据管理
+const NE_DATA_KEY = "ne_data:"
diff --git a/src/framework/constants/captcha/captcha.go b/src/framework/constants/captcha/captcha.go
new file mode 100644
index 0000000..816a65e
--- /dev/null
+++ b/src/framework/constants/captcha/captcha.go
@@ -0,0 +1,12 @@
+package captcha
+
+// 验证码常量信息
+
+// 验证码有效期,单位秒
+const EXPIRATION = 2 * 60
+
+// 验证码类型-数值计算
+const TYPE_CHAR = "char"
+
+// 验证码类型-字符验证
+const TYPE_MATH = "math"
diff --git a/src/framework/constants/common/common.go b/src/framework/constants/common/common.go
new file mode 100644
index 0000000..3786104
--- /dev/null
+++ b/src/framework/constants/common/common.go
@@ -0,0 +1,21 @@
+package common
+
+// 通用常量信息
+
+// www主域
+const WWW = "www."
+
+// http请求
+const HTTP = "http://"
+
+// https请求
+const HTTPS = "https://"
+
+// 通用状态标识-正常/成功/是
+const STATUS_YES = "1"
+
+// 通用状态标识-停用/失败/否
+const STATUS_NO = "0"
+
+// 上下文信息-登录用户
+const CTX_LOGIN_USER = "loginuser"
diff --git a/src/framework/constants/menu/menu.go b/src/framework/constants/menu/menu.go
new file mode 100644
index 0000000..94913cb
--- /dev/null
+++ b/src/framework/constants/menu/menu.go
@@ -0,0 +1,24 @@
+package menu
+
+// 系统菜单常量信息
+
+const (
+ // 组件布局类型-基础布局组件标识
+ COMPONENT_LAYOUT_BASIC = "BasicLayout"
+ // 组件布局类型-空白布局组件标识
+ COMPONENT_LAYOUT_BLANK = "BlankLayout"
+ // 组件布局类型-内链接布局组件标识
+ COMPONENT_LAYOUT_LINK = "LinkLayout"
+)
+
+const (
+ // 菜单类型-目录
+ TYPE_DIR = "D"
+ // 菜单类型-菜单
+ TYPE_MENU = "M"
+ // 菜单类型-按钮
+ TYPE_BUTTON = "B"
+)
+
+// 菜单内嵌地址标识-带/前缀
+const PATH_INLINE = "/inline"
diff --git a/src/framework/constants/result/result.go b/src/framework/constants/result/result.go
new file mode 100644
index 0000000..dbd8630
--- /dev/null
+++ b/src/framework/constants/result/result.go
@@ -0,0 +1,15 @@
+package result
+
+// 响应结果常量信息
+
+const (
+ // 响应-code错误失败
+ CODE_ERROR = 0
+ // 响应-msg错误失败
+ MSG_ERROR = "error"
+
+ // 响应-msg正常成功
+ CODE_SUCCESS = 1
+ // 响应-code正常成功
+ MSG_SUCCESS = "success"
+)
diff --git a/src/framework/constants/roledatascope/roledatascope.go b/src/framework/constants/roledatascope/roledatascope.go
new file mode 100644
index 0000000..c18cc89
--- /dev/null
+++ b/src/framework/constants/roledatascope/roledatascope.go
@@ -0,0 +1,20 @@
+package roledatascope
+
+// 系统角色数据范围常量
+
+const (
+ // 全部数据权限
+ ALL = "1"
+
+ // 自定数据权限
+ CUSTOM = "2"
+
+ // 部门数据权限
+ DEPT = "3"
+
+ // 部门及以下数据权限
+ DEPT_AND_CHILD = "4"
+
+ // 仅本人数据权限
+ SELF = "5"
+)
diff --git a/src/framework/constants/token/token.go b/src/framework/constants/token/token.go
new file mode 100644
index 0000000..5c71b08
--- /dev/null
+++ b/src/framework/constants/token/token.go
@@ -0,0 +1,21 @@
+package token
+
+// 令牌常量信息
+
+// 令牌-数据响应字段
+const RESPONSE_FIELD = "access_token"
+
+// 令牌-请求头标识前缀
+const HEADER_PREFIX = "Bearer "
+
+// 令牌-请求头标识
+const HEADER_KEY = "Authorization"
+
+// 令牌-JWT唯一标识字段
+const JWT_UUID = "login_key"
+
+// 令牌-JWT标识用户主键字段
+const JWT_KEY = "user_id"
+
+// 令牌-JWT标识用户登录账号字段
+const JWT_NAME = "user_name"
diff --git a/src/framework/constants/uploadsubpath/uploadsubpath.go b/src/framework/constants/uploadsubpath/uploadsubpath.go
new file mode 100644
index 0000000..c58e87f
--- /dev/null
+++ b/src/framework/constants/uploadsubpath/uploadsubpath.go
@@ -0,0 +1,41 @@
+package uploadsubpath
+
+// 文件上传-子路径类型常量
+
+const (
+ // 默认
+ DEFAULT = "default"
+
+ // 头像
+ AVATART = "avatar"
+
+ // 导入
+ IMPORT = "import"
+
+ // 导出
+ EXPORT = "export"
+
+ // 通用上传
+ COMMON = "common"
+
+ // 下载
+ DOWNLOAD = "download"
+
+ // 切片
+ CHUNK = "chunk"
+
+ // 软件包
+ SOFTWARE = "software"
+)
+
+// 子路径类型映射
+var UploadSubpath = map[string]string{
+ DEFAULT: "默认",
+ AVATART: "头像",
+ IMPORT: "导入",
+ EXPORT: "导出",
+ COMMON: "通用上传",
+ DOWNLOAD: "下载",
+ CHUNK: "切片",
+ SOFTWARE: "软件包",
+}
diff --git a/src/framework/cron/cron.go b/src/framework/cron/cron.go
new file mode 100644
index 0000000..e835d37
--- /dev/null
+++ b/src/framework/cron/cron.go
@@ -0,0 +1,209 @@
+package cron
+
+import (
+ "fmt"
+ "time"
+
+ "github.com/robfig/cron/v3"
+)
+
+// 定义内部调度任务实例
+var c *cron.Cron
+
+// 任务队列
+var queueMap map[string]Queue
+
+// StartCron 启动调度任务实例
+func StartCron() {
+ queueMap = make(map[string]Queue)
+ c = cron.New(cron.WithSeconds())
+ c.Start()
+}
+
+// StopCron 停止调度任务实例
+func StopCron() {
+ c.Stop()
+}
+
+// CreateQueue 创建队列注册处理器
+func CreateQueue(name string, processor QueueProcessor) Queue {
+ queue := Queue{
+ Name: name,
+ Processor: processor,
+ Job: &[]*QueueJob{},
+ }
+ queueMap[name] = queue
+ return queue
+}
+
+// GetQueue 通过名称获取队列实例
+func GetQueue(name string) Queue {
+ if v, ok := queueMap[name]; ok {
+ return v
+ }
+ return Queue{}
+}
+
+// QueueNames 获取注册的队列名称
+func QueueNames() []string {
+ keys := make([]string, 0, len(queueMap))
+ for k := range queueMap {
+ keys = append(keys, k)
+ }
+ return keys
+}
+
+// Queue 任务队列
+type Queue struct {
+ Name string // 队列名
+ Processor QueueProcessor
+ Job *[]*QueueJob
+}
+
+// QueueProcessor 队列处理函数接口
+type QueueProcessor interface {
+ // Execute 实际执行函数
+ // any 返回有效值最终序列化为字符串,记录为成功
+ // error 存在错误,记录为失败
+ Execute(data any) (any, error)
+}
+
+// RunJob 运行任务,data是传入的数据
+func (q *Queue) RunJob(data any, opts JobOptions) int {
+ job := &QueueJob{
+ Status: Waiting,
+ Data: data,
+ Opts: opts,
+ queueName: q.Name,
+ queueProcessor: &q.Processor,
+ }
+
+ // 非重复任务立即执行
+ if opts.Cron == "" {
+ // 获取执行的任务
+ currentJob := job.GetJob(false)
+ if currentJob.Status == Active {
+ return Active
+ }
+ // 从切片 jobs 中删除指定索引位置的元素
+ for i, v := range *q.Job {
+ if v.cid == 0 {
+ jobs := *q.Job
+ jobs = append(jobs[:i], jobs[i+1:]...)
+ *q.Job = jobs
+ break
+ }
+ }
+ go job.Run()
+ } else {
+ // 移除已存的任务ID
+ q.RemoveJob(opts.JobId)
+ // 添加新任务
+ cid, err := c.AddJob(opts.Cron, job)
+ if err != nil {
+ newLog.Error(err, "err")
+ job.Status = Failed
+ }
+ job.cid = cid
+ }
+
+ *q.Job = append(*q.Job, job)
+ newLog.Info("RunJob", job.cid, opts.JobId, job.Status)
+ return job.Status
+}
+
+// RemoveJob 移除任务
+func (q *Queue) RemoveJob(jobId string) bool {
+ for i, v := range *q.Job {
+ if jobId == v.Opts.JobId {
+ newLog.Info("RemoveJob", v.cid, jobId, v.Status)
+ c.Remove(v.cid)
+ // 从切片 jobs 中删除指定索引位置的元素
+ jobs := *q.Job
+ jobs = append(jobs[:i], jobs[i+1:]...)
+ *q.Job = jobs
+ return true
+ }
+ }
+ return false
+}
+
+// Status 任务执行状态
+const (
+ Waiting = iota
+ Active
+ Completed
+ Failed
+)
+
+// JobOptions 任务参数信息
+type JobOptions struct {
+ JobId string // 执行任务编号
+ Cron string // 重复任务cron表达式
+}
+
+// QueueJob 队列内部执行任务
+type QueueJob struct {
+ Status int // 任务执行状态
+ Timestamp int64 // 执行时间
+ Data any // 执行任务时传入的参数
+ Opts JobOptions
+
+ cid cron.EntryID // 执行ID
+
+ queueName string //队列名
+ queueProcessor *QueueProcessor
+}
+
+// GetJob 获取当前执行任务
+func (job *QueueJob) GetJob(repeat bool) *QueueJob {
+ q := GetQueue(job.queueName)
+ for _, v := range *q.Job {
+ if repeat && v.Opts.JobId == job.Opts.JobId {
+ return v
+ }
+ if !repeat && v.cid == 0 {
+ return v
+ }
+ }
+ return job
+}
+
+// Run 实现的接口函数
+func (s QueueJob) Run() {
+ // 检查当前任务
+ job := s.GetJob(s.cid != 0)
+
+ // Active 状态不执行
+ if job.Status == Active {
+ return
+ }
+
+ // panics 异常收集
+ defer func() {
+ if r := recover(); r != nil {
+ err, ok := r.(error)
+ if !ok {
+ err = fmt.Errorf("%v", r)
+ }
+ job.Status = Failed
+ newLog.Error(err, "failed", job)
+ }
+ }()
+
+ // 开始执行
+ job.Status = Active
+ job.Timestamp = time.Now().UnixMilli()
+ newLog.Info("run", job.cid, job.Opts.JobId)
+
+ // 获取队列处理器接口实现
+ processor := *job.queueProcessor
+ result, err := processor.Execute(job.Data)
+ if err != nil {
+ job.Status = Failed
+ newLog.Error(err, "failed", job)
+ } else {
+ job.Status = Completed
+ newLog.Completed(result, "completed", job)
+ }
+}
diff --git a/src/framework/cron/cron_test.go b/src/framework/cron/cron_test.go
new file mode 100644
index 0000000..29002a2
--- /dev/null
+++ b/src/framework/cron/cron_test.go
@@ -0,0 +1,179 @@
+package cron
+
+import (
+ "testing"
+ "time"
+
+ "nms_nbi/src/framework/logger"
+)
+
+// 参考文章:
+// https://blog.csdn.net/zjbyough/article/details/113853582
+// https://mp.weixin.qq.com/s/Ak7RBv1NuS-VBeDNo8_fww
+func init() {
+ StartCron()
+}
+
+// 简单示例 队列任务处理
+var NewSimple = &Simple{}
+
+type Simple struct{}
+
+func (s *Simple) Execute(data any) (any, error) {
+ logger.Infof("执行=> %+v ", data)
+ // 实现任务处理逻辑
+ return data, nil
+}
+
+func TestSimple(t *testing.T) {
+
+ simple := CreateQueue("simple", NewSimple)
+ simple.RunJob(map[string]string{
+ "ok": "ok",
+ "data": "data",
+ }, JobOptions{
+ JobId: "101",
+ })
+
+ simpleC := CreateQueue("simple", NewSimple)
+ simpleC.RunJob(map[string]string{
+ "corn": "*/5 * * * * *",
+ "id": "102",
+ }, JobOptions{
+ JobId: "102",
+ Cron: "*/5 * * * * *",
+ })
+
+ // simpleC.RunJob(map[string]string{
+ // "corn": "*/15 * * * * *",
+ // "id": "103",
+ // }, JobOptions{
+ // JobId: "103",
+ // Cron: "*/15 * * * * *",
+ // })
+
+ // simpleC.RemoveJob("102")
+
+ select {}
+}
+
+// Foo 队列任务处理
+var NewFooProcessor = &FooProcessor{
+ progress: 0,
+ count: 0,
+}
+
+type FooProcessor struct {
+ progress int
+ count int
+}
+
+func (s *FooProcessor) Execute(data any) (any, error) {
+ logger.Infof("执行 %d %d => %+v ", s.count, s.progress, data)
+ s.count++
+
+ // 实现任务处理逻辑
+ i := 0
+ s.progress = i
+ for i < 10 {
+ // 获取任务进度
+ progress := s.progress
+ logger.Infof("data: %v => 任务进度:%d", data, progress)
+ // 延迟响应
+ time.Sleep(time.Second * 2)
+ i++
+ // 改变任务进度
+ s.progress = i
+ }
+ return data, nil
+}
+
+func TestFoo(t *testing.T) {
+
+ foo := CreateQueue("foo", NewFooProcessor)
+ foo.RunJob(map[string]string{
+ "data": "2",
+ }, JobOptions{
+ JobId: "2",
+ })
+
+ fooC := CreateQueue("foo", NewFooProcessor)
+ fooC.RunJob(map[string]string{
+ "corn": "*/5 * * * * *",
+ }, JobOptions{
+ JobId: "3",
+ Cron: "*/5 * * * * *",
+ })
+
+ select {}
+}
+
+// Bar 队列任务处理
+var NewBarProcessor = &BarProcessor{
+ progress: 0,
+ count: 0,
+}
+
+type BarProcessor struct {
+ progress int
+ count int
+}
+
+func (s *BarProcessor) Execute(data any) (any, error) {
+ logger.Infof("执行 %d %d => %+v ", s.count, s.progress, data)
+ s.count++
+
+ // 实现任务处理逻辑
+ i := 0
+ s.progress = i
+ for i < 5 {
+ // 获取任务进度
+ progress := s.progress
+ logger.Infof("data: %v => 任务进度:%d", data, progress)
+ // 延迟响应
+ time.Sleep(time.Second * 2)
+ // 程序中途执行错误
+ if i == 3 {
+ // arr := [1]int{1}
+ // arr[i] = 3
+ // fmt.Println(arr)
+ // return "i = 3"
+ panic("程序中途执行错误")
+ }
+ i++
+ // 改变任务进度
+ s.progress = i
+ }
+
+ return data, nil
+}
+
+func TestBar(t *testing.T) {
+
+ bar := CreateQueue("bar", NewBarProcessor)
+ bar.RunJob(map[string]string{
+ "data": "wdf",
+ }, JobOptions{
+ JobId: "81923",
+ })
+
+ barC := CreateQueue("bar", NewBarProcessor)
+ barC.RunJob(map[string]string{
+ "corn": "*/5 * * * * *",
+ }, JobOptions{
+ JobId: "789",
+ Cron: "*/5 * * * * *",
+ })
+
+ // barDB := CreateQueue("barDB", NewBarProcessor)
+ // barDB.RunJob(JobData{
+ // SysJob: model.SysJob{
+ // JobID: "9123",
+ // JobName: "测试任务",
+ // },
+ // }, JobOptions{
+ // JobId: "9123",
+ // })
+
+ select {}
+}
diff --git a/src/framework/cron/log.go b/src/framework/cron/log.go
new file mode 100644
index 0000000..20660ff
--- /dev/null
+++ b/src/framework/cron/log.go
@@ -0,0 +1,125 @@
+package cron
+
+import (
+ "encoding/json"
+ "time"
+
+ "nms_nbi/src/framework/constants/common"
+ "nms_nbi/src/modules/monitor/model"
+ "nms_nbi/src/modules/monitor/repository"
+)
+
+// 实例任务执行日志收集
+var newLog = cronlog{}
+
+// cronlog 任务执行日志收集
+type cronlog struct{}
+
+// Info 任务普通信息收集
+func (s cronlog) Info(msg string, keysAndValues ...any) {
+ // logger.Infof("Info msg: %v ====> kv: %v", msg, keysAndValues)
+
+}
+
+// Error 任务异常错误收集
+func (s cronlog) Error(err error, msg string, keysAndValues ...any) {
+ // logger.Errorf("Error: %v -> msg: %v ====> kv: %v", err, msg, keysAndValues)
+ // logger.Errorf("k0: %v", keysAndValues[0].(*QueueJob))
+
+ // 指定的错误收集
+ if msg == "failed" {
+ // 任务对象
+ job := keysAndValues[0].(*QueueJob)
+
+ // 读取任务信息进行保存日志
+ if data, ok := job.Data.(JobData); ok {
+ // 日志数据
+ jobLog := jobLogData{
+ Timestamp: job.Timestamp,
+ Data: data,
+ Result: err.Error(),
+ }
+ jobLog.SaveLog(common.STATUS_NO)
+ }
+ }
+}
+
+// Completed 任务完成return的结果收集
+func (s cronlog) Completed(result any, msg string, keysAndValues ...any) {
+ // logger.Infof("Completed: %v -> msg: %v ====> kv: %v", result, msg, keysAndValues)
+ // logger.Infof("k0: %v", keysAndValues[0].(*QueueJob))
+
+ // 指定的完成收集
+ if msg == "completed" {
+ // 任务对象
+ job := keysAndValues[0].(*QueueJob)
+
+ // 读取任务信息进行保存日志
+ if data, ok := job.Data.(JobData); ok {
+ // 日志数据
+ jobLog := jobLogData{
+ Timestamp: job.Timestamp,
+ Data: data,
+ Result: result,
+ }
+ jobLog.SaveLog(common.STATUS_YES)
+ }
+ }
+}
+
+// jobLogData 日志记录数据
+type jobLogData struct {
+ Timestamp int64
+ Data JobData
+ Result any
+}
+
+// SaveLog 日志记录保存
+func (jl *jobLogData) SaveLog(status string) {
+ // 读取任务信息
+ sysJob := jl.Data.SysJob
+
+ // 任务日志不需要记录
+ if sysJob.SaveLog == "" || sysJob.SaveLog == common.STATUS_NO {
+ return
+ }
+
+ // 结果信息key的Name
+ resultNmae := "failed"
+ if status == common.STATUS_YES {
+ resultNmae = "completed"
+ }
+
+ // 结果信息序列化字符串
+ jsonByte, _ := json.Marshal(map[string]any{
+ "name": resultNmae,
+ "crom": jl.Data.Repeat,
+ "message": jl.Result,
+ })
+ jobMsg := string(jsonByte)
+ if len(jobMsg) > 500 {
+ jobMsg = jobMsg[:500]
+ }
+
+ // 创建日志对象
+ duration := time.Since(time.UnixMilli(jl.Timestamp))
+ sysJobLog := model.SysJobLog{
+ JobName: sysJob.JobName,
+ JobGroup: sysJob.JobGroup,
+ InvokeTarget: sysJob.InvokeTarget,
+ TargetParams: sysJob.TargetParams,
+ Status: status,
+ JobMsg: jobMsg,
+ CostTime: duration.Milliseconds(),
+ }
+ // 插入数据
+ repository.NewSysJobLogImpl.InsertJobLog(sysJobLog)
+}
+
+// JobData 调度任务日志收集结构体,执行任务时传入的接收参数
+type JobData struct {
+ // 触发执行cron重复多次
+ Repeat bool
+ // 定时任务调度表记录信息
+ SysJob model.SysJob
+}
diff --git a/src/framework/datasource/datasource.go b/src/framework/datasource/datasource.go
new file mode 100644
index 0000000..fd50c90
--- /dev/null
+++ b/src/framework/datasource/datasource.go
@@ -0,0 +1,161 @@
+package datasource
+
+import (
+ "fmt"
+ "log"
+ "os"
+ "regexp"
+ "time"
+
+ "nms_nbi/src/framework/config"
+ "nms_nbi/src/framework/logger"
+
+ "gorm.io/driver/mysql"
+ "gorm.io/gorm"
+ gormLog "gorm.io/gorm/logger"
+)
+
+// 数据库连接实例
+var dbMap = make(map[string]*gorm.DB)
+
+type dialectInfo struct {
+ dialector gorm.Dialector
+ logging bool
+}
+
+// 载入数据库连接
+func loadDialect() map[string]dialectInfo {
+ dialects := make(map[string]dialectInfo, 0)
+
+ // 读取数据源配置
+ datasource := config.Get("gorm.datasource").(map[string]any)
+ for key, value := range datasource {
+ item := value.(map[string]any)
+ // 数据库类型对应的数据库连接
+ switch item["type"] {
+ case "mysql":
+ dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
+ item["username"],
+ item["password"],
+ item["host"],
+ item["port"],
+ item["database"],
+ )
+ dialects[key] = dialectInfo{
+ dialector: mysql.Open(dsn),
+ logging: item["logging"].(bool),
+ }
+ default:
+ logger.Fatalf("%s: %v\n Not Load DB Config Type", key, item)
+ }
+ }
+
+ return dialects
+}
+
+// 载入连接日志配置
+func loadLogger() gormLog.Interface {
+ newLogger := gormLog.New(
+ log.New(os.Stdout, "[GORM] ", log.LstdFlags), // 将日志输出到控制台
+ gormLog.Config{
+ SlowThreshold: time.Second, // Slow SQL 阈值
+ LogLevel: gormLog.Info, // 日志级别 Silent不输出任何日志
+ ParameterizedQueries: false, // 参数化查询SQL 用实际值带入?的执行语句
+ Colorful: false, // 彩色日志输出
+ },
+ )
+ return newLogger
+}
+
+// 连接数据库实例
+func Connect() {
+ // 遍历进行连接数据库实例
+ for key, info := range loadDialect() {
+ opts := &gorm.Config{}
+ // 是否需要日志输出
+ if info.logging {
+ opts.Logger = loadLogger()
+ }
+ // 创建连接
+ db, err := gorm.Open(info.dialector, opts)
+ if err != nil {
+ logger.Fatalf("failed error db connect: %s", err)
+ }
+ // 获取底层 SQL 数据库连接
+ sqlDB, err := db.DB()
+ if err != nil {
+ logger.Fatalf("failed error underlying SQL database: %v", err)
+ }
+ // 测试数据库连接
+ err = sqlDB.Ping()
+ if err != nil {
+ logger.Fatalf("failed error ping database: %v", err)
+ }
+ logger.Infof("database %s connection is successful.", key)
+ dbMap[key] = db
+ }
+}
+
+// 关闭数据库实例
+func Close() {
+ for _, db := range dbMap {
+ sqlDB, err := db.DB()
+ if err != nil {
+ continue
+ }
+ if err := sqlDB.Close(); err != nil {
+ logger.Errorf("fatal error db close: %s", err)
+ }
+ }
+}
+
+// 获取默认数据源
+func DefaultDB() *gorm.DB {
+ source := config.Get("gorm.defaultDataSourceName").(string)
+ return dbMap[source]
+}
+
+// 获取数据源
+func DB(source string) *gorm.DB {
+ return dbMap[source]
+}
+
+// RawDB 原生查询语句
+func RawDB(source string, sql string, parameters []any) ([]map[string]any, error) {
+ // 数据源
+ db := DefaultDB()
+ if source != "" {
+ db = DB(source)
+ }
+
+ // 使用正则表达式替换连续的空白字符为单个空格
+ fmtSql := regexp.MustCompile(`\s+`).ReplaceAllString(sql, " ")
+
+ // logger.Infof("sql=> %v", fmtSql)
+ // logger.Infof("parameters=> %v", parameters)
+
+ // 查询结果
+ var rows []map[string]any
+ res := db.Raw(fmtSql, parameters...).Scan(&rows)
+ if res.Error != nil {
+ return nil, res.Error
+ }
+ return rows, nil
+}
+
+// ExecDB 原生执行语句
+func ExecDB(source string, sql string, parameters []any) (int64, error) {
+ // 数据源
+ db := DefaultDB()
+ if source != "" {
+ db = DB(source)
+ }
+ // 使用正则表达式替换连续的空白字符为单个空格
+ fmtSql := regexp.MustCompile(`\s+`).ReplaceAllString(sql, " ")
+ // 执行结果
+ res := db.Exec(fmtSql, parameters...)
+ if res.Error != nil {
+ return 0, res.Error
+ }
+ return res.RowsAffected, nil
+}
diff --git a/src/framework/errorcatch/errorcatch.go b/src/framework/errorcatch/errorcatch.go
new file mode 100644
index 0000000..363f348
--- /dev/null
+++ b/src/framework/errorcatch/errorcatch.go
@@ -0,0 +1,40 @@
+package errorcatch
+
+import (
+ "fmt"
+
+ "nms_nbi/src/framework/config"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/vo/result"
+
+ "github.com/gin-gonic/gin"
+)
+
+// ErrorCatch 全局异常捕获
+func ErrorCatch() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ defer func() {
+ // 在这里处理 Panic 异常,例如记录日志或返回错误信息给客户端
+ if err := recover(); err != nil {
+ logger.Errorf("Panic Error: %s => %v", c.Request.URL, err)
+
+ // 返回错误响应给客户端
+ if config.Env() == "prod" {
+ c.JSON(500, result.ErrMsg("Internal Server Errors"))
+ } else {
+ // 通过实现 error 接口的 Error() 方法自定义错误类型进行捕获
+ switch v := err.(type) {
+ case error:
+ c.JSON(500, result.ErrMsg(v.Error()))
+ default:
+ c.JSON(500, result.ErrMsg(fmt.Sprint(err)))
+ }
+ }
+
+ c.Abort() // 停止执行后续的处理函数
+ }
+ }()
+
+ c.Next()
+ }
+}
diff --git a/src/framework/i18n/i18n.go b/src/framework/i18n/i18n.go
new file mode 100644
index 0000000..9c1e37c
--- /dev/null
+++ b/src/framework/i18n/i18n.go
@@ -0,0 +1,107 @@
+package i18n
+
+import (
+ "fmt"
+ "regexp"
+
+ systemService "nms_nbi/src/modules/system/service"
+)
+
+// localeItem 国际化数据项
+type localeItem struct {
+ Key string `json:"key"`
+ Value string `json:"value"`
+ Code string `json:"code"`
+}
+
+// localeMap 国际化数据组
+var localeMap = make(map[string][]localeItem)
+
+// ClearLocaleData 清空国际化数据
+func ClearLocaleData() {
+ localeMap = make(map[string][]localeItem)
+}
+
+// LoadLocaleData 加载国际化数据
+func LoadLocaleData(language string) []localeItem {
+ dictType := fmt.Sprintf("i18n_%s", language)
+ dictTypeList := systemService.NewSysDictTypeImpl.DictDataCache(dictType)
+ localeData := []localeItem{}
+ for _, v := range dictTypeList {
+ localeData = append(localeData, localeItem{
+ Key: v.DictLabel,
+ Value: v.DictValue,
+ Code: v.DictCode,
+ })
+ }
+ localeMap[language] = localeData
+ return localeData
+}
+
+// UpdateKeyValue 更新键对应的值
+func UpdateKeyValue(language, key, value string) bool {
+ arr, ok := localeMap[language]
+ if !ok || len(arr) == 0 {
+ arr = LoadLocaleData(language)
+ }
+
+ code := ""
+ if key == "" {
+ return false
+ }
+ for _, v := range arr {
+ if v.Key == key {
+ code = v.Code
+ break
+ }
+ }
+
+ // 更新字典数据
+ sysDictDataService := systemService.NewSysDictDataImpl
+ item := sysDictDataService.SelectDictDataByCode(code)
+ if item.DictCode == code && item.DictLabel == key {
+ item.DictValue = value
+ row := sysDictDataService.UpdateDictData(item)
+ if row > 0 {
+ delete(localeMap, language)
+ return true
+ }
+ }
+ return false
+}
+
+// TKey 翻译键
+func TKey(language, key string) string {
+ value := key
+ if key == "" {
+ return value
+ }
+ arr, ok := localeMap[language]
+ if !ok || len(arr) == 0 {
+ arr = LoadLocaleData(language)
+ }
+
+ for _, v := range arr {
+ if v.Key == key {
+ value = v.Value
+ break
+ }
+ }
+ return value
+}
+
+// TTemplate 翻译模板字符串
+func TTemplate(language, key string, params map[string]any) string {
+ templateString := TKey(language, key)
+
+ re := regexp.MustCompile("{(.*?)}")
+ result := re.ReplaceAllStringFunc(templateString, func(match string) string {
+ key := match[1 : len(match)-1]
+ if val, ok := params[key]; ok {
+ return fmt.Sprint(val)
+ }
+ return match
+ })
+
+ return result
+}
diff --git a/src/framework/logger/logger.go b/src/framework/logger/logger.go
new file mode 100644
index 0000000..543252c
--- /dev/null
+++ b/src/framework/logger/logger.go
@@ -0,0 +1,49 @@
+package logger
+
+import (
+ "log"
+
+ "github.com/spf13/viper"
+)
+
+var logWriter *Logger
+
+// 初始程序日志
+func InitLogger() {
+ env := viper.GetString("env")
+ conf := viper.GetStringMap("logger")
+ fileDir := conf["filedir"].(string)
+ fileName := conf["filename"].(string)
+ level := conf["level"].(int)
+ maxDay := conf["maxday"].(int)
+ maxSize := conf["maxsize"].(int)
+
+ newLog, err := NewLogger(env, fileDir, fileName, level, maxDay, maxSize)
+ if err != nil {
+ log.Fatalf("failed to initialize logger: %v", err)
+ }
+
+ logWriter = newLog
+}
+
+// 关闭程序日志写入
+func Close() {
+ logWriter.Close()
+}
+
+func Infof(format string, v ...any) {
+ logWriter.Infof(format, v...)
+}
+
+func Warnf(format string, v ...any) {
+ logWriter.Warnf(format, v...)
+}
+
+func Errorf(format string, v ...any) {
+ logWriter.Errorf(format, v...)
+}
+
+// Fatalf 抛出错误并退出程序
+func Fatalf(format string, v ...any) {
+ log.Fatalf(format, v...)
+}
diff --git a/src/framework/logger/writer.go b/src/framework/logger/writer.go
new file mode 100644
index 0000000..3209686
--- /dev/null
+++ b/src/framework/logger/writer.go
@@ -0,0 +1,194 @@
+package logger
+
+import (
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+)
+
+// 日志器对象
+type Logger struct {
+ env string // 运行环境
+ filePath string // 文件路径
+ fileName string // 文件名
+ level int // 日志等级标识
+ maxDay int // 保留最长天数
+ maxSize int64 // 文件最大空间
+ fileHandle *os.File // 文件实例
+ logger *log.Logger // 日志实例
+ logLevelMap map[int]string // 日志等级标识名
+ logDay int // 日志当前日
+}
+
+const (
+ LOG_LEVEL_SILENT = iota
+ LOG_LEVEL_INFO
+ LOG_LEVEL_WARN
+ LOG_LEVEL_ERROR
+)
+
+// NewLogger 实例日志器对象
+func NewLogger(env, fileDir, fileName string, level, maxDay, maxSize int) (*Logger, error) {
+ logFilePath := filepath.Join(fileDir, fileName)
+ if err := os.MkdirAll(filepath.Dir(logFilePath), 0750); err != nil {
+ return nil, fmt.Errorf("failed to mkdir logger dir: %v", err)
+ }
+ fileHandle, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
+ if err != nil {
+ return nil, fmt.Errorf("failed to open log file: %v", err)
+ }
+
+ writer := io.Writer(fileHandle)
+ if env == "local" {
+ writer = io.MultiWriter(fileHandle, os.Stderr)
+ }
+
+ logger := log.New(writer, "", log.LstdFlags|log.Lshortfile)
+
+ logLevelMap := map[int]string{
+ LOG_LEVEL_INFO: "INFO",
+ LOG_LEVEL_WARN: "WARN",
+ LOG_LEVEL_ERROR: "ERROR",
+ }
+
+ stdLogger := &Logger{
+ env: env,
+ filePath: fileDir,
+ fileName: fileName,
+ level: level,
+ maxDay: maxDay,
+ maxSize: int64(maxSize * 1024 * 1024),
+ fileHandle: fileHandle,
+ logger: logger,
+ logLevelMap: logLevelMap,
+ logDay: time.Now().Day(),
+ }
+
+ go stdLogger.checkFile()
+
+ return stdLogger, nil
+}
+
+// checkFile 检查文件分割,自定时调用
+func (l *Logger) checkFile() {
+ fileInfo, err := l.fileHandle.Stat()
+ if err != nil {
+ l.logger.Printf("Failed to get log file info: %v\n", err)
+ return
+ }
+
+ currTime := time.Now()
+ if l.logDay != currTime.Day() {
+ l.logDay = currTime.Day()
+ l.rotateFile(currTime.AddDate(0, 0, -1).Format("2006-01-02"))
+ // 移除超过保存最长天数的文件
+ l.removeOldFile(currTime.AddDate(0, 0, -l.maxDay))
+ } else if fileInfo.Size() >= l.maxSize {
+ l.rotateFile(currTime.Format("2006-01-02_150405"))
+ } else if time.Since(fileInfo.ModTime()).Hours() > 24 {
+ l.rotateFile(fileInfo.ModTime().Format("2006-01-02"))
+ }
+
+ time.AfterFunc(1*time.Minute, l.checkFile)
+}
+
+// rotateFile 检查文件大小进行分割
+func (l *Logger) rotateFile(timeFormat string) {
+ l.fileHandle.Close()
+
+ newFileName := fmt.Sprintf("%s.%s", l.fileName, timeFormat)
+ newFilePath := filepath.Join(l.filePath, newFileName)
+ oldfilePath := filepath.Join(l.filePath, l.fileName)
+
+ // 重命名
+ os.Rename(oldfilePath, newFilePath)
+
+ // 新文件句柄
+ fileHandle, err := os.OpenFile(oldfilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
+ if err != nil {
+ l.logger.Printf("Failed to open log file: %v\n", err)
+ return
+ }
+
+ l.fileHandle = fileHandle
+
+ // 重新设置 logger 的 writer
+ writer := io.Writer(l.fileHandle)
+ if l.env == "local" {
+ writer = io.MultiWriter(l.fileHandle, os.Stderr)
+ }
+ l.logger.SetOutput(writer)
+}
+
+// RemoveOldFile 删除旧文件
+func (l *Logger) removeOldFile(oldFileDate time.Time) {
+ // 遍历目标文件夹中的文件
+ files, err := os.ReadDir(l.filePath)
+ if err != nil {
+ l.Errorf("logger RemoveOldFile ReadDir err: %v", err.Error())
+ return
+ }
+
+ for _, file := range files {
+ // 跳过非指定日志文件名
+ if !strings.HasPrefix(file.Name(), l.fileName+".") {
+ continue
+ }
+ idx := strings.LastIndex(file.Name(), ".")
+ if idx == -1 {
+ continue
+ }
+ dateStr := file.Name()[idx+1 : idx+11]
+
+ // 解析日期字符串
+ fileDate, err := time.Parse("2006-01-02", dateStr)
+ if err != nil {
+ l.Errorf("logger RemoveOldFile Parse err: %v", err.Error())
+ continue
+ }
+
+ // 判断文件日期是否在给定日期之前
+ if fileDate.Before(oldFileDate) {
+ // 删除旧文件
+ err := os.Remove(filepath.Join(l.filePath, file.Name()))
+ if err != nil {
+ l.Errorf("logger RemoveOldFile Remove err: %v", err.Error())
+ continue
+ }
+ }
+ }
+}
+
+// writeLog 写入chan
+func (l *Logger) writeLog(level int, format string, args ...interface{}) {
+ if level < l.level {
+ return
+ }
+
+ logMsg := fmt.Sprintf("[%s] %s\n", l.logLevelMap[level], fmt.Sprintf(format, args...))
+ l.logger.Output(4, logMsg)
+}
+
+func (l *Logger) Infof(format string, args ...interface{}) {
+ l.writeLog(LOG_LEVEL_INFO, format, args...)
+}
+
+func (l *Logger) Warnf(format string, args ...interface{}) {
+ l.writeLog(LOG_LEVEL_WARN, format, args...)
+}
+
+func (l *Logger) Errorf(format string, args ...interface{}) {
+ l.writeLog(LOG_LEVEL_ERROR, format, args...)
+}
+
+// Close 日志关闭
+func (l *Logger) Close() {
+ err := l.fileHandle.Close()
+ if err != nil {
+ l.logger.Printf("Failed to close log file: %v\n", err)
+ }
+}
diff --git a/src/framework/middleware/collectlogs/operate_log.go b/src/framework/middleware/collectlogs/operate_log.go
new file mode 100644
index 0000000..d83896f
--- /dev/null
+++ b/src/framework/middleware/collectlogs/operate_log.go
@@ -0,0 +1,217 @@
+package collectlogs
+
+import (
+ "encoding/json"
+ "fmt"
+ "reflect"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/constants/common"
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/system/model"
+ "nms_nbi/src/modules/system/service"
+
+ "github.com/gin-gonic/gin"
+)
+
+const (
+ // 业务操作类型-其它
+ BUSINESS_TYPE_OTHER = "0"
+
+ // 业务操作类型-新增
+ BUSINESS_TYPE_INSERT = "1"
+
+ // 业务操作类型-修改
+ BUSINESS_TYPE_UPDATE = "2"
+
+ // 业务操作类型-删除
+ BUSINESS_TYPE_DELETE = "3"
+
+ // 业务操作类型-授权
+ BUSINESS_TYPE_GRANT = "4"
+
+ // 业务操作类型-导出
+ BUSINESS_TYPE_EXPORT = "5"
+
+ // 业务操作类型-导入
+ BUSINESS_TYPE_IMPORT = "6"
+
+ // 业务操作类型-强退
+ BUSINESS_TYPE_FORCE = "7"
+
+ // 业务操作类型-清空数据
+ BUSINESS_TYPE_CLEAN = "8"
+)
+
+const (
+ // 操作人类别-其它
+ OPERATOR_TYPE_OTHER = "0"
+
+ // 操作人类别-后台用户
+ OPERATOR_TYPE_MANAGE = "1"
+
+ // 操作人类别-手机端用户
+ OPERATOR_TYPE_MOBILE = "2"
+)
+
+// Option 操作日志参数
+type Options struct {
+ Title string `json:"title"` // 标题
+ BusinessType string `json:"businessType"` // 类型,默认常量 BUSINESS_TYPE_OTHER
+ OperatorType string `json:"operatorType"` // 操作人类别,默认常量 OPERATOR_TYPE_OTHER
+ IsSaveRequestData bool `json:"isSaveRequestData"` // 是否保存请求的参数
+ IsSaveResponseData bool `json:"isSaveResponseData"` // 是否保存响应的参数
+}
+
+// OptionNew 操作日志参数默认值
+//
+// 标题 "title":"--"
+//
+// 类型 "businessType": BUSINESS_TYPE_OTHER
+//
+// 注意之后JSON反序列使用:c.ShouldBindBodyWith(¶ms, binding.JSON)
+func OptionNew(title, businessType string) Options {
+ return Options{
+ Title: title,
+ BusinessType: businessType,
+ OperatorType: OPERATOR_TYPE_OTHER,
+ IsSaveRequestData: true,
+ IsSaveResponseData: true,
+ }
+}
+
+// OperateLog 访问操作日志记录
+//
+// 请在用户身份授权认证校验后使用以便获取登录用户信息
+func OperateLog(options Options) gin.HandlerFunc {
+ return func(c *gin.Context) {
+ c.Set("startTime", time.Now())
+ language := ctx.AcceptLanguage(c)
+
+ // 函数名
+ funcName := c.HandlerName()
+ lastDotIndex := strings.LastIndex(funcName, "/")
+ funcName = funcName[lastDotIndex+1:]
+
+ // 解析ip地址
+ ipaddr, location := ctx.IPAddrLocation(c)
+
+ // 获取登录用户信息
+ loginUser, err := ctx.LoginUser(c)
+ if err != nil {
+ c.JSON(401, result.CodeMsg(401, i18n.TKey(language, err.Error())))
+ c.Abort() // 停止执行后续的处理函数
+ return
+ }
+
+ // 操作日志记录
+ operLog := model.SysLogOperate{
+ Title: options.Title,
+ BusinessType: options.BusinessType,
+ OperatorType: options.OperatorType,
+ Method: funcName,
+ OperURL: c.Request.URL.Path,
+ RequestMethod: c.Request.Method,
+ OperIP: ipaddr,
+ OperLocation: location,
+ OperName: loginUser.User.UserName,
+ DeptName: loginUser.User.Dept.DeptName,
+ }
+
+ if loginUser.User.UserType == "sys" {
+ operLog.OperatorType = OPERATOR_TYPE_MANAGE
+ }
+
+ // 是否需要保存request,参数和值
+ if options.IsSaveRequestData {
+ params := ctx.RequestParamsMap(c)
+ // 敏感属性字段进行掩码
+ processSensitiveFields(params)
+ jsonStr, _ := json.Marshal(params)
+ paramsStr := string(jsonStr)
+ if len(paramsStr) > 2000 {
+ paramsStr = paramsStr[:2000]
+ }
+ operLog.OperParam = paramsStr
+ }
+
+ // 调用下一个处理程序
+ c.Next()
+
+ // 响应状态
+ status := c.Writer.Status()
+ if status == 200 {
+ operLog.Status = common.STATUS_YES
+ } else {
+ operLog.Status = common.STATUS_NO
+ }
+
+ // 是否需要保存response,参数和值
+ if options.IsSaveResponseData {
+ contentDisposition := c.Writer.Header().Get("Content-Disposition")
+ contentType := c.Writer.Header().Get("Content-Type")
+ content := contentType + contentDisposition
+ msg := fmt.Sprintf(`{"status":"%d","size":"%d","content-type":"%s"}`, status, c.Writer.Size(), content)
+ operLog.OperMsg = msg
+ }
+
+ // 日志记录时间
+ duration := time.Since(c.GetTime("startTime"))
+ operLog.CostTime = duration.Milliseconds()
+ operLog.OperTime = time.Now().UnixMilli()
+
+ // 保存操作记录到数据库
+ service.NewSysLogOperateImpl.InsertSysLogOperate(operLog)
+ }
+}
+
+// 敏感属性字段进行掩码
+var maskProperties []string = []string{
+ "password",
+ "privateKey",
+ "privatePassword",
+ "passPhrase",
+ "oldPassword",
+ "newPassword",
+ "confirmPassword",
+}
+
+// processSensitiveFields 处理敏感属性字段
+func processSensitiveFields(obj interface{}) {
+ val := reflect.ValueOf(obj)
+
+ switch val.Kind() {
+ case reflect.Map:
+ for _, key := range val.MapKeys() {
+ value := val.MapIndex(key)
+ keyStr := key.Interface().(string)
+
+ // 遍历是否敏感属性
+ hasMaskKey := false
+ for _, v := range maskProperties {
+ if v == keyStr {
+ hasMaskKey = true
+ break
+ }
+ }
+
+ if hasMaskKey {
+ valueStr := value.Interface().(string)
+ if len(valueStr) > 100 {
+ valueStr = valueStr[0:100]
+ }
+ val.SetMapIndex(key, reflect.ValueOf(parse.SafeContent(valueStr)))
+ } else {
+ processSensitiveFields(value.Interface())
+ }
+ }
+ case reflect.Slice, reflect.Array:
+ for i := 0; i < val.Len(); i++ {
+ processSensitiveFields(val.Index(i).Interface())
+ }
+ }
+}
diff --git a/src/framework/middleware/cors.go b/src/framework/middleware/cors.go
new file mode 100644
index 0000000..ad2462f
--- /dev/null
+++ b/src/framework/middleware/cors.go
@@ -0,0 +1,83 @@
+package middleware
+
+import (
+ "fmt"
+ "strings"
+
+ "nms_nbi/src/framework/config"
+
+ "github.com/gin-gonic/gin"
+)
+
+// Cors 跨域
+func Cors() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ // 设置Vary头部
+ c.Header("Vary", "Origin")
+ c.Header("Keep-Alive", "timeout=5")
+
+ requestOrigin := c.GetHeader("Origin")
+ if requestOrigin == "" {
+ c.Next()
+ return
+ }
+
+ origin := requestOrigin
+ if v := config.Get("cors.origin"); v != nil {
+ origin = v.(string)
+ }
+ c.Header("Access-Control-Allow-Origin", origin)
+
+ if v := config.Get("cors.credentials"); v != nil && v.(bool) {
+ c.Header("Access-Control-Allow-Credentials", "true")
+ }
+
+ // OPTIONS
+ if method := c.Request.Method; method == "OPTIONS" {
+ requestMethod := c.GetHeader("Access-Control-Request-Method")
+ if requestMethod == "" {
+ c.Next()
+ return
+ }
+
+ // 响应最大时间值
+ if v := config.Get("cors.maxAge"); v != nil && v.(int) > 10000 {
+ c.Header("Access-Control-Max-Age", fmt.Sprint(v))
+ }
+
+ // 允许方法
+ if v := config.Get("cors.allowMethods"); v != nil {
+ var allowMethods = make([]string, 0)
+ for _, s := range v.([]any) {
+ allowMethods = append(allowMethods, s.(string))
+ }
+ c.Header("Access-Control-Allow-Methods", strings.Join(allowMethods, ","))
+ } else {
+ c.Header("Access-Control-Allow-Methods", "GET,HEAD,PUT,POST,DELETE,PATCH")
+ }
+
+ // 允许请求头
+ if v := config.Get("cors.allowHeaders"); v != nil {
+ var allowHeaders = make([]string, 0)
+ for _, s := range v.([]any) {
+ allowHeaders = append(allowHeaders, s.(string))
+ }
+ c.Header("Access-Control-Allow-Headers", strings.Join(allowHeaders, ","))
+ }
+
+ c.AbortWithStatus(204)
+ return
+ }
+
+ // 暴露请求头
+ if v := config.Get("cors.exposeHeaders"); v != nil {
+ var exposeHeaders = make([]string, 0)
+ for _, s := range v.([]any) {
+ exposeHeaders = append(exposeHeaders, s.(string))
+ }
+ c.Header("Access-Control-Expose-Headers", strings.Join(exposeHeaders, ","))
+ }
+
+ c.Next()
+ }
+}
diff --git a/src/framework/middleware/pre_authorize.go b/src/framework/middleware/pre_authorize.go
new file mode 100644
index 0000000..ab038c5
--- /dev/null
+++ b/src/framework/middleware/pre_authorize.go
@@ -0,0 +1,209 @@
+package middleware
+
+import (
+ "strings"
+
+ AdminConstants "nms_nbi/src/framework/constants/admin"
+ commonConstants "nms_nbi/src/framework/constants/common"
+ "nms_nbi/src/framework/i18n"
+ ctxUtils "nms_nbi/src/framework/utils/ctx"
+ tokenUtils "nms_nbi/src/framework/utils/token"
+ "nms_nbi/src/framework/vo/result"
+
+ "github.com/gin-gonic/gin"
+)
+
+/**无Token可访问白名单 */
+var URL_WHITE_LIST = []string{
+ "/performanceManagement",
+ "/faultManagement",
+ "/systemState",
+ "/omcNeConfig",
+ "/cdrEvent",
+ "/upload-ue",
+ "/oauth/token",
+}
+
+// PreAuthorize 用户身份授权认证校验
+//
+// 只需含有其中角色 "hasRoles": {"xxx"},
+//
+// 只需含有其中权限 "hasPerms": {"xxx"},
+//
+// 同时匹配其中角色 "matchRoles": {"xxx"},
+//
+// 同时匹配其中权限 "matchPerms": {"xxx"},
+func PreAuthorize(options map[string][]string) gin.HandlerFunc {
+ return func(c *gin.Context) {
+ language := ctxUtils.AcceptLanguage(c)
+
+ requestURI := c.Request.RequestURI
+
+ // 判断白名单
+ isWhite := false
+ for _, w := range URL_WHITE_LIST {
+ if strings.Contains(requestURI, w) {
+ isWhite = true
+ break
+ }
+ }
+ if isWhite {
+ c.Next()
+ return
+ }
+
+ // 获取请求头标识信息
+ tokenStr := ctxUtils.Authorization(c)
+ if tokenStr == "" {
+ c.JSON(401, result.CodeMsg(401, i18n.TKey(language, "app.common.err401")))
+ c.Abort() // 停止执行后续的处理函数
+ return
+ }
+
+ // 验证令牌
+ claims, err := tokenUtils.Verify(tokenStr)
+ if err != nil {
+ c.JSON(401, result.CodeMsg(401, err.Error()))
+ c.Abort() // 停止执行后续的处理函数
+ return
+ }
+
+ // 获取缓存的用户信息
+ loginUser := tokenUtils.LoginUser(claims)
+ if loginUser.UserID == "" {
+ c.JSON(401, result.CodeMsg(401, i18n.TKey(language, "app.common.err401")))
+ c.Abort() // 停止执行后续的处理函数
+ return
+ }
+
+ // 检查刷新有效期后存入上下文
+ tokenUtils.RefreshIn(&loginUser)
+ c.Set(commonConstants.CTX_LOGIN_USER, loginUser)
+
+ // 登录用户角色权限校验
+ if options != nil {
+ var roles []string
+ for _, item := range loginUser.User.Roles {
+ roles = append(roles, item.RoleKey)
+ }
+ perms := loginUser.Permissions
+ verifyOk := verifyRolePermission(roles, perms, options)
+ if !verifyOk {
+ msg := i18n.TTemplate(language, "app.common.err403", map[string]any{"method": c.Request.Method, "requestURI": requestURI})
+ c.JSON(403, result.CodeMsg(403, msg))
+ c.Abort() // 停止执行后续的处理函数
+ return
+ }
+ }
+
+ // 调用下一个处理程序
+ c.Next()
+ }
+}
+
+// verifyRolePermission 校验角色权限是否满足
+//
+// roles 角色字符数组
+//
+// perms 权限字符数组
+//
+// options 参数
+func verifyRolePermission(roles, perms []string, options map[string][]string) bool {
+ // 直接放行 管理员角色或任意权限
+ if contains(roles, AdminConstants.ROLE_KEY) || contains(perms, AdminConstants.PERMISSION) {
+ return true
+ }
+ opts := make([]bool, 4)
+
+ // 只需含有其中角色
+ hasRole := false
+ if arr, ok := options["hasRoles"]; ok && len(arr) > 0 {
+ hasRole = some(roles, arr)
+ opts[0] = true
+ }
+
+ // 只需含有其中权限
+ hasPerms := false
+ if arr, ok := options["hasPerms"]; ok && len(arr) > 0 {
+ hasPerms = some(perms, arr)
+ opts[1] = true
+ }
+
+ // 同时匹配其中角色
+ matchRoles := false
+ if arr, ok := options["matchRoles"]; ok && len(arr) > 0 {
+ matchRoles = every(roles, arr)
+ opts[2] = true
+ }
+
+ // 同时匹配其中权限
+ matchPerms := false
+ if arr, ok := options["matchPerms"]; ok && len(arr) > 0 {
+ matchPerms = every(perms, arr)
+ opts[3] = true
+ }
+
+ // 同时判断 含有其中
+ if opts[0] && opts[1] {
+ return hasRole || hasPerms
+ }
+ // 同时判断 匹配其中
+ if opts[2] && opts[3] {
+ return matchRoles && matchPerms
+ }
+ // 同时判断 含有其中且匹配其中
+ if opts[0] && opts[3] {
+ return hasRole && matchPerms
+ }
+ if opts[1] && opts[2] {
+ return hasPerms && matchRoles
+ }
+
+ return hasRole || hasPerms || matchRoles || matchPerms
+}
+
+// contains 检查字符串数组中是否包含指定的字符串
+func contains(arr []string, target string) bool {
+ for _, str := range arr {
+ if str == target {
+ return true
+ }
+ }
+ return false
+}
+
+// some 检查字符串数组中含有其中一项
+func some(origin []string, target []string) bool {
+ has := false
+ for _, t := range target {
+ for _, o := range origin {
+ if t == o {
+ has = true
+ break
+ }
+ }
+ if has {
+ break
+ }
+ }
+ return has
+}
+
+// every 检查字符串数组中同时包含所有项
+func every(origin []string, target []string) bool {
+ match := true
+ for _, t := range target {
+ found := false
+ for _, o := range origin {
+ if t == o {
+ found = true
+ break
+ }
+ }
+ if !found {
+ match = false
+ break
+ }
+ }
+ return match
+}
diff --git a/src/framework/middleware/rate_limit.go b/src/framework/middleware/rate_limit.go
new file mode 100644
index 0000000..8eb9e28
--- /dev/null
+++ b/src/framework/middleware/rate_limit.go
@@ -0,0 +1,105 @@
+package middleware
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/constants/cachekey"
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/redis"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/ip2region"
+ "nms_nbi/src/framework/vo/result"
+
+ "github.com/gin-gonic/gin"
+)
+
+const (
+ // 默认策略全局限流
+ LIMIT_GLOBAL = 1
+
+ // 根据请求者IP进行限流
+ LIMIT_IP = 2
+
+ // 根据用户ID进行限流
+ LIMIT_USER = 3
+)
+
+// LimitOption 请求限流参数
+type LimitOption struct {
+ Time int64 `json:"time"` // 限流时间,单位秒
+ Count int64 `json:"count"` // 限流次数
+ Type int64 `json:"type"` // 限流条件类型,默认LIMIT_GLOBAL
+}
+
+// RateLimit 请求限流
+//
+// 示例参数:middleware.LimitOption{ Time: 5, Count: 10, Type: middleware.LIMIT_IP }
+//
+// 参数表示:5秒内,最多请求10次,限制类型为 IP
+//
+// 使用 USER 时,请在用户身份授权认证校验后使用
+// 以便获取登录用户信息,无用户信息时默认为 GLOBAL
+func RateLimit(option LimitOption) gin.HandlerFunc {
+ return func(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+
+ // 初始可选参数数据
+ if option.Time < 5 {
+ option.Time = 5
+ }
+ if option.Count < 10 {
+ option.Count = 10
+ }
+ if option.Type == 0 {
+ option.Type = LIMIT_GLOBAL
+ }
+
+ // 获取执行函数名称
+ funcName := c.HandlerName()
+ lastDotIndex := strings.LastIndex(funcName, "/")
+ funcName = funcName[lastDotIndex+1:]
+ // 生成限流key
+ var limitKey string = cachekey.RATE_LIMIT_KEY + funcName
+
+ // 用户
+ if option.Type == LIMIT_USER {
+ loginUser, err := ctx.LoginUser(c)
+ if err != nil {
+ c.JSON(401, result.Err(map[string]any{
+ "code": 401,
+ "msg": i18n.TKey(language, err.Error()),
+ }))
+ c.Abort() // 停止执行后续的处理函数
+ return
+ }
+ limitKey = cachekey.RATE_LIMIT_KEY + loginUser.UserID + ":" + funcName
+ }
+
+ // IP
+ if option.Type == LIMIT_IP {
+ clientIP := ip2region.ClientIP(c.ClientIP())
+ limitKey = cachekey.RATE_LIMIT_KEY + clientIP + ":" + funcName
+ }
+
+ // 在Redis查询并记录请求次数
+ rateCount, _ := redis.RateLimit("", limitKey, option.Time, option.Count)
+ rateTime, _ := redis.GetExpire("", limitKey)
+
+ // 设置响应头中的限流声明字段
+ c.Header("X-RateLimit-Limit", fmt.Sprintf("%d", option.Count)) // 总请求数限制
+ c.Header("X-RateLimit-Remaining", fmt.Sprintf("%d", option.Count-rateCount)) // 剩余可用请求数
+ c.Header("X-RateLimit-Reset", fmt.Sprintf("%d", time.Now().Unix()+int64(rateTime))) // 重置时间戳
+
+ if rateCount >= option.Count {
+ // 访问过于频繁,请稍候再试
+ c.JSON(200, i18n.TKey(language, "app.common.rateLimitTip"))
+ c.Abort() // 停止执行后续的处理函数
+ return
+ }
+
+ // 调用下一个处理程序
+ c.Next()
+ }
+}
diff --git a/src/framework/middleware/repeat/repeat.go b/src/framework/middleware/repeat/repeat.go
new file mode 100644
index 0000000..37e2aef
--- /dev/null
+++ b/src/framework/middleware/repeat/repeat.go
@@ -0,0 +1,85 @@
+package repeat
+
+import (
+ "encoding/json"
+ "strconv"
+ "time"
+
+ "nms_nbi/src/framework/constants/cachekey"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/redis"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/ip2region"
+ "nms_nbi/src/framework/vo/result"
+
+ "github.com/gin-gonic/gin"
+)
+
+// repeatParam 重复提交参数的类型定义
+type repeatParam struct {
+ Time int64 `json:"time"`
+ Params string `json:"params"`
+}
+
+// RepeatSubmit 防止表单重复提交,小于间隔时间视为重复提交
+//
+// 间隔时间(单位秒) 默认:5
+//
+// 注意之后JSON反序列使用:c.ShouldBindBodyWith(¶ms, binding.JSON)
+func RepeatSubmit(interval int64) gin.HandlerFunc {
+ return func(c *gin.Context) {
+ if interval < 5 {
+ interval = 5
+ }
+
+ // 提交参数
+ params := ctx.RequestParamsMap(c)
+ paramsJSONByte, err := json.Marshal(params)
+ if err != nil {
+ logger.Errorf("RepeatSubmit params json marshal err: %v", err)
+ }
+ paramsJSONStr := string(paramsJSONByte)
+
+ // 唯一标识(指定key + 客户端IP + 请求地址)
+ clientIP := ip2region.ClientIP(c.ClientIP())
+ repeatKey := cachekey.REPEAT_SUBMIT_KEY + clientIP + ":" + c.Request.RequestURI
+
+ // 在Redis查询并记录请求次数
+ repeatStr, _ := redis.Get("", repeatKey)
+ if repeatStr != "" {
+ var rp repeatParam
+ err := json.Unmarshal([]byte(repeatStr), &rp)
+ if err != nil {
+ logger.Errorf("RepeatSubmit repeatStr json unmarshal err: %v", err)
+ }
+ compareTime := time.Now().Unix() - rp.Time
+ compareParams := rp.Params == paramsJSONStr
+
+ // 设置重复提交声明响应头(毫秒)
+ c.Header("X-RepeatSubmit-Rest", strconv.FormatInt(time.Now().Add(time.Duration(compareTime)*time.Second).UnixNano()/int64(time.Millisecond), 10))
+
+ // 小于间隔时间且参数内容一致
+ if compareTime < interval && compareParams {
+ // 不允许重复提交,请稍候再试
+ c.JSON(200, result.ErrMsg("Duplicate submissions are not allowed. Please try again later"))
+ c.Abort()
+ return
+ }
+ }
+
+ // 当前请求参数
+ rp := repeatParam{
+ Time: time.Now().Unix(),
+ Params: paramsJSONStr,
+ }
+ rpJSON, err := json.Marshal(rp)
+ if err != nil {
+ logger.Errorf("RepeatSubmit rp json marshal err: %v", err)
+ }
+ // 保存请求时间和参数
+ redis.SetByExpire("", repeatKey, string(rpJSON), time.Duration(interval)*time.Second)
+
+ // 调用下一个处理程序
+ c.Next()
+ }
+}
diff --git a/src/framework/middleware/report.go b/src/framework/middleware/report.go
new file mode 100644
index 0000000..2ac4f70
--- /dev/null
+++ b/src/framework/middleware/report.go
@@ -0,0 +1,23 @@
+package middleware
+
+import (
+ "time"
+
+ "nms_nbi/src/framework/logger"
+
+ "github.com/gin-gonic/gin"
+)
+
+// Report 请求响应日志
+func Report() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ start := time.Now()
+
+ // 调用下一个处理程序
+ c.Next()
+
+ // 计算请求处理时间,并打印日志
+ duration := time.Since(start)
+ logger.Infof("%s %s report end=> %v", c.Request.Method, c.Request.RequestURI, duration)
+ }
+}
diff --git a/src/framework/middleware/security/csp.go b/src/framework/middleware/security/csp.go
new file mode 100644
index 0000000..d319f55
--- /dev/null
+++ b/src/framework/middleware/security/csp.go
@@ -0,0 +1,22 @@
+package security
+
+import (
+ "nms_nbi/src/framework/config"
+ "nms_nbi/src/framework/utils/generate"
+
+ "github.com/gin-gonic/gin"
+)
+
+// TODO
+// csp 这将帮助防止跨站脚本攻击(XSS)。
+// HTTP 响应头 Content-Security-Policy 允许站点管理者控制指定的页面加载哪些资源。
+func csp(c *gin.Context) {
+ enable := false
+ if v := config.Get("security.csp.enable"); v != nil {
+ enable = v.(bool)
+ }
+
+ if enable {
+ c.Header("x-csp-nonce", generate.Code(8))
+ }
+}
diff --git a/src/framework/middleware/security/hsts.go b/src/framework/middleware/security/hsts.go
new file mode 100644
index 0000000..da9eedb
--- /dev/null
+++ b/src/framework/middleware/security/hsts.go
@@ -0,0 +1,37 @@
+package security
+
+import (
+ "fmt"
+
+ "nms_nbi/src/framework/config"
+
+ "github.com/gin-gonic/gin"
+)
+
+// hsts 是一个安全功能 HTTP Strict Transport Security(通常简称为 HSTS )
+// 它告诉浏览器只能通过 HTTPS 访问当前资源,而不是 HTTP。
+func hsts(c *gin.Context) {
+ enable := false
+ if v := config.Get("security.hsts.enable"); v != nil {
+ enable = v.(bool)
+ }
+
+ maxAge := 365 * 24 * 3600
+ if v := config.Get("security.hsts.maxAge"); v != nil {
+ maxAge = v.(int)
+ }
+
+ includeSubdomains := false
+ if v := config.Get("security.hsts.includeSubdomains"); v != nil {
+ includeSubdomains = v.(bool)
+ }
+
+ str := fmt.Sprintf("max-age=%d", maxAge)
+ if includeSubdomains {
+ str += "; includeSubdomains"
+ }
+
+ if enable {
+ c.Header("strict-transport-security", str)
+ }
+}
diff --git a/src/framework/middleware/security/noopen.go b/src/framework/middleware/security/noopen.go
new file mode 100644
index 0000000..d18ba5e
--- /dev/null
+++ b/src/framework/middleware/security/noopen.go
@@ -0,0 +1,20 @@
+package security
+
+import (
+ "nms_nbi/src/framework/config"
+
+ "github.com/gin-gonic/gin"
+)
+
+// noopen 用于指定 IE 8 以上版本的用户不打开文件而直接保存文件。
+// 在下载对话框中不显式“打开”选项。
+func noopen(c *gin.Context) {
+ enable := false
+ if v := config.Get("security.noopen.enable"); v != nil {
+ enable = v.(bool)
+ }
+
+ if enable {
+ c.Header("x-download-options", "noopen")
+ }
+}
diff --git a/src/framework/middleware/security/nosniff.go b/src/framework/middleware/security/nosniff.go
new file mode 100644
index 0000000..d300f52
--- /dev/null
+++ b/src/framework/middleware/security/nosniff.go
@@ -0,0 +1,26 @@
+package security
+
+import (
+ "nms_nbi/src/framework/config"
+
+ "github.com/gin-gonic/gin"
+)
+
+// nosniff 用于防止 XSS 等跨站脚本攻击
+// 如果从 script 或 stylesheet 读入的文件的 MIME 类型与指定 MIME 类型不匹配,不允许读取该文件。
+func nosniff(c *gin.Context) {
+ // 排除状态码范围
+ status := c.Writer.Status()
+ if status >= 300 && status <= 308 {
+ return
+ }
+
+ enable := false
+ if v := config.Get("security.nosniff.enable"); v != nil {
+ enable = v.(bool)
+ }
+
+ if enable {
+ c.Header("x-content-type-options", "nosniff")
+ }
+}
diff --git a/src/framework/middleware/security/referer.go b/src/framework/middleware/security/referer.go
new file mode 100644
index 0000000..947cae1
--- /dev/null
+++ b/src/framework/middleware/security/referer.go
@@ -0,0 +1,79 @@
+package security
+
+import (
+ "net/url"
+
+ "nms_nbi/src/framework/config"
+ "nms_nbi/src/framework/vo/result"
+
+ "github.com/gin-gonic/gin"
+)
+
+// referer 配置 referer 的 host 部分
+func referer(c *gin.Context) {
+ enable := false
+ if v := config.Get("security.csrf.enable"); v != nil {
+ enable = v.(bool)
+ }
+ if !enable {
+ return
+ }
+
+ // csrf 校验类型
+ okType := false
+ if v := config.Get("security.csrf.type"); v != nil {
+ vType := v.(string)
+ if vType == "all" || vType == "any" || vType == "referer" {
+ okType = true
+ }
+ }
+ if !okType {
+ return
+ }
+
+ // 忽略请求方法
+ method := c.Request.Method
+ ignoreMethods := []string{"GET", "HEAD", "OPTIONS", "TRACE"}
+ for _, ignore := range ignoreMethods {
+ if ignore == method {
+ return
+ }
+ }
+
+ referer := c.GetHeader("Referer")
+ if referer == "" {
+ // 无效 Referer 未知
+ c.AbortWithStatusJSON(200, result.ErrMsg("Invalid referer unknown"))
+ return
+ }
+
+ // 获取host
+ u, err := url.Parse(referer)
+ if err != nil {
+ // 无效 Referer 未知
+ c.AbortWithStatusJSON(200, result.ErrMsg("Invalid referer unknown"))
+ return
+ }
+ host := u.Host
+
+ // 允许的来源白名单
+ refererWhiteList := make([]string, 0)
+ if v := config.Get("security.csrf.refererWhiteList"); v != nil {
+ for _, s := range v.([]any) {
+ refererWhiteList = append(refererWhiteList, s.(string))
+ }
+ }
+
+ // 遍历检查
+ ok := false
+ for _, domain := range refererWhiteList {
+ if domain == host {
+ ok = true
+ }
+ }
+ if !ok {
+ // 无效 Referer
+ c.AbortWithStatusJSON(200, result.ErrMsg("Invalid referer "+host))
+ return
+ }
+}
diff --git a/src/framework/middleware/security/security.go b/src/framework/middleware/security/security.go
new file mode 100644
index 0000000..d922a09
--- /dev/null
+++ b/src/framework/middleware/security/security.go
@@ -0,0 +1,23 @@
+package security
+
+import (
+ "github.com/gin-gonic/gin"
+)
+
+// Security 安全
+func Security() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ // 拦截,判断是否有效Referer
+ referer(c)
+
+ // 无拦截,仅仅设置响应头
+ xframe(c)
+ csp(c)
+ hsts(c)
+ noopen(c)
+ nosniff(c)
+ xssProtection(c)
+
+ c.Next()
+ }
+}
diff --git a/src/framework/middleware/security/xframe.go b/src/framework/middleware/security/xframe.go
new file mode 100644
index 0000000..411c208
--- /dev/null
+++ b/src/framework/middleware/security/xframe.go
@@ -0,0 +1,26 @@
+package security
+
+import (
+ "nms_nbi/src/framework/config"
+
+ "github.com/gin-gonic/gin"
+)
+
+// xframe 用来配置 X-Frame-Options 响应头
+// 用来给浏览器指示允许一个页面可否在 frame, iframe, embed 或者 object 中展现的标记。
+// 站点可以通过确保网站没有被嵌入到别人的站点里面,从而避免 clickjacking 攻击。
+func xframe(c *gin.Context) {
+ enable := false
+ if v := config.Get("security.xframe.enable"); v != nil {
+ enable = v.(bool)
+ }
+
+ value := "sameorigin"
+ if v := config.Get("security.xframe.value"); v != nil {
+ value = v.(string)
+ }
+
+ if enable {
+ c.Header("x-frame-options", value)
+ }
+}
diff --git a/src/framework/middleware/security/xss_protection.go b/src/framework/middleware/security/xss_protection.go
new file mode 100644
index 0000000..8d75d74
--- /dev/null
+++ b/src/framework/middleware/security/xss_protection.go
@@ -0,0 +1,24 @@
+package security
+
+import (
+ "nms_nbi/src/framework/config"
+
+ "github.com/gin-gonic/gin"
+)
+
+// xssProtection 用于启用浏览器的XSS过滤功能,以防止 XSS 跨站脚本攻击。
+func xssProtection(c *gin.Context) {
+ enable := false
+ if v := config.Get("security.xssProtection.enable"); v != nil {
+ enable = v.(bool)
+ }
+
+ value := "1; mode=block"
+ if v := config.Get("security.xssProtection.value"); v != nil {
+ value = v.(string)
+ }
+
+ if enable {
+ c.Header("x-xss-protection", value)
+ }
+}
diff --git a/src/framework/redis/redis.go b/src/framework/redis/redis.go
new file mode 100644
index 0000000..8ea549d
--- /dev/null
+++ b/src/framework/redis/redis.go
@@ -0,0 +1,358 @@
+package redis
+
+import (
+ "context"
+ "fmt"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/config"
+ "nms_nbi/src/framework/logger"
+
+ "github.com/redis/go-redis/v9"
+)
+
+// Redis连接实例
+var rdbMap = make(map[string]*redis.Client)
+
+// 声明定义限流脚本命令
+var rateLimitCommand = redis.NewScript(`
+local key = KEYS[1]
+local time = tonumber(ARGV[1])
+local count = tonumber(ARGV[2])
+local current = redis.call('get', key);
+if current and tonumber(current) >= count then
+ return tonumber(current);
+end
+current = redis.call('incr', key)
+if tonumber(current) == 1 then
+ redis.call('expire', key, time)
+end
+return tonumber(current);`)
+
+// 连接Redis实例
+func Connect() {
+ ctx := context.Background()
+ // 读取数据源配置
+ datasource := config.Get("redis.dataSource").(map[string]any)
+ for k, v := range datasource {
+ client := v.(map[string]any)
+ // 创建连接
+ address := fmt.Sprintf("%s:%d", client["host"], client["port"])
+ rdb := redis.NewClient(&redis.Options{
+ Addr: address,
+ Password: client["password"].(string),
+ DB: client["db"].(int),
+ })
+ // 测试数据库连接
+ pong, err := rdb.Ping(ctx).Result()
+ if err != nil {
+ logger.Fatalf("Ping redis %s is %v", k, err)
+ }
+ logger.Infof("redis %s %s %d connection is successful.", k, pong, client["db"].(int))
+ rdbMap[k] = rdb
+ }
+}
+
+// 关闭Redis实例
+func Close() {
+ for _, rdb := range rdbMap {
+ if err := rdb.Close(); err != nil {
+ logger.Errorf("fatal error db close: %s", err)
+ }
+ }
+}
+
+// 获取默认实例
+func DefaultRDB() *redis.Client {
+ source := config.Get("redis.defaultDataSourceName").(string)
+ return rdbMap[source]
+}
+
+// 获取实例
+func RDB(source string) *redis.Client {
+ return rdbMap[source]
+}
+
+// Info 获取redis服务信息
+func Info(source string) map[string]map[string]string {
+ // 数据源
+ rdb := DefaultRDB()
+ if source != "" {
+ rdb = RDB(source)
+ }
+
+ ctx := context.Background()
+ info, err := rdb.Info(ctx).Result()
+ if err != nil {
+ return map[string]map[string]string{}
+ }
+ infoObj := make(map[string]map[string]string)
+ lines := strings.Split(info, "\r\n")
+ label := ""
+ for _, line := range lines {
+ if strings.Contains(line, "#") {
+ label = strings.Fields(line)[len(strings.Fields(line))-1]
+ label = strings.ToLower(label)
+ infoObj[label] = make(map[string]string)
+ continue
+ }
+ kvArr := strings.Split(line, ":")
+ if len(kvArr) >= 2 {
+ key := strings.TrimSpace(kvArr[0])
+ value := strings.TrimSpace(kvArr[len(kvArr)-1])
+ infoObj[label][key] = value
+ }
+ }
+ return infoObj
+}
+
+// KeySize 获取redis当前连接可用键Key总数信息
+func KeySize(source string) int64 {
+ // 数据源
+ rdb := DefaultRDB()
+ if source != "" {
+ rdb = RDB(source)
+ }
+
+ ctx := context.Background()
+ size, err := rdb.DBSize(ctx).Result()
+ if err != nil {
+ return 0
+ }
+ return size
+}
+
+// CommandStats 获取redis命令状态信息
+func CommandStats(source string) []map[string]string {
+ // 数据源
+ rdb := DefaultRDB()
+ if source != "" {
+ rdb = RDB(source)
+ }
+
+ ctx := context.Background()
+ commandstats, err := rdb.Info(ctx, "commandstats").Result()
+ if err != nil {
+ return []map[string]string{}
+ }
+ statsObjArr := make([]map[string]string, 0)
+ lines := strings.Split(commandstats, "\r\n")
+ for _, line := range lines {
+ if !strings.HasPrefix(line, "cmdstat_") {
+ continue
+ }
+ kvArr := strings.Split(line, ":")
+ key := kvArr[0]
+ valueStr := kvArr[len(kvArr)-1]
+ statsObj := make(map[string]string)
+ statsObj["name"] = key[8:]
+ statsObj["value"] = valueStr[6:strings.Index(valueStr, ",usec=")]
+ statsObjArr = append(statsObjArr, statsObj)
+ }
+ return statsObjArr
+}
+
+// 获取键的剩余有效时间(秒)
+func GetExpire(source string, key string) (float64, error) {
+ // 数据源
+ rdb := DefaultRDB()
+ if source != "" {
+ rdb = RDB(source)
+ }
+
+ ctx := context.Background()
+ ttl, err := rdb.TTL(ctx, key).Result()
+ if err != nil {
+ return 0, err
+ }
+ return ttl.Seconds(), nil
+}
+
+// 获得缓存数据的key列表
+func GetKeys(source string, pattern string) ([]string, error) {
+ // 数据源
+ rdb := DefaultRDB()
+ if source != "" {
+ rdb = RDB(source)
+ }
+
+ // 初始化变量
+ var keys []string
+ var cursor uint64 = 0
+ ctx := context.Background()
+ // 循环遍历获取匹配的键
+ for {
+ // 使用 SCAN 命令获取匹配的键
+ batchKeys, nextCursor, err := rdb.Scan(ctx, cursor, pattern, 100).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
+ }
+ }
+ return keys, nil
+}
+
+// 批量获得缓存数据
+func GetBatch(source string, keys []string) ([]any, error) {
+ if len(keys) == 0 {
+ return []any{}, fmt.Errorf("not keys")
+ }
+
+ // 数据源
+ rdb := DefaultRDB()
+ if source != "" {
+ rdb = RDB(source)
+ }
+
+ // 获取缓存数据
+ result, err := rdb.MGet(context.Background(), keys...).Result()
+ if err != nil {
+ logger.Errorf("Failed to get batch data: %v", err)
+ return []any{}, err
+ }
+ return result, nil
+}
+
+// 获得缓存数据
+func Get(source, key string) (string, error) {
+ // 数据源
+ rdb := DefaultRDB()
+ if source != "" {
+ rdb = RDB(source)
+ }
+
+ ctx := context.Background()
+ value, err := rdb.Get(ctx, key).Result()
+ if err == redis.Nil || err != nil {
+ return "", err
+ }
+ return value, nil
+}
+
+// 获得缓存数据Hash
+func GetHash(source, key string) (map[string]string, error) {
+ // 数据源
+ rdb := DefaultRDB()
+ if source != "" {
+ rdb = RDB(source)
+ }
+
+ ctx := context.Background()
+ value, err := rdb.HGetAll(ctx, key).Result()
+ if err == redis.Nil || err != nil {
+ return map[string]string{}, err
+ }
+ return value, nil
+}
+
+// 判断是否存在
+func Has(source string, keys ...string) (bool, error) {
+ // 数据源
+ rdb := DefaultRDB()
+ if source != "" {
+ rdb = RDB(source)
+ }
+
+ ctx := context.Background()
+ exists, err := rdb.Exists(ctx, keys...).Result()
+ if err != nil {
+ return false, err
+ }
+ return exists >= 1, nil
+}
+
+// 设置缓存数据
+func Set(source, key string, value any) (bool, error) {
+ // 数据源
+ rdb := DefaultRDB()
+ if source != "" {
+ rdb = RDB(source)
+ }
+
+ ctx := context.Background()
+ err := rdb.Set(ctx, key, value, 0).Err()
+ if err != nil {
+ logger.Errorf("redis Set err %v", err)
+ return false, err
+ }
+ return true, nil
+}
+
+// 设置缓存数据与过期时间
+func SetByExpire(source, key string, value any, expiration time.Duration) (bool, error) {
+ // 数据源
+ rdb := DefaultRDB()
+ if source != "" {
+ rdb = RDB(source)
+ }
+
+ ctx := context.Background()
+ err := rdb.Set(ctx, key, value, expiration).Err()
+ if err != nil {
+ logger.Errorf("redis SetByExpire err %v", err)
+ return false, err
+ }
+ return true, nil
+}
+
+// 删除单个
+func Del(source string, key string) (bool, error) {
+ // 数据源
+ rdb := DefaultRDB()
+ if source != "" {
+ rdb = RDB(source)
+ }
+
+ ctx := context.Background()
+ err := rdb.Del(ctx, key).Err()
+ if err != nil {
+ logger.Errorf("redis Del err %v", err)
+ return false, err
+ }
+ return true, nil
+}
+
+// 删除多个
+func DelKeys(source string, keys []string) (bool, error) {
+ if len(keys) == 0 {
+ return false, fmt.Errorf("no keys")
+ }
+
+ // 数据源
+ rdb := DefaultRDB()
+ if source != "" {
+ rdb = RDB(source)
+ }
+
+ ctx := context.Background()
+ err := rdb.Del(ctx, keys...).Err()
+ if err != nil {
+ logger.Errorf("redis DelKeys err %v", err)
+ return false, err
+ }
+ return true, nil
+}
+
+// 限流查询并记录
+func RateLimit(source, limitKey string, time, count int64) (int64, error) {
+ // 数据源
+ rdb := DefaultRDB()
+ if source != "" {
+ rdb = RDB(source)
+ }
+
+ ctx := context.Background()
+ result, err := rateLimitCommand.Run(ctx, rdb, []string{limitKey}, time, count).Result()
+ if err != nil {
+ logger.Errorf("redis RateLimit err %v", err)
+ return 0, err
+ }
+ return result.(int64), err
+}
diff --git a/src/framework/utils/cmd/cmd.go b/src/framework/utils/cmd/cmd.go
new file mode 100644
index 0000000..f26919f
--- /dev/null
+++ b/src/framework/utils/cmd/cmd.go
@@ -0,0 +1,208 @@
+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 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
+}
+
+// Execf 本地执行命令 列如:("ssh %s@%s", "user", "localhost")
+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
+}
+
+// ExecWithTimeOut 本地执行命令超时退出 列如:("ssh user@localhost", 20*time.Second)
+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
+}
+
+// ExecWithCheck 执行命令程序带参数 例如:("ls", "-r", "-l", "-s")
+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
+}
+
+// CheckIllegal 检查传入的字符串参数中是否包含一些特殊字符
+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
+}
+
+// HasNoPasswordSudo 检查当前用户是否拥有sudo权限
+func HasNoPasswordSudo() bool {
+ cmd2 := exec.Command("sudo", "-n", "uname")
+ err2 := cmd2.Run()
+ return err2 == nil
+}
+
+// SudoHandleCmd 是否拥有sudo权限并拼接
+func SudoHandleCmd() string {
+ cmd := exec.Command("sudo", "-n", "uname")
+ if err := cmd.Run(); err == nil {
+ return "sudo "
+ }
+ return ""
+}
+
+// Which 可执行文件是否在系统的PATH环境变量中
+func Which(name string) bool {
+ _, err := exec.LookPath(name)
+ return err == nil
+}
diff --git a/src/framework/utils/crypto/aes.go b/src/framework/utils/crypto/aes.go
new file mode 100644
index 0000000..4db36ac
--- /dev/null
+++ b/src/framework/utils/crypto/aes.go
@@ -0,0 +1,88 @@
+package crypto
+
+import (
+ "bytes"
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/rand"
+ "encoding/base64"
+ "fmt"
+ "io"
+)
+
+// aesKey 字符串AES加解密密钥
+const aesKey = "AGT66VfY4SMaiT97"
+
+// StringEncryptByAES 字符串AES加密
+func StringEncryptByAES(text string) (string, error) {
+ if len(text) == 0 {
+ return "", nil
+ }
+ pass := []byte(text)
+ xpass, err := aesEncryptWithSalt([]byte(aesKey), pass)
+ if err == nil {
+ pass64 := base64.StdEncoding.EncodeToString(xpass)
+ return pass64, err
+ }
+ return "", err
+}
+
+// StringDecryptByAES 字符串AES解密
+func StringDecryptByAES(text string) (string, error) {
+ if len(text) == 0 {
+ return "", nil
+ }
+ bytesPass, err := base64.StdEncoding.DecodeString(text)
+ if err != nil {
+ return "", err
+ }
+ var tpass []byte
+ tpass, err = aesDecryptWithSalt([]byte(aesKey), bytesPass)
+ if err == nil {
+ result := string(tpass[:])
+ return result, err
+ }
+ return "", err
+}
+
+// aesEncryptWithSalt AES加密
+func aesEncryptWithSalt(key, plaintext []byte) ([]byte, error) {
+ blockSize := aes.BlockSize
+ padding := blockSize - len(plaintext)%blockSize
+ padtext := bytes.Repeat([]byte{byte(padding)}, padding)
+ plaintext = append(plaintext, padtext...)
+
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ return nil, err
+ }
+ ciphertext := make([]byte, blockSize+len(plaintext))
+ iv := ciphertext[0:blockSize]
+ if _, err := io.ReadFull(rand.Reader, iv); err != nil {
+ return nil, err
+ }
+ cbc := cipher.NewCBCEncrypter(block, iv)
+ cbc.CryptBlocks(ciphertext[blockSize:], plaintext)
+ return ciphertext, nil
+}
+
+// aesDecryptWithSalt AES解密
+func aesDecryptWithSalt(key, ciphertext []byte) ([]byte, error) {
+ blockSize := aes.BlockSize
+ var block cipher.Block
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ return nil, err
+ }
+ if len(ciphertext) < blockSize {
+ return nil, fmt.Errorf("iciphertext too short")
+ }
+ iv := ciphertext[:blockSize]
+ ciphertext = ciphertext[blockSize:]
+ cbc := cipher.NewCBCDecrypter(block, iv)
+ cbc.CryptBlocks(ciphertext, ciphertext)
+ length := len(ciphertext)
+ unpadding := int(ciphertext[len(ciphertext)-1])
+ ciphertext = ciphertext[:(length - unpadding)]
+ return ciphertext, nil
+}
diff --git a/src/framework/utils/crypto/bcrypt.go b/src/framework/utils/crypto/bcrypt.go
new file mode 100644
index 0000000..c242f7f
--- /dev/null
+++ b/src/framework/utils/crypto/bcrypt.go
@@ -0,0 +1,20 @@
+package crypto
+
+import (
+ "golang.org/x/crypto/bcrypt"
+)
+
+// BcryptHash Bcrypt密码加密
+func BcryptHash(originStr string) string {
+ hash, err := bcrypt.GenerateFromPassword([]byte(originStr), bcrypt.DefaultCost)
+ if err != nil {
+ return ""
+ }
+ return string(hash)
+}
+
+// BcryptCompare Bcrypt密码匹配检查
+func BcryptCompare(originStr, hashStr string) bool {
+ err := bcrypt.CompareHashAndPassword([]byte(hashStr), []byte(originStr))
+ return err == nil
+}
diff --git a/src/framework/utils/ctx/ctx.go b/src/framework/utils/ctx/ctx.go
new file mode 100644
index 0000000..2cdc954
--- /dev/null
+++ b/src/framework/utils/ctx/ctx.go
@@ -0,0 +1,232 @@
+package ctx
+
+import (
+ "fmt"
+ "strings"
+
+ "nms_nbi/src/framework/config"
+ "nms_nbi/src/framework/constants/common"
+ "nms_nbi/src/framework/constants/roledatascope"
+ "nms_nbi/src/framework/constants/token"
+ "nms_nbi/src/framework/utils/ip2region"
+ "nms_nbi/src/framework/utils/ua"
+ "nms_nbi/src/framework/vo"
+
+ "golang.org/x/text/language"
+
+ "github.com/gin-gonic/gin"
+ "github.com/gin-gonic/gin/binding"
+)
+
+// QueryMap 查询参数转换Map
+func QueryMap(c *gin.Context) map[string]any {
+ queryValues := c.Request.URL.Query()
+ queryParams := make(map[string]any)
+ for key, values := range queryValues {
+ queryParams[key] = values[0]
+ }
+ return queryParams
+}
+
+// BodyJSONMap JSON参数转换Map
+func BodyJSONMap(c *gin.Context) map[string]any {
+ params := make(map[string]any)
+ c.ShouldBindBodyWith(¶ms, binding.JSON)
+ return params
+}
+
+// RequestParamsMap 请求参数转换Map
+func RequestParamsMap(c *gin.Context) map[string]any {
+ params := make(map[string]any)
+ // json
+ if strings.HasPrefix(c.ContentType(), "application/json") {
+ c.ShouldBindBodyWith(¶ms, binding.JSON)
+ }
+
+ // 表单
+ bodyParams := c.Request.PostForm
+ for key, value := range bodyParams {
+ params[key] = value[0]
+ }
+
+ // 查询
+ queryParams := c.Request.URL.Query()
+ for key, value := range queryParams {
+ params[key] = value[0]
+ }
+ return params
+}
+
+// IPAddrLocation 解析ip地址
+func IPAddrLocation(c *gin.Context) (string, string) {
+ ip := ip2region.ClientIP(c.ClientIP())
+ location := ip2region.RealAddressByIp(ip)
+ return ip, location
+}
+
+// Authorization 解析请求头
+func Authorization(c *gin.Context) string {
+ // Query请求查询
+ if authQuery, ok := c.GetQuery(token.RESPONSE_FIELD); ok && authQuery != "" {
+ return authQuery
+ }
+ // Header请求头
+ authHeader := c.GetHeader(token.HEADER_KEY)
+ if authHeader == "" {
+ return ""
+ }
+ // 拆分 Authorization 请求头,提取 JWT 令牌部分
+ arr := strings.Split(authHeader, token.HEADER_PREFIX)
+ if len(arr) == 2 && arr[1] == "" {
+ return ""
+ }
+ return arr[1]
+}
+
+// UaOsBrowser 解析请求用户代理信息
+func UaOsBrowser(c *gin.Context) (string, string) {
+ userAgent := c.GetHeader("user-agent")
+ uaInfo := ua.Info(userAgent)
+
+ browser := "app.common.noUaOsBrowser"
+ bName, bVersion := uaInfo.Browser()
+ if bName != "" && bVersion != "" {
+ browser = bName + " " + bVersion
+ }
+
+ os := "app.common.noUaOsBrowser"
+ bos := uaInfo.OS()
+ if bos != "" {
+ os = bos
+ }
+ return os, browser
+}
+
+// AcceptLanguage 解析客户端接收语言 zh:中文 en: 英文
+func AcceptLanguage(c *gin.Context) string {
+ preferredLanguage := language.English
+
+ // Query请求查询
+ if v, ok := c.GetQuery("language"); ok && v != "" {
+ tags, _, _ := language.ParseAcceptLanguage(v)
+ if len(tags) > 0 {
+ preferredLanguage = tags[0]
+ }
+ }
+ // Header请求头
+ if v := c.GetHeader("Accept-Language"); v != "" {
+ tags, _, _ := language.ParseAcceptLanguage(v)
+ if len(tags) > 0 {
+ preferredLanguage = tags[0]
+ }
+ }
+
+ // 只取前缀
+ lang := preferredLanguage.String()
+ arr := strings.Split(lang, "-")
+ return arr[0]
+}
+
+// LoginUser 登录用户信息
+func LoginUser(c *gin.Context) (vo.LoginUser, error) {
+ value, exists := c.Get(common.CTX_LOGIN_USER)
+ if exists {
+ return value.(vo.LoginUser), nil
+ }
+ // 登录用户信息无效
+ return vo.LoginUser{}, fmt.Errorf("app.common.noLoginUser")
+}
+
+// LoginUserToUserID 登录用户信息-用户ID
+func LoginUserToUserID(c *gin.Context) string {
+ value, exists := c.Get(common.CTX_LOGIN_USER)
+ if exists {
+ loginUser := value.(vo.LoginUser)
+ return loginUser.UserID
+ }
+ return ""
+}
+
+// LoginUserToUserName 登录用户信息-用户名称
+func LoginUserToUserName(c *gin.Context) string {
+ value, exists := c.Get(common.CTX_LOGIN_USER)
+ if exists {
+ loginUser := value.(vo.LoginUser)
+ return loginUser.User.UserName
+ }
+ return ""
+}
+
+// LoginUserToDataScopeSQL 登录用户信息-角色数据范围过滤SQL字符串
+func LoginUserToDataScopeSQL(c *gin.Context, deptAlias string, userAlias string) string {
+ dataScopeSQL := ""
+ // 登录用户信息
+ loginUser, err := LoginUser(c)
+ if err != nil {
+ return dataScopeSQL
+ }
+ userInfo := loginUser.User
+
+ // 如果是管理员,则不过滤数据
+ if config.IsAdmin(userInfo.UserID) {
+ return dataScopeSQL
+ }
+ // 无用户角色
+ if len(userInfo.Roles) <= 0 {
+ return dataScopeSQL
+ }
+
+ // 记录角色权限范围定义添加过, 非自定数据权限不需要重复拼接SQL
+ var scopeKeys []string
+ var conditions []string
+ for _, role := range userInfo.Roles {
+ dataScope := role.DataScope
+
+ if roledatascope.ALL == dataScope {
+ break
+ }
+
+ if roledatascope.CUSTOM != dataScope {
+ hasKey := false
+ for _, key := range scopeKeys {
+ if key == dataScope {
+ hasKey = true
+ break
+ }
+ }
+ if hasKey {
+ continue
+ }
+ }
+
+ if roledatascope.CUSTOM == dataScope {
+ sql := fmt.Sprintf(`%s.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = '%s' )`, deptAlias, role.RoleID)
+ conditions = append(conditions, sql)
+ }
+
+ if roledatascope.DEPT_AND_CHILD == dataScope {
+ sql := fmt.Sprintf(`%s.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = '%s' or find_in_set('%s' , ancestors ) )`, deptAlias, userInfo.DeptID, userInfo.DeptID)
+ conditions = append(conditions, sql)
+ }
+
+ if roledatascope.SELF == dataScope {
+ // 数据权限为仅本人且没有userAlias别名不查询任何数据
+ if userAlias == "" {
+ sql := fmt.Sprintf(`%s.dept_id = '0'`, deptAlias)
+ conditions = append(conditions, sql)
+ } else {
+ sql := fmt.Sprintf(`%s.user_id = '%s'`, userAlias, userInfo.UserID)
+ conditions = append(conditions, sql)
+ }
+ }
+
+ // 记录角色范围
+ scopeKeys = append(scopeKeys, dataScope)
+ }
+
+ // 构建查询条件语句
+ if len(conditions) > 0 {
+ dataScopeSQL = fmt.Sprintf(" AND ( %s ) ", strings.Join(conditions, " OR "))
+ }
+ return dataScopeSQL
+}
diff --git a/src/framework/utils/date/date.go b/src/framework/utils/date/date.go
new file mode 100644
index 0000000..dd03efb
--- /dev/null
+++ b/src/framework/utils/date/date.go
@@ -0,0 +1,69 @@
+package date
+
+import (
+ "time"
+
+ "nms_nbi/src/framework/logger"
+)
+
+const (
+ // 年 列如:2022
+ YYYY = "2006"
+ // 年-月 列如:2022-12
+ YYYY_MM = "2006-01"
+ // 年-月-日 列如:2022-12-30
+ YYYY_MM_DD = "2006-01-02"
+ // 年月日时分秒 列如:20221230010159
+ YYYYMMDDHHMMSS = "20060102150405"
+ // 年-月-日 时:分:秒 列如:2022-12-30 01:01:59
+ YYYY_MM_DD_HH_MM_SS = "2006-01-02 15:04:05"
+)
+
+// 格式时间字符串
+//
+// dateStr 时间字符串
+//
+// formatStr 时间格式 默认YYYY-MM-DD HH:mm:ss
+func ParseStrToDate(dateStr, formatStr string) time.Time {
+ t, err := time.Parse(formatStr, dateStr)
+ if err != nil {
+ logger.Infof("utils ParseStrToDate err %v", err)
+ return time.Time{}
+ }
+ return t
+}
+
+// 格式时间
+//
+// date 可转的Date对象
+//
+// formatStr 时间格式 默认YYYY-MM-DD HH:mm:ss
+func ParseDateToStr(date any, formatStr string) string {
+ t, ok := date.(time.Time)
+ if !ok {
+ switch v := date.(type) {
+ case int64:
+ if v == 0 {
+ return ""
+ }
+ t = time.UnixMilli(v)
+ case string:
+ parsedTime, err := time.Parse(formatStr, v)
+ if err != nil {
+ logger.Infof("utils ParseDateToStr err %v", err)
+ return ""
+ }
+ t = parsedTime
+ default:
+ return ""
+ }
+ }
+ return t.Format(formatStr)
+}
+
+// 格式时间成日期路径
+//
+// 年/月 列如:2022/12
+func ParseDatePath(date time.Time) string {
+ return date.Format("2006/01")
+}
diff --git a/src/framework/utils/fetch/fetch.go b/src/framework/utils/fetch/fetch.go
new file mode 100644
index 0000000..008b557
--- /dev/null
+++ b/src/framework/utils/fetch/fetch.go
@@ -0,0 +1,247 @@
+package fetch
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "mime/multipart"
+ "net/http"
+ "net/url"
+ "os"
+ "strings"
+ "time"
+)
+
+// Get 发送 GET 请求
+// timeout 超时时间(毫秒)
+func Get(url string, headers map[string]string, timeout int) ([]byte, error) {
+ if timeout < 100 || timeout > 180_000 {
+ timeout = 100
+ }
+ client := &http.Client{
+ Timeout: time.Duration(timeout) * time.Millisecond, // 超时时间
+ }
+
+ req, err := http.NewRequest("GET", url, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ for key, value := range headers {
+ req.Header.Set(key, value)
+ }
+
+ resp, err := client.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return nil, errors.New(resp.Status)
+ }
+
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+
+ return body, nil
+}
+
+// Post 发送 POST 请求
+func Post(url string, data url.Values, headers map[string]string) ([]byte, error) {
+ client := &http.Client{}
+
+ req, err := http.NewRequest("POST", url, strings.NewReader(data.Encode()))
+ if err != nil {
+ return nil, err
+ }
+
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+
+ for key, value := range headers {
+ req.Header.Set(key, value)
+ }
+
+ resp, err := client.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return body, errors.New(resp.Status)
+ }
+
+ return body, nil
+}
+
+// PostJSON 发送 POST 请求,并将请求体序列化为 JSON 格式
+func PostJSON(url string, data any, headers map[string]string) ([]byte, error) {
+ client := &http.Client{
+ Timeout: 3 * time.Second, // 超时时间
+ }
+
+ jsonData, err := json.Marshal(data)
+ if err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequest("POST", url, bytes.NewReader(jsonData))
+ if err != nil {
+ return nil, err
+ }
+
+ req.Header.Set("Content-Type", "application/json")
+
+ for key, value := range headers {
+ req.Header.Set(key, value)
+ }
+
+ resp, err := client.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return body, errors.New(resp.Status)
+ }
+
+ return body, nil
+}
+
+// UploadFile 上传文件函数,接收 URL 地址、表单参数和文件对象,返回响应内容或错误信息
+func PostUploadFile(url string, params map[string]string, file *os.File) ([]byte, error) {
+ body := &bytes.Buffer{}
+ writer := multipart.NewWriter(body)
+
+ part, err := writer.CreateFormFile("file", file.Name())
+ if err != nil {
+ return nil, fmt.Errorf("failed to create form file: %v", err)
+ }
+
+ _, err = io.Copy(part, file)
+ if err != nil {
+ return nil, fmt.Errorf("failed to copy file content: %v", err)
+ }
+
+ for key, value := range params {
+ err = writer.WriteField(key, value)
+ if err != nil {
+ return nil, fmt.Errorf("failed to write form field: %v", err)
+ }
+ }
+
+ err = writer.Close()
+ if err != nil {
+ return nil, fmt.Errorf("failed to close writer: %v", err)
+ }
+
+ req, err := http.NewRequest("POST", url, body)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create HTTP request: %v", err)
+ }
+
+ req.Header.Set("Content-Type", writer.FormDataContentType())
+
+ client := &http.Client{}
+ resp, err := client.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("HTTP request failed: %v", err)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("HTTP request returned status: %s", resp.Status)
+ }
+
+ responseBody, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, fmt.Errorf("failed to read response body: %v", err)
+ }
+
+ return responseBody, nil
+}
+
+// PutJSON 发送 PUT 请求,并将请求体序列化为 JSON 格式
+func PutJSON(url string, data any, headers map[string]string) ([]byte, error) {
+ client := &http.Client{
+ Timeout: 3 * time.Second, // 超时时间
+ }
+
+ jsonData, err := json.Marshal(data)
+ if err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequest("PUT", url, bytes.NewReader(jsonData))
+ if err != nil {
+ return nil, err
+ }
+
+ for key, value := range headers {
+ req.Header.Set(key, value)
+ }
+
+ resp, err := client.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return body, errors.New(resp.Status)
+ }
+
+ return body, nil
+}
+
+// Delete 发送 DELETE 请求
+func Delete(url string, headers map[string]string) ([]byte, error) {
+ client := &http.Client{}
+
+ req, err := http.NewRequest("DELETE", url, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ for key, value := range headers {
+ req.Header.Set(key, value)
+ }
+
+ resp, err := client.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return body, errors.New(resp.Status)
+ }
+
+ return body, nil
+}
diff --git a/src/framework/utils/file/csv.go b/src/framework/utils/file/csv.go
new file mode 100644
index 0000000..a1ead0c
--- /dev/null
+++ b/src/framework/utils/file/csv.go
@@ -0,0 +1,88 @@
+package file
+
+import (
+ "encoding/csv"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "nms_nbi/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("MkdirAll dir %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("Open CSV file: %v", err)
+ return arr
+ }
+ defer file.Close()
+
+ // 创建 CSV Reader
+ reader := csv.NewReader(file)
+
+ // 读取 CSV 头部行
+ header, err := reader.Read()
+ if err != nil {
+ logger.Errorf("Read CSV header rows: %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/excel.go b/src/framework/utils/file/excel.go
new file mode 100644
index 0000000..6af40df
--- /dev/null
+++ b/src/framework/utils/file/excel.go
@@ -0,0 +1,154 @@
+package file
+
+import (
+ "fmt"
+ "mime/multipart"
+ "os"
+ "path/filepath"
+ "time"
+
+ "nms_nbi/src/framework/constants/uploadsubpath"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/date"
+
+ "github.com/xuri/excelize/v2"
+)
+
+// TransferExeclUploadFile 表格文件上传保存
+//
+// file 上传文件对象
+func TransferExeclUploadFile(file *multipart.FileHeader) (string, error) {
+ // 上传文件检查
+ err := isAllowWrite(file.Filename, []string{".xls", ".xlsx"}, file.Size)
+ if err != nil {
+ return "", err
+ }
+ // 上传资源路径
+ _, dir := resourceUpload()
+ // 新文件名称并组装文件地址
+ filePath := filepath.Join(uploadsubpath.IMPORT, date.ParseDatePath(time.Now()))
+ fileName := generateFileName(file.Filename)
+ writePathFile := filepath.Join(dir, filePath, fileName)
+ // 存入新文件路径
+ err = transferToNewFile(file, writePathFile)
+ if err != nil {
+ return "", err
+ }
+ return filepath.ToSlash(writePathFile), nil
+}
+
+// 表格读取数据
+//
+// filePath 文件路径地址
+//
+// sheetName 工作簿名称, 空字符默认Sheet1
+func ReadSheet(filePath, sheetName string) ([]map[string]string, error) {
+ data := make([]map[string]string, 0)
+ // 打开 Excel 文件
+ f, err := excelize.OpenFile(filePath)
+ if err != nil {
+ return data, err
+ }
+ defer func() {
+ if err := f.Close(); err != nil {
+ logger.Errorf("ReadSheet to close worksheet file : %v", err)
+ }
+ }()
+
+ // 检查工作簿是否存在
+ if sheetName == "" {
+ sheetName = "Sheet1"
+ }
+ if visible, _ := f.GetSheetVisible(sheetName); !visible {
+ return data, fmt.Errorf("failed to read workbook %s", sheetName)
+ }
+
+ // 获取工作簿记录
+ rows, err := f.GetRows(sheetName)
+ if err != nil {
+ return data, err
+ }
+
+ for i, row := range rows {
+ // 跳过第一行
+ if i == 0 {
+ continue
+ }
+ // 遍历每个单元格
+ rowData := map[string]string{}
+ for columnIndex, cellValue := range row {
+ columnName, _ := excelize.ColumnNumberToName(columnIndex + 1)
+ rowData[columnName] = cellValue
+ }
+
+ data = append(data, rowData)
+ }
+ return data, nil
+}
+
+// 表格写入数据
+//
+// headerCells 第一行表头标题 "A1":"?"
+//
+// dataCells 从第二行开始的数据 "A2":"?"
+//
+// fileName 文件名称
+//
+// sheetName 工作簿名称, 空字符默认Sheet1
+func WriteSheet(headerCells map[string]string, dataCells []map[string]any, fileName, sheetName string) (string, error) {
+ f := excelize.NewFile()
+ defer func() {
+ if err := f.Close(); err != nil {
+ logger.Errorf("WriteSheet to close worksheet file: %v", err)
+ }
+ }()
+
+ // 创建一个工作表
+ if sheetName == "" {
+ sheetName = "Sheet1"
+ }
+ index, err := f.NewSheet(sheetName)
+ if err != nil {
+ return "", fmt.Errorf("failed to create worksheet %v", err)
+ }
+ // 设置工作簿的默认工作表
+ f.SetActiveSheet(index)
+
+ // 首个和最后key名称
+ firstKey := "A"
+ lastKey := "B"
+
+ // 第一行表头标题
+ for key, title := range headerCells {
+ f.SetCellValue(sheetName, key, title)
+ if key[:1] > lastKey {
+ lastKey = key[:1]
+ }
+ }
+
+ // 设置工作表上宽度为 20
+ f.SetColWidth(sheetName, firstKey, lastKey, 20)
+
+ // 从第二行开始的数据
+ for _, cell := range dataCells {
+ for key, value := range cell {
+ f.SetCellValue(sheetName, key, value)
+ }
+ }
+
+ // 上传资源路径
+ _, dir := resourceUpload()
+ filePath := filepath.Join(uploadsubpath.EXPORT, date.ParseDatePath(time.Now()))
+ saveFilePath := filepath.Join(dir, filePath, fileName)
+
+ // 创建文件目录
+ if err := os.MkdirAll(filepath.Dir(saveFilePath), 0750); err != nil {
+ return "", fmt.Errorf("failed to create save file %v", err)
+ }
+
+ // 根据指定路径保存文件
+ if err := f.SaveAs(saveFilePath); err != nil {
+ return "", fmt.Errorf("failed to save worksheet %v", err)
+ }
+ return saveFilePath, nil
+}
diff --git a/src/framework/utils/file/file.go b/src/framework/utils/file/file.go
new file mode 100644
index 0000000..b9008c1
--- /dev/null
+++ b/src/framework/utils/file/file.go
@@ -0,0 +1,352 @@
+package file
+
+import (
+ "fmt"
+ "io"
+ "mime/multipart"
+ "os"
+ "path"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/config"
+ "nms_nbi/src/framework/constants/uploadsubpath"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/date"
+ "nms_nbi/src/framework/utils/generate"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/regular"
+)
+
+/**最大文件名长度 */
+const DEFAULT_FILE_NAME_LENGTH = 100
+
+// 文件上传路径 prefix, dir
+func resourceUpload() (string, string) {
+ upload := config.Get("staticFile.upload").(map[string]any)
+ dir, err := filepath.Abs(upload["dir"].(string))
+ if err != nil {
+ logger.Errorf("file resourceUpload err %v", err)
+ }
+ return upload["prefix"].(string), dir
+}
+
+// 最大上传文件大小
+func uploadFileSize() int64 {
+ fileSize := 1 * 1024 * 1024
+ size := config.Get("upload.fileSize").(int)
+ if size > 1 {
+ fileSize = size * 1024 * 1024
+ }
+ return int64(fileSize)
+}
+
+// 文件上传扩展名白名单
+func uploadWhiteList() []string {
+ arr := config.Get("upload.whitelist").([]any)
+ strings := make([]string, len(arr))
+ for i, val := range arr {
+ if str, ok := val.(string); ok {
+ strings[i] = str
+ }
+ }
+ return strings
+}
+
+// 生成文件名称 fileName_随机值.extName
+//
+// fileName 原始文件名称含后缀,如:logo.png
+func generateFileName(fileName string) string {
+ fileExt := filepath.Ext(fileName)
+ // 替换掉后缀和特殊字符保留文件名
+ newFileName := regular.Replace(fileName, fileExt, "")
+ newFileName = regular.Replace(newFileName, `[<>:"\\|?*]+`, "")
+ newFileName = strings.TrimSpace(newFileName)
+ return fmt.Sprintf("%s_%s%s", newFileName, generate.Code(6), fileExt)
+}
+
+// 检查文件允许写入本地
+//
+// fileName 原始文件名称含后缀,如:midway1_logo_iipc68.png
+//
+// allowExts 允许上传拓展类型,['.png']
+func isAllowWrite(fileName string, allowExts []string, fileSize int64) error {
+ // 判断上传文件名称长度
+ if len(fileName) > DEFAULT_FILE_NAME_LENGTH {
+ return fmt.Errorf("the maximum length limit for uploading file names is %d", DEFAULT_FILE_NAME_LENGTH)
+ }
+
+ // 最大上传文件大小
+ maxFileSize := uploadFileSize()
+ if fileSize > maxFileSize {
+ return fmt.Errorf("maximum upload file size %s", parse.Bit(float64(maxFileSize)))
+ }
+
+ // 判断文件拓展是否为允许的拓展类型
+ fileExt := filepath.Ext(fileName)
+ hasExt := false
+ if len(allowExts) == 0 {
+ allowExts = uploadWhiteList()
+ }
+ for _, ext := range allowExts {
+ if ext == fileExt {
+ hasExt = true
+ break
+ }
+ }
+ if !hasExt {
+ return fmt.Errorf("the upload file type is not supported, only the following types are supported: %s", strings.Join(allowExts, ","))
+ }
+
+ return nil
+}
+
+// 检查文件允许本地读取
+//
+// filePath 文件存放资源路径,URL相对地址
+func isAllowRead(filePath string) error {
+ // 禁止目录上跳级别
+ if strings.Contains(filePath, "..") {
+ return fmt.Errorf("prohibit jumping levels on the directory")
+ }
+
+ // 检查允许下载的文件规则
+ fileExt := filepath.Ext(filePath)
+ hasExt := false
+ for _, ext := range uploadWhiteList() {
+ if ext == fileExt {
+ hasExt = true
+ break
+ }
+ }
+ if !hasExt {
+ return fmt.Errorf("rules for illegally downloaded files: %s", fileExt)
+ }
+
+ return nil
+}
+
+// TransferUploadFile 上传资源文件转存
+//
+// subPath 子路径,默认 UploadSubPath.DEFAULT
+//
+// allowExts 允许上传拓展类型(含“.”),如 ['.png','.jpg']
+func TransferUploadFile(file *multipart.FileHeader, subPath string, allowExts []string) (string, error) {
+ // 上传文件检查
+ err := isAllowWrite(file.Filename, allowExts, file.Size)
+ if err != nil {
+ return "", err
+ }
+ // 上传资源路径
+ prefix, dir := resourceUpload()
+ // 新文件名称并组装文件地址
+ fileName := generateFileName(file.Filename)
+ filePath := filepath.Join(subPath, date.ParseDatePath(time.Now()))
+ writePathFile := filepath.Join(dir, filePath, fileName)
+ // 存入新文件路径
+ err = transferToNewFile(file, writePathFile)
+ if err != nil {
+ return "", err
+ }
+ urlPath := filepath.Join(prefix, filePath, fileName)
+ return filepath.ToSlash(urlPath), nil
+}
+
+// ReadUploadFileStream 上传资源文件读取
+//
+// filePath 文件存放资源路径,URL相对地址 如:/upload/common/2023/06/xxx.png
+//
+// headerRange 断点续传范围区间,bytes=0-12131
+func ReadUploadFileStream(filePath, headerRange string) (map[string]any, error) {
+ // 读取文件检查
+ err := isAllowRead(filePath)
+ if err != nil {
+ return map[string]any{}, err
+ }
+ // 上传资源路径
+ prefix, dir := resourceUpload()
+ fileAsbPath := strings.Replace(filePath, prefix, dir, 1)
+
+ // 响应结果
+ result := map[string]any{
+ "range": "",
+ "chunkSize": 0,
+ "fileSize": 0,
+ "data": nil,
+ }
+
+ // 文件大小
+ fileSize := getFileSize(fileAsbPath)
+ if fileSize <= 0 {
+ return result, nil
+ }
+ result["fileSize"] = fileSize
+
+ if headerRange != "" {
+ partsStr := strings.Replace(headerRange, "bytes=", "", 1)
+ parts := strings.Split(partsStr, "-")
+ start, err := strconv.ParseInt(parts[0], 10, 64)
+ if err != nil || start > fileSize {
+ start = 0
+ }
+ end, err := strconv.ParseInt(parts[1], 10, 64)
+ if err != nil || end > fileSize {
+ end = fileSize - 1
+ }
+ if start > end {
+ start = end
+ }
+
+ // 分片结果
+ result["range"] = fmt.Sprintf("bytes %d-%d/%d", start, end, fileSize)
+ result["chunkSize"] = end - start + 1
+ byteArr, err := getFileStream(fileAsbPath, start, end)
+ if err != nil {
+ return map[string]any{}, fmt.Errorf("fail to read file")
+ }
+ result["data"] = byteArr
+ return result, nil
+ }
+
+ byteArr, err := getFileStream(fileAsbPath, 0, fileSize)
+ if err != nil {
+ return map[string]any{}, fmt.Errorf("fail to read file")
+ }
+ result["data"] = byteArr
+ return result, nil
+}
+
+// TransferChunkUploadFile 上传资源切片文件转存
+//
+// file 上传文件对象
+//
+// index 切片文件序号
+//
+// identifier 切片文件目录标识符
+func TransferChunkUploadFile(file *multipart.FileHeader, index, identifier string) (string, error) {
+ // 上传文件检查
+ err := isAllowWrite(file.Filename, []string{}, file.Size)
+ if err != nil {
+ return "", err
+ }
+ // 上传资源路径
+ prefix, dir := resourceUpload()
+ // 新文件名称并组装文件地址
+ filePath := filepath.Join(uploadsubpath.CHUNK, date.ParseDatePath(time.Now()), identifier)
+ writePathFile := filepath.Join(dir, filePath, index)
+ // 存入新文件路径
+ err = transferToNewFile(file, writePathFile)
+ if err != nil {
+ return "", err
+ }
+ urlPath := filepath.Join(prefix, filePath, index)
+ return filepath.ToSlash(urlPath), nil
+}
+
+// 上传资源切片文件检查
+//
+// identifier 切片文件目录标识符
+//
+// originalFileName 原始文件名称,如logo.png
+func ChunkCheckFile(identifier, originalFileName string) ([]string, error) {
+ // 读取文件检查
+ err := isAllowWrite(originalFileName, []string{}, 0)
+ if err != nil {
+ return []string{}, err
+ }
+ // 上传资源路径
+ _, dir := resourceUpload()
+ dirPath := path.Join(uploadsubpath.CHUNK, date.ParseDatePath(time.Now()), identifier)
+ readPath := path.Join(dir, dirPath)
+ fileList, err := getDirFileNameList(readPath)
+ if err != nil {
+ return []string{}, fmt.Errorf("fail to read file")
+ }
+ return fileList, nil
+}
+
+// 上传资源切片文件检查
+//
+// identifier 切片文件目录标识符
+//
+// originalFileName 原始文件名称,如logo.png
+//
+// subPath 子路径,默认 DEFAULT
+func ChunkMergeFile(identifier, originalFileName, subPath string) (string, error) {
+ // 读取文件检查
+ err := isAllowWrite(originalFileName, []string{}, 0)
+ if err != nil {
+ return "", err
+ }
+ // 上传资源路径
+ prefix, dir := resourceUpload()
+ // 切片存放目录
+ dirPath := path.Join(uploadsubpath.CHUNK, date.ParseDatePath(time.Now()), identifier)
+ readPath := path.Join(dir, dirPath)
+ // 组合存放文件路径
+ fileName := generateFileName(originalFileName)
+ filePath := path.Join(subPath, date.ParseDatePath(time.Now()))
+ writePath := path.Join(dir, filePath)
+ err = mergeToNewFile(readPath, writePath, fileName)
+ if err != nil {
+ return "", err
+ }
+ urlPath := filepath.Join(prefix, filePath, fileName)
+ return filepath.ToSlash(urlPath), nil
+}
+
+// CopyUploadFile 将上传文件资源转移新目录
+//
+// filePath 上传得到的文件路径 /upload....
+// dst 新文件路径 /a/xx.pdf
+func CopyUploadFile(filePath, dst string) error {
+ srcPath := ParseUploadFilePath(filePath)
+ src, err := os.Open(srcPath)
+ if err != nil {
+ return err
+ }
+ defer src.Close()
+
+ if err := os.MkdirAll(filepath.Dir(dst), 0750); err != nil {
+ return err
+ }
+
+ // 如果目标文件已经存在,先将目标文件重命名
+ if _, err := os.Stat(dst); err == nil {
+ ext := filepath.Ext(dst)
+ name := dst[0 : len(dst)-len(ext)]
+ newName := fmt.Sprintf("%s-%s%s", name, time.Now().Format("20060102_150405"), ext)
+ err := os.Rename(dst, newName)
+ if err != nil {
+ return err
+ }
+ }
+
+ out, err := os.Create(dst)
+ if err != nil {
+ return err
+ }
+ defer out.Close()
+
+ _, err = io.Copy(out, src)
+ return err
+}
+
+// ParseUploadFileDir 得到上传资源目录
+//
+// subPath 子路径,默认 UploadSubPath.DEFAULT
+func ParseUploadFileDir(subPath string) string {
+ _, dir := resourceUpload()
+ filePath := filepath.Join(subPath, date.ParseDatePath(time.Now()))
+ return filepath.Join(dir, filePath)
+}
+
+// ParseUploadFilePath 上传资源本地绝对资源路径
+//
+// filePath 上传文件路径
+func ParseUploadFilePath(filePath string) string {
+ prefix, dir := resourceUpload()
+ return strings.Replace(filePath, prefix, dir, 1)
+}
diff --git a/src/framework/utils/file/txt.go b/src/framework/utils/file/txt.go
new file mode 100644
index 0000000..6ca3ae2
--- /dev/null
+++ b/src/framework/utils/file/txt.go
@@ -0,0 +1,79 @@
+package file
+
+import (
+ "bufio"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "nms_nbi/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/file/utils.go b/src/framework/utils/file/utils.go
new file mode 100644
index 0000000..6b8a40a
--- /dev/null
+++ b/src/framework/utils/file/utils.go
@@ -0,0 +1,185 @@
+package file
+
+import (
+ "fmt"
+ "io"
+ "mime/multipart"
+ "os"
+ "path/filepath"
+ "sort"
+ "strconv"
+
+ "nms_nbi/src/framework/logger"
+)
+
+// transferToNewFile 读取目标文件转移到新路径下
+//
+// readFilePath 读取目标文件
+//
+// writePath 写入路径
+//
+// fileName 文件名称
+func transferToNewFile(file *multipart.FileHeader, dst string) error {
+ src, err := file.Open()
+ if err != nil {
+ return err
+ }
+ defer src.Close()
+
+ if err = os.MkdirAll(filepath.Dir(dst), 0750); err != nil {
+ return err
+ }
+
+ out, err := os.Create(dst)
+ if err != nil {
+ return err
+ }
+ defer out.Close()
+
+ _, err = io.Copy(out, src)
+ return err
+}
+
+// mergeToNewFile 将多个文件合并成一个文件并删除合并前的切片目录文件
+//
+// dirPath 读取要合并文件的目录
+//
+// writePath 写入路径
+//
+// fileName 文件名称
+func mergeToNewFile(dirPath string, writePath string, fileName string) error {
+ // 读取目录下所有文件并排序,注意文件名称是否数值
+ fileNameList, err := getDirFileNameList(dirPath)
+ if err != nil {
+ return fmt.Errorf("failed to read merge target file: %v", err)
+ }
+ if len(fileNameList) <= 0 {
+ return fmt.Errorf("failed to read merge target file")
+ }
+
+ // 排序
+ sort.Slice(fileNameList, func(i, j int) bool {
+ numI, _ := strconv.Atoi(fileNameList[i])
+ numJ, _ := strconv.Atoi(fileNameList[j])
+ return numI < numJ
+ })
+
+ // 写入到新路径文件
+ newFilePath := filepath.Join(writePath, fileName)
+ if err = os.MkdirAll(filepath.Dir(newFilePath), 0750); err != nil {
+ return err
+ }
+
+ // 转移完成后删除切片目录
+ defer os.Remove(dirPath)
+
+ // 打开新路径文件
+ outputFile, err := os.Create(newFilePath)
+ if err != nil {
+ return fmt.Errorf("failed to create file: %v", err)
+ }
+ defer outputFile.Close()
+
+ // 逐个读取文件后进行流拷贝数据块
+ for _, fileName := range fileNameList {
+ chunkPath := filepath.Join(dirPath, fileName)
+ // 拷贝结束后删除切片
+ defer os.Remove(chunkPath)
+ // 打开切片文件
+ inputFile, err := os.Open(chunkPath)
+ if err != nil {
+ return fmt.Errorf("failed to open file: %v", err)
+ }
+ defer inputFile.Close()
+ // 拷贝文件流
+ _, err = io.Copy(outputFile, inputFile)
+ if err != nil {
+ return fmt.Errorf("failed to copy file data: %w", err)
+ }
+ }
+
+ return nil
+}
+
+// getFileSize 读取文件大小
+func getFileSize(filePath string) int64 {
+ // 获取文件信息
+ fileInfo, err := os.Stat(filePath)
+ if err != nil {
+ logger.Errorf("Failed stat %s: %v", filePath, err)
+ return 0
+ }
+ // 获取文件大小(字节数)
+ return fileInfo.Size()
+}
+
+// 读取文件流用于返回下载
+//
+// filePath 文件路径
+// startOffset, endOffset 分片块读取区间,根据文件切片的块范围
+func getFileStream(filePath string, startOffset, endOffset int64) ([]byte, error) {
+ // 打开文件
+ file, err := os.Open(filePath)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+
+ // 获取文件的大小
+ fileInfo, err := file.Stat()
+ if err != nil {
+ return nil, err
+ }
+ fileSize := fileInfo.Size()
+
+ // 确保起始和结束偏移量在文件范围内
+ if startOffset > fileSize {
+ startOffset = 0
+ }
+ if endOffset >= fileSize {
+ endOffset = fileSize - 1
+ }
+
+ // 计算切片的大小
+ chunkSize := endOffset - startOffset + 1
+
+ // 创建 SectionReader
+ reader := io.NewSectionReader(file, startOffset, chunkSize)
+
+ // 创建一个缓冲区来存储读取的数据
+ buffer := make([]byte, chunkSize)
+
+ // 读取数据到缓冲区
+ _, err = reader.Read(buffer)
+ if err != nil && err != io.EOF {
+ return nil, err
+ }
+
+ return buffer, nil
+}
+
+// 获取文件目录下所有文件名称,不含目录名称
+//
+// filePath 文件路径
+func getDirFileNameList(dirPath string) ([]string, error) {
+ fileNames := []string{}
+
+ dir, err := os.Open(dirPath)
+ if err != nil {
+ return fileNames, nil
+ }
+ defer dir.Close()
+
+ fileInfos, err := dir.Readdir(-1)
+ if err != nil {
+ return fileNames, err
+ }
+
+ for _, fileInfo := range fileInfos {
+ if fileInfo.Mode().IsRegular() {
+ fileNames = append(fileNames, fileInfo.Name())
+ }
+ }
+
+ return fileNames, nil
+}
diff --git a/src/framework/utils/generate/generate.go b/src/framework/utils/generate/generate.go
new file mode 100644
index 0000000..4de60db
--- /dev/null
+++ b/src/framework/utils/generate/generate.go
@@ -0,0 +1,43 @@
+package generate
+
+import (
+ "math/rand"
+ "time"
+
+ "nms_nbi/src/framework/logger"
+
+ gonanoid "github.com/matoous/go-nanoid/v2"
+)
+
+// 生成随机Code
+// 包含数字、小写字母
+// 不保证长度满足
+func Code(size int) string {
+ str, err := gonanoid.Generate("0123456789abcdefghijklmnopqrstuvwxyz", size)
+ if err != nil {
+ logger.Infof("%d : %v", size, err)
+ return ""
+ }
+ return str
+}
+
+// 生成随机字符串
+// 包含数字、大小写字母、下划线、横杠
+// 不保证长度满足
+func String(size int) string {
+ str, err := gonanoid.New(size)
+ if err != nil {
+ logger.Infof("%d : %v", size, err)
+ return ""
+ }
+ return str
+}
+
+// 随机数 纯数字0-9
+func Number(size int) int {
+ source := rand.NewSource(time.Now().UnixNano())
+ random := rand.New(source)
+ min := int64(0)
+ max := int64(9 * int(size))
+ return int(random.Int63n(max-min+1) + min)
+}
diff --git a/src/framework/utils/ip2region/binding.go b/src/framework/utils/ip2region/binding.go
new file mode 100644
index 0000000..4782f67
--- /dev/null
+++ b/src/framework/utils/ip2region/binding.go
@@ -0,0 +1,238 @@
+package ip2region
+
+import (
+ "encoding/binary"
+ "fmt"
+ "os"
+)
+
+const (
+ HeaderInfoLength = 256
+ VectorIndexRows = 256
+ VectorIndexCols = 256
+ VectorIndexSize = 8
+ SegmentIndexBlockSize = 14
+)
+
+// --- Index policy define
+
+type IndexPolicy int
+
+const (
+ VectorIndexPolicy IndexPolicy = 1
+ BTreeIndexPolicy IndexPolicy = 2
+)
+
+func (i IndexPolicy) String() string {
+ switch i {
+ case VectorIndexPolicy:
+ return "VectorIndex"
+ case BTreeIndexPolicy:
+ return "BtreeIndex"
+ default:
+ return "unknown"
+ }
+}
+
+// --- Header define
+
+type Header struct {
+ // data []byte
+ Version uint16
+ IndexPolicy IndexPolicy
+ CreatedAt uint32
+ StartIndexPtr uint32
+ EndIndexPtr uint32
+}
+
+func NewHeader(input []byte) (*Header, error) {
+ if len(input) < 16 {
+ return nil, fmt.Errorf("invalid input buffer")
+ }
+
+ return &Header{
+ Version: binary.LittleEndian.Uint16(input),
+ IndexPolicy: IndexPolicy(binary.LittleEndian.Uint16(input[2:])),
+ CreatedAt: binary.LittleEndian.Uint32(input[4:]),
+ StartIndexPtr: binary.LittleEndian.Uint32(input[8:]),
+ EndIndexPtr: binary.LittleEndian.Uint32(input[12:]),
+ }, nil
+}
+
+// --- searcher implementation
+
+type Searcher struct {
+ handle *os.File
+
+ ioCount int
+
+ // use it only when this feature enabled.
+ // Preload the vector index will reduce the number of IO operations
+ // thus speedup the search process
+ vectorIndex []byte
+
+ // content buffer.
+ // running with the whole xdb file cached
+ contentBuff []byte
+}
+
+func baseNew(dbFile string, vIndex []byte, cBuff []byte) (*Searcher, error) {
+ var err error
+
+ // content buff first
+ if cBuff != nil {
+ return &Searcher{
+ vectorIndex: nil,
+ contentBuff: cBuff,
+ }, nil
+ }
+
+ // open the xdb binary file
+ handle, err := os.OpenFile(dbFile, os.O_RDONLY, 0600)
+ if err != nil {
+ return nil, err
+ }
+
+ return &Searcher{
+ handle: handle,
+ vectorIndex: vIndex,
+ }, nil
+}
+
+func NewWithFileOnly(dbFile string) (*Searcher, error) {
+ return baseNew(dbFile, nil, nil)
+}
+
+func NewWithVectorIndex(dbFile string, vIndex []byte) (*Searcher, error) {
+ return baseNew(dbFile, vIndex, nil)
+}
+
+func NewWithBuffer(cBuff []byte) (*Searcher, error) {
+ return baseNew("", nil, cBuff)
+}
+
+func (s *Searcher) Close() {
+ if s.handle != nil {
+ err := s.handle.Close()
+ if err != nil {
+ return
+ }
+ }
+}
+
+// GetIOCount return the global io count for the last search
+func (s *Searcher) GetIOCount() int {
+ return s.ioCount
+}
+
+// SearchByStr find the region for the specified ip string
+func (s *Searcher) SearchByStr(str string) (string, error) {
+ ip, err := CheckIP(str)
+ if err != nil {
+ return "", err
+ }
+
+ return s.Search(ip)
+}
+
+// Search find the region for the specified long ip
+func (s *Searcher) Search(ip uint32) (string, error) {
+ // reset the global ioCount
+ s.ioCount = 0
+
+ // locate the segment index block based on the vector index
+ var il0 = (ip >> 24) & 0xFF
+ var il1 = (ip >> 16) & 0xFF
+ var idx = il0*VectorIndexCols*VectorIndexSize + il1*VectorIndexSize
+ var sPtr, ePtr = uint32(0), uint32(0)
+ if s.vectorIndex != nil {
+ sPtr = binary.LittleEndian.Uint32(s.vectorIndex[idx:])
+ ePtr = binary.LittleEndian.Uint32(s.vectorIndex[idx+4:])
+ } else if s.contentBuff != nil {
+ sPtr = binary.LittleEndian.Uint32(s.contentBuff[HeaderInfoLength+idx:])
+ ePtr = binary.LittleEndian.Uint32(s.contentBuff[HeaderInfoLength+idx+4:])
+ } else {
+ // read the vector index block
+ var buff = make([]byte, VectorIndexSize)
+ err := s.read(int64(HeaderInfoLength+idx), buff)
+ if err != nil {
+ return "", fmt.Errorf("read vector index block at %d: %w", HeaderInfoLength+idx, err)
+ }
+
+ sPtr = binary.LittleEndian.Uint32(buff)
+ ePtr = binary.LittleEndian.Uint32(buff[4:])
+ }
+
+ // fmt.Printf("sPtr=%d, ePtr=%d", sPtr, ePtr)
+
+ // binary search the segment index to get the region
+ var dataLen, dataPtr = 0, uint32(0)
+ var buff = make([]byte, SegmentIndexBlockSize)
+ var l, h = 0, int((ePtr - sPtr) / SegmentIndexBlockSize)
+ for l <= h {
+ m := (l + h) >> 1
+ p := sPtr + uint32(m*SegmentIndexBlockSize)
+ err := s.read(int64(p), buff)
+ if err != nil {
+ return "", fmt.Errorf("read segment index at %d: %w", p, err)
+ }
+
+ // decode the data step by step to reduce the unnecessary operations
+ sip := binary.LittleEndian.Uint32(buff)
+ if ip < sip {
+ h = m - 1
+ } else {
+ eip := binary.LittleEndian.Uint32(buff[4:])
+ if ip > eip {
+ l = m + 1
+ } else {
+ dataLen = int(binary.LittleEndian.Uint16(buff[8:]))
+ dataPtr = binary.LittleEndian.Uint32(buff[10:])
+ break
+ }
+ }
+ }
+
+ //fmt.Printf("dataLen: %d, dataPtr: %d", dataLen, dataPtr)
+ if dataLen == 0 {
+ return "", nil
+ }
+
+ // load and return the region data
+ var regionBuff = make([]byte, dataLen)
+ err := s.read(int64(dataPtr), regionBuff)
+ if err != nil {
+ return "", fmt.Errorf("read region at %d: %w", dataPtr, err)
+ }
+
+ return string(regionBuff), nil
+}
+
+// do the data read operation based on the setting.
+// content buffer first or will read from the file.
+// this operation will invoke the Seek for file based read.
+func (s *Searcher) read(offset int64, buff []byte) error {
+ if s.contentBuff != nil {
+ cLen := copy(buff, s.contentBuff[offset:])
+ if cLen != len(buff) {
+ return fmt.Errorf("incomplete read: readed bytes should be %d", len(buff))
+ }
+ } else {
+ _, err := s.handle.Seek(offset, 0)
+ if err != nil {
+ return fmt.Errorf("seek to %d: %w", offset, err)
+ }
+
+ s.ioCount++
+ rLen, err := s.handle.Read(buff)
+ if err != nil {
+ return fmt.Errorf("handle read: %w", err)
+ }
+
+ if rLen != len(buff) {
+ return fmt.Errorf("incomplete read: readed bytes should be %d", len(buff))
+ }
+ }
+
+ return nil
+}
diff --git a/src/framework/utils/ip2region/ip2region.go b/src/framework/utils/ip2region/ip2region.go
new file mode 100644
index 0000000..f825d76
--- /dev/null
+++ b/src/framework/utils/ip2region/ip2region.go
@@ -0,0 +1,93 @@
+package ip2region
+
+import (
+ "embed"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/logger"
+)
+
+// 网络地址(内网)
+const LOCAT_HOST = "127.0.0.1"
+
+// 全局查询对象
+var searcher *Searcher
+
+//go:embed ip2region.xdb
+var ip2regionDB embed.FS
+
+func init() {
+ // 从 dbPath 加载整个 xdb 到内存
+ buf, err := ip2regionDB.ReadFile("ip2region.xdb")
+ if err != nil {
+ logger.Fatalf("failed error load xdb from : %s\n", err)
+ return
+ }
+
+ // 用全局的 cBuff 创建完全基于内存的查询对象。
+ base, err := NewWithBuffer(buf)
+ if err != nil {
+ logger.Errorf("failed error create searcher with content: %s\n", err)
+ return
+ }
+
+ // 赋值到全局查询对象
+ searcher = base
+}
+
+// RegionSearchByIp 查询IP所在地
+//
+// 国家|区域|省份|城市|ISP
+func RegionSearchByIp(ip string) (string, int, int64) {
+ ip = ClientIP(ip)
+ if ip == LOCAT_HOST {
+ // "0|0|0|内网IP|内网IP"
+ return "0|0|0|app.common.noIPregion|app.common.noIPregion", 0, 0
+ }
+ tStart := time.Now()
+ region, err := searcher.SearchByStr(ip)
+ if err != nil {
+ logger.Errorf("failed to SearchIP(%s): %s\n", ip, err)
+ return "0|0|0|0|0", 0, 0
+ }
+ return region, 0, time.Since(tStart).Milliseconds()
+}
+
+// RealAddressByIp 地址IP所在地
+//
+// 218.4.167.70 江苏省 苏州市
+func RealAddressByIp(ip string) string {
+ ip = ClientIP(ip)
+ if ip == LOCAT_HOST {
+ return "app.common.noIPregion" // 内网IP
+ }
+ region, err := searcher.SearchByStr(ip)
+ if err != nil {
+ logger.Errorf("failed to SearchIP(%s): %s\n", ip, err)
+ return "app.common.unknown" // 未知
+ }
+ parts := strings.Split(region, "|")
+ province := parts[2]
+ city := parts[3]
+ if province == "0" && city != "0" {
+ if city == "内网IP" {
+ return "app.common.noIPregion" // 内网IP
+ }
+ return city
+ }
+ return province + " " + city
+}
+
+// ClientIP 处理客户端IP地址显示iPv4
+//
+// 转换 ip2region.ClientIP(c.ClientIP())
+func ClientIP(ip string) string {
+ if strings.HasPrefix(ip, "::ffff:") {
+ ip = strings.Replace(ip, "::ffff:", "", 1)
+ }
+ if ip == LOCAT_HOST || ip == "::1" {
+ return LOCAT_HOST
+ }
+ return ip
+}
diff --git a/src/framework/utils/ip2region/ip2region.xdb b/src/framework/utils/ip2region/ip2region.xdb
new file mode 100644
index 0000000..c78b792
Binary files /dev/null and b/src/framework/utils/ip2region/ip2region.xdb differ
diff --git a/src/framework/utils/ip2region/util.go b/src/framework/utils/ip2region/util.go
new file mode 100644
index 0000000..cfef29e
--- /dev/null
+++ b/src/framework/utils/ip2region/util.go
@@ -0,0 +1,175 @@
+package ip2region
+
+import (
+ "fmt"
+ "os"
+ "strconv"
+ "strings"
+)
+
+var shiftIndex = []int{24, 16, 8, 0}
+
+func CheckIP(ip string) (uint32, error) {
+ var ps = strings.Split(strings.TrimSpace(ip), ".")
+ if len(ps) != 4 {
+ return 0, fmt.Errorf("invalid ip address `%s`", ip)
+ }
+
+ var val = uint32(0)
+ for i, s := range ps {
+ d, err := strconv.Atoi(s)
+ if err != nil {
+ return 0, fmt.Errorf("the %dth part `%s` is not an integer", i, s)
+ }
+
+ if d < 0 || d > 255 {
+ return 0, fmt.Errorf("the %dth part `%s` should be an integer bettween 0 and 255", i, s)
+ }
+
+ val |= uint32(d) << shiftIndex[i]
+ }
+
+ // convert the ip to integer
+ return val, nil
+}
+
+func Long2IP(ip uint32) string {
+ return fmt.Sprintf("%d.%d.%d.%d", (ip>>24)&0xFF, (ip>>16)&0xFF, (ip>>8)&0xFF, ip&0xFF)
+}
+
+func MidIP(sip uint32, eip uint32) uint32 {
+ return uint32((uint64(sip) + uint64(eip)) >> 1)
+}
+
+// LoadHeader load the header info from the specified handle
+func LoadHeader(handle *os.File) (*Header, error) {
+ _, err := handle.Seek(0, 0)
+ if err != nil {
+ return nil, fmt.Errorf("seek to the header: %w", err)
+ }
+
+ var buff = make([]byte, HeaderInfoLength)
+ rLen, err := handle.Read(buff)
+ if err != nil {
+ return nil, err
+ }
+
+ if rLen != len(buff) {
+ return nil, fmt.Errorf("incomplete read: readed bytes should be %d", len(buff))
+ }
+
+ return NewHeader(buff)
+}
+
+// LoadHeaderFromFile load header info from the specified db file path
+func LoadHeaderFromFile(dbFile string) (*Header, error) {
+ handle, err := os.OpenFile(dbFile, os.O_RDONLY, 0600)
+ if err != nil {
+ return nil, fmt.Errorf("open xdb file `%s`: %w", dbFile, err)
+ }
+
+ defer func(handle *os.File) {
+ _ = handle.Close()
+ }(handle)
+
+ header, err := LoadHeader(handle)
+ if err != nil {
+ return nil, err
+ }
+
+ return header, nil
+}
+
+// LoadHeaderFromBuff wrap the header info from the content buffer
+func LoadHeaderFromBuff(cBuff []byte) (*Header, error) {
+ return NewHeader(cBuff[0:256])
+}
+
+// LoadVectorIndex util function to load the vector index from the specified file handle
+func LoadVectorIndex(handle *os.File) ([]byte, error) {
+ // load all the vector index block
+ _, err := handle.Seek(HeaderInfoLength, 0)
+ if err != nil {
+ return nil, fmt.Errorf("seek to vector index: %w", err)
+ }
+
+ var buff = make([]byte, VectorIndexRows*VectorIndexCols*VectorIndexSize)
+ rLen, err := handle.Read(buff)
+ if err != nil {
+ return nil, err
+ }
+
+ if rLen != len(buff) {
+ return nil, fmt.Errorf("incomplete read: readed bytes should be %d", len(buff))
+ }
+
+ return buff, nil
+}
+
+// LoadVectorIndexFromFile load vector index from a specified file path
+func LoadVectorIndexFromFile(dbFile string) ([]byte, error) {
+ handle, err := os.OpenFile(dbFile, os.O_RDONLY, 0600)
+ if err != nil {
+ return nil, fmt.Errorf("open xdb file `%s`: %w", dbFile, err)
+ }
+
+ defer func() {
+ _ = handle.Close()
+ }()
+
+ vIndex, err := LoadVectorIndex(handle)
+ if err != nil {
+ return nil, err
+ }
+
+ return vIndex, nil
+}
+
+// LoadContent load the whole xdb content from the specified file handle
+func LoadContent(handle *os.File) ([]byte, error) {
+ // get file size
+ fi, err := handle.Stat()
+ if err != nil {
+ return nil, fmt.Errorf("stat: %w", err)
+ }
+
+ size := fi.Size()
+
+ // seek to the head of the file
+ _, err = handle.Seek(0, 0)
+ if err != nil {
+ return nil, fmt.Errorf("seek to get xdb file length: %w", err)
+ }
+
+ var buff = make([]byte, size)
+ rLen, err := handle.Read(buff)
+ if err != nil {
+ return nil, err
+ }
+
+ if rLen != len(buff) {
+ return nil, fmt.Errorf("incomplete read: readed bytes should be %d", len(buff))
+ }
+
+ return buff, nil
+}
+
+// LoadContentFromFile load the whole xdb content from the specified db file path
+func LoadContentFromFile(dbFile string) ([]byte, error) {
+ handle, err := os.OpenFile(dbFile, os.O_RDONLY, 0600)
+ if err != nil {
+ return nil, fmt.Errorf("open xdb file `%s`: %w", dbFile, err)
+ }
+
+ defer func() {
+ _ = handle.Close()
+ }()
+
+ cBuff, err := LoadContent(handle)
+ if err != nil {
+ return nil, err
+ }
+
+ return cBuff, nil
+}
+
diff --git a/src/framework/utils/parse/parse.go b/src/framework/utils/parse/parse.go
new file mode 100644
index 0000000..ff8145c
--- /dev/null
+++ b/src/framework/utils/parse/parse.go
@@ -0,0 +1,167 @@
+package parse
+
+import (
+ "fmt"
+ "image/color"
+ "reflect"
+ "regexp"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/robfig/cron/v3"
+)
+
+// Number 解析数值型
+func Number(str any) int64 {
+ switch str := str.(type) {
+ case string:
+ if str == "" {
+ return 0
+ }
+ num, err := strconv.ParseInt(str, 10, 64)
+ if err != nil {
+ return 0
+ }
+ return num
+ case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
+ return reflect.ValueOf(str).Int()
+ case float32, float64:
+ return int64(reflect.ValueOf(str).Float())
+ default:
+ return 0
+ }
+}
+
+// Boolean 解析布尔型
+func Boolean(str any) bool {
+ switch str := str.(type) {
+ case string:
+ if str == "" || str == "false" || str == "0" {
+ return false
+ }
+ // 尝试将字符串解析为数字
+ if num, err := strconv.ParseFloat(str, 64); err == nil {
+ return num != 0
+ }
+ return true
+ case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
+ num := reflect.ValueOf(str).Int()
+ return num != 0
+ case float32, float64:
+ num := reflect.ValueOf(str).Float()
+ return num != 0
+ default:
+ return false
+ }
+}
+
+// ConvertToCamelCase 字符串转换驼峰形式
+//
+// 字符串 dict/inline/data/:dictId 结果 DictInlineDataDictId
+func ConvertToCamelCase(str string) string {
+ if len(str) == 0 {
+ return str
+ }
+ reg := regexp.MustCompile(`[-_:/]\w`)
+ result := reg.ReplaceAllStringFunc(str, func(match string) string {
+ return strings.ToUpper(string(match[1]))
+ })
+
+ words := strings.Fields(result)
+ for i, word := range words {
+ str := word[1:]
+ str = strings.ReplaceAll(str, "/", "")
+ words[i] = strings.ToUpper(word[:1]) + str
+ }
+
+ return strings.Join(words, "")
+}
+
+// Bit 比特位为单位
+func Bit(bit float64) string {
+ var GB, MB, KB string
+
+ if bit > float64(1<<30) {
+ GB = fmt.Sprintf("%0.2f", bit/(1<<30))
+ }
+
+ if bit > float64(1<<20) && bit < (1<<30) {
+ MB = fmt.Sprintf("%.2f", bit/(1<<20))
+ }
+
+ if bit > float64(1<<10) && bit < (1<<20) {
+ KB = fmt.Sprintf("%.2f", bit/(1<<10))
+ }
+
+ if GB != "" {
+ return GB + "GB"
+ } else if MB != "" {
+ return MB + "MB"
+ } else if KB != "" {
+ return KB + "KB"
+ } else {
+ return fmt.Sprintf("%vB", bit)
+ }
+}
+
+// CronExpression 解析 Cron 表达式,返回下一次执行的时间戳(毫秒)
+//
+// 【*/5 * * * * ?】 6个参数
+func CronExpression(expression string) int64 {
+ specParser := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)
+ schedule, err := specParser.Parse(expression)
+ if err != nil {
+ return 0
+ }
+ return schedule.Next(time.Now()).UnixMilli()
+}
+
+// SafeContent 内容值进行安全掩码
+func SafeContent(value string) string {
+ if len(value) < 3 {
+ return strings.Repeat("*", len(value))
+ } else if len(value) < 6 {
+ return string(value[0]) + strings.Repeat("*", len(value)-1)
+ } else if len(value) < 10 {
+ return string(value[0]) + strings.Repeat("*", len(value)-2) + string(value[len(value)-1])
+ } else if len(value) < 15 {
+ return value[:2] + strings.Repeat("*", len(value)-4) + value[len(value)-2:]
+ } else {
+ return value[:3] + strings.Repeat("*", len(value)-6) + value[len(value)-3:]
+ }
+}
+
+// RemoveDuplicates 数组内字符串去重
+func RemoveDuplicates(ids []string) []string {
+ uniqueIDs := make(map[string]bool)
+ uniqueIDSlice := make([]string, 0)
+
+ for _, id := range ids {
+ _, ok := uniqueIDs[id]
+ if !ok && id != "" {
+ uniqueIDs[id] = true
+ uniqueIDSlice = append(uniqueIDSlice, id)
+ }
+ }
+
+ return uniqueIDSlice
+}
+
+// Color 解析颜色 #fafafa
+func Color(colorStr string) *color.RGBA {
+ // 去除 # 号
+ colorStr = colorStr[1:]
+
+ // 将颜色字符串拆分为 R、G、B 分量
+ r, _ := strconv.ParseInt(colorStr[0:2], 16, 0)
+ g, _ := strconv.ParseInt(colorStr[2:4], 16, 0)
+ b, _ := strconv.ParseInt(colorStr[4:6], 16, 0)
+
+ return &color.RGBA{
+ R: uint8(r),
+ G: uint8(g),
+ B: uint8(b),
+ A: 255, // 不透明
+ }
+}
diff --git a/src/framework/utils/regular/regular.go b/src/framework/utils/regular/regular.go
new file mode 100644
index 0000000..9feebc3
--- /dev/null
+++ b/src/framework/utils/regular/regular.go
@@ -0,0 +1,86 @@
+package regular
+
+import (
+ "regexp"
+
+ "github.com/dlclark/regexp2"
+)
+
+// Replace 正则替换
+func Replace(originStr, pattern, repStr string) string {
+ regex := regexp.MustCompile(pattern)
+ return regex.ReplaceAllString(originStr, repStr)
+}
+
+// 判断是否为有效用户名格式
+//
+// 用户名不能以数字开头,可包含大写小写字母,数字,且不少于5位
+func ValidUsername(username string) bool {
+ if username == "" {
+ return false
+ }
+ pattern := `^[a-zA-Z][a-z0-9A-Z]{5,}`
+ match, err := regexp.MatchString(pattern, username)
+ if err != nil {
+ return false
+ }
+ return match
+}
+
+// 判断是否为有效密码格式
+//
+// 密码至少包含大小写字母、数字、特殊符号,且不少于6位
+func ValidPassword(password string) bool {
+ if password == "" {
+ return false
+ }
+ pattern := `^(?![A-Za-z0-9]+$)(?![a-z0-9\W]+$)(?![A-Za-z\W]+$)(?![A-Z0-9\W]+$)[a-zA-Z0-9\W]{6,}$`
+ re := regexp2.MustCompile(pattern, 0)
+ match, err := re.MatchString(password)
+ if err != nil {
+ return false
+ }
+ return match
+}
+
+// 判断是否为有效手机号格式,1开头的11位手机号
+func ValidMobile(mobile string) bool {
+ if mobile == "" {
+ return false
+ }
+ pattern := `^1[3|4|5|6|7|8|9][0-9]\d{8}$`
+ match, err := regexp.MatchString(pattern, mobile)
+ if err != nil {
+ return false
+ }
+ return match
+}
+
+// 判断是否为有效邮箱格式
+func ValidEmail(email string) bool {
+ if email == "" {
+ return false
+ }
+ pattern := `^(([^<>()\\.,;:\s@"]+(\.[^<>()\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+\.)+[a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{2,}))$`
+ re := regexp2.MustCompile(pattern, 0)
+ match, err := re.MatchString(email)
+ if err != nil {
+ return false
+ }
+ return match
+}
+
+// 判断是否为http(s)://开头
+//
+// link 网络链接
+func ValidHttp(link string) bool {
+ if link == "" {
+ return false
+ }
+ pattern := `^http(s)?:\/\/+`
+ match, err := regexp.MatchString(pattern, link)
+ if err != nil {
+ return false
+ }
+ return match
+}
diff --git a/src/framework/utils/repo/repo.go b/src/framework/utils/repo/repo.go
new file mode 100644
index 0000000..5a310b1
--- /dev/null
+++ b/src/framework/utils/repo/repo.go
@@ -0,0 +1,126 @@
+package repo
+
+import (
+ "fmt"
+ "reflect"
+ "strconv"
+ "strings"
+
+ "nms_nbi/src/framework/utils/parse"
+)
+
+// PageNumSize 分页页码记录数
+func PageNumSize(pageNum, pageSize any) (int64, int64) {
+ // 记录起始索引
+ num := parse.Number(pageNum)
+ if num < 1 {
+ num = 1
+ }
+
+ // 显示记录数
+ size := parse.Number(pageSize)
+ if size < 0 {
+ size = 10
+ }
+ return num - 1, size
+}
+
+// SetFieldValue 判断结构体内是否存在指定字段并设置值
+func SetFieldValue(obj any, fieldName string, value any) {
+ // 获取结构体的反射值
+ userValue := reflect.ValueOf(obj)
+
+ // 获取字段的反射值
+ fieldValue := userValue.Elem().FieldByName(fieldName)
+
+ // 检查字段是否存在
+ if fieldValue.IsValid() && fieldValue.CanSet() {
+ // 获取字段的类型
+ fieldType := fieldValue.Type()
+
+ // 转换传入的值类型为字段类型
+ switch fieldType.Kind() {
+ case reflect.String:
+ if value == nil {
+ fieldValue.SetString("")
+ } else {
+ fieldValue.SetString(fmt.Sprintf("%v", value))
+ }
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ intValue, err := strconv.ParseInt(fmt.Sprintf("%v", value), 10, 64)
+ if err != nil {
+ intValue = 0
+ }
+ fieldValue.SetInt(intValue)
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ uintValue, err := strconv.ParseUint(fmt.Sprintf("%v", value), 10, 64)
+ if err != nil {
+ uintValue = 0
+ }
+ fieldValue.SetUint(uintValue)
+ case reflect.Float32, reflect.Float64:
+ floatValue, err := strconv.ParseFloat(fmt.Sprintf("%v", value), 64)
+ if err != nil {
+ floatValue = 0
+ }
+ fieldValue.SetFloat(floatValue)
+ default:
+ // 设置字段的值
+ fieldValue.Set(reflect.ValueOf(value).Convert(fieldValue.Type()))
+ }
+ }
+}
+
+// ConvertIdsSlice 将 []string 转换为 []any
+func ConvertIdsSlice(ids []string) []any {
+ // 将 []string 转换为 []any
+ arr := make([]any, len(ids))
+ for i, v := range ids {
+ arr[i] = v
+ }
+ return arr
+}
+
+// 查询-参数值的占位符
+func KeyPlaceholderByQuery(sum int) string {
+ placeholders := make([]string, sum)
+ for i := 0; i < sum; i++ {
+ placeholders[i] = "?"
+ }
+ return strings.Join(placeholders, ",")
+}
+
+// 插入-参数映射键值占位符 keys, placeholder, values
+func KeyPlaceholderValueByInsert(params map[string]any) ([]string, string, []any) {
+ // 参数映射的键
+ keys := make([]string, len(params))
+ // 参数映射的值
+ values := make([]any, len(params))
+ sum := 0
+ for k, v := range params {
+ keys[sum] = k
+ values[sum] = v
+ sum++
+ }
+ // 参数值的占位符
+ placeholders := make([]string, sum)
+ for i := 0; i < sum; i++ {
+ placeholders[i] = "?"
+ }
+ return keys, strings.Join(placeholders, ","), values
+}
+
+// 更新-参数映射键值占位符 keys, values
+func KeyValueByUpdate(params map[string]any) ([]string, []any) {
+ // 参数映射的键
+ keys := make([]string, len(params))
+ // 参数映射的值
+ values := make([]any, len(params))
+ sum := 0
+ for k, v := range params {
+ keys[sum] = k + "=?"
+ values[sum] = v
+ sum++
+ }
+ return keys, values
+}
diff --git a/src/framework/utils/ssh/files.go b/src/framework/utils/ssh/files.go
new file mode 100644
index 0000000..adad7af
--- /dev/null
+++ b/src/framework/utils/ssh/files.go
@@ -0,0 +1,104 @@
+package ssh
+
+import (
+ "fmt"
+ "strings"
+
+ "nms_nbi/src/framework/config"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/cmd"
+ "nms_nbi/src/framework/utils/parse"
+)
+
+// FileListRow 文件列表行数据
+type FileListRow struct {
+ FileType string `json:"fileType"` // 文件类型
+ FileMode string `json:"fileMode"` // 文件的权限
+ LinkCount int64 `json:"linkCount"` // 硬链接数目
+ Owner string `json:"owner"` // 所属用户
+ Group string `json:"group"` // 所属组
+ Size string `json:"size"` // 文件的大小
+ ModifiedTime int64 `json:"modifiedTime"` // 最后修改时间,单位为秒
+ FileName string `json:"fileName"` // 文件的名称
+}
+
+// 文件列表
+// neIp 网元IP空字符串为本地
+// search 文件名后模糊*
+//
+// return 目录大小,行记录,异常
+func FileList(path, neIp, search string) (string, []FileListRow, error) {
+ totalSize := ""
+ var rows []FileListRow
+ rowStr := ""
+
+ // 发送命令
+ searchStr := ""
+ if search != "" {
+ searchStr = search + "*"
+ }
+ pathStr := fmt.Sprintf("cd %s \n", path)
+ cmdStr := fmt.Sprintf("ls -lht --time-style=+%%s %s \n", searchStr)
+
+ // 是否远程读取
+ if neIp != "" {
+ usernameNe := config.Get("ne.user").(string) // 网元统一用户
+ sshHost := fmt.Sprintf("%s@%s", usernameNe, neIp)
+ resultStr, err := cmd.ExecWithCheck("ssh", sshHost, pathStr, cmdStr)
+ if err != nil {
+ logger.Errorf("Ne FileList Path: %s, Search: %s, Error:%s", path, search, err.Error())
+ return totalSize, rows, err
+ }
+ rowStr = resultStr
+ } else {
+ resultStr, err := cmd.Execf(pathStr, cmdStr)
+ if err != nil {
+ logger.Errorf("Ne FileList Path: %s, Search: %s, Error:%s", path, search, err.Error())
+ return totalSize, rows, err
+ }
+ rowStr = resultStr
+ }
+
+ // 遍历组装
+ rowStrList := strings.Split(rowStr, "\n")
+ for i, rowStr := range rowStrList {
+ if rowStr == "" {
+ continue
+ }
+ // 使用空格对字符串进行切割
+ fields := strings.Fields(rowStr)
+
+ // 无查询过滤会有total总计
+ if i == 0 && searchStr == "" {
+ totalSize = fields[1]
+ continue
+ }
+
+ // 拆分不足7位跳过
+ if len(fields) != 7 {
+ continue
+ }
+
+ // 文件类型
+ fileMode := fields[0]
+ fileType := "file"
+ if fileMode[0] == 'd' {
+ fileType = "dir"
+ } else if fileMode[0] == 'l' {
+ fileType = "symlink"
+ }
+
+ // 提取各个字段的值
+ rows = append(rows, FileListRow{
+ FileMode: fileMode,
+ FileType: fileType,
+ LinkCount: parse.Number(fields[1]),
+ Owner: fields[2],
+ Group: fields[3],
+ Size: fields[4],
+ ModifiedTime: parse.Number(fields[5]),
+ FileName: fields[6],
+ })
+ }
+ return totalSize, rows, nil
+}
diff --git a/src/framework/utils/ssh/scp.go b/src/framework/utils/ssh/scp.go
new file mode 100644
index 0000000..0191109
--- /dev/null
+++ b/src/framework/utils/ssh/scp.go
@@ -0,0 +1,44 @@
+package ssh
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+
+ "nms_nbi/src/framework/config"
+ "nms_nbi/src/framework/logger"
+)
+
+// 网元NE 文件复制到远程文件
+func FileSCPLocalToNe(neIp, localPath, nePath string) error {
+ usernameNe := config.Get("ne.user").(string)
+ // scp /path/to/local/file.txt user@remote-server:/path/to/remote/directory/
+ neDir := fmt.Sprintf("%s@%s:%s", usernameNe, neIp, nePath)
+ cmd := exec.Command("scp", "-r", localPath, neDir)
+ _, err := cmd.CombinedOutput()
+ if err != nil {
+ logger.Errorf("FileSCPLocalToNe %s", err.Error())
+ return err
+ }
+ return nil
+}
+
+// 网元NE 远程文件复制到本地文件
+func FileSCPNeToLocal(neIp, nePath, localPath string) error {
+ // 确保文件夹路径存在
+ if err := os.MkdirAll(filepath.Dir(localPath), 0750); err != nil {
+ logger.Errorf("FileSCPNeToLocal MkdirAll err %v", err)
+ return err
+ }
+ usernameNe := config.Get("ne.user").(string)
+ // scp user@remote-server:/path/to/remote/directory/ /path/to/local/file.txt
+ neDir := fmt.Sprintf("%s@%s:%s", usernameNe, neIp, nePath)
+ cmd := exec.Command("scp", "-r", neDir, localPath)
+ _, err := cmd.CombinedOutput()
+ if err != nil {
+ logger.Errorf("FileSCPNeToLocal %s", err.Error())
+ return err
+ }
+ return nil
+}
diff --git a/src/framework/utils/ssh/ssh.go b/src/framework/utils/ssh/ssh.go
new file mode 100644
index 0000000..72c5531
--- /dev/null
+++ b/src/framework/utils/ssh/ssh.go
@@ -0,0 +1,246 @@
+package ssh
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "os"
+ "os/user"
+ "strings"
+ "sync"
+ "time"
+
+ "nms_nbi/src/framework/logger"
+
+ gossh "golang.org/x/crypto/ssh"
+)
+
+// ConnSSH 连接SSH对象
+type ConnSSH struct {
+ User string `json:"user"` // 主机用户名
+ Addr string `json:"addr"` // 主机地址
+ Port int64 `json:"port"` // SSH端口
+ AuthMode string `json:"authMode"` // 认证模式(0密码 1主机私钥)
+ Password string `json:"password"` // 认证密码
+ PrivateKey string `json:"privateKey"` // 认证私钥
+ PassPhrase string `json:"passPhrase"` // 认证私钥密码
+
+ DialTimeOut time.Duration `json:"dialTimeOut"` // 连接超时断开
+
+ Client *gossh.Client `json:"client"`
+ LastResult string `json:"lastResult"` // 记最后一次执行命令的结果
+}
+
+// NewClient 创建SSH客户端
+func (c *ConnSSH) NewClient() (*ConnSSH, error) {
+ // IPV6地址协议
+ proto := "tcp"
+ if strings.Contains(c.Addr, ":") {
+ proto = "tcp6"
+ c.Addr = fmt.Sprintf("[%s]", c.Addr)
+ }
+ addr := fmt.Sprintf("%s:%d", c.Addr, c.Port)
+
+ // ssh客户端配置
+ config := &gossh.ClientConfig{}
+ config.SetDefaults()
+ config.HostKeyCallback = gossh.InsecureIgnoreHostKey()
+ config.User = c.User
+
+ // 默认等待5s
+ if c.DialTimeOut == 0 {
+ c.DialTimeOut = 5 * time.Second
+ }
+ config.Timeout = c.DialTimeOut
+
+ // 认证模式-0密码 1私钥
+ if c.AuthMode == "1" {
+ var signer gossh.Signer
+ var err error
+ if len(c.PassPhrase) != 0 {
+ signer, err = gossh.ParsePrivateKeyWithPassphrase([]byte(c.PrivateKey), []byte(c.PassPhrase))
+ } else {
+ signer, err = gossh.ParsePrivateKey([]byte(c.PrivateKey))
+ }
+ if err != nil {
+ logger.Errorf("NewClient parse private key => %s", err.Error())
+ return nil, err
+ }
+ config.Auth = []gossh.AuthMethod{gossh.PublicKeys(signer)}
+ } else {
+ config.Auth = []gossh.AuthMethod{gossh.Password(c.Password)}
+ }
+
+ client, err := gossh.Dial(proto, addr, config)
+ if nil != err {
+ logger.Errorf("NewClient dial => %s", err.Error())
+ return c, err
+ }
+ c.Client = client
+ return c, nil
+}
+
+// Close 关闭当前SSH客户端
+func (c *ConnSSH) Close() {
+ if c.Client != nil {
+ c.Client.Close()
+ }
+}
+
+// NewClientByLocalPrivate 创建SSH客户端-本地私钥(~/.ssh/id_rsa)直连
+//
+// ssh.ConnSSH{
+// User: "user",
+// Addr: "192.168.x.x",
+// Port: body.Port,
+// }
+func (c *ConnSSH) NewClientByLocalPrivate() (*ConnSSH, error) {
+ c.Port = 22
+ c.AuthMode = "1"
+ usr, err := user.Current()
+ if err != nil {
+ logger.Errorf("NewClientByLocal get current user => %s", err.Error())
+ return nil, err
+ }
+
+ // 读取用户默认的私钥文件
+ keyPath := fmt.Sprintf("%s/.ssh/id_rsa", usr.HomeDir)
+ key, err := os.ReadFile(keyPath)
+ if err != nil {
+ logger.Errorf("NewClientByLocal [%s] read private key => %s", usr.Username, err.Error())
+ return nil, err
+ }
+ c.PrivateKey = string(key)
+ return c.NewClient()
+}
+
+// RunCMD 执行单次命令
+func (c *ConnSSH) RunCMD(cmd string) (string, error) {
+ if c.Client == nil {
+ if _, err := c.NewClient(); err != nil {
+ return "", err
+ }
+ }
+ session, err := c.Client.NewSession()
+ if err != nil {
+ logger.Errorf("RunCMD failed to create session: => %s", err.Error())
+ return "", err
+ }
+ defer session.Close()
+ buf, err := session.CombinedOutput(cmd)
+ if err != nil {
+ logger.Errorf("RunCMD failed run command: => %s", err.Error())
+ }
+ c.LastResult = string(buf)
+ return c.LastResult, err
+}
+
+// NewClient 创建SSH客户端会话对象
+func (c *ConnSSH) NewClientSession(cols, rows int) (*SSHClientSession, error) {
+ sshSession, err := c.Client.NewSession()
+ if err != nil {
+ return nil, err
+ }
+
+ stdin, err := sshSession.StdinPipe()
+ if err != nil {
+ return nil, err
+ }
+
+ comboWriter := new(singleWriter)
+ sshSession.Stdout = comboWriter
+ sshSession.Stderr = comboWriter
+
+ modes := gossh.TerminalModes{
+ gossh.ECHO: 1,
+ gossh.TTY_OP_ISPEED: 14400,
+ gossh.TTY_OP_OSPEED: 14400,
+ }
+
+ if err := sshSession.RequestPty("xterm", rows, cols, modes); err != nil {
+ return nil, err
+ }
+
+ if err := sshSession.Shell(); err != nil {
+ return nil, err
+ }
+
+ return &SSHClientSession{
+ Stdin: stdin,
+ Stdout: comboWriter,
+ Session: sshSession,
+ }, nil
+}
+
+// SSHClientSession SSH客户端会话对象
+type SSHClientSession struct {
+ Stdin io.WriteCloser
+ Stdout *singleWriter
+ Session *gossh.Session
+}
+
+// Close 关闭会话
+func (s *SSHClientSession) Close() {
+ if s.Stdin != nil {
+ s.Stdin.Close()
+ }
+ if s.Stdout != nil {
+ s.Stdout = nil
+ }
+ if s.Session != nil {
+ s.Session.Close()
+ }
+}
+
+// Write 写入命令 回车(\n)才会执行
+func (s *SSHClientSession) Write(cmd string) (int, error) {
+ if s.Stdin == nil {
+ return 0, fmt.Errorf("ssh client session is nil to content write failed")
+ }
+ return s.Stdin.Write([]byte(cmd))
+}
+
+// Read 读取结果 等待一会才有结果
+func (s *SSHClientSession) Read() []byte {
+ if s.Stdout == nil {
+ return []byte{}
+ }
+ // time.Sleep(300 * time.Millisecond)
+ bs := s.Stdout.Bytes()
+ if len(bs) > 0 {
+ s.Stdout.Reset()
+ return bs
+ }
+ return []byte{}
+}
+
+// CombinedOutput 发送命令带结果返回
+func (s *SSHClientSession) CombinedOutput(cmd string) (string, error) {
+ n, err := s.Write(cmd)
+ if n == 0 || err != nil {
+ return "", err
+ }
+ return string(s.Read()), nil
+}
+
+// singleWriter SSH客户端会话消息
+type singleWriter struct {
+ b bytes.Buffer
+ mu sync.Mutex
+}
+
+func (w *singleWriter) Write(p []byte) (int, error) {
+ w.mu.Lock()
+ defer w.mu.Unlock()
+ return w.b.Write(p)
+}
+func (w *singleWriter) Bytes() []byte {
+ w.mu.Lock()
+ defer w.mu.Unlock()
+ return w.b.Bytes()
+}
+func (w *singleWriter) Reset() {
+ w.mu.Lock()
+ defer w.mu.Unlock()
+ w.b.Reset()
+}
diff --git a/src/framework/utils/telnet/telnet.go b/src/framework/utils/telnet/telnet.go
new file mode 100644
index 0000000..651ede9
--- /dev/null
+++ b/src/framework/utils/telnet/telnet.go
@@ -0,0 +1,218 @@
+package telnet
+
+import (
+ "bytes"
+ "fmt"
+ "net"
+ "strings"
+ "time"
+)
+
+// ConnTelnet 连接telnet对象
+type ConnTelnet struct {
+ User string `json:"user"` // 主机用户名
+ Addr string `json:"addr"` // 主机地址
+ Port int64 `json:"port"` // telnet端口
+ Password string `json:"password"` // 认证密码
+
+ DialTimeOut time.Duration `json:"dialTimeOut"` // 连接超时断开
+
+ Client *net.Conn `json:"client"`
+ LastResult string `json:"lastResult"` // 记最后一次执行命令的结果
+}
+
+// NewClient 创建Telnet客户端
+func (c *ConnTelnet) NewClient() (*ConnTelnet, error) {
+ // IPV6地址协议
+ proto := "tcp"
+ if strings.Contains(c.Addr, ":") {
+ proto = "tcp6"
+ c.Addr = fmt.Sprintf("[%s]", c.Addr)
+ }
+ addr := fmt.Sprintf("%s:%d", c.Addr, c.Port)
+
+ // 默认等待5s
+ if c.DialTimeOut == 0 {
+ c.DialTimeOut = 5 * time.Second
+ }
+
+ // 连接到 Telnet 服务器
+ client, err := net.DialTimeout(proto, addr, c.DialTimeOut)
+ if err != nil {
+ return nil, err
+ }
+
+ // 进行登录
+ time.Sleep(100 * time.Millisecond)
+ client.Write([]byte(c.User + "\r\n"))
+ time.Sleep(100 * time.Millisecond)
+ client.Write([]byte(c.Password + "\r\n"))
+ // fmt.Fprintln(client, c.User)
+ // fmt.Fprintln(client, c.Password)
+
+ c.Client = &client
+ return c, nil
+}
+
+// Close 关闭当前Telnet客户端
+func (c *ConnTelnet) Close() {
+ if c.Client != nil {
+ (*c.Client).Close()
+ }
+}
+
+// RunCMD 执行单次命令
+func (c *ConnTelnet) RunCMD(cmd string) (string, error) {
+ if c.Client == nil {
+ return "", fmt.Errorf("telnet client not connected")
+ }
+ conn := *c.Client
+ var buf bytes.Buffer
+ tmp := make([]byte, 1024)
+
+ // 排空连接登录的信息
+ for {
+ // 设置读取超时时间为100毫秒
+ conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
+ n, err := conn.Read(tmp)
+ if err != nil {
+ // 判断是否是超时错误
+ if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
+ break
+ }
+ break
+ }
+ if n == 0 {
+ break
+ }
+ buf.Write(tmp[:n])
+ }
+ buf.Reset()
+
+ // 写入命令
+ _, err := conn.Write([]byte(cmd))
+ if err != nil {
+ return "", err
+ }
+
+ // 读取本次响应命令消息
+ for {
+ // 设置读取超时时间为1000毫秒
+ conn.SetReadDeadline(time.Now().Add(1000 * time.Millisecond))
+ n, err := conn.Read(tmp)
+ if err != nil {
+ // 判断是否是超时错误
+ if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
+ break
+ }
+ break
+ }
+ if n == 0 {
+ break
+ }
+ buf.Write(tmp[:n])
+ }
+ defer buf.Reset()
+
+ c.LastResult = buf.String()
+ return c.LastResult, nil
+}
+
+// NewClient 创建Telnet客户端会话对象
+func (c *ConnTelnet) NewClientSession() (*TelnetClientSession, error) {
+ if c.Client == nil {
+ return nil, fmt.Errorf("telnet client not connected")
+ }
+ conn := *c.Client
+
+ var buf bytes.Buffer
+ tmp := make([]byte, 1024)
+ // 排空连接登录的信息
+ for {
+ // 设置读取超时时间为5毫秒
+ conn.SetReadDeadline(time.Now().Add(5 * time.Millisecond))
+ n, err := conn.Read(tmp)
+ if err != nil {
+ // 判断是否是超时错误
+ if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
+ break
+ }
+ break
+ }
+ if n == 0 {
+ break
+ }
+ buf.Write(tmp[:n])
+ }
+ buf.Reset()
+
+ return &TelnetClientSession{
+ Client: conn,
+ }, nil
+}
+
+// TelnetClientSession Telnet客户端会话对象
+type TelnetClientSession struct {
+ Client net.Conn
+}
+
+// Close 关闭会话
+func (s *TelnetClientSession) Close() {
+ if s.Client != nil {
+ s.Client.Close()
+ }
+}
+
+// Write 写入命令 不带回车(\n)也会执行根据客户端情况
+func (s *TelnetClientSession) Write(cmd string) (int, error) {
+ if s.Client == nil {
+ return 0, fmt.Errorf("client is nil to content write failed")
+ }
+ return s.Client.Write([]byte(cmd))
+}
+
+// Read 读取结果 等待一会才有结果
+func (s *TelnetClientSession) Read() []byte {
+ if s.Client == nil {
+ return []byte{}
+ }
+
+ buf := make([]byte, 1024)
+ // 设置读取超时时间为100毫秒
+ s.Client.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
+ _, err := s.Client.Read(buf)
+ if err != nil {
+ return []byte{}
+ }
+ return buf
+}
+
+// CombinedOutput 发送命令带结果返回
+func (s *TelnetClientSession) CombinedOutput(cmd string) (string, error) {
+ n, err := s.Write(cmd)
+ if n == 0 || err != nil {
+ return "", err
+ }
+
+ var buf bytes.Buffer
+ tmp := make([]byte, 1024)
+ for {
+ // 设置读取超时时间为1000毫秒
+ s.Client.SetReadDeadline(time.Now().Add(1000 * time.Millisecond))
+ n, err := s.Client.Read(tmp)
+ if err != nil {
+ // 判断是否是超时错误
+ if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
+ break
+ }
+ break
+ }
+ if n == 0 {
+ break
+ }
+ buf.Write(tmp[:n])
+ }
+ defer buf.Reset()
+
+ return buf.String(), nil
+}
diff --git a/src/framework/utils/token/token.go b/src/framework/utils/token/token.go
new file mode 100644
index 0000000..8d013cb
--- /dev/null
+++ b/src/framework/utils/token/token.go
@@ -0,0 +1,156 @@
+package token
+
+import (
+ "encoding/json"
+ "fmt"
+ "time"
+
+ "nms_nbi/src/framework/config"
+ cachekeyConstants "nms_nbi/src/framework/constants/cachekey"
+ tokenConstants "nms_nbi/src/framework/constants/token"
+ "nms_nbi/src/framework/logger"
+ redisCahe "nms_nbi/src/framework/redis"
+ "nms_nbi/src/framework/utils/generate"
+ "nms_nbi/src/framework/vo"
+
+ jwt "github.com/golang-jwt/jwt/v5"
+)
+
+// Remove 清除登录用户信息UUID
+func Remove(tokenStr string) string {
+ claims, err := Verify(tokenStr)
+ if err != nil {
+ logger.Errorf("token verify err %v", err)
+ return ""
+ }
+ // 清除缓存KEY
+ uuid := claims[tokenConstants.JWT_UUID].(string)
+ tokenKey := cachekeyConstants.LOGIN_TOKEN_KEY + uuid
+ hasKey, _ := redisCahe.Has("", tokenKey)
+ if hasKey {
+ redisCahe.Del("", tokenKey)
+ }
+ return claims[tokenConstants.JWT_NAME].(string)
+}
+
+// Create 令牌生成
+func Create(loginUser *vo.LoginUser, ilobArgs ...string) string {
+ // 生成用户唯一tokne16位
+ loginUser.UUID = generate.Code(16)
+
+ // 设置请求用户登录客户端
+ loginUser.IPAddr = ilobArgs[0]
+ loginUser.LoginLocation = ilobArgs[1]
+ loginUser.OS = ilobArgs[2]
+ loginUser.Browser = ilobArgs[3]
+
+ // 设置用户令牌有效期并存入缓存
+ Cache(loginUser)
+
+ // 设置登录IP和登录时间
+ loginUser.User.LoginIP = loginUser.IPAddr
+ loginUser.User.LoginDate = loginUser.LoginTime
+
+ // 令牌算法 HS256 HS384 HS512
+ algorithm := config.Get("jwt.algorithm").(string)
+ var method *jwt.SigningMethodHMAC
+ switch algorithm {
+ case "HS512":
+ method = jwt.SigningMethodHS512
+ case "HS384":
+ method = jwt.SigningMethodHS384
+ case "HS256":
+ default:
+ method = jwt.SigningMethodHS256
+ }
+ // 生成令牌负荷绑定uuid标识
+ jwtToken := jwt.NewWithClaims(method, jwt.MapClaims{
+ tokenConstants.JWT_UUID: loginUser.UUID,
+ tokenConstants.JWT_KEY: loginUser.UserID,
+ tokenConstants.JWT_NAME: loginUser.User.UserName,
+ "exp": loginUser.ExpireTime,
+ "ait": loginUser.LoginTime,
+ })
+
+ // 生成令牌设置密钥
+ secret := config.Get("jwt.secret").(string)
+ tokenStr, err := jwtToken.SignedString([]byte(secret))
+ if err != nil {
+ logger.Infof("jwt sign err : %v", err)
+ return ""
+ }
+ return tokenStr
+}
+
+// Cache 缓存登录用户信息
+func Cache(loginUser *vo.LoginUser) {
+ // 计算配置的有效期
+ expTime := config.Get("jwt.expiresIn").(int)
+ expTimestamp := time.Duration(expTime) * time.Minute
+ iatTimestamp := time.Now().UnixMilli()
+ loginUser.LoginTime = iatTimestamp
+ loginUser.ExpireTime = iatTimestamp + expTimestamp.Milliseconds()
+ // 根据登录标识将loginUser缓存
+ tokenKey := cachekeyConstants.LOGIN_TOKEN_KEY + loginUser.UUID
+ jsonBytes, err := json.Marshal(loginUser)
+ if err != nil {
+ return
+ }
+ redisCahe.SetByExpire("", tokenKey, string(jsonBytes), expTimestamp)
+}
+
+// RefreshIn 验证令牌有效期,相差不足xx分钟,自动刷新缓存
+func RefreshIn(loginUser *vo.LoginUser) {
+ // 相差不足xx分钟,自动刷新缓存
+ refreshTime := config.Get("jwt.refreshIn").(int)
+ refreshTimestamp := time.Duration(refreshTime) * time.Minute
+ // 过期时间
+ expireTimestamp := loginUser.ExpireTime
+ currentTimestamp := time.Now().UnixMilli()
+ if expireTimestamp-currentTimestamp <= refreshTimestamp.Milliseconds() {
+ Cache(loginUser)
+ }
+}
+
+// Verify 校验令牌是否有效
+func Verify(tokenString string) (jwt.MapClaims, error) {
+ token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
+ // 判断加密算法是预期的加密算法
+ if _, ok := token.Method.(*jwt.SigningMethodHMAC); ok {
+ secret := config.Get("jwt.secret").(string)
+ return []byte(secret), nil
+ }
+ return nil, jwt.ErrSignatureInvalid
+ })
+ if err != nil {
+ logger.Errorf("token String Verify : %v", err)
+ // 无效身份授权
+ return nil, fmt.Errorf("invalid identity authorization")
+ }
+ // 如果解析负荷成功并通过签名校验
+ if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
+ return claims, nil
+ }
+ return nil, fmt.Errorf("token valid error")
+}
+
+// LoginUser 缓存的登录用户信息
+func LoginUser(claims jwt.MapClaims) vo.LoginUser {
+ uuid := claims[tokenConstants.JWT_UUID].(string)
+ tokenKey := cachekeyConstants.LOGIN_TOKEN_KEY + uuid
+ hasKey, _ := redisCahe.Has("", tokenKey)
+ var loginUser vo.LoginUser
+ if hasKey {
+ loginUserStr, _ := redisCahe.Get("", tokenKey)
+ if loginUserStr == "" {
+ return loginUser
+ }
+ err := json.Unmarshal([]byte(loginUserStr), &loginUser)
+ if err != nil {
+ logger.Errorf("loginuser info json err : %v", err)
+ return loginUser
+ }
+ return loginUser
+ }
+ return loginUser
+}
diff --git a/src/framework/utils/ua/ua.go b/src/framework/utils/ua/ua.go
new file mode 100644
index 0000000..1a12531
--- /dev/null
+++ b/src/framework/utils/ua/ua.go
@@ -0,0 +1,8 @@
+package ua
+
+import "github.com/mssola/user_agent"
+
+// 获取user-agent信息
+func Info(userAgent string) *user_agent.UserAgent {
+ return user_agent.New(userAgent)
+}
diff --git a/src/framework/vo/loginuser.go b/src/framework/vo/loginuser.go
new file mode 100644
index 0000000..71c9880
--- /dev/null
+++ b/src/framework/vo/loginuser.go
@@ -0,0 +1,39 @@
+package vo
+
+import systemModel "nms_nbi/src/modules/system/model"
+
+// LoginUser 登录用户身份权限信息对象
+type LoginUser struct {
+ // UserID 用户ID
+ UserID string `json:"userId"`
+
+ // DeptID 部门ID
+ DeptID string `json:"deptId"`
+
+ // UUID 用户唯一标识
+ UUID string `json:"uuid"`
+
+ // LoginTime 登录时间时间戳
+ LoginTime int64 `json:"loginTime"`
+
+ // ExpireTime 过期时间时间戳
+ ExpireTime int64 `json:"expireTime"`
+
+ // IPAddr 登录IP地址 x.x.x.x
+ IPAddr string `json:"ipaddr"`
+
+ // LoginLocation 登录地点 xx xx
+ LoginLocation string `json:"loginLocation"`
+
+ // Browser 浏览器类型
+ Browser string `json:"browser"`
+
+ // OS 操作系统
+ OS string `json:"os"`
+
+ // Permissions 权限列表
+ Permissions []string `json:"permissions"`
+
+ // User 用户信息
+ User systemModel.SysUser `json:"user"`
+}
diff --git a/src/framework/vo/result/result.go b/src/framework/vo/result/result.go
new file mode 100644
index 0000000..fde4261
--- /dev/null
+++ b/src/framework/vo/result/result.go
@@ -0,0 +1,71 @@
+package result
+
+import (
+ "nms_nbi/src/framework/constants/result"
+)
+
+// CodeMsg 响应结果
+func CodeMsg(code int, msg string) map[string]any {
+ args := make(map[string]any)
+ args["code"] = code
+ args["msg"] = msg
+ return args
+}
+
+// 响应成功结果 map[string]any{}
+func Ok(v map[string]any) map[string]any {
+ args := make(map[string]any)
+ args["code"] = result.CODE_SUCCESS
+ args["msg"] = result.MSG_SUCCESS
+ // v合并到args
+ for key, value := range v {
+ args[key] = value
+ }
+ return args
+}
+
+// 响应成功结果信息
+func OkMsg(msg string) map[string]any {
+ args := make(map[string]any)
+ args["code"] = result.CODE_SUCCESS
+ args["msg"] = msg
+ return args
+}
+
+// 响应成功结果数据
+func OkData(data any) map[string]any {
+ args := make(map[string]any)
+ args["code"] = result.CODE_SUCCESS
+ args["msg"] = result.MSG_SUCCESS
+ args["data"] = data
+ return args
+}
+
+// 响应失败结果 map[string]any{}
+func Err(v map[string]any) map[string]any {
+ args := make(map[string]any)
+ args["code"] = result.CODE_ERROR
+ args["msg"] = result.MSG_ERROR
+ // v合并到args
+ for key, value := range v {
+ args[key] = value
+ }
+ return args
+}
+
+// 响应失败结果信息
+func ErrMsg(msg string) map[string]any {
+ args := make(map[string]any)
+ args["code"] = result.CODE_ERROR
+ args["msg"] = msg
+ return args
+}
+
+// 响应失败结果数据
+func ErrData(data any) map[string]any {
+ args := make(map[string]any)
+ args["code"] = result.CODE_ERROR
+ args["msg"] = result.MSG_ERROR
+ args["data"] = data
+ return args
+}
diff --git a/src/framework/vo/router.go b/src/framework/vo/router.go
new file mode 100644
index 0000000..a3cc408
--- /dev/null
+++ b/src/framework/vo/router.go
@@ -0,0 +1,17 @@
+package vo
+
+// Router 路由信息对象
+type Router struct {
+ // 路由名字 英文首字母大写
+ Name string `json:"name"`
+ // 路由地址
+ Path string `json:"path"`
+ // 其他元素
+ Meta RouterMeta `json:"meta"`
+ // 组件地址
+ Component string `json:"component"`
+ // 重定向地址
+ Redirect string `json:"redirect"`
+ // 子路由
+ Children []Router `json:"children,omitempty"`
+}
diff --git a/src/framework/vo/router_meta.go b/src/framework/vo/router_meta.go
new file mode 100644
index 0000000..b3447e0
--- /dev/null
+++ b/src/framework/vo/router_meta.go
@@ -0,0 +1,17 @@
+package vo
+
+// RouterMeta 路由元信息对象
+type RouterMeta struct {
+ // 设置该菜单在侧边栏和面包屑中展示的名字
+ Title string `json:"title"`
+ // 设置该菜单的图标
+ Icon string `json:"icon"`
+ // 设置为true,则不会被 缓存
+ Cache bool `json:"cache"`
+ // 内链地址(http(s)://开头), 打开目标位置 '_blank' | '_self' | ''
+ Target string `json:"target"`
+ // 在菜单中隐藏子节点
+ HideChildInMenu bool `json:"hideChildInMenu"`
+ // 在菜单中隐藏自己和子节点
+ HideInMenu bool `json:"hideInMenu"`
+}
diff --git a/src/framework/vo/treeselect.go b/src/framework/vo/treeselect.go
new file mode 100644
index 0000000..493e5ca
--- /dev/null
+++ b/src/framework/vo/treeselect.go
@@ -0,0 +1,51 @@
+package vo
+
+import systemModel "nms_nbi/src/modules/system/model"
+
+// TreeSelect 树结构实体类
+type TreeSelect struct {
+ // ID 节点ID
+ ID string `json:"id"`
+
+ // Label 节点名称
+ Label string `json:"label"`
+
+ // Children 子节点
+ Children []TreeSelect `json:"children"`
+}
+
+// SysMenuTreeSelect 使用给定的 SysMenu 对象解析为 TreeSelect 对象
+func SysMenuTreeSelect(sysMenu systemModel.SysMenu) TreeSelect {
+ t := TreeSelect{}
+ t.ID = sysMenu.MenuID
+ t.Label = sysMenu.MenuName
+
+ if len(sysMenu.Children) > 0 {
+ for _, menu := range sysMenu.Children {
+ child := SysMenuTreeSelect(menu)
+ t.Children = append(t.Children, child)
+ }
+ } else {
+ t.Children = []TreeSelect{}
+ }
+
+ return t
+}
+
+// SysDeptTreeSelect 使用给定的 SysDept 对象解析为 TreeSelect 对象
+func SysDeptTreeSelect(sysDept systemModel.SysDept) TreeSelect {
+ t := TreeSelect{}
+ t.ID = sysDept.DeptID
+ t.Label = sysDept.DeptName
+
+ if len(sysDept.Children) > 0 {
+ for _, dept := range sysDept.Children {
+ child := SysDeptTreeSelect(dept)
+ t.Children = append(t.Children, child)
+ }
+ } else {
+ t.Children = []TreeSelect{}
+ }
+
+ return t
+}
diff --git a/src/lib_features/account/account.go b/src/lib_features/account/account.go
new file mode 100644
index 0000000..ffb9bcb
--- /dev/null
+++ b/src/lib_features/account/account.go
@@ -0,0 +1,39 @@
+package libfeatures
+
+import (
+ "time"
+
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/oauth"
+ libConfig "nms_nbi/restagent/config"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/redis"
+)
+
+// SessionToken 设置登录会话-兼容旧登录方式
+func SessionToken(username, sourceAddr string) bool {
+ // token, _ := redis.Get("", "session_token")
+ // if token != "" {
+ // se, err := dborm.XormUpdateSessionShakeTime(token)
+ // if se.AccountId != username || err != nil {
+ // // 过期时间单位秒 配置1800是半小时
+ // expireTime := time.Duration(int64(libConfig.GetExpiresFromConfig())) * time.Second
+ // redis.SetByExpire("", "session_token", token, expireTime)
+ // return true
+ // }
+ // }
+ // 不管原先的登录情况,直接插入写入覆盖redis中session
+ //
+ token := oauth.GenRandToken("omc") // Generate new token to session ID
+ affected, err := dborm.XormInsertSession(username, sourceAddr, token, libConfig.GetExpiresFromConfig(), libConfig.GetYamlConfig().Auth.Session)
+ if err != nil {
+ logger.Errorf("SessionToken XormInsertSession err %v", err)
+ }
+ if affected >= 1 {
+ // 过期时间单位秒 配置1800是半小时
+ expireTime := time.Duration(int64(libConfig.GetExpiresFromConfig())) * time.Second
+ redis.SetByExpire("", "session_token", token, expireTime)
+ return true
+ }
+ return false
+}
diff --git a/src/lib_features/config/config.go b/src/lib_features/config/config.go
new file mode 100644
index 0000000..f8e3aac
--- /dev/null
+++ b/src/lib_features/config/config.go
@@ -0,0 +1,54 @@
+package libfeatures
+
+import (
+ "fmt"
+
+ libConf "nms_nbi/lib/core/conf"
+ libGlobal "nms_nbi/lib/global"
+ libConfig "nms_nbi/restagent/config"
+
+ "github.com/spf13/viper"
+)
+
+// BuildInfo 程序-V查看编译版本号信息
+func BuildInfo() string {
+ return fmt.Sprintf("OMC restagent version: %s\n%s\n%s\n\n", libGlobal.Version, libGlobal.BuildTime, libGlobal.GoVer)
+}
+
+// ConfigRead 指定配置文件读取
+func ConfigRead(configFile string) {
+ // 外层lib和features使用的配置
+ libConfig.ReadConfig(configFile)
+ uriPrefix := libConfig.GetYamlConfig().OMC.UriPrefix
+ if uriPrefix != "" {
+ libConfig.UriPrefix = uriPrefix
+ }
+ if libConfig.GetYamlConfig().TestConfig.Enabled {
+ libConfig.ReadTestConfigYaml(libConfig.GetYamlConfig().TestConfig.File)
+ }
+ // 外层lib和features使用配置
+ libConf.InitConfig(configFile)
+}
+
+// 配置文件读取进行内部参数合并
+func ConfigInMerge() {
+ // 合并外层lib和features使用配置
+ for key, value := range libConf.AllSettings() {
+ // 跳过配置
+ if key == "testconfig" || key == "rest" || key == "logger" {
+ continue
+ }
+ // 数据库配置
+ if key == "database" {
+ item := value.(map[string]any)
+ defaultItem := viper.GetStringMap("gorm.datasource.default")
+ defaultItem["host"] = item["host"]
+ defaultItem["port"] = item["port"]
+ defaultItem["username"] = item["user"]
+ defaultItem["password"] = item["password"]
+ defaultItem["database"] = item["name"]
+ continue
+ }
+ viper.Set(key, value)
+ }
+}
diff --git a/src/lib_features/readme.md b/src/lib_features/readme.md
new file mode 100644
index 0000000..8e91940
--- /dev/null
+++ b/src/lib_features/readme.md
@@ -0,0 +1,5 @@
+# 外层 lib 和 features 粘合层
+
+- config.go 配置合并: restagent.yaml 文件内容,主要是数据库配置
+- account.go 登录会话生成 token
+- session.go 中间件方式设置请求头 token 值
diff --git a/src/lib_features/session/session.go b/src/lib_features/session/session.go
new file mode 100644
index 0000000..fb701e7
--- /dev/null
+++ b/src/lib_features/session/session.go
@@ -0,0 +1,29 @@
+package session
+
+import (
+ "time"
+
+ libConfig "nms_nbi/restagent/config"
+ "nms_nbi/src/framework/redis"
+
+ "github.com/gin-gonic/gin"
+)
+
+// SessionHeader 旧登录方式token头
+func SessionHeader() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ // 读取登录生成的会话token
+ token, _ := redis.Get("", "session_token")
+ if token != "" {
+ // 过期时间单位秒 配置1800是半小时
+ expireTime := time.Duration(int64(libConfig.GetExpiresFromConfig())) * time.Second
+ redis.SetByExpire("", "session_token", token, expireTime)
+ c.Request.Header.Set("Accesstoken", token)
+ }
+
+ // Accesstoken: omc-ce4d0a86-8515-ad51-3249-4913c95f8e34
+ // 调用下一个处理程序
+ c.Next()
+
+ }
+}
diff --git a/src/modules/api_rest/api_rest.go b/src/modules/api_rest/api_rest.go
new file mode 100644
index 0000000..98e9ad9
--- /dev/null
+++ b/src/modules/api_rest/api_rest.go
@@ -0,0 +1,25 @@
+package chart
+
+import (
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/middleware"
+ "nms_nbi/src/modules/chart/controller"
+
+ "github.com/gin-gonic/gin"
+)
+
+// 模块路由注册
+func Setup(router *gin.Engine) {
+ logger.Infof("开始加载 ====> NMS 北向模块路由")
+
+ apiRest := router.Group("/api/rest")
+
+ // 关系图
+ systemManagementGroup := apiRest.Group("/systemManagement")
+ {
+ systemManagementGroup.GET("",
+ middleware.PreAuthorize(nil),
+ controller.NewChartGraph.Load,
+ )
+ }
+}
diff --git a/src/modules/chart/chart.go b/src/modules/chart/chart.go
new file mode 100644
index 0000000..65fdacc
--- /dev/null
+++ b/src/modules/chart/chart.go
@@ -0,0 +1,40 @@
+package chart
+
+import (
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/middleware"
+ "nms_nbi/src/framework/middleware/collectlogs"
+ "nms_nbi/src/modules/chart/controller"
+
+ "github.com/gin-gonic/gin"
+)
+
+// 模块路由注册
+func Setup(router *gin.Engine) {
+ logger.Infof("开始加载 ====> chart 模块路由")
+
+ chartGroup := router.Group("/chart")
+
+ // 关系图
+ chartGraphGroup := chartGroup.Group("/graph")
+ {
+ chartGraphGroup.GET("",
+ middleware.PreAuthorize(nil),
+ controller.NewChartGraph.Load,
+ )
+ chartGraphGroup.GET("/groups",
+ middleware.PreAuthorize(nil),
+ controller.NewChartGraph.GroupNames,
+ )
+ chartGraphGroup.POST("",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.chartGraph", collectlogs.BUSINESS_TYPE_UPDATE)),
+ controller.NewChartGraph.Save,
+ )
+ chartGraphGroup.DELETE("/:group",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.chartGraph", collectlogs.BUSINESS_TYPE_DELETE)),
+ controller.NewChartGraph.Delete,
+ )
+ }
+}
diff --git a/src/modules/chart/controller/chart_graph.go b/src/modules/chart/controller/chart_graph.go
new file mode 100644
index 0000000..643ab65
--- /dev/null
+++ b/src/modules/chart/controller/chart_graph.go
@@ -0,0 +1,101 @@
+package controller
+
+import (
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/vo/result"
+ chartService "nms_nbi/src/modules/chart/service"
+
+ "github.com/gin-gonic/gin"
+ "github.com/gin-gonic/gin/binding"
+)
+
+// 实例化控制层 ChartGraphController 结构体
+var NewChartGraph = &ChartGraphController{
+ chartGraphService: chartService.NewChartGraphImpl,
+}
+
+// G6关系图
+//
+// PATH /graph
+type ChartGraphController struct {
+ // G6关系图数据表服务
+ chartGraphService chartService.IChartGraph
+}
+
+// 获取关系图组名
+//
+// GET /groups
+func (s *ChartGraphController) GroupNames(c *gin.Context) {
+ data := s.chartGraphService.SelectGroup()
+ c.JSON(200, result.OkData(data))
+}
+
+// 获取关系图数据
+//
+// GET /
+func (s *ChartGraphController) Load(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var querys struct {
+ Group string `form:"group" binding:"required"`
+ Type string `form:"type" binding:"omitempty,oneof=node edge combo"`
+ }
+ if err := c.ShouldBindQuery(&querys); err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ data := s.chartGraphService.LoadData(querys.Group, querys.Type)
+ c.JSON(200, result.OkData(data))
+}
+
+// 保存关系图数据
+//
+// POST /
+func (s *ChartGraphController) Save(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body struct {
+ Group string `json:"group" binding:"required"`
+ Data struct {
+ Nodes []map[string]any `json:"nodes" binding:"required"`
+ Edges []map[string]any `json:"edges" binding:"required"`
+ Combos []map[string]any `json:"combos" binding:"required"`
+ } `json:"data" binding:"required"`
+ }
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ data := map[string]any{
+ "nodes": body.Data.Nodes,
+ "edges": body.Data.Edges,
+ "combos": body.Data.Combos,
+ }
+ saveNum := s.chartGraphService.SaveData(body.Group, data)
+ if saveNum > 0 {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 删除关系图数据
+//
+// DELETE /:group
+func (s *ChartGraphController) Delete(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ group := c.Param("group")
+ if group == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ deleteNum := s.chartGraphService.DeleteGroup(group)
+ if deleteNum > 0 {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
diff --git a/src/modules/chart/model/chart_graph.go b/src/modules/chart/model/chart_graph.go
new file mode 100644
index 0000000..26dd823
--- /dev/null
+++ b/src/modules/chart/model/chart_graph.go
@@ -0,0 +1,31 @@
+package model
+
+// ChartGraph G6关系图数据对象 chart_graph
+type ChartGraph struct {
+ RowID int64 `json:"rowId,omitempty" gorm:"column:row_id;primaryKey;autoIncrement"` // 记录ID
+ RowType string `json:"rowType,omitempty" gorm:"column:row_type"` // 记录类型(node/edge/combo)
+ RowGroup string `json:"rowGroup,omitempty" gorm:"column:row_group"` // 记录组名
+ ID string `json:"id,omitempty" gorm:"column:id"` // 元素ID
+ Type string `json:"type,omitempty" gorm:"column:type"` // node/combo 类型
+ Depth int `json:"depth,omitempty" gorm:"column:depth"` // node/combo 深度
+ X float64 `json:"x,omitempty" gorm:"column:x"` // node/combo 横向坐标
+ Y float64 `json:"y,omitempty" gorm:"column:y"` // node/combo 纵向坐标
+ Size string `json:"size,omitempty" gorm:"column:size"` // node/combo 大小-JSON数组
+ Icon string `json:"icon,omitempty" gorm:"column:icon"` // node-部分类型支持图标JSON配置
+ Img string `json:"img,omitempty" gorm:"column:img"` // node-img 图片
+ ClipCfg string `json:"clipCfg,omitempty" gorm:"column:clip_cfg"` // node-img 图片裁剪JSON配置
+ Direction string `json:"direction,omitempty" gorm:"column:direction"` // node-triangle 三角形的方向(up/down/left/right)
+ Source string `json:"source,omitempty" gorm:"column:source"` // edge-边起始
+ Target string `json:"target,omitempty" gorm:"column:target"` // edge-边目标
+ ComboID string `json:"combo_id,omitempty" gorm:"column:combo_id"` // combo-分组
+ Padding string `json:"padding,omitempty" gorm:"column:padding"` // combo-JSON分组内边距
+ ParentID string `json:"parentId,omitempty" gorm:"column:parent_id"` // combo-父级分组
+ Children string `json:"children,omitempty" gorm:"column:children"` // combo-JSON分组内含元素
+ Style string `json:"style,omitempty" gorm:"column:style"` // 元素样式-JONS配置
+ Label string `json:"label,omitempty" gorm:"column:label"` // 标签文本
+ LabelCfg string `json:"labelCfg,omitempty" gorm:"column:label_cfg"` // 标签文本-JSON配置
+}
+
+func (ChartGraph) TableName() string {
+ return "chart_graph"
+}
diff --git a/src/modules/chart/repository/chart_graph.go b/src/modules/chart/repository/chart_graph.go
new file mode 100644
index 0000000..bd46a59
--- /dev/null
+++ b/src/modules/chart/repository/chart_graph.go
@@ -0,0 +1,21 @@
+package repository
+
+import "nms_nbi/src/modules/chart/model"
+
+// G6关系图数据 数据层接口
+type IChartGraph interface {
+ // SelectPage 根据条件分页查询字典类型
+ SelectPage(query map[string]any) map[string]any
+
+ // SelectList 根据实体查询
+ SelectList(graph model.ChartGraph) []model.ChartGraph
+
+ // SelectGroup 查询组名
+ SelectGroup() []string
+
+ // Insert 批量添加
+ Inserts(graphs []model.ChartGraph) int64
+
+ // Delete 删除组数据
+ DeleteGroup(rowGroup string) int64
+}
diff --git a/src/modules/chart/repository/chart_graph.impl.go b/src/modules/chart/repository/chart_graph.impl.go
new file mode 100644
index 0000000..5f5a373
--- /dev/null
+++ b/src/modules/chart/repository/chart_graph.impl.go
@@ -0,0 +1,194 @@
+package repository
+
+import (
+ "strings"
+
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/repo"
+ "nms_nbi/src/modules/chart/model"
+)
+
+// 实例化数据层 NewChartGraphImpl 结构体
+var NewChartGraphImpl = &ChartGraphImpl{
+ selectSql: `select
+ row_id, row_type, row_group,
+ id, type, depth, x, y, size, icon, img,
+ clip_cfg, direction,
+ source, target, combo_id,
+ padding, parent_id, children,
+ style, label, label_cfg
+ from chart_graph`,
+
+ resultMap: map[string]string{
+ "row_id": "RowID",
+ "row_type": "RowType",
+ "row_group": "RowGroup",
+ "id": "ID",
+ "type": "Type",
+ "depth": "Depth",
+ "x": "X",
+ "y": "Y",
+ "size": "Size",
+ "icon": "Icon",
+ "img": "Img",
+ "clip_cfg": "ClipCfg",
+ "direction": "Direction",
+ "source": "Source",
+ "target": "Target",
+ "combo_id": "ComboID",
+ "padding": "Padding",
+ "parent_id": "ParentID",
+ "children": "Children",
+ "style": "Style",
+ "label": "Label",
+ "label_cfg": "LabelCfg",
+ },
+}
+
+// ChartGraphImpl G6关系图数据表 数据层处理
+type ChartGraphImpl struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *ChartGraphImpl) convertResultRows(rows []map[string]any) []model.ChartGraph {
+ arr := make([]model.ChartGraph, 0)
+ for _, row := range rows {
+ item := model.ChartGraph{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ repo.SetFieldValue(&item, keyMapper, value)
+ }
+ }
+ arr = append(arr, item)
+ }
+ return arr
+}
+
+// SelectPage 根据条件分页查询字典类型
+func (r *ChartGraphImpl) SelectPage(query map[string]any) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if v, ok := query["rowType"]; ok && v != "" {
+ conditions = append(conditions, "row_type = ?")
+ params = append(params, strings.Trim(v.(string), " "))
+ }
+ if v, ok := query["rowGroup"]; ok && v != "" {
+ conditions = append(conditions, "row_group = ?")
+ params = append(params, strings.Trim(v.(string), " "))
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ result := map[string]any{
+ "total": 0,
+ "rows": []model.ChartGraph{},
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := "select count(1) as 'total' from chart_graph"
+ totalRows, err := datasource.RawDB("", totalSql+whereSql, params)
+ if err != nil {
+ logger.Errorf("total err => %v", err)
+ return result
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return result
+ } else {
+ result["total"] = total
+ }
+
+ // 分页
+ pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return result
+ }
+
+ // 转换实体
+ result["rows"] = r.convertResultRows(results)
+ return result
+}
+
+// SelectList 根据实体查询
+func (r *ChartGraphImpl) SelectList(graph model.ChartGraph) []model.ChartGraph {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if graph.RowType != "" {
+ conditions = append(conditions, "row_type = ?")
+ params = append(params, graph.RowType)
+ }
+ if graph.RowGroup != "" {
+ conditions = append(conditions, "row_group = ?")
+ params = append(params, graph.RowGroup)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + " order by depth asc "
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ }
+
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// SelectGroup 查询组名
+func (r *ChartGraphImpl) SelectGroup() []string {
+ rows := []string{}
+ // 查询数量 长度为0直接返回
+ querySql := "select row_group as 'str' from chart_graph GROUP BY row_group"
+ strRows, err := datasource.RawDB("", querySql, nil)
+ if err != nil {
+ logger.Errorf("Query err => %v", err)
+ return rows
+ }
+ for _, v := range strRows {
+ rows = append(rows, v["str"].(string))
+ }
+ return rows
+}
+
+// Insert 批量添加
+func (r *ChartGraphImpl) Inserts(graphs []model.ChartGraph) int64 {
+ tx := datasource.DefaultDB().CreateInBatches(graphs, 2000)
+ if err := tx.Error; err != nil {
+ logger.Errorf("CreateInBatches err => %v", err)
+ }
+ return tx.RowsAffected
+}
+
+// Delete 删除组数据
+func (r *ChartGraphImpl) DeleteGroup(rowGroup string) int64 {
+ tx := datasource.DefaultDB().Where("row_group = ?", rowGroup).Delete(&model.ChartGraph{})
+ if err := tx.Error; err != nil {
+ logger.Errorf("Delete err => %v", err)
+ }
+ return tx.RowsAffected
+}
diff --git a/src/modules/chart/service/chart_graph.go b/src/modules/chart/service/chart_graph.go
new file mode 100644
index 0000000..470183d
--- /dev/null
+++ b/src/modules/chart/service/chart_graph.go
@@ -0,0 +1,16 @@
+package service
+
+// G6关系图数据 服务层接口
+type IChartGraph interface {
+ // SelectGroup 查询组名
+ SelectGroup() []string
+
+ // LoadData 查询所组图数据
+ LoadData(rowGroup, rowType string) map[string]any
+
+ // SaveData 添加组图数据
+ SaveData(rowGroup string, data map[string]any) int64
+
+ // DeleteGroup 删除所组图数据
+ DeleteGroup(rowGroup string) int64
+}
diff --git a/src/modules/chart/service/chart_graph.impl.go b/src/modules/chart/service/chart_graph.impl.go
new file mode 100644
index 0000000..e041aab
--- /dev/null
+++ b/src/modules/chart/service/chart_graph.impl.go
@@ -0,0 +1,360 @@
+package service
+
+import (
+ "strings"
+
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/modules/chart/model"
+ chartRepository "nms_nbi/src/modules/chart/repository"
+
+ "github.com/goccy/go-json"
+)
+
+// 实例化服务层 ChartGraphImpl 结构体
+var NewChartGraphImpl = &ChartGraphImpl{
+ graphRepository: chartRepository.NewChartGraphImpl,
+}
+
+// ChartGraphImpl G6关系图数据表 服务层处理
+type ChartGraphImpl struct {
+ // G6关系图数据服务
+ graphRepository chartRepository.IChartGraph
+}
+
+// SelectGroup 查询组名
+func (s *ChartGraphImpl) SelectGroup() []string {
+ return s.graphRepository.SelectGroup()
+}
+
+// LoadData 查询所组图数据
+func (s *ChartGraphImpl) LoadData(rowGroup, rowType string) map[string]any {
+ // 查询数据
+ graph := model.ChartGraph{
+ RowGroup: rowGroup,
+ }
+ if rowType != "" {
+ graph.RowType = rowType
+ }
+ data := s.graphRepository.SelectList(graph)
+
+ // 数据项
+ nodes := []map[string]any{}
+ edges := []map[string]any{}
+ combos := []map[string]any{}
+
+ for _, v := range data {
+ if v.RowType == "node" {
+ nodes = append(nodes, s.loadNode(v))
+ }
+ if v.RowType == "edge" {
+ edges = append(edges, s.loadEdge(v))
+ }
+ if v.RowType == "combo" {
+ combos = append(combos, s.loadCombo(v))
+ }
+ }
+
+ return map[string]any{
+ "nodes": nodes,
+ "edges": edges,
+ "combos": combos,
+ }
+}
+
+// loadNode 图数据Node
+func (s *ChartGraphImpl) loadNode(v model.ChartGraph) map[string]any {
+ node := map[string]any{
+ "id": v.ID,
+ "comboId": v.ComboID,
+ "x": v.X,
+ "y": v.Y,
+ "type": v.Type,
+ "depth": v.Depth,
+ }
+
+ // 元素样式
+ style := map[string]any{}
+ if len(v.Style) > 7 {
+ json.Unmarshal([]byte(v.Style), &style)
+ }
+ node["style"] = style
+
+ // 元素大小
+ if strings.Contains(v.Size, "[") {
+ sizeArr := []int64{}
+ json.Unmarshal([]byte(v.Size), &sizeArr)
+ node["size"] = sizeArr
+ } else {
+ node["size"] = parse.Number(v.Size)
+ }
+
+ // 标签文本
+ node["label"] = v.Label
+ labelCfg := map[string]any{}
+ if len(v.LabelCfg) > 7 {
+ json.Unmarshal([]byte(v.LabelCfg), &labelCfg)
+ }
+ node["labelCfg"] = labelCfg
+
+ // 三角形属性
+ if v.Type == "triangle" {
+ node["direction"] = v.Direction
+ }
+
+ // 图片属性
+ if strings.Index(v.Type, "image") == 0 {
+ node["img"] = v.Img
+ clipCfg := map[string]any{}
+ if len(v.ClipCfg) > 7 {
+ json.Unmarshal([]byte(v.ClipCfg), &clipCfg)
+ }
+ node["clipCfg"] = clipCfg
+ }
+
+ // 图标属性
+ if v.Icon != "" {
+ icon := map[string]any{}
+ if len(v.Icon) > 7 {
+ json.Unmarshal([]byte(v.Icon), &icon)
+ }
+ node["icon"] = icon
+ }
+
+ return node
+}
+
+// loadEdge 图数据Edge
+func (s *ChartGraphImpl) loadEdge(v model.ChartGraph) map[string]any {
+ edge := map[string]any{
+ "id": v.ID,
+ "source": v.Source,
+ "target": v.Target,
+ "type": v.Type,
+ }
+
+ // 元素样式
+ style := map[string]any{}
+ if len(v.Style) > 7 {
+ json.Unmarshal([]byte(v.Style), &style)
+ }
+ edge["style"] = style
+
+ // 标签文本
+ edge["label"] = v.Label
+ labelCfg := map[string]any{}
+ if len(v.LabelCfg) > 7 {
+ json.Unmarshal([]byte(v.LabelCfg), &labelCfg)
+ }
+ edge["labelCfg"] = labelCfg
+
+ return edge
+}
+
+// loadCombo 图数据Combo
+func (s *ChartGraphImpl) loadCombo(v model.ChartGraph) map[string]any {
+ combo := map[string]any{
+ "id": v.ID,
+ "x": v.X,
+ "y": v.Y,
+ "type": v.Type,
+ "depth": v.Depth,
+ }
+
+ // 元素样式
+ style := map[string]any{}
+ if len(v.Style) > 7 {
+ json.Unmarshal([]byte(v.Style), &style)
+ }
+ combo["style"] = style
+
+ // 元素大小
+ if strings.Contains(v.Size, "[") {
+ sizeArr := []int64{}
+ json.Unmarshal([]byte(v.Size), &sizeArr)
+ combo["size"] = sizeArr
+ } else {
+ combo["size"] = parse.Number(v.Size)
+ }
+
+ // 元素内边距
+ if strings.Contains(v.Padding, "[") {
+ paddingArr := []int64{}
+ json.Unmarshal([]byte(v.Padding), &paddingArr)
+ combo["padding"] = paddingArr
+ } else {
+ combo["padding"] = parse.Number(v.Padding)
+ }
+
+ // 标签文本
+ combo["label"] = v.Label
+ labelCfg := map[string]any{}
+ if len(v.LabelCfg) > 7 {
+ json.Unmarshal([]byte(v.LabelCfg), &labelCfg)
+ }
+ combo["labelCfg"] = labelCfg
+
+ // 分组内元素
+ if v.Children != "" {
+ children := []map[string]any{}
+ if len(v.Children) > 7 {
+ json.Unmarshal([]byte(v.Children), &children)
+ }
+ combo["children"] = children
+ }
+
+ return combo
+}
+
+// SaveData 添加组图数据
+func (s *ChartGraphImpl) SaveData(rowGroup string, data map[string]any) int64 {
+ graphs := []model.ChartGraph{}
+ nodes := data["nodes"].([]map[string]any)
+ graphNodes := s.saveNode(rowGroup, nodes)
+ graphs = append(graphs, graphNodes...)
+ edges := data["edges"].([]map[string]any)
+ graphEdges := s.saveEdge(rowGroup, edges)
+ graphs = append(graphs, graphEdges...)
+ combos := data["combos"].([]map[string]any)
+ graphCombos := s.saveCombo(rowGroup, combos)
+ graphs = append(graphs, graphCombos...)
+ // 删除组数据后插入
+ if len(graphs) > 0 {
+ s.graphRepository.DeleteGroup(rowGroup)
+ }
+ return s.graphRepository.Inserts(graphs)
+}
+
+// saveNode 图数据Node
+func (s *ChartGraphImpl) saveNode(rowGroup string, nodes []map[string]any) []model.ChartGraph {
+ var graphs []model.ChartGraph
+ for _, v := range nodes {
+ node := model.ChartGraph{
+ RowType: "node",
+ RowGroup: rowGroup,
+ ID: v["id"].(string),
+ X: v["x"].(float64),
+ Y: v["y"].(float64),
+ Type: v["type"].(string),
+ }
+ if comboId, ok := v["comboId"]; ok && comboId != nil {
+ node.ComboID = comboId.(string)
+ }
+ if depth, ok := v["depth"]; ok && depth != nil {
+ node.Depth = int(depth.(float64))
+ }
+ if styleByte, err := json.Marshal(v["style"]); err == nil {
+ node.Style = string(styleByte)
+ }
+
+ // 元素大小
+ if sizeByte, err := json.Marshal(v["size"]); err == nil {
+ node.Size = string(sizeByte)
+ }
+
+ // 标签文本
+ if label, ok := v["label"]; ok && label != nil {
+ node.Label = label.(string)
+ }
+ if labelCfgByte, err := json.Marshal(v["labelCfg"]); err == nil {
+ node.LabelCfg = string(labelCfgByte)
+ }
+ // 三角形属性
+ if direction, ok := v["direction"]; ok && direction != nil && node.Type == "triangle" {
+ node.Direction = direction.(string)
+ }
+ // 图片属性
+ if img, ok := v["img"]; ok && img != nil {
+ node.Img = img.(string)
+ if clipCfgByte, err := json.Marshal(v["clipCfg"]); err == nil {
+ node.ClipCfg = string(clipCfgByte)
+ }
+ }
+ // 图标属性
+ if icon, ok := v["icon"]; ok && icon != nil {
+ if iconByte, err := json.Marshal(icon); err == nil {
+ node.Icon = string(iconByte)
+ }
+ }
+
+ graphs = append(graphs, node)
+ }
+ return graphs
+}
+
+// saveEdge 图数据Edge
+func (s *ChartGraphImpl) saveEdge(rowGroup string, edges []map[string]any) []model.ChartGraph {
+ var graphs []model.ChartGraph
+ for _, v := range edges {
+ edge := model.ChartGraph{
+ RowType: "edge",
+ RowGroup: rowGroup,
+ ID: v["id"].(string),
+ Source: v["source"].(string),
+ Target: v["target"].(string),
+ Type: v["type"].(string),
+ }
+
+ if styleByte, err := json.Marshal(v["style"]); err == nil {
+ edge.Style = string(styleByte)
+ }
+
+ // 标签文本
+ if label, ok := v["label"]; ok && label != nil {
+ edge.Label = label.(string)
+ }
+ if labelCfgByte, err := json.Marshal(v["labelCfg"]); err == nil {
+ edge.LabelCfg = string(labelCfgByte)
+ }
+
+ graphs = append(graphs, edge)
+ }
+ return graphs
+}
+
+// saveCombo 图数据Combo
+func (s *ChartGraphImpl) saveCombo(rowGroup string, combos []map[string]any) []model.ChartGraph {
+ var graphs []model.ChartGraph
+ for _, v := range combos {
+ combo := model.ChartGraph{
+ RowType: "combo",
+ RowGroup: rowGroup,
+ ID: v["id"].(string),
+ X: v["x"].(float64),
+ Y: v["y"].(float64),
+ Type: v["type"].(string),
+ }
+ if depth, ok := v["depth"]; ok && depth != nil {
+ combo.Depth = int(depth.(float64))
+ }
+ if styleByte, err := json.Marshal(v["style"]); err == nil {
+ combo.Style = string(styleByte)
+ }
+ if paddingByte, err := json.Marshal(v["padding"]); err == nil {
+ combo.Padding = string(paddingByte)
+ }
+ if childrenByte, err := json.Marshal(v["children"]); err == nil {
+ combo.Children = string(childrenByte)
+ }
+
+ // 元素大小
+ if sizeByte, err := json.Marshal(v["size"]); err == nil {
+ combo.Size = string(sizeByte)
+ }
+
+ // 标签文本
+ if label, ok := v["label"]; ok && label != nil {
+ combo.Label = label.(string)
+ }
+ if labelCfgByte, err := json.Marshal(v["labelCfg"]); err == nil {
+ combo.LabelCfg = string(labelCfgByte)
+ }
+
+ graphs = append(graphs, combo)
+ }
+ return graphs
+}
+
+// Delete 删除所组图数据
+func (s *ChartGraphImpl) DeleteGroup(rowGroup string) int64 {
+ return s.graphRepository.DeleteGroup(rowGroup)
+}
diff --git a/src/modules/common/common.go b/src/modules/common/common.go
new file mode 100644
index 0000000..2eace5b
--- /dev/null
+++ b/src/modules/common/common.go
@@ -0,0 +1,90 @@
+package common
+
+import (
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/middleware"
+ "nms_nbi/src/modules/common/controller"
+
+ "github.com/gin-gonic/gin"
+)
+
+// 模块路由注册
+func Setup(router *gin.Engine) {
+ logger.Infof("开始加载 ====> common 模块路由")
+
+ // 路由主页
+ indexGroup := router.Group("/")
+ indexGroup.GET("",
+ // middleware.RateLimit(middleware.LimitOption{
+ // Time: 300,
+ // Count: 10,
+ // Type: middleware.LIMIT_IP,
+ // }),
+ controller.NewIndex.Handler,
+ )
+
+ // 系统可暴露的配置信息
+ indexGroup.GET("/sys-conf", controller.NewCommont.SysConfig)
+
+ // 验证码操作处理
+ indexGroup.GET("/captchaImage",
+ // middleware.RateLimit(middleware.LimitOption{
+ // Time: 300,
+ // Count: 60,
+ // Type: middleware.LIMIT_IP,
+ // }),
+ controller.NewCaptcha.Image,
+ )
+
+ // 账号身份操作处理
+ {
+ indexGroup.POST("/login",
+ // middleware.RateLimit(middleware.LimitOption{
+ // Time: 300,
+ // Count: 10,
+ // Type: middleware.LIMIT_IP,
+ // }),
+ controller.NewAccount.Login,
+ )
+ indexGroup.GET("/getInfo", middleware.PreAuthorize(nil), controller.NewAccount.Info)
+ indexGroup.GET("/getRouters", middleware.PreAuthorize(nil), controller.NewAccount.Router)
+ indexGroup.POST("/logout",
+ middleware.RateLimit(middleware.LimitOption{
+ Time: 300,
+ Count: 5,
+ Type: middleware.LIMIT_IP,
+ }),
+ controller.NewAccount.Logout,
+ )
+ }
+
+ // 账号注册操作处理
+ {
+ indexGroup.POST("/register",
+ // middleware.RateLimit(middleware.LimitOption{
+ // Time: 300,
+ // Count: 10,
+ // Type: middleware.LIMIT_IP,
+ // }),
+ controller.NewRegister.Register,
+ )
+ }
+
+ // 通用请求
+ commonGroup := router.Group("/common")
+ {
+ commonGroup.POST("/hash", middleware.PreAuthorize(nil), controller.NewCommont.Hash)
+ commonGroup.GET("/i18n", controller.NewCommont.I18n)
+ }
+
+ // 文件操作处理
+ fileGroup := router.Group("/file")
+ {
+ fileGroup.GET("/download/:filePath", middleware.PreAuthorize(nil), controller.NewFile.Download)
+ fileGroup.POST("/upload", middleware.PreAuthorize(nil), controller.NewFile.Upload)
+ fileGroup.POST("/chunkCheck", middleware.PreAuthorize(nil), controller.NewFile.ChunkCheck)
+ fileGroup.POST("/chunkUpload", middleware.PreAuthorize(nil), controller.NewFile.ChunkUpload)
+ fileGroup.POST("/chunkMerge", middleware.PreAuthorize(nil), controller.NewFile.ChunkMerge)
+ fileGroup.POST("/transferStaticFile", middleware.PreAuthorize(nil), controller.NewCommont.TransferStaticFile)
+ }
+}
diff --git a/src/modules/common/controller/account.go b/src/modules/common/controller/account.go
new file mode 100644
index 0000000..6a3b862
--- /dev/null
+++ b/src/modules/common/controller/account.go
@@ -0,0 +1,173 @@
+package controller
+
+import (
+ "nms_nbi/src/framework/config"
+ commonConstants "nms_nbi/src/framework/constants/common"
+ tokenConstants "nms_nbi/src/framework/constants/token"
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ tokenUtils "nms_nbi/src/framework/utils/token"
+ "nms_nbi/src/framework/vo"
+ "nms_nbi/src/framework/vo/result"
+ libAccount "nms_nbi/src/lib_features/account"
+ commonModel "nms_nbi/src/modules/common/model"
+ commonService "nms_nbi/src/modules/common/service"
+ systemService "nms_nbi/src/modules/system/service"
+
+ "github.com/gin-gonic/gin"
+)
+
+// 实例化控制层 AccountController 结构体
+var NewAccount = &AccountController{
+ accountService: commonService.NewAccountImpl,
+ sysLogLoginService: systemService.NewSysLogLoginImpl,
+}
+
+// 账号身份操作处理
+//
+// PATH /
+type AccountController struct {
+ // 账号身份操作服务
+ accountService commonService.IAccount
+ // 系统登录访问
+ sysLogLoginService systemService.ISysLogLogin
+}
+
+// 系统登录
+//
+// POST /login
+func (s *AccountController) Login(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var loginBody commonModel.LoginBody
+ if err := c.ShouldBindJSON(&loginBody); err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 当前请求信息
+ ipaddr, location := ctx.IPAddrLocation(c)
+ os, browser := ctx.UaOsBrowser(c)
+
+ // 校验验证码
+ err := s.accountService.ValidateCaptcha(
+ loginBody.Code,
+ loginBody.UUID,
+ )
+ // 根据错误信息,创建系统访问记录
+ if err != nil {
+ s.sysLogLoginService.CreateSysLogLogin(
+ loginBody.Username, commonConstants.STATUS_NO, err.Error(),
+ ipaddr, location, os, browser,
+ )
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error())))
+ return
+ }
+
+ // 登录用户信息
+ loginUser, err := s.accountService.LoginByUsername(loginBody.Username, loginBody.Password)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error())))
+ return
+ }
+
+ // 生成令牌,创建系统访问记录
+ tokenStr := tokenUtils.Create(&loginUser, ipaddr, location, os, browser)
+ if tokenStr == "" {
+ c.JSON(200, result.Err(nil))
+ return
+ } else {
+ s.accountService.UpdateLoginDateAndIP(&loginUser)
+ // 登录成功
+ s.sysLogLoginService.CreateSysLogLogin(
+ loginBody.Username, commonConstants.STATUS_YES, "app.common.loginSuccess",
+ ipaddr, location, os, browser,
+ )
+ }
+
+ // 设置登录会话-兼容旧登录方式
+ libAccount.SessionToken(loginBody.Username, ipaddr)
+
+ c.JSON(200, result.OkData(map[string]any{
+ tokenConstants.RESPONSE_FIELD: tokenStr,
+ }))
+}
+
+// 登录用户信息
+//
+// GET /getInfo
+func (s *AccountController) Info(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ loginUser, err := ctx.LoginUser(c)
+ if err != nil {
+ c.JSON(401, result.CodeMsg(401, i18n.TKey(language, err.Error())))
+ return
+ }
+
+ // 角色权限集合,管理员拥有所有权限
+ isAdmin := config.IsAdmin(loginUser.UserID)
+ roles, perms := s.accountService.RoleAndMenuPerms(loginUser.UserID, isAdmin)
+
+ loginUser.User.NickName = i18n.TKey(language, loginUser.User.NickName)
+ loginUser.User.Remark = i18n.TKey(language, loginUser.User.Remark)
+ loginUser.User.Dept.DeptName = i18n.TKey(language, loginUser.User.Dept.DeptName)
+ for ri := range loginUser.User.Roles {
+ loginUser.User.Roles[ri].RoleName = i18n.TKey(language, loginUser.User.Roles[ri].RoleName)
+ }
+ c.JSON(200, result.OkData(map[string]any{
+ "user": loginUser.User,
+ "roles": roles,
+ "permissions": perms,
+ }))
+}
+
+// 登录用户路由信息
+//
+// GET /getRouters
+func (s *AccountController) Router(c *gin.Context) {
+ userID := ctx.LoginUserToUserID(c)
+
+ // 前端路由,管理员拥有所有
+ isAdmin := config.IsAdmin(userID)
+ buildMenus := s.accountService.RouteMenus(userID, isAdmin)
+
+ // 闭包函数处理多语言
+ language := ctx.AcceptLanguage(c)
+ var converI18n func(language string, arr *[]vo.Router)
+ converI18n = func(language string, arr *[]vo.Router) {
+ for i := range *arr {
+ (*arr)[i].Meta.Title = i18n.TKey(language, (*arr)[i].Meta.Title)
+ if len((*arr)[i].Children) > 0 {
+ converI18n(language, &(*arr)[i].Children)
+ }
+ }
+ }
+ converI18n(language, &buildMenus)
+
+ c.JSON(200, result.OkData(buildMenus))
+}
+
+// 系统登出
+//
+// POST /logout
+func (s *AccountController) Logout(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ tokenStr := ctx.Authorization(c)
+ if tokenStr != "" {
+ // 存在token时记录退出信息
+ userName := tokenUtils.Remove(tokenStr)
+ if userName != "" {
+ // 当前请求信息
+ ipaddr, location := ctx.IPAddrLocation(c)
+ os, browser := ctx.UaOsBrowser(c)
+
+ // 创建系统访问记录 退出成功
+
+ s.sysLogLoginService.CreateSysLogLogin(
+ userName, commonConstants.STATUS_YES, "app.common.logoutSuccess",
+ ipaddr, location, os, browser,
+ )
+ }
+ }
+
+ c.JSON(200, result.OkMsg(i18n.TKey(language, "app.common.logoutSuccess")))
+}
diff --git a/src/modules/common/controller/captcha.go b/src/modules/common/controller/captcha.go
new file mode 100644
index 0000000..0fbf230
--- /dev/null
+++ b/src/modules/common/controller/captcha.go
@@ -0,0 +1,129 @@
+package controller
+
+import (
+ "time"
+
+ "nms_nbi/src/framework/config"
+ "nms_nbi/src/framework/constants/cachekey"
+ "nms_nbi/src/framework/constants/captcha"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/redis"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/vo/result"
+ systemService "nms_nbi/src/modules/system/service"
+
+ "github.com/gin-gonic/gin"
+ "github.com/mojocn/base64Captcha"
+)
+
+// 实例化控制层 CaptchaController 结构体
+var NewCaptcha = &CaptchaController{
+ sysConfigService: systemService.NewSysConfigImpl,
+}
+
+// 验证码操作处理
+//
+// PATH /
+type CaptchaController struct {
+ // 参数配置服务
+ sysConfigService systemService.ISysConfig
+}
+
+// 获取验证码
+//
+// GET /captchaImage
+func (s *CaptchaController) Image(c *gin.Context) {
+ // 从数据库配置获取验证码开关 true开启,false关闭
+ captchaEnabledStr := s.sysConfigService.SelectConfigValueByKey("sys.account.captchaEnabled")
+ captchaEnabled := parse.Boolean(captchaEnabledStr)
+ if !captchaEnabled {
+ c.JSON(200, result.Ok(map[string]any{
+ "captchaEnabled": captchaEnabled,
+ }))
+ return
+ }
+
+ // 生成唯一标识
+ verifyKey := ""
+ data := map[string]any{
+ "captchaEnabled": captchaEnabled,
+ "uuid": "",
+ "img": "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
+ }
+
+ // 从数据库配置获取验证码类型 math 数值计算 char 字符验证
+ captchaType := s.sysConfigService.SelectConfigValueByKey("sys.account.captchaType")
+ if captchaType == captcha.TYPE_MATH {
+ math := config.Get("mathCaptcha").(map[string]any)
+ driverCaptcha := &base64Captcha.DriverMath{
+ //Height png height in pixel.
+ Height: math["height"].(int),
+ // Width Captcha png width in pixel.
+ Width: math["width"].(int),
+ //NoiseCount text noise count.
+ NoiseCount: math["noise"].(int),
+ //ShowLineOptions := OptionShowHollowLine | OptionShowSlimeLine | OptionShowSineLine .
+ ShowLineOptions: base64Captcha.OptionShowHollowLine,
+ }
+ if math["color"].(bool) {
+ //BgColor captcha image background color (optional)
+ driverCaptcha.BgColor = parse.Color(math["background"].(string))
+ }
+ // 验证码生成
+ id, question, answer := driverCaptcha.GenerateIdQuestionAnswer()
+ // 验证码表达式解析输出
+ item, err := driverCaptcha.DrawCaptcha(question)
+ if err != nil {
+ logger.Infof("Generate Id Question Answer %s %s : %v", captchaType, question, err)
+ } else {
+ data["uuid"] = id
+ data["img"] = item.EncodeB64string()
+ expiration := captcha.EXPIRATION * time.Second
+ verifyKey = cachekey.CAPTCHA_CODE_KEY + id
+ redis.SetByExpire("", verifyKey, answer, expiration)
+ }
+ }
+ if captchaType == captcha.TYPE_CHAR {
+ char := config.Get("charCaptcha").(map[string]any)
+ driverCaptcha := &base64Captcha.DriverString{
+ //Height png height in pixel.
+ Height: char["height"].(int),
+ // Width Captcha png width in pixel.
+ Width: char["width"].(int),
+ //NoiseCount text noise count.
+ NoiseCount: char["noise"].(int),
+ //Length random string length.
+ Length: char["size"].(int),
+ //Source is a unicode which is the rand string from.
+ Source: char["chars"].(string),
+ //ShowLineOptions := OptionShowHollowLine | OptionShowSlimeLine | OptionShowSineLine .
+ ShowLineOptions: base64Captcha.OptionShowHollowLine,
+ }
+ if char["color"].(bool) {
+ //BgColor captcha image background color (optional)
+ driverCaptcha.BgColor = parse.Color(char["background"].(string))
+ }
+ // 验证码生成
+ id, question, answer := driverCaptcha.GenerateIdQuestionAnswer()
+ // 验证码表达式解析输出
+ item, err := driverCaptcha.DrawCaptcha(question)
+ if err != nil {
+ logger.Infof("Generate Id Question Answer %s %s : %v", captchaType, question, err)
+ } else {
+ data["uuid"] = id
+ data["img"] = item.EncodeB64string()
+ expiration := captcha.EXPIRATION * time.Second
+ verifyKey = cachekey.CAPTCHA_CODE_KEY + id
+ redis.SetByExpire("", verifyKey, answer, expiration)
+ }
+ }
+
+ // 本地开发下返回验证码结果,方便接口调试
+ if config.Env() == "local" {
+ text, _ := redis.Get("", verifyKey)
+ data["text"] = text
+ c.JSON(200, result.Ok(data))
+ return
+ }
+ c.JSON(200, result.Ok(data))
+}
diff --git a/src/modules/common/controller/common.go b/src/modules/common/controller/common.go
new file mode 100644
index 0000000..8a904b4
--- /dev/null
+++ b/src/modules/common/controller/common.go
@@ -0,0 +1,72 @@
+package controller
+
+import (
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/vo/result"
+ commonService "nms_nbi/src/modules/common/service"
+
+ "github.com/gin-gonic/gin"
+)
+
+// 实例化控制层 CommontController 结构体
+var NewCommont = &CommontController{
+ commontService: commonService.NewCommontImpl,
+}
+
+// 通用请求
+//
+// PATH /
+type CommontController struct {
+ // 通用请求服务
+ commontService commonService.ICommont
+}
+
+// 哈希加密
+//
+// POST /hash
+func (s *CommontController) Hash(c *gin.Context) {
+ c.String(200, "commont Hash")
+}
+
+// 多语言处理
+//
+// GET /i18n
+func (s *CommontController) I18n(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+
+ i18nLang := i18n.TKey(language, "i18n")
+ hello := i18n.TKey(language, "hello")
+ ivs := i18n.TTemplate(language, "errorFields", nil)
+ errorFields := i18n.TTemplate(language, "errorFields", map[string]any{
+ "num": 1000,
+ "hello": "你好",
+ "h2o2": false,
+ "fileName": " ====",
+ })
+ c.JSON(200, map[string]any{
+ "lang": language,
+ "i18nLang": i18nLang,
+ "ivs": ivs,
+ "hello": hello,
+ "errorFields": errorFields,
+ })
+}
+
+// 系统可暴露的配置信息
+//
+// GET /sys-conf
+func (s *CommontController) SysConfig(c *gin.Context) {
+ data := s.commontService.SystemConfigInfo()
+
+ // 闭包函数处理多语言
+ language := ctx.AcceptLanguage(c)
+ converI18n := func(language string, arr *map[string]string) {
+ for k, v := range *arr {
+ (*arr)[k] = i18n.TKey(language, v)
+ }
+ }
+ converI18n(language, &data)
+
+ c.JSON(200, result.OkData(data))
+}
diff --git a/src/modules/common/controller/file.go b/src/modules/common/controller/file.go
new file mode 100644
index 0000000..543b483
--- /dev/null
+++ b/src/modules/common/controller/file.go
@@ -0,0 +1,235 @@
+package controller
+
+import (
+ "encoding/base64"
+ "fmt"
+ "net/url"
+ "path/filepath"
+ "strings"
+
+ "nms_nbi/src/framework/config"
+ "nms_nbi/src/framework/constants/uploadsubpath"
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/file"
+ "nms_nbi/src/framework/vo/result"
+
+ "github.com/gin-gonic/gin"
+ "github.com/gin-gonic/gin/binding"
+)
+
+// 实例化控制层 FileController 结构体
+var NewFile = &FileController{}
+
+// 文件操作处理
+//
+// PATH /
+type FileController struct{}
+
+// 下载文件
+//
+// GET /download/:filePath
+func (s *FileController) Download(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ filePath := c.Param("filePath")
+ if len(filePath) < 8 {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ // base64解析出地址
+ decodedBytes, err := base64.StdEncoding.DecodeString(filePath)
+ if err != nil {
+ c.JSON(400, result.CodeMsg(400, err.Error()))
+ return
+ }
+ routerPath := string(decodedBytes)
+ // 地址文件名截取
+ fileName := routerPath[strings.LastIndex(routerPath, "/")+1:]
+
+ // 响应头
+ c.Writer.Header().Set("Content-Disposition", `attachment; filename="`+url.QueryEscape(fileName)+`"`)
+ c.Writer.Header().Set("Accept-Ranges", "bytes")
+ c.Writer.Header().Set("Content-Type", "application/octet-stream")
+
+ // 断点续传
+ headerRange := c.GetHeader("Range")
+ resultMap, err := file.ReadUploadFileStream(routerPath, headerRange)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+ if headerRange != "" {
+ c.Writer.Header().Set("Content-Range", fmt.Sprint(resultMap["range"]))
+ c.Writer.Header().Set("Content-Length", fmt.Sprint(resultMap["chunkSize"]))
+ c.Status(206)
+ } else {
+ c.Writer.Header().Set("Content-Length", fmt.Sprint(resultMap["fileSize"]))
+ c.Status(200)
+
+ }
+ c.Writer.Write(resultMap["data"].([]byte))
+}
+
+// 上传文件
+//
+// POST /upload
+func (s *FileController) Upload(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ // 上传的文件
+ formFile, err := c.FormFile("file")
+ if err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ // 子路径
+ subPath := c.PostForm("subPath")
+ if _, ok := uploadsubpath.UploadSubpath[subPath]; !ok {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 上传文件转存
+ upFilePath, err := file.TransferUploadFile(formFile, subPath, nil)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ newFileName := upFilePath[strings.LastIndex(upFilePath, "/")+1:]
+ c.JSON(200, result.OkData(map[string]string{
+ "url": "http://" + c.Request.Host + upFilePath,
+ "fileName": upFilePath,
+ "newFileName": newFileName,
+ "originalFileName": formFile.Filename,
+ }))
+}
+
+// 切片文件检查
+//
+// POST /chunkCheck
+func (s *FileController) ChunkCheck(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body struct {
+ // 唯一标识
+ Identifier string `json:"identifier" binding:"required"`
+ // 文件名
+ FileName string `json:"fileName" binding:"required"`
+ }
+ err := c.ShouldBindJSON(&body)
+ if err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 读取标识目录
+ chunks, err := file.ChunkCheckFile(body.Identifier, body.FileName)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+ c.JSON(200, result.OkData(chunks))
+}
+
+// 切片文件合并
+//
+// POST /chunkMerge
+func (s *FileController) ChunkMerge(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body struct {
+ // 唯一标识
+ Identifier string `json:"identifier" binding:"required"`
+ // 文件名
+ FileName string `json:"fileName" binding:"required"`
+ // 子路径类型
+ SubPath string `json:"subPath" binding:"required"`
+ }
+ err := c.ShouldBindJSON(&body)
+ if err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ if _, ok := uploadsubpath.UploadSubpath[body.SubPath]; !ok {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 切片文件合并
+ mergeFilePath, err := file.ChunkMergeFile(body.Identifier, body.FileName, body.SubPath)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ newFileName := mergeFilePath[strings.LastIndex(mergeFilePath, "/")+1:]
+ c.JSON(200, result.OkData(map[string]string{
+ "url": "http://" + c.Request.Host + mergeFilePath,
+ "fileName": mergeFilePath,
+ "newFileName": newFileName,
+ "originalFileName": body.FileName,
+ }))
+}
+
+// 切片文件上传
+//
+// POST /chunkUpload
+func (s *FileController) ChunkUpload(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ // 切片编号
+ index := c.PostForm("index")
+ // 切片唯一标识
+ identifier := c.PostForm("identifier")
+ // 上传的文件
+ formFile, err := c.FormFile("file")
+ if index == "" || identifier == "" || err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 上传文件转存
+ chunkFilePath, err := file.TransferChunkUploadFile(formFile, index, identifier)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+ c.JSON(206, result.OkData(chunkFilePath))
+}
+
+// 转存指定对应文件到静态目录
+//
+// POST /transferStaticFile
+func (s *CommontController) TransferStaticFile(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body struct {
+ UploadPath string `json:"uploadPath" binding:"required"`
+ StaticPath string `json:"staticPath" binding:"required"`
+ Language string `json:"language" binding:"required"`
+ }
+ if err := c.ShouldBindBodyWith(&body, binding.JSON); err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 取语言前缀
+ lang := strings.SplitN(body.Language, "_", 2)[0]
+
+ // 默认静态资源
+ static := config.Get("staticFile.default").(map[string]any)
+ dir, err := filepath.Abs(static["dir"].(string))
+ if err != nil {
+ c.JSON(400, result.CodeMsg(400, err.Error()))
+ return
+ }
+
+ delPrefix := strings.Replace(body.StaticPath, static["prefix"].(string), "", 1)
+ staticPath := strings.Replace(delPrefix, "{language}", lang, 1)
+ newFile := fmt.Sprintf("%s%s", dir, staticPath)
+
+ err = file.CopyUploadFile(body.UploadPath, newFile)
+ if err != nil {
+ c.JSON(400, result.CodeMsg(400, err.Error()))
+ return
+ }
+
+ urlPath := strings.Replace(newFile, dir, static["prefix"].(string), 1)
+ c.JSON(200, result.OkData(filepath.ToSlash(urlPath)))
+}
diff --git a/src/modules/common/controller/index.go b/src/modules/common/controller/index.go
new file mode 100644
index 0000000..2d45b01
--- /dev/null
+++ b/src/modules/common/controller/index.go
@@ -0,0 +1,29 @@
+package controller
+
+import (
+ "fmt"
+
+ "nms_nbi/src/framework/config"
+ "nms_nbi/src/framework/vo/result"
+
+ "github.com/gin-gonic/gin"
+)
+
+// 实例化控制层 IndexController 结构体
+var NewIndex = &IndexController{}
+
+// 路由主页
+//
+// PATH /
+type IndexController struct{}
+
+// 根路由
+//
+// GET /
+func (s *IndexController) Handler(c *gin.Context) {
+ name := config.Get("framework.name").(string)
+ version := config.Get("framework.version").(string)
+ // 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/controller/register.go b/src/modules/common/controller/register.go
new file mode 100644
index 0000000..966032f
--- /dev/null
+++ b/src/modules/common/controller/register.go
@@ -0,0 +1,91 @@
+package controller
+
+import (
+ commonConstants "nms_nbi/src/framework/constants/common"
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/regular"
+ "nms_nbi/src/framework/vo/result"
+ commonModel "nms_nbi/src/modules/common/model"
+ commonService "nms_nbi/src/modules/common/service"
+ systemService "nms_nbi/src/modules/system/service"
+
+ "github.com/gin-gonic/gin"
+)
+
+// 实例化控制层 RegisterController 结构体
+var NewRegister = &RegisterController{
+ registerService: commonService.NewRegisterImpl,
+ sysLogLoginService: systemService.NewSysLogLoginImpl,
+}
+
+// 账号注册操作处理
+//
+// PATH /
+type RegisterController struct {
+ // 账号注册操作服务
+ registerService commonService.IRegister
+ // 系统登录访问
+ sysLogLoginService systemService.ISysLogLogin
+}
+
+// 账号注册
+//
+// GET /register
+func (s *RegisterController) Register(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var registerBody commonModel.RegisterBody
+ if err := c.ShouldBindJSON(®isterBody); err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 判断必传参数
+ if !regular.ValidUsername(registerBody.Username) {
+ // 账号不能以数字开头,可包含大写小写字母,数字,且不少于5位
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "register.errUsername")))
+ return
+ }
+ if !regular.ValidPassword(registerBody.Password) {
+ // 登录密码至少包含大小写字母、数字、特殊符号,且不少于6位
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "register.errPasswd")))
+ return
+ }
+ if registerBody.Password != registerBody.ConfirmPassword {
+ // 用户确认输入密码不一致
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "register.errPasswdNotEq")))
+ return
+ }
+
+ // 当前请求信息
+ ipaddr, location := ctx.IPAddrLocation(c)
+ os, browser := ctx.UaOsBrowser(c)
+
+ // 校验验证码
+ err := s.registerService.ValidateCaptcha(
+ registerBody.Code,
+ registerBody.UUID,
+ )
+ // 根据错误信息,创建系统访问记录
+ if err != nil {
+ s.sysLogLoginService.CreateSysLogLogin(
+ registerBody.Username, commonConstants.STATUS_NO, err.Error(),
+ ipaddr, location, os, browser,
+ )
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ userID, err := s.registerService.ByUserName(registerBody.Username, registerBody.Password, registerBody.UserType)
+ if err == nil {
+ msg := i18n.TTemplate(language, "register.successMsg", map[string]any{"name": registerBody.Username, "id": userID})
+ s.sysLogLoginService.CreateSysLogLogin(
+ registerBody.Username, commonConstants.STATUS_YES, msg,
+ ipaddr, location, os, browser,
+ )
+ // 注册成功
+ c.JSON(200, result.OkMsg(i18n.TKey(language, "register.success")))
+ return
+ }
+ c.JSON(200, result.ErrMsg(err.Error()))
+}
diff --git a/src/modules/common/model/login_body.go b/src/modules/common/model/login_body.go
new file mode 100644
index 0000000..ec37763
--- /dev/null
+++ b/src/modules/common/model/login_body.go
@@ -0,0 +1,16 @@
+package model
+
+// LoginBody 用户登录对象
+type LoginBody struct {
+ // Username 用户名
+ Username string `json:"username" binding:"required"`
+
+ // Password 用户密码
+ Password string `json:"password" binding:"required"`
+
+ // Code 验证码
+ Code string `json:"code"`
+
+ // UUID 验证码唯一标识
+ UUID string `json:"uuid"`
+}
diff --git a/src/modules/common/model/register_body.go b/src/modules/common/model/register_body.go
new file mode 100644
index 0000000..4bf1114
--- /dev/null
+++ b/src/modules/common/model/register_body.go
@@ -0,0 +1,22 @@
+package model
+
+// RegisterBody 用户注册对象
+type RegisterBody struct {
+ // Username 用户名
+ Username string `json:"username" binding:"required"`
+
+ // Password 用户密码
+ Password string `json:"password" binding:"required"`
+
+ // ConfirmPassword 用户确认密码
+ ConfirmPassword string `json:"confirmPassword" binding:"required"`
+
+ // Code 验证码
+ Code string `json:"code"`
+
+ // UUID 验证码唯一标识
+ UUID string `json:"uuid"`
+
+ // UserType 标记用户类型
+ UserType string `json:"userType"`
+}
diff --git a/src/modules/common/service/account.go b/src/modules/common/service/account.go
new file mode 100644
index 0000000..4523177
--- /dev/null
+++ b/src/modules/common/service/account.go
@@ -0,0 +1,24 @@
+package service
+
+import "nms_nbi/src/framework/vo"
+
+// 账号身份操作服务 服务层接口
+type IAccount interface {
+ // ValidateCaptcha 校验验证码
+ ValidateCaptcha(code, uuid string) error
+
+ // LoginByUsername 登录生成token
+ LoginByUsername(username, password string) (vo.LoginUser, error)
+
+ // UpdateLoginDateAndIP 更新登录时间和IP
+ UpdateLoginDateAndIP(loginUser *vo.LoginUser) bool
+
+ // ClearLoginRecordCache 清除错误记录次数
+ ClearLoginRecordCache(username string) bool
+
+ // RoleAndMenuPerms 角色和菜单数据权限
+ RoleAndMenuPerms(userId string, isAdmin bool) ([]string, []string)
+
+ // RouteMenus 前端路由所需要的菜单
+ RouteMenus(userId string, isAdmin bool) []vo.Router
+}
diff --git a/src/modules/common/service/account.impl.go b/src/modules/common/service/account.impl.go
new file mode 100644
index 0000000..3614173
--- /dev/null
+++ b/src/modules/common/service/account.impl.go
@@ -0,0 +1,190 @@
+package service
+
+import (
+ "fmt"
+ "time"
+
+ "nms_nbi/src/framework/config"
+ adminConstants "nms_nbi/src/framework/constants/admin"
+ "nms_nbi/src/framework/constants/cachekey"
+ "nms_nbi/src/framework/constants/common"
+ "nms_nbi/src/framework/redis"
+ "nms_nbi/src/framework/utils/crypto"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/vo"
+ "nms_nbi/src/modules/system/model"
+ systemService "nms_nbi/src/modules/system/service"
+)
+
+// 实例化服务层 AccountImpl 结构体
+var NewAccountImpl = &AccountImpl{
+ sysUserService: systemService.NewSysUserImpl,
+ sysConfigService: systemService.NewSysConfigImpl,
+ sysRoleService: systemService.NewSysRoleImpl,
+ sysMenuService: systemService.NewSysMenuImpl,
+}
+
+// 账号身份操作服务 服务层处理
+type AccountImpl struct {
+ // 用户信息服务
+ sysUserService systemService.ISysUser
+ // 参数配置服务
+ sysConfigService systemService.ISysConfig
+ // 角色服务
+ sysRoleService systemService.ISysRole
+ // 菜单服务
+ sysMenuService systemService.ISysMenu
+}
+
+// ValidateCaptcha 校验验证码
+func (s *AccountImpl) ValidateCaptcha(code, uuid string) error {
+ // 验证码检查,从数据库配置获取验证码开关 true开启,false关闭
+ captchaEnabledStr := s.sysConfigService.SelectConfigValueByKey("sys.account.captchaEnabled")
+ if !parse.Boolean(captchaEnabledStr) {
+ return nil
+ }
+ if code == "" || uuid == "" {
+ // 验证码信息错误
+ return fmt.Errorf("captcha.err")
+ }
+ verifyKey := cachekey.CAPTCHA_CODE_KEY + uuid
+ captcha, _ := redis.Get("", verifyKey)
+ if captcha == "" {
+ // 验证码已失效
+ return fmt.Errorf("captcha.errValid")
+ }
+ redis.Del("", verifyKey)
+ if captcha != code {
+ // 验证码错误
+ return fmt.Errorf("captcha.err")
+ }
+ return nil
+}
+
+// LoginByUsername 登录创建用户信息
+func (s *AccountImpl) LoginByUsername(username, password string) (vo.LoginUser, error) {
+ loginUser := vo.LoginUser{}
+
+ // 检查密码重试次数
+ retrykey, retryCount, lockTime, err := s.passwordRetryCount(username)
+ if err != nil {
+ return loginUser, err
+ }
+
+ // 查询用户登录账号
+ sysUser := s.sysUserService.SelectUserByUserName(username)
+ if sysUser.UserName != username {
+ return loginUser, fmt.Errorf("login.errNameOrPasswd")
+ }
+ if sysUser.DelFlag == common.STATUS_YES {
+ // 对不起,您的账号已被删除
+ return loginUser, fmt.Errorf("login.errDelFlag")
+ }
+ if sysUser.Status == common.STATUS_NO {
+ return loginUser, fmt.Errorf("login.errStatus")
+ }
+
+ // 检验用户密码
+ compareBool := crypto.BcryptCompare(password, sysUser.Password)
+ if !compareBool {
+ redis.SetByExpire("", retrykey, retryCount+1, lockTime)
+ // 用户不存在或密码错误
+ return loginUser, fmt.Errorf("login.errNameOrPasswd")
+ } else {
+ // 清除错误记录次数
+ s.ClearLoginRecordCache(username)
+ }
+
+ // 登录用户信息
+ loginUser.UserID = sysUser.UserID
+ loginUser.DeptID = sysUser.DeptID
+ loginUser.User = sysUser
+ // 用户权限组标识
+ isAdmin := config.IsAdmin(sysUser.UserID)
+ if isAdmin {
+ loginUser.Permissions = []string{adminConstants.PERMISSION}
+ } else {
+ perms := s.sysMenuService.SelectMenuPermsByUserId(sysUser.UserID)
+ loginUser.Permissions = parse.RemoveDuplicates(perms)
+ }
+ return loginUser, nil
+}
+
+// UpdateLoginDateAndIP 更新登录时间和IP
+func (s *AccountImpl) UpdateLoginDateAndIP(loginUser *vo.LoginUser) bool {
+ sysUser := loginUser.User
+ userInfo := model.SysUser{
+ UserID: sysUser.UserID,
+ LoginIP: sysUser.LoginIP,
+ LoginDate: sysUser.LoginDate,
+ UpdateBy: sysUser.UserName,
+ }
+ rows := s.sysUserService.UpdateUser(userInfo)
+ return rows > 0
+}
+
+// ClearLoginRecordCache 清除错误记录次数
+func (s *AccountImpl) ClearLoginRecordCache(username string) bool {
+ cacheKey := cachekey.PWD_ERR_CNT_KEY + username
+ hasKey, _ := redis.Has("", cacheKey)
+ if hasKey {
+ delOk, _ := redis.Del("", cacheKey)
+ return delOk
+ }
+ return false
+}
+
+// passwordRetryCount 密码重试次数
+func (s *AccountImpl) passwordRetryCount(username string) (string, int64, time.Duration, error) {
+ // 从数据库配置获取登录次数和错误锁定时间
+ maxRetryCountStr := s.sysConfigService.SelectConfigValueByKey("sys.user.maxRetryCount")
+ lockTimeStr := s.sysConfigService.SelectConfigValueByKey("sys.user.lockTime")
+
+ // 验证登录次数和错误锁定时间
+ maxRetryCount := parse.Number(maxRetryCountStr)
+ lockTime := parse.Number(lockTimeStr)
+ // 验证缓存记录次数
+ retrykey := cachekey.PWD_ERR_CNT_KEY + username
+ retryCount, err := redis.Get("", retrykey)
+ if retryCount == "" || err != nil {
+ retryCount = "0"
+ }
+ // 是否超过错误值
+ retryCountInt64 := parse.Number(retryCount)
+ if retryCountInt64 >= maxRetryCount {
+ // 密码输入错误多次,帐户已被锁定
+ errorMsg := fmt.Errorf("login.errRetryPasswd")
+ return retrykey, retryCountInt64, time.Duration(lockTime) * time.Minute, errorMsg
+ }
+ return retrykey, retryCountInt64, time.Duration(lockTime) * time.Minute, nil
+}
+
+// RoleAndMenuPerms 角色和菜单数据权限
+func (s *AccountImpl) RoleAndMenuPerms(userId string, isAdmin bool) ([]string, []string) {
+ if isAdmin {
+ return []string{adminConstants.ROLE_KEY}, []string{adminConstants.PERMISSION}
+ } else {
+ // 角色key
+ roleGroup := []string{}
+ roles := s.sysRoleService.SelectRoleListByUserId(userId)
+ for _, role := range roles {
+ roleGroup = append(roleGroup, role.RoleKey)
+ }
+ // 菜单权限key
+ perms := s.sysMenuService.SelectMenuPermsByUserId(userId)
+ return parse.RemoveDuplicates(roleGroup), parse.RemoveDuplicates(perms)
+ }
+}
+
+// RouteMenus 前端路由所需要的菜单
+func (s *AccountImpl) RouteMenus(userId string, isAdmin bool) []vo.Router {
+ var buildMenus []vo.Router
+ if isAdmin {
+ menus := s.sysMenuService.SelectMenuTreeByUserId("*")
+ buildMenus = s.sysMenuService.BuildRouteMenus(menus, "")
+ } else {
+ menus := s.sysMenuService.SelectMenuTreeByUserId(userId)
+ buildMenus = s.sysMenuService.BuildRouteMenus(menus, "")
+ }
+ return buildMenus
+}
diff --git a/src/modules/common/service/commont.go b/src/modules/common/service/commont.go
new file mode 100644
index 0000000..e8be8db
--- /dev/null
+++ b/src/modules/common/service/commont.go
@@ -0,0 +1,7 @@
+package service
+
+// 通用请求 服务层接口
+type ICommont interface {
+ // SystemConfigInfo 系统配置信息
+ SystemConfigInfo() map[string]string
+}
diff --git a/src/modules/common/service/commont.impl.go b/src/modules/common/service/commont.impl.go
new file mode 100644
index 0000000..c677ed7
--- /dev/null
+++ b/src/modules/common/service/commont.impl.go
@@ -0,0 +1,67 @@
+package service
+
+import (
+ "fmt"
+
+ "nms_nbi/lib/global"
+ "nms_nbi/src/framework/config"
+ systemService "nms_nbi/src/modules/system/service"
+)
+
+// 实例化服务层 CommontImpl 结构体
+var NewCommontImpl = &CommontImpl{
+ sysUserService: systemService.NewSysUserImpl,
+ sysConfigService: systemService.NewSysConfigImpl,
+}
+
+// 通用请求 服务层处理
+type CommontImpl struct {
+ // 用户信息服务
+ sysUserService systemService.ISysUser
+ // 参数配置服务
+ sysConfigService systemService.ISysConfig
+}
+
+// SystemConfigInfo 系统配置信息
+func (s *CommontImpl) SystemConfigInfo() map[string]string {
+ infoMap := map[string]string{}
+ // 获取打包注入的全局变量信息
+ infoMap["version"] = global.Version
+ infoMap["buildTime"] = global.BuildTime
+ infoMap["goVer"] = global.GoVer
+ // 序列号
+ infoMap["serialNum"] = fmt.Sprint(config.Get("omc.sn"))
+ // 获取LOGO类型
+ logoType := s.sysConfigService.SelectConfigValueByKey("sys.logo.type")
+ infoMap["logoType"] = logoType
+ // 获取LOGO文件
+ filePathIcon := s.sysConfigService.SelectConfigValueByKey("sys.logo.filePathIcon")
+ infoMap["filePathIcon"] = filePathIcon
+ filePathBrand := s.sysConfigService.SelectConfigValueByKey("sys.logo.filePathBrand")
+ infoMap["filePathBrand"] = filePathBrand
+ // 获取系统名称
+ title := s.sysConfigService.SelectConfigValueByKey("sys.title")
+ infoMap["title"] = title
+ // 获取版权声明
+ copyright := s.sysConfigService.SelectConfigValueByKey("sys.copyright")
+ infoMap["copyright"] = copyright
+ // 获取是否开启用户注册功能
+ registerUser := s.sysConfigService.SelectConfigValueByKey("sys.account.registerUser")
+ infoMap["registerUser"] = registerUser
+ // 获取登录界面背景
+ loginBackground := s.sysConfigService.SelectConfigValueByKey("sys.loginBackground")
+ infoMap["loginBackground"] = loginBackground
+ // 系统设置-官网网址
+ officialUrl := s.sysConfigService.SelectConfigValueByKey("sys.officialUrl")
+ infoMap["officialUrl"] = officialUrl
+ // 系统设置-系统使用文档
+ helpDoc := s.sysConfigService.SelectConfigValueByKey("sys.helpDoc")
+ infoMap["helpDoc"] = helpDoc
+ // 国际化切换
+ i18nOpen := s.sysConfigService.SelectConfigValueByKey("sys.i18n.open")
+ infoMap["i18nOpen"] = i18nOpen
+ // 国际化默认语言
+ i18nDefault := s.sysConfigService.SelectConfigValueByKey("sys.i18n.default")
+ infoMap["i18nDefault"] = i18nDefault
+ return infoMap
+}
diff --git a/src/modules/common/service/register.go b/src/modules/common/service/register.go
new file mode 100644
index 0000000..dc98683
--- /dev/null
+++ b/src/modules/common/service/register.go
@@ -0,0 +1,10 @@
+package service
+
+// 账号注册操作处理 服务层接口
+type IRegister interface {
+ // ValidateCaptcha 校验验证码
+ ValidateCaptcha(code, uuid string) error
+
+ // ByUserName 账号注册
+ ByUserName(username, password, userType string) (string, error)
+}
diff --git a/src/modules/common/service/register.impl.go b/src/modules/common/service/register.impl.go
new file mode 100644
index 0000000..a564a16
--- /dev/null
+++ b/src/modules/common/service/register.impl.go
@@ -0,0 +1,106 @@
+package service
+
+import (
+ "fmt"
+
+ "nms_nbi/src/framework/constants/cachekey"
+ "nms_nbi/src/framework/constants/common"
+ "nms_nbi/src/framework/redis"
+ "nms_nbi/src/framework/utils/parse"
+ systemModel "nms_nbi/src/modules/system/model"
+ systemService "nms_nbi/src/modules/system/service"
+)
+
+// 实例化服务层 RegisterImpl 结构体
+var NewRegisterImpl = &RegisterImpl{
+ sysUserService: systemService.NewSysUserImpl,
+ sysConfigService: systemService.NewSysConfigImpl,
+ sysRoleService: systemService.NewSysRoleImpl,
+}
+
+// 账号注册操作处理 服务层处理
+type RegisterImpl struct {
+ // 用户信息服务
+ sysUserService systemService.ISysUser
+ // 参数配置服务
+ sysConfigService systemService.ISysConfig
+ // 角色服务
+ sysRoleService systemService.ISysRole
+}
+
+// ValidateCaptcha 校验验证码
+func (s *RegisterImpl) ValidateCaptcha(code, uuid string) error {
+ // 验证码检查,从数据库配置获取验证码开关 true开启,false关闭
+ captchaEnabledStr := s.sysConfigService.SelectConfigValueByKey("sys.account.captchaEnabled")
+ if !parse.Boolean(captchaEnabledStr) {
+ return nil
+ }
+ if code == "" || uuid == "" {
+ return fmt.Errorf("verification code information error")
+ }
+ verifyKey := cachekey.CAPTCHA_CODE_KEY + uuid
+ captcha, err := redis.Get("", verifyKey)
+ if err != nil {
+ return fmt.Errorf("the verification code has expired")
+ }
+ redis.Del("", verifyKey)
+ if captcha != code {
+ return fmt.Errorf("verification code error")
+ }
+ return nil
+}
+
+// ByUserName 账号注册
+func (s *RegisterImpl) ByUserName(username, password, userType string) (string, error) {
+ // 是否开启用户注册功能 true开启,false关闭
+ registerUserStr := s.sysConfigService.SelectConfigValueByKey("sys.account.registerUser")
+ captchaEnabled := parse.Boolean(registerUserStr)
+ if !captchaEnabled {
+ return "", fmt.Errorf("failed to register user [%s]. Sorry, the system has closed the external user registration channel", username)
+ }
+
+ // 检查用户登录账号是否唯一
+ uniqueUserName := s.sysUserService.CheckUniqueUserName(username, "")
+ if !uniqueUserName {
+ return "", fmt.Errorf("failed to register user [%s], registered account already exists", username)
+ }
+
+ sysUser := systemModel.SysUser{
+ UserName: username,
+ NickName: username, // 昵称使用名称账号
+ Password: password, // 原始密码
+ Status: common.STATUS_YES, // 账号状态激活
+ DeptID: "100", // 归属部门为根节点
+ CreateBy: "register", // 创建来源
+ }
+ // 标记用户类型
+ if userType == "" {
+ sysUser.UserType = "sys"
+ }
+ // 新增用户的角色管理
+ sysUser.RoleIDs = s.registerRoleInit(userType)
+ // 新增用户的岗位管理
+ sysUser.PostIDs = s.registerPostInit(userType)
+
+ insertId := s.sysUserService.InsertUser(sysUser)
+ if insertId != "" {
+ return insertId, nil
+ }
+ return "", fmt.Errorf("failed to register user [%s]. Please contact the system administrator", username)
+}
+
+// registerRoleInit 注册初始角色
+func (s *RegisterImpl) registerRoleInit(userType string) []string {
+ if userType == "sys" {
+ return []string{}
+ }
+ return []string{}
+}
+
+// registerPostInit 注册初始岗位
+func (s *RegisterImpl) registerPostInit(userType string) []string {
+ if userType == "sys" {
+ return []string{}
+ }
+ return []string{}
+}
diff --git a/src/modules/crontask/crontask.go b/src/modules/crontask/crontask.go
new file mode 100644
index 0000000..7e8211c
--- /dev/null
+++ b/src/modules/crontask/crontask.go
@@ -0,0 +1,23 @@
+package crontask
+
+import (
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/modules/crontask/processor"
+
+ "github.com/gin-gonic/gin"
+)
+
+// Setup 模块路由注册
+func Setup(router *gin.Engine) {
+ logger.Infof("开始加载 ====> crontask 模块路由")
+
+ // 启动时需要的初始参数
+ InitLoad()
+
+}
+
+// InitLoad 初始参数
+func InitLoad() {
+ // 初始化定时任务处理
+ processor.InitCronQueue()
+}
diff --git a/src/modules/crontask/processor/backupEtcFromNE/backupEtcFromNE.go b/src/modules/crontask/processor/backupEtcFromNE/backupEtcFromNE.go
new file mode 100644
index 0000000..bdcc32d
--- /dev/null
+++ b/src/modules/crontask/processor/backupEtcFromNE/backupEtcFromNE.go
@@ -0,0 +1,174 @@
+package backupEtcFromNE
+
+import (
+ "fmt"
+ "os"
+ "strings"
+ "time"
+
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/global"
+ "nms_nbi/lib/log"
+ "nms_nbi/restagent/config"
+ "nms_nbi/src/framework/cron"
+)
+
+var NewProcessor = &BarProcessor{
+ progress: 0,
+ count: 0,
+}
+
+// bar 队列任务处理
+type BarProcessor struct {
+ // 任务进度
+ progress int
+ // 执行次数
+ count int
+}
+
+type BarParams struct {
+ Duration int `json:"duration"`
+ TableName string `json:"tableName"`
+ ColName string `json:"colName"` // column name of time string
+ Extras string `json:"extras"` // extras condition for where
+}
+
+func (s *BarProcessor) Execute(data any) (any, error) {
+ log.Infof("execute %d,last progress: %d ", s.count, s.progress)
+ s.count++
+
+ options := data.(cron.JobData)
+ sysJob := options.SysJob
+ // var params BarParams
+
+ // err := json.Unmarshal([]byte(sysJob.TargetParams), ¶ms)
+ // if err != nil {
+ // return nil, err
+ // }
+
+ log.Infof("Repeat %v Job ID %s", options.Repeat, sysJob.JobID)
+
+ var nes []dborm.NeInfo
+ _, err := dborm.XormGetAllNeInfo(&nes)
+ if err != nil {
+ return nil, err
+ }
+
+ var successfulNEs, failureNEs []string
+ for _, neInfo := range nes {
+ neTypeUpper := strings.ToUpper(neInfo.NeType)
+ neTypeLower := strings.ToLower(neInfo.NeType)
+ nePath := fmt.Sprintf("%s/etc/%s", config.GetYamlConfig().OMC.Backup, neTypeLower)
+ isExist, err := global.PathExists(nePath)
+ if err != nil {
+ log.Errorf("Failed to PathExists:", err)
+ failureNEs = append(failureNEs, neInfo.NeType+"/"+neInfo.NeId)
+ continue
+ }
+ if isExist {
+ err = os.RemoveAll(nePath)
+ if err != nil {
+ log.Errorf("Failed to RemoveAll:", err)
+ failureNEs = append(failureNEs, neInfo.NeType+"/"+neInfo.NeId)
+ continue
+ }
+ }
+ err = os.MkdirAll(nePath, os.ModePerm)
+ if err != nil {
+ log.Errorf("Failed to MkdirAll:", err)
+ failureNEs = append(failureNEs, neInfo.NeType+"/"+neInfo.NeId)
+ continue
+ }
+
+ var scpCmd string
+ ipType := global.ParseIPAddr(neInfo.Ip)
+ 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/etc/*.yaml %s/etc/%s", config.GetYamlConfig().NE.User,
+ neInfo.Ip, config.GetYamlConfig().NE.OmcDir, config.GetYamlConfig().OMC.Backup, neTypeLower)
+ } else {
+ 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)
+ }
+
+ 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)
+ }
+
+ case "mme":
+ if ipType == global.IsIPv4 {
+ scpCmd = fmt.Sprintf("scp -r %s@%s:%s/%s/*.conf %s/etc/%s", config.GetYamlConfig().NE.User,
+ neInfo.Ip, config.GetYamlConfig().NE.EtcDir,
+ neTypeLower, config.GetYamlConfig().OMC.Backup, neTypeLower)
+ } else {
+ scpCmd = fmt.Sprintf("scp -r %s@[%s]:%s/%s/*.conf %s/etc/%s", config.GetYamlConfig().NE.User,
+ neInfo.Ip, config.GetYamlConfig().NE.EtcDir,
+ neTypeLower, config.GetYamlConfig().OMC.Backup, neTypeLower)
+ }
+
+ default:
+ 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,
+ neTypeLower, config.GetYamlConfig().OMC.Backup, neTypeLower)
+ } else {
+ scpCmd = fmt.Sprintf("scp -r %s@[%s]:%s/%s/*.yaml %s/etc/%s", config.GetYamlConfig().NE.User,
+ neInfo.Ip, config.GetYamlConfig().NE.EtcDir,
+ neTypeLower, config.GetYamlConfig().OMC.Backup, neTypeLower)
+ }
+
+ }
+
+ zipFile := fmt.Sprintf("%s-%s-etc-%s.zip", neTypeLower, strings.ToLower(neInfo.NeId), time.Now().Format(global.DateData))
+ zipFilePath := config.GetYamlConfig().OMC.Backup + "/" + zipFile
+ zipCmd := fmt.Sprintf("cd %s/etc && zip -r %s %s/*", config.GetYamlConfig().OMC.Backup, zipFilePath, neTypeLower)
+
+ command := fmt.Sprintf("%s&&%s", scpCmd, zipCmd)
+
+ log.Trace("command:", command)
+ out, err := global.ExecCmd(command)
+ if err != nil {
+ log.Error("Faile to exec command:", err)
+ failureNEs = append(failureNEs, neInfo.NeType+"/"+neInfo.NeId)
+ continue
+ }
+ log.Trace("command output:", out)
+
+ md5Sum, err := global.GetFileMD5Sum(zipFilePath)
+ if err != nil {
+ log.Error("Faile to md5sum:", err)
+ failureNEs = append(failureNEs, neInfo.NeType+"/"+neInfo.NeId)
+ continue
+ }
+ //log.Debug("md5Str:", md5Sum)
+ path := config.GetYamlConfig().OMC.Backup
+ neBackup := dborm.NeBackup{NeType: neTypeUpper, NeId: neInfo.NeId, FileName: zipFile, Path: path, Md5Sum: md5Sum}
+ _, err = dborm.XormInsertTableOne("ne_backup", neBackup)
+ if err != nil {
+ log.Error("Faile to XormInsertTableOne:", err)
+ failureNEs = append(failureNEs, neInfo.NeType+"/"+neInfo.NeId)
+ continue
+ }
+ successfulNEs = append(successfulNEs, neInfo.NeType+"/"+neInfo.NeId)
+ }
+
+ log.Infof("successfulNEs: %s failureNEs: %s", successfulNEs, failureNEs)
+ // result
+ return map[string]any{
+ "successfulNEs": successfulNEs,
+ "failureNEs": failureNEs,
+ }, nil
+}
diff --git a/src/modules/crontask/processor/delExpiredNeBackup/delExpiredNeBackup.go b/src/modules/crontask/processor/delExpiredNeBackup/delExpiredNeBackup.go
new file mode 100644
index 0000000..f389d1e
--- /dev/null
+++ b/src/modules/crontask/processor/delExpiredNeBackup/delExpiredNeBackup.go
@@ -0,0 +1,80 @@
+package delExpiredNeBackup
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/log"
+ "nms_nbi/src/framework/cron"
+)
+
+var NewProcessor = &BarProcessor{
+ progress: 0,
+ count: 0,
+}
+
+// bar 队列任务处理
+type BarProcessor struct {
+ // 任务进度
+ progress int
+ // 执行次数
+ count int
+}
+
+type BarParams struct {
+ Duration int `json:"duration"`
+}
+
+func (s *BarProcessor) Execute(data any) (any, error) {
+ log.Infof("执行 %d 次,上次进度: %d ", s.count, s.progress)
+ s.count++
+
+ options := data.(cron.JobData)
+ sysJob := options.SysJob
+ var params BarParams
+ duration := 60
+
+ err := json.Unmarshal([]byte(sysJob.TargetParams), ¶ms)
+ if err == nil {
+ duration = params.Duration
+ }
+ log.Infof("重复 %v 任务ID %s", options.Repeat, sysJob.JobID)
+
+ // // 实现任务处理逻辑
+ // i := 0
+ // s.progress = i
+ // for i < 5 {
+ // // 获取任务进度
+ // progress := s.progress
+ // log.Infof("jonId: %s => 任务进度:%d", sysJob.JobID, progress)
+ // // 延迟响应
+ // time.Sleep(time.Second * 2)
+ // // 程序中途执行错误
+ // if i == 3 {
+ // // arr := [1]int{1}
+ // // arr[i] = 3
+ // // fmt.Println(arr)
+ // // return "i = 3"
+ // panic("程序中途执行错误")
+ // }
+ // i++
+ // // 改变任务进度
+ // s.progress = i
+ // }
+ where := fmt.Sprintf("NOW()>ADDDATE(`create_time`,interval %d day)", duration)
+ affected, err := dborm.XormDeleteDataByWhere(where, "ne_backup")
+ if err != nil {
+ // panic(fmt.Sprintf("Failed to XormDeleteDataByWhere:%v", err))
+ return nil, err
+ }
+
+ // delete expired files in backup directory
+ // todo ...
+
+ // 返回结果,用于记录执行结果
+ return map[string]any{
+ "msg": "sucess",
+ "affected": affected,
+ }, nil
+}
diff --git a/src/modules/crontask/processor/deleteExpiredRecord/deleteExpiredRecord.go b/src/modules/crontask/processor/deleteExpiredRecord/deleteExpiredRecord.go
new file mode 100644
index 0000000..8a0ea97
--- /dev/null
+++ b/src/modules/crontask/processor/deleteExpiredRecord/deleteExpiredRecord.go
@@ -0,0 +1,88 @@
+package deleteExpiredRecord
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/log"
+ "nms_nbi/src/framework/cron"
+)
+
+var NewProcessor = &BarProcessor{
+ progress: 0,
+ count: 0,
+}
+
+// bar 队列任务处理
+type BarProcessor struct {
+ // 任务进度
+ progress int
+ // 执行次数
+ count int
+}
+
+type BarParams struct {
+ Duration int `json:"duration"`
+ TableName string `json:"tableName"`
+ ColName string `json:"colName"` // column name of time string
+ Extras string `json:"extras"` // extras condition for where
+}
+
+func (s *BarProcessor) Execute(data any) (any, error) {
+ log.Infof("执行 %d 次,上次进度: %d ", s.count, s.progress)
+ s.count++
+
+ options := data.(cron.JobData)
+ sysJob := options.SysJob
+ var params BarParams
+
+ err := json.Unmarshal([]byte(sysJob.TargetParams), ¶ms)
+ if err != nil {
+ return nil, err
+ }
+
+ //duration = params.Duration
+ log.Infof("重复 %v 任务ID %s", options.Repeat, sysJob.JobID)
+
+ // // 实现任务处理逻辑
+ // i := 0
+ // s.progress = i
+ // for i < 5 {
+ // // 获取任务进度
+ // progress := s.progress
+ // log.Infof("jonId: %s => 任务进度:%d", sysJob.JobID, progress)
+ // // 延迟响应
+ // time.Sleep(time.Second * 2)
+ // // 程序中途执行错误
+ // if i == 3 {
+ // // arr := [1]int{1}
+ // // arr[i] = 3
+ // // fmt.Println(arr)
+ // // return "i = 3"
+ // panic("程序中途执行错误")
+ // }
+ // i++
+ // // 改变任务进度
+ // s.progress = i
+ // }
+
+ var where string
+ if params.Extras == "" {
+ where = fmt.Sprintf("NOW()>ADDDATE(`%s`,interval %d day)", params.ColName, params.Duration)
+ } else {
+ where = fmt.Sprintf("NOW()>ADDDATE(`%s`,interval %d day) and %s", params.ColName, params.Duration, params.Extras)
+ }
+
+ affected, err := dborm.XormDeleteDataByWhere(where, params.TableName)
+ if err != nil {
+ // panic(fmt.Sprintf("Failed to XormDeleteDataByWhere:%v", err))
+ return nil, err
+ }
+
+ // 返回结果,用于记录执行结果
+ return map[string]any{
+ "msg": "sucess",
+ "affected": affected,
+ }, nil
+}
diff --git a/src/modules/crontask/processor/genNeStateAlarm/genNeStateAlarm.go b/src/modules/crontask/processor/genNeStateAlarm/genNeStateAlarm.go
new file mode 100644
index 0000000..ff2ccfd
--- /dev/null
+++ b/src/modules/crontask/processor/genNeStateAlarm/genNeStateAlarm.go
@@ -0,0 +1,310 @@
+package genNeStateAlarm
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+ "time"
+
+ "nms_nbi/features/fm"
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/global"
+ "nms_nbi/lib/log"
+ "nms_nbi/restagent/config"
+ "nms_nbi/src/framework/cron"
+
+ "github.com/go-resty/resty/v2"
+)
+
+var NewProcessor = &BarProcessor{
+ progress: 0,
+ count: 0,
+}
+
+// bar 队列任务处理
+type BarProcessor struct {
+ // 任务进度
+ progress int
+ // 执行次数
+ count int
+}
+type BarParams struct {
+ AlarmID string `json:"alarmID"`
+ AlarmCode int `json:"alarmCode"`
+ AlarmTitle string `json:"alarmTitle"`
+ AlarmType string `json:"alarmType"`
+ OrigSeverity string `json:"origSeverity"`
+ ObjectUID string `json:"objectUID"`
+ ObjectName string `json:"objectName"`
+ ObjectType string `json:"objectType"`
+ SpecificProblem string `json:"specificProblem"`
+ SpecificProblemID string `json:"specificProblemID"`
+ AddInfo string `json:"AddInfo"`
+ Threshold int64 `json:"threshold"`
+}
+
+// type BarParams struct {
+// Duration int `json:"duration"`
+// }
+
+type Alarm struct {
+ Id int `json:"-" xorm:"pk 'id' autoincr"`
+ AlarmSeq int `json:"alarmSeq"`
+ AlarmId string `json:"alarmId" xorm:"alarm_id"`
+ NeId string `json:"neId"`
+ AlarmCode int `json:"alarmCode"`
+ AlarmTitle string `json:"alarmTitle"`
+ EventTime string `json:"eventTime"`
+ AlarmType string `json:"alarmType"`
+ OrigSeverity string `json:"origSeverity"`
+ PerceivedSeverity string `json:"perceivedSeverity"`
+ PVFlag string `json:"pvFlag" xorm:"pv_flag"`
+ NeName string `json:"neName"`
+ NeType string `json:"neType"`
+ ObjectUid string `json:"objectUid" xorm:"object_uid"`
+ ObjectName string `json:"objectName" xorm:"object_name"`
+ ObjectType string `json:"objectType" xorm:"object_type"`
+ LocationInfo string `json:"locationInfo"`
+ Province string `json:"province"`
+ AlarmStatus int `json:"alarmStatus" xorm:"alarm_status"`
+ SpecificProblem string `json:"specificProblem"`
+ SpecificProblemID string `json:"specificProblemID" xorm:"specific_problem_id"`
+ AddInfo string `json:"addInfo"`
+
+ // ClearType int `json:"-" xorm:"clear_type"` // 0: Unclear, 1: Auto clear, 2: Manual clear
+ // ClearTime sql.NullTime `json:"-" xorm:"clear_time"`
+}
+
+var client = resty.New()
+
+func init() {
+ client.
+ SetTimeout(time.Duration(400 * time.Millisecond))
+}
+
+func (s *BarProcessor) Execute(data any) (any, error) {
+ var err error
+
+ s.count++
+ options := data.(cron.JobData)
+ sysJob := options.SysJob
+ var alarmDefine BarParams
+
+ err = json.Unmarshal([]byte(sysJob.TargetParams), &alarmDefine)
+ if err != nil {
+ log.Error("Failed to Unmarshal:", err)
+ return nil, err
+ }
+
+ var nes []dborm.NeInfo
+ _, err = dborm.XormGetAllNeInfo(&nes)
+ if err != nil {
+ log.Error("Failed to get all ne info:", err)
+ return nil, err
+ }
+
+ succActiveAlarmNum := 0
+ failActiveAlarmNum := 0
+ succClearAlarmNum := 0
+ failClearAlarmNum := 0
+
+ for _, ne := range nes {
+ //log.Debug("ne:", ne)
+
+ sql := fmt.Sprintf("select * from ne_state where ne_type = '%s' and ne_id = '%s' order by timestamp desc limit 1", ne.NeType, ne.NeId)
+ neState, err := dborm.XormGetDataBySQL(sql)
+ if err != nil {
+ log.Error("Failed to get ne_state:", err)
+ continue
+ }
+ if len(*neState) == 0 {
+ log.Warn("Not found record in ne_state:")
+ continue
+ }
+ //log.Debug("neState:", *neState)
+
+ // params := "10000"
+
+ // alarmDefine, err := dborm.XormGetAlarmDefine(params)
+ // if err != nil {
+ // log.Error("Failed to get alarm_define:", err)
+ // continue
+ // } else if alarmDefine == nil {
+ // log.Error("Not found data from alarm_define")
+ // continue
+ // }
+
+ // log.Debug("alarmDefine:", alarmDefine)
+
+ sql = fmt.Sprintf("select * from alarm where alarm_id = '%s' and ne_type='%s' and ne_id = '%s' order by event_time desc limit 1",
+ alarmDefine.AlarmID, ne.NeType, ne.RmUID)
+ alarm, err := dborm.XormGetDataBySQL(sql)
+ if err != nil {
+ log.Error("Failed to get alarm:", err)
+ continue
+ }
+ //log.Debug("alarm:", *alarm)
+
+ var timestamp string
+ if len(*neState) == 0 {
+ log.Infof("Not found ne_state neType:%s, neId:%s", ne.NeType, ne.NeId)
+ timestamp = ne.UpdateTime
+ } else {
+ timestamp = (*neState)[0]["timestamp"]
+ }
+
+ // 解析日期时间字符串为时间对象
+ seconds, err := global.GetSecondsSinceDatetime(timestamp)
+ if err != nil {
+ log.Error("Failed to GetSecondsSinceDatetime:", err)
+ continue
+ }
+ log.Debugf("timestamp:%s seconds:%d", timestamp, seconds)
+
+ if seconds <= alarmDefine.Threshold {
+ if len(*alarm) == 0 || (*alarm)[0]["alarm_status"] == fm.AlarmStatusClearString {
+ continue
+ }
+
+ // clear alarm, todo
+ var alarmSeq int = 1
+ threshold := strconv.FormatInt(alarmDefine.Threshold, 10)
+ SpecificProblem := strings.ReplaceAll(alarmDefine.SpecificProblem, "{threshold}", threshold)
+ locationInfo := fmt.Sprintf("SystemManagement.State: NE heartbeat timestamp=%s,threshold=%v", timestamp, alarmDefine.Threshold)
+ alarmData := &Alarm{
+ AlarmSeq: alarmSeq,
+ AlarmId: alarmDefine.AlarmID,
+ NeId: ne.RmUID,
+ NeType: ne.NeType,
+ NeName: ne.NeName,
+ Province: ne.Province,
+ PVFlag: ne.PvFlag,
+ AlarmCode: alarmDefine.AlarmCode,
+ AlarmTitle: alarmDefine.AlarmTitle,
+ AlarmType: alarmDefine.AlarmType,
+ AlarmStatus: fm.AlarmStatusClear,
+ OrigSeverity: alarmDefine.OrigSeverity,
+ ObjectUid: alarmDefine.ObjectUID,
+ ObjectName: alarmDefine.ObjectName,
+ ObjectType: alarmDefine.ObjectType,
+ LocationInfo: locationInfo,
+ SpecificProblem: SpecificProblem,
+ SpecificProblemID: alarmDefine.SpecificProblemID,
+ AddInfo: alarmDefine.AddInfo,
+ EventTime: time.Now().Local().Format(time.RFC3339),
+ }
+
+ alarmArray := &[]Alarm{*alarmData}
+ body, _ := json.Marshal(alarmArray)
+ //log.Debug("body: ", string(body))
+
+ var response *resty.Response
+ requestURI := fmt.Sprintf("/api/rest/faultManagement/v1/elementType/%s/objectType/alarms", ne.NeType)
+ restHost := fmt.Sprintf("http://127.0.0.1:%d", config.GetYamlConfig().Rest[0].Port)
+ requestURL := fmt.Sprintf("%s%s", restHost, requestURI)
+ log.Debug("requestURL: POST ", requestURL)
+ response, err = client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ SetBody(body).
+ Post(requestURL)
+ if err != nil {
+ log.Error("Failed to post:", err)
+ failClearAlarmNum++
+ continue
+ }
+
+ log.Debug("StatusCode: ", response.StatusCode())
+ switch response.StatusCode() {
+ case http.StatusOK, http.StatusCreated, http.StatusNoContent, http.StatusAccepted:
+ log.Debug("response body:", string(response.Body()))
+ body := new(map[string]interface{})
+ _ = json.Unmarshal(response.Body(), &body)
+ succClearAlarmNum++
+ default:
+ log.Debug("response body:", string(response.Body()))
+ body := new(map[string]interface{})
+ _ = json.Unmarshal(response.Body(), &body)
+ failClearAlarmNum++
+ }
+ } else {
+ var alarmSeq int = 1
+ if len(*alarm) > 0 && (*alarm)[0]["alarm_status"] == fm.AlarmStatusActiveString {
+ log.Info("System state alarm has exist")
+ continue
+ }
+
+ threshold := strconv.FormatInt(alarmDefine.Threshold, 10)
+ SpecificProblem := strings.ReplaceAll(alarmDefine.SpecificProblem, "{threshold}", threshold)
+ locationInfo := fmt.Sprintf("SystemManagement.State: NE heartbeat timestamp=%s,threshold=%v", timestamp, alarmDefine.Threshold)
+ alarmData := &Alarm{
+ AlarmSeq: alarmSeq,
+ AlarmId: alarmDefine.AlarmID,
+ NeId: ne.RmUID,
+ NeType: ne.NeType,
+ NeName: ne.NeName,
+ Province: ne.Province,
+ PVFlag: ne.PvFlag,
+ AlarmCode: alarmDefine.AlarmCode,
+ AlarmTitle: alarmDefine.AlarmTitle,
+ AlarmType: alarmDefine.AlarmType,
+ AlarmStatus: fm.AlarmStatusActive,
+ OrigSeverity: alarmDefine.OrigSeverity,
+ ObjectUid: alarmDefine.ObjectUID,
+ ObjectName: alarmDefine.ObjectName,
+ ObjectType: alarmDefine.ObjectType,
+ LocationInfo: locationInfo,
+ SpecificProblem: SpecificProblem,
+ SpecificProblemID: alarmDefine.SpecificProblemID,
+ AddInfo: alarmDefine.AddInfo,
+ EventTime: time.Now().Local().Format(time.RFC3339),
+ }
+
+ alarmArray := &[]Alarm{*alarmData}
+ body, _ := json.Marshal(alarmArray)
+ //log.Debug("body: ", string(body))
+
+ var response *resty.Response
+ requestURI := fmt.Sprintf("/api/rest/faultManagement/v1/elementType/%s/objectType/alarms", ne.NeType)
+ restHost := fmt.Sprintf("http://127.0.0.1:%d", config.GetYamlConfig().Rest[0].Port)
+ requestURL := fmt.Sprintf("%s%s", restHost, requestURI)
+ log.Debug("requestURL: POST ", requestURL)
+ response, err = client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ SetBody(body).
+ Post(requestURL)
+ if err != nil {
+ log.Error("Failed to post:", err)
+ failActiveAlarmNum++
+ continue
+ }
+
+ log.Debug("StatusCode: ", response.StatusCode())
+ switch response.StatusCode() {
+ case http.StatusOK, http.StatusCreated, http.StatusNoContent, http.StatusAccepted:
+ log.Debug("response body:", string(response.Body()))
+ body := new(map[string]interface{})
+ _ = json.Unmarshal(response.Body(), &body)
+ succActiveAlarmNum++
+ default:
+ log.Debug("response body:", string(response.Body()))
+ body := new(map[string]interface{})
+ _ = json.Unmarshal(response.Body(), &body)
+ failActiveAlarmNum++
+ }
+ }
+ }
+
+ // 返回结果,用于记录执行结果
+ return map[string]any{
+ "succActiveAlarmNum": succActiveAlarmNum,
+ "failActiveAlarmNum": failActiveAlarmNum,
+ "succClearAlarmNum": succClearAlarmNum,
+ "failClearAlarmNum": failClearAlarmNum,
+ }, nil
+}
diff --git a/src/modules/crontask/processor/getStateFromNE/getStateFromNE.go b/src/modules/crontask/processor/getStateFromNE/getStateFromNE.go
new file mode 100644
index 0000000..fc62959
--- /dev/null
+++ b/src/modules/crontask/processor/getStateFromNE/getStateFromNE.go
@@ -0,0 +1,158 @@
+package getStateFromNE
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strings"
+ "time"
+
+ "nms_nbi/lib/dborm"
+ "nms_nbi/lib/log"
+ "nms_nbi/restagent/config"
+ "nms_nbi/src/framework/cron"
+
+ "github.com/go-resty/resty/v2"
+)
+
+var NewProcessor = &BarProcessor{
+ progress: 0,
+ count: 0,
+}
+
+// bar 队列任务处理
+type BarProcessor struct {
+ // 任务进度
+ progress int
+ // 执行次数
+ count int
+}
+
+type BarParams struct {
+ Duration int `json:"duration"`
+}
+
+type CpuUsage struct {
+ NfCpuUsage uint16 `json:"nfCpuUsage"`
+ SysCpuUsage uint16 `json:"sysCpuUsage"`
+}
+
+type MemUsage struct {
+ TotalMem uint32 `json:"totalMem"`
+ NfUsedMem uint32 `json:"nfUsedMem"`
+ SysMemUsage uint16 `json:"sysMemUsage"`
+}
+
+type PartitionInfo struct {
+ Total uint32 `json:"total"` // MB
+ Used uint32 `json:"used"` // MB
+}
+
+type DiskSpace struct {
+ PartitionNum uint8 `json:"partitionNum"`
+
+ PartitionInfo []PartitionInfo `json:"partitionInfo"`
+}
+
+type SystemState struct {
+ Version string `json:"version"`
+ Capability uint32 `json:"capability"`
+ SerialNum string `json:"serialNum"`
+ ExpiryDate string `json:"expiryDate"`
+ //Timestamp string `json:"timestamp"`
+
+ CpuUsage CpuUsage `json:"cpuUsage"`
+ MemUsage MemUsage `json:"memUsage"`
+
+ DiskSpace DiskSpace `json:"diskSpace"`
+}
+
+var client = resty.New()
+
+func init() {
+ client.
+ SetTimeout(time.Duration(400 * time.Millisecond))
+}
+
+func (s *BarProcessor) Execute(data any) (any, error) {
+ var err error
+
+ s.count++
+ options := data.(cron.JobData)
+ sysJob := options.SysJob
+ var params BarParams
+
+ _ = json.Unmarshal([]byte(sysJob.TargetParams), ¶ms)
+ // if err == nil {
+ // duration = params.Duration
+ // }
+
+ var nes []dborm.NeInfo
+ _, err = dborm.XormGetAllNeInfo(&nes)
+ if err != nil {
+ log.Error("Failed to get all ne info:", err)
+ return nil, err
+ }
+
+ failNum := 0
+ succNum := 0
+ for _, ne := range nes {
+ requestURI := fmt.Sprintf("/api/rest/systemManagement/v1/elementType/%s/objectType/systemState", strings.ToLower(ne.NeType))
+ requestURL := fmt.Sprintf("http://%s:%s%s", ne.Ip, ne.Port, requestURI)
+ log.Debug("requestURL: Get", requestURL)
+ response, err := client.R().
+ EnableTrace().
+ SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}).
+ SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
+ Get(requestURL)
+ if err != nil {
+ log.Error("Failed to Get:", err)
+ failNum++
+ continue
+ }
+
+ log.Debug("StatusCode: ", response.StatusCode())
+ switch response.StatusCode() {
+ case http.StatusOK, http.StatusCreated, http.StatusNoContent, http.StatusAccepted:
+ log.Trace("response body:", string(response.Body()))
+ state := new(SystemState)
+ _ = json.Unmarshal(response.Body(), &state)
+ var dateStr *string = nil
+ if state.ExpiryDate != "" && state.ExpiryDate != "-" {
+ dateStr = &state.ExpiryDate
+ }
+ neState := new(dborm.NeState)
+ neState.NeType = ne.NeType
+ neState.NeId = ne.NeId
+ neState.Version = state.Version
+ neState.Capability = state.Capability
+ neState.SerialNum = state.SerialNum
+ neState.ExpiryDate = *dateStr
+ cu, _ := json.Marshal(state.CpuUsage)
+ neState.CpuUsage = string(cu)
+ mu, _ := json.Marshal(state.MemUsage)
+ neState.MemUsage = string(mu)
+ ds, _ := json.Marshal(state.DiskSpace)
+ neState.DiskSpace = string(ds)
+ log.Trace("neState:", neState)
+ _, err := dborm.XormInsertNeState(neState)
+ if err != nil {
+ log.Error("Failed to insert ne_state:", err)
+ failNum++
+ continue
+ }
+ succNum++
+ default:
+ log.Trace("response body:", string(response.Body()))
+ body := new(map[string]interface{})
+ _ = json.Unmarshal(response.Body(), &body)
+ failNum++
+ }
+ }
+
+ // 返回结果,用于记录执行结果
+ return map[string]any{
+ "succNum": succNum,
+ "failNum": failNum,
+ }, nil
+}
diff --git a/src/modules/crontask/processor/monitor_sys_resource/monitor_sys_resource.go b/src/modules/crontask/processor/monitor_sys_resource/monitor_sys_resource.go
new file mode 100644
index 0000000..6f3831f
--- /dev/null
+++ b/src/modules/crontask/processor/monitor_sys_resource/monitor_sys_resource.go
@@ -0,0 +1,56 @@
+package monitorsysresource
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "nms_nbi/src/framework/cron"
+ "nms_nbi/src/framework/logger"
+ monitorService "nms_nbi/src/modules/monitor/service"
+)
+
+var NewProcessor = &MonitorSysResourceProcessor{
+ monitorService: monitorService.NewMonitorImpl,
+ count: 0,
+ openDataCancel: false,
+}
+
+// MonitorSysResourceProcessor 系统资源CPU/IO/Netword收集
+type MonitorSysResourceProcessor struct {
+ // 服务器系统相关信息服务
+ monitorService monitorService.IMonitor
+ // 执行次数
+ count int
+ // 是否已经开启数据通道
+ openDataCancel bool
+}
+
+func (s *MonitorSysResourceProcessor) Execute(data any) (any, error) {
+ s.count++ // 执行次数加一
+ options := data.(cron.JobData)
+ sysJob := options.SysJob
+ logger.Infof("重复 %v 任务ID %s", options.Repeat, sysJob.JobID)
+
+ // 读取参数值
+ var params struct {
+ Interval float64 `json:"interval"`
+ }
+ err := json.Unmarshal([]byte(sysJob.TargetParams), ¶ms)
+ if err != nil {
+ return nil, fmt.Errorf("json params 'interval' err: %v", err)
+ }
+
+ // 避免重复开启chan通道
+ if !s.openDataCancel {
+ s.monitorService.RunMonitorDataCancel(false, params.Interval)
+ s.openDataCancel = true
+ }
+
+ s.monitorService.RunMonitor()
+
+ // 返回结果,用于记录执行结果
+ result := map[string]any{
+ "count": s.count,
+ }
+ return result, nil
+}
diff --git a/src/modules/crontask/processor/processor.go b/src/modules/crontask/processor/processor.go
new file mode 100644
index 0000000..4e4b173
--- /dev/null
+++ b/src/modules/crontask/processor/processor.go
@@ -0,0 +1,23 @@
+package processor
+
+import (
+ "nms_nbi/src/framework/cron"
+ "nms_nbi/src/modules/crontask/processor/backupEtcFromNE"
+ "nms_nbi/src/modules/crontask/processor/delExpiredNeBackup"
+ "nms_nbi/src/modules/crontask/processor/deleteExpiredRecord"
+ "nms_nbi/src/modules/crontask/processor/genNeStateAlarm"
+ "nms_nbi/src/modules/crontask/processor/getStateFromNE"
+ monitorsysresource "nms_nbi/src/modules/crontask/processor/monitor_sys_resource"
+)
+
+// InitCronQueue 初始定时任务队列
+func InitCronQueue() {
+ // 监控-系统资源
+ cron.CreateQueue("monitor_sys_resource", monitorsysresource.NewProcessor)
+ // delete expired NE backup file
+ cron.CreateQueue("delExpiredNeBackup", delExpiredNeBackup.NewProcessor)
+ cron.CreateQueue("deleteExpiredRecord", deleteExpiredRecord.NewProcessor)
+ cron.CreateQueue("backupEtcFromNE", backupEtcFromNE.NewProcessor)
+ cron.CreateQueue("getStateFromNE", getStateFromNE.NewProcessor)
+ cron.CreateQueue("genNeStateAlarm", genNeStateAlarm.NewProcessor)
+}
diff --git a/src/modules/monitor/controller/monitor.go b/src/modules/monitor/controller/monitor.go
new file mode 100644
index 0000000..10b174f
--- /dev/null
+++ b/src/modules/monitor/controller/monitor.go
@@ -0,0 +1,60 @@
+package controller
+
+import (
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/monitor/service"
+
+ "github.com/gin-gonic/gin"
+)
+
+// 实例化控制层 MonitorInfoController 结构体
+var NewMonitor = &MonitorController{
+ monitorService: service.NewMonitorImpl,
+}
+
+// 服务器资源监控信息
+//
+// PATH /monitor
+type MonitorController struct {
+ // 服务器系统相关信息服务
+ monitorService service.IMonitor
+}
+
+// 资源监控信息加载
+//
+// GET /load
+func (s *MonitorController) Load(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var querys struct {
+ // 数据类型all/load/cpu/memory/io/network
+ Type string `form:"type" binding:"required,oneof=all load cpu memory io network"`
+ // 开始时间
+ StartTime int64 `form:"startTime" binding:"required"`
+ // 结束时间
+ EndTime int64 `form:"endTime" binding:"required"`
+ // 网元类型
+ NeType string `form:"neType"`
+ // 网元ID
+ NeID string `form:"neId"`
+ // 名称,networ和iok时有效
+ Name string `form:"name"`
+ }
+ err := c.ShouldBindQuery(&querys)
+ if err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 查询数据
+ data := s.monitorService.SelectMonitorInfo(map[string]any{
+ "type": querys.Type,
+ "startTime": querys.StartTime,
+ "endTime": querys.EndTime,
+ "neType": querys.NeType,
+ "neId": querys.NeID,
+ "name": querys.Name,
+ })
+ c.JSON(200, result.OkData(data))
+}
diff --git a/src/modules/monitor/controller/sys_cache.go b/src/modules/monitor/controller/sys_cache.go
new file mode 100644
index 0000000..1500bac
--- /dev/null
+++ b/src/modules/monitor/controller/sys_cache.go
@@ -0,0 +1,162 @@
+package controller
+
+import (
+ "nms_nbi/src/framework/constants/cachekey"
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/redis"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/monitor/model"
+
+ "github.com/gin-gonic/gin"
+)
+
+// 实例化控制层 SysCacheController 结构体
+var NewSysCache = &SysCacheController{}
+
+// 缓存监控信息
+//
+// PATH /monitor/cache
+type SysCacheController struct{}
+
+// Redis信息
+//
+// GET /
+func (s *SysCacheController) Info(c *gin.Context) {
+ c.JSON(200, result.OkData(map[string]any{
+ "info": redis.Info(""),
+ "dbSize": redis.KeySize(""),
+ "commandStats": redis.CommandStats(""),
+ }))
+}
+
+// 缓存名称列表
+//
+// GET /getNames
+func (s *SysCacheController) Names(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ caches := []model.SysCache{
+ model.NewSysCacheNames(i18n.TKey(language, "cache.name.user"), cachekey.LOGIN_TOKEN_KEY),
+ model.NewSysCacheNames(i18n.TKey(language, "cache.name.sys_config"), cachekey.SYS_CONFIG_KEY),
+ model.NewSysCacheNames(i18n.TKey(language, "cache.name.sys_dict"), cachekey.SYS_DICT_KEY),
+ model.NewSysCacheNames(i18n.TKey(language, "cache.name.captcha_codes"), cachekey.CAPTCHA_CODE_KEY),
+ model.NewSysCacheNames(i18n.TKey(language, "cache.name.repeat_submit"), cachekey.REPEAT_SUBMIT_KEY),
+ model.NewSysCacheNames(i18n.TKey(language, "cache.name.rate_limit"), cachekey.RATE_LIMIT_KEY),
+ model.NewSysCacheNames(i18n.TKey(language, "cache.name.pwd_err_cnt"), cachekey.PWD_ERR_CNT_KEY),
+ model.NewSysCacheNames(i18n.TKey(language, "cache.name.ne_info"), cachekey.NE_KEY),
+ model.NewSysCacheNames(i18n.TKey(language, "cache.name.ne_data"), cachekey.NE_DATA_KEY),
+ }
+ c.JSON(200, result.OkData(caches))
+}
+
+// 缓存名称下键名列表
+//
+// GET /getKeys/:cacheName
+func (s *SysCacheController) Keys(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ cacheName := c.Param("cacheName")
+ if cacheName == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ caches := []model.SysCache{}
+
+ // 遍历组装
+ cacheKeys, _ := redis.GetKeys("", cacheName+":*")
+ for _, key := range cacheKeys {
+ caches = append(caches, model.NewSysCacheKeys(cacheName, key))
+ }
+
+ c.JSON(200, result.OkData(caches))
+}
+
+// 缓存内容
+//
+// GET /getValue/:cacheName/:cacheKey
+func (s *SysCacheController) Value(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ cacheName := c.Param("cacheName")
+ cacheKey := c.Param("cacheKey")
+ if cacheName == "" || cacheKey == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ cacheValue, err := redis.Get("", cacheName+":"+cacheKey)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+ sysCache := model.NewSysCacheValue(cacheName, cacheKey, cacheValue)
+ c.JSON(200, result.OkData(sysCache))
+}
+
+// 删除缓存名称下键名列表
+//
+// DELETE /clearCacheName/:cacheName
+func (s *SysCacheController) ClearCacheName(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ cacheName := c.Param("cacheName")
+ if cacheName == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ cacheKeys, err := redis.GetKeys("", cacheName+":*")
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+ ok, _ := redis.DelKeys("", cacheKeys)
+ if ok {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 删除缓存键名
+//
+// DELETE /clearCacheKey/:cacheName/:cacheKey
+func (s *SysCacheController) ClearCacheKey(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ cacheName := c.Param("cacheName")
+ cacheKey := c.Param("cacheKey")
+ if cacheName == "" || cacheKey == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ ok, _ := redis.Del("", cacheName+":"+cacheKey)
+ if ok {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 安全清理缓存名称
+//
+// DELETE /clearCacheSafe
+func (s *SysCacheController) ClearCacheSafe(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ caches := []model.SysCache{
+ model.NewSysCacheNames(i18n.TKey(language, "cache.name.user"), cachekey.LOGIN_TOKEN_KEY),
+ model.NewSysCacheNames(i18n.TKey(language, "cache.name.sys_config"), cachekey.SYS_CONFIG_KEY),
+ model.NewSysCacheNames(i18n.TKey(language, "cache.name.sys_dict"), cachekey.SYS_DICT_KEY),
+ model.NewSysCacheNames(i18n.TKey(language, "cache.name.captcha_codes"), cachekey.CAPTCHA_CODE_KEY),
+ model.NewSysCacheNames(i18n.TKey(language, "cache.name.repeat_submit"), cachekey.REPEAT_SUBMIT_KEY),
+ model.NewSysCacheNames(i18n.TKey(language, "cache.name.rate_limit"), cachekey.RATE_LIMIT_KEY),
+ model.NewSysCacheNames(i18n.TKey(language, "cache.name.pwd_err_cnt"), cachekey.PWD_ERR_CNT_KEY),
+ model.NewSysCacheNames(i18n.TKey(language, "cache.name.ne_info"), cachekey.NE_KEY),
+ model.NewSysCacheNames(i18n.TKey(language, "cache.name.ne_data"), cachekey.NE_DATA_KEY),
+ }
+ for _, v := range caches {
+ cacheKeys, err := redis.GetKeys("", v.CacheName+":*")
+ if err != nil {
+ continue
+ }
+ redis.DelKeys("", cacheKeys)
+ }
+ c.JSON(200, result.Ok(nil))
+}
diff --git a/src/modules/monitor/controller/sys_job.go b/src/modules/monitor/controller/sys_job.go
new file mode 100644
index 0000000..a1ea552
--- /dev/null
+++ b/src/modules/monitor/controller/sys_job.go
@@ -0,0 +1,388 @@
+package controller
+
+import (
+ "encoding/json"
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/file"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/monitor/model"
+ "nms_nbi/src/modules/monitor/service"
+ systemService "nms_nbi/src/modules/system/service"
+
+ "github.com/gin-gonic/gin"
+ "github.com/gin-gonic/gin/binding"
+)
+
+// 实例化控制层 SysJobLogController 结构体
+var NewSysJob = &SysJobController{
+ sysJobService: service.NewSysJobImpl,
+ sysDictDataService: systemService.NewSysDictDataImpl,
+}
+
+// 调度任务信息
+//
+// PATH /monitor/job
+type SysJobController struct {
+ // 调度任务服务
+ sysJobService service.ISysJob
+ // 字典数据服务
+ sysDictDataService systemService.ISysDictData
+}
+
+// 调度任务列表
+//
+// GET /list
+func (s *SysJobController) List(c *gin.Context) {
+ querys := ctx.QueryMap(c)
+ data := s.sysJobService.SelectJobPage(querys)
+
+ rows := data["rows"].([]model.SysJob)
+ // 闭包函数处理多语言
+ language := ctx.AcceptLanguage(c)
+ converI18n := func(language string, arr *[]model.SysJob) {
+ for i := range *arr {
+ (*arr)[i].JobName = i18n.TKey(language, (*arr)[i].JobName)
+ (*arr)[i].Remark = i18n.TKey(language, (*arr)[i].Remark)
+ }
+ }
+ converI18n(language, &rows)
+
+ c.JSON(200, result.Ok(data))
+}
+
+// 调度任务信息
+//
+// GET /:jobId
+func (s *SysJobController) Info(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ jobId := c.Param("jobId")
+ if jobId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ data := s.sysJobService.SelectJobById(jobId)
+ if data.JobID == jobId {
+ // 处理多语言
+ data.JobName = i18n.TKey(language, data.JobName)
+ data.Remark = i18n.TKey(language, data.Remark)
+ c.JSON(200, result.OkData(data))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 调度任务新增
+//
+// POST /
+func (s *SysJobController) Add(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.SysJob
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.JobID != "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查cron表达式格式
+ if parse.CronExpression(body.CronExpression) == 0 {
+ // 调度任务新增【%s】失败,Cron表达式不正确
+ msg := i18n.TTemplate(language, "job.errCronExpression", map[string]any{"name": body.JobName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 检查任务调用传入参数是否json格式
+ if body.TargetParams != "" {
+ // 调度任务新增【%s】失败,任务传入参数json字符串不正确
+ msg := i18n.TTemplate(language, "job.errTargetParams", map[string]any{"name": body.JobName})
+ if len(body.TargetParams) < 7 {
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+ if !json.Valid([]byte(body.TargetParams)) {
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+ }
+
+ // 检查属性值唯一
+ uniqueJob := s.sysJobService.CheckUniqueJobName(body.JobName, body.JobGroup, "")
+ if !uniqueJob {
+ // 调度任务新增【%s】失败,同任务组内有相同任务名称
+ msg := i18n.TTemplate(language, "job.errJobExists", map[string]any{"name": body.JobName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ body.CreateBy = ctx.LoginUserToUserName(c)
+ insertId := s.sysJobService.InsertJob(body)
+ if insertId != "" {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 调度任务修改
+//
+// PUT /
+func (s *SysJobController) Edit(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.SysJob
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.JobID == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查cron表达式格式
+ if parse.CronExpression(body.CronExpression) == 0 {
+ // 调度任务修改【%s】失败,Cron表达式不正确
+ msg := i18n.TTemplate(language, "job.errCronExpression", map[string]any{"name": body.JobName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 检查任务调用传入参数是否json格式
+ if body.TargetParams != "" {
+ // 调度任务修改【%s】失败,任务传入参数json字符串不正确
+ msg := i18n.TTemplate(language, "job.errTargetParams", map[string]any{"name": body.JobName})
+ if len(body.TargetParams) < 7 {
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+ if !json.Valid([]byte(body.TargetParams)) {
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+ }
+
+ // 检查是否存在
+ job := s.sysJobService.SelectJobById(body.JobID)
+ if job.JobID != body.JobID {
+ // 没有可访问调度任务数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "job.noData")))
+ return
+ }
+
+ // 检查属性值唯一
+ uniqueJob := s.sysJobService.CheckUniqueJobName(body.JobName, body.JobGroup, body.JobID)
+ if !uniqueJob {
+ // 调度任务修改【%s】失败,同任务组内有相同任务名称
+ msg := i18n.TTemplate(language, "job.errJobExists", map[string]any{"name": body.JobName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 多语言非原始值
+ i18nValue := i18n.TKey(language, job.JobName)
+ if i18nValue != job.JobName {
+ i18n.UpdateKeyValue(language, job.JobName, body.JobName)
+ body.JobName = job.JobName
+ }
+ // 多语言非原始值
+ i18nValue2 := i18n.TKey(language, job.Remark)
+ if i18nValue2 != job.Remark {
+ i18n.UpdateKeyValue(language, job.Remark, body.Remark)
+ body.Remark = job.Remark
+ }
+
+ body.UpdateBy = ctx.LoginUserToUserName(c)
+ rows := s.sysJobService.UpdateJob(body)
+ if rows > 0 {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 调度任务删除
+//
+// DELETE /:jobIds
+func (s *SysJobController) Remove(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ jobIds := c.Param("jobIds")
+ if jobIds == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ // 处理字符转id数组后去重
+ ids := strings.Split(jobIds, ",")
+ uniqueIDs := parse.RemoveDuplicates(ids)
+ if len(uniqueIDs) <= 0 {
+ c.JSON(200, result.Err(nil))
+ return
+ }
+ rows, err := s.sysJobService.DeleteJobByIds(uniqueIDs)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+ msg := i18n.TTemplate(language, "app.common.deleteSuccess", map[string]any{"num": rows})
+ c.JSON(200, result.OkMsg(msg))
+}
+
+// 调度任务修改状态
+//
+// PUT /changeStatus
+func (s *SysJobController) Status(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body struct {
+ // 任务ID
+ JobId string `json:"jobId" binding:"required"`
+ // 状态
+ Status string `json:"status" binding:"required"`
+ }
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查是否存在
+ job := s.sysJobService.SelectJobById(body.JobId)
+ if job.JobID != body.JobId {
+ // 没有可访问调度任务数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "job.noData")))
+ return
+ }
+
+ // 与旧值相等不变更
+ if job.Status == body.Status {
+ // 变更状态与旧值相等!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "job.statusEq")))
+ return
+ }
+
+ // 更新状态
+ job.Status = body.Status
+ job.UpdateBy = ctx.LoginUserToUserName(c)
+ rows := s.sysJobService.UpdateJob(job)
+ if rows > 0 {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 调度任务立即执行一次
+//
+// PUT /run/:jobId
+func (s *SysJobController) Run(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ jobId := c.Param("jobId")
+ if jobId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查是否存在
+ job := s.sysJobService.SelectJobById(jobId)
+ if job.JobID != jobId {
+ // 没有可访问调度任务数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "job.noData")))
+ return
+ }
+
+ ok := s.sysJobService.RunQueueJob(job)
+ if ok {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 调度任务重置刷新队列
+//
+// PUT /resetQueueJob
+func (s *SysJobController) ResetQueueJob(c *gin.Context) {
+ s.sysJobService.ResetQueueJob()
+ c.JSON(200, result.Ok(nil))
+}
+
+// 导出调度任务信息
+//
+// POST /export
+func (s *SysJobController) Export(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ // 查询结果,根据查询条件结果,单页最大值限制
+ querys := ctx.BodyJSONMap(c)
+ data := s.sysJobService.SelectJobPage(querys)
+ if data["total"].(int64) == 0 {
+ // 导出数据记录为空
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.exportEmpty")))
+ return
+ }
+ rows := data["rows"].([]model.SysJob)
+
+ // 闭包函数处理多语言
+ converI18n := func(language string, arr *[]model.SysJob) {
+ for i := range *arr {
+ (*arr)[i].JobName = i18n.TKey(language, (*arr)[i].JobName)
+ (*arr)[i].Remark = i18n.TKey(language, (*arr)[i].Remark)
+ }
+ }
+ converI18n(language, &rows)
+
+ // 导出文件名称
+ fileName := fmt.Sprintf("job_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli())
+ // 第一行表头标题
+ headerCells := map[string]string{
+ "A1": i18n.TKey(language, "job.export.jobID"),
+ "B1": i18n.TKey(language, "job.export.jobName"),
+ "C1": i18n.TKey(language, "job.export.jobGroupName"),
+ "D1": i18n.TKey(language, "job.export.invokeTarget"),
+ "E1": i18n.TKey(language, "job.export.targetParams"),
+ "F1": i18n.TKey(language, "job.export.cronExpression"),
+ "G1": i18n.TKey(language, "job.export.status"),
+ "H1": i18n.TKey(language, "job.export.remark"),
+ }
+ // 读取任务组名字典数据
+ dictSysJobGroup := s.sysDictDataService.SelectDictDataByType("sys_job_group")
+ // 从第二行开始的数据
+ dataCells := make([]map[string]any, 0)
+ for i, row := range rows {
+ idx := strconv.Itoa(i + 2)
+ // 任务组名
+ sysJobGroup := ""
+ for _, v := range dictSysJobGroup {
+ if row.JobGroup == v.DictValue {
+ sysJobGroup = i18n.TKey(language, v.DictLabel)
+ break
+ }
+ }
+
+ // 状态
+ statusValue := i18n.TKey(language, "dictData.fail")
+ if row.Status == "1" {
+ statusValue = i18n.TKey(language, "dictData.success")
+ }
+ dataCells = append(dataCells, map[string]any{
+ "A" + idx: row.JobID,
+ "B" + idx: row.JobName,
+ "C" + idx: sysJobGroup,
+ "D" + idx: row.InvokeTarget,
+ "E" + idx: row.TargetParams,
+ "F" + idx: row.CronExpression,
+ "G" + idx: statusValue,
+ "H" + idx: row.Remark,
+ })
+ }
+
+ // 导出数据表格
+ saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "")
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ c.FileAttachment(saveFilePath, fileName)
+}
diff --git a/src/modules/monitor/controller/sys_job_log.go b/src/modules/monitor/controller/sys_job_log.go
new file mode 100644
index 0000000..4816be2
--- /dev/null
+++ b/src/modules/monitor/controller/sys_job_log.go
@@ -0,0 +1,197 @@
+package controller
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/date"
+ "nms_nbi/src/framework/utils/file"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/monitor/model"
+ "nms_nbi/src/modules/monitor/service"
+ systemService "nms_nbi/src/modules/system/service"
+
+ "github.com/gin-gonic/gin"
+)
+
+// 实例化控制层 SysJobLogController 结构体
+var NewSysJobLog = &SysJobLogController{
+ sysJobLogService: service.NewSysJobLogImpl,
+ sysDictDataService: systemService.NewSysDictDataImpl,
+}
+
+// 调度任务日志信息
+//
+// PATH /monitor/jobLog
+type SysJobLogController struct {
+ // 调度任务日志服务
+ sysJobLogService service.ISysJobLog
+ // 字典数据服务
+ sysDictDataService systemService.ISysDictData
+}
+
+// 调度任务日志列表
+//
+// GET /list
+func (s *SysJobLogController) List(c *gin.Context) {
+ // 查询参数转换map
+ querys := ctx.QueryMap(c)
+ // 任务ID优先级更高
+ if v, ok := querys["jobId"]; ok && v != nil {
+ jobInfo := service.NewSysJobImpl.SelectJobById(v.(string))
+ querys["jobName"] = jobInfo.JobName
+ querys["jobGroup"] = jobInfo.JobGroup
+ }
+ data := s.sysJobLogService.SelectJobLogPage(querys)
+
+ rows := data["rows"].([]model.SysJobLog)
+ // 闭包函数处理多语言
+ language := ctx.AcceptLanguage(c)
+ converI18n := func(language string, arr *[]model.SysJobLog) {
+ for i := range *arr {
+ (*arr)[i].JobName = i18n.TKey(language, (*arr)[i].JobName)
+ }
+ }
+ converI18n(language, &rows)
+
+ c.JSON(200, result.Ok(data))
+}
+
+// 调度任务日志信息
+//
+// GET /:jobLogId
+func (s *SysJobLogController) Info(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ jobLogId := c.Param("jobLogId")
+ if jobLogId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ data := s.sysJobLogService.SelectJobLogById(jobLogId)
+ if data.JobLogID == jobLogId {
+ c.JSON(200, result.OkData(data))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 调度任务日志删除
+//
+// DELETE /:jobLogIds
+func (s *SysJobLogController) Remove(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ jobLogIds := c.Param("jobLogIds")
+ if jobLogIds == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 处理字符转id数组后去重
+ ids := strings.Split(jobLogIds, ",")
+ uniqueIDs := parse.RemoveDuplicates(ids)
+ if len(uniqueIDs) <= 0 {
+ c.JSON(200, result.Err(nil))
+ return
+ }
+ rows := s.sysJobLogService.DeleteJobLogByIds(uniqueIDs)
+ if rows > 0 {
+ // 删除成功:%d
+ msg := i18n.TTemplate(language, "app.common.deleteSuccess", map[string]any{"num": rows})
+ c.JSON(200, result.OkMsg(msg))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 调度任务日志清空
+//
+// DELETE /clean
+func (s *SysJobLogController) Clean(c *gin.Context) {
+ err := s.sysJobLogService.CleanJobLog()
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+ c.JSON(200, result.Ok(nil))
+}
+
+// 导出调度任务日志信息
+//
+// POST /export
+func (s *SysJobLogController) Export(c *gin.Context) {
+ // 查询结果,根据查询条件结果,单页最大值限制
+ querys := ctx.BodyJSONMap(c)
+ data := s.sysJobLogService.SelectJobLogPage(querys)
+ if data["total"].(int64) == 0 {
+ c.JSON(200, result.ErrMsg("Export data record is empty"))
+ return
+ }
+ rows := data["rows"].([]model.SysJobLog)
+
+ // 闭包函数处理多语言
+ language := ctx.AcceptLanguage(c)
+ converI18n := func(language string, arr *[]model.SysJobLog) {
+ for i := range *arr {
+ (*arr)[i].JobName = i18n.TKey(language, (*arr)[i].JobName)
+ }
+ }
+ converI18n(language, &rows)
+
+ // 导出文件名称
+ fileName := fmt.Sprintf("jobLog_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli())
+ // 第一行表头标题
+ headerCells := map[string]string{
+ "A1": i18n.TKey(language, "job.export.jobLogID"),
+ "B1": i18n.TKey(language, "job.export.jobName"),
+ "C1": i18n.TKey(language, "job.export.jobGroupName"),
+ "D1": i18n.TKey(language, "job.export.invokeTarget"),
+ "E1": i18n.TKey(language, "job.export.targetParams"),
+ "F1": i18n.TKey(language, "job.export.jobID"),
+ "G1": i18n.TKey(language, "job.export.jobLogStatus"),
+ "H1": i18n.TKey(language, "job.export.jobLogTime"),
+ }
+ // 读取任务组名字典数据
+ dictSysJobGroup := s.sysDictDataService.SelectDictDataByType("sys_job_group")
+ // 从第二行开始的数据
+ dataCells := make([]map[string]any, 0)
+ for i, row := range rows {
+ idx := strconv.Itoa(i + 2)
+ // 任务组名
+ sysJobGroup := ""
+ for _, v := range dictSysJobGroup {
+ if row.JobGroup == v.DictValue {
+ sysJobGroup = i18n.TKey(language, v.DictLabel)
+ break
+ }
+ }
+ // 状态
+ statusValue := i18n.TKey(language, "dictData.fail")
+ if row.Status == "1" {
+ statusValue = i18n.TKey(language, "dictData.success")
+ }
+ dataCells = append(dataCells, map[string]any{
+ "A" + idx: row.JobLogID,
+ "B" + idx: row.JobName,
+ "C" + idx: sysJobGroup,
+ "D" + idx: row.InvokeTarget,
+ "E" + idx: row.TargetParams,
+ "F" + idx: row.JobMsg,
+ "G" + idx: statusValue,
+ "H" + idx: date.ParseDateToStr(row.CreateTime, date.YYYY_MM_DD_HH_MM_SS),
+ })
+ }
+
+ // 导出数据表格
+ saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "")
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ c.FileAttachment(saveFilePath, fileName)
+}
diff --git a/src/modules/monitor/controller/sys_user_online.go b/src/modules/monitor/controller/sys_user_online.go
new file mode 100644
index 0000000..942ab73
--- /dev/null
+++ b/src/modules/monitor/controller/sys_user_online.go
@@ -0,0 +1,131 @@
+package controller
+
+import (
+ "encoding/json"
+ "sort"
+ "strings"
+
+ "nms_nbi/src/framework/constants/cachekey"
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/redis"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/vo"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/monitor/model"
+ "nms_nbi/src/modules/monitor/service"
+
+ "github.com/gin-gonic/gin"
+)
+
+// 实例化控制层 SysUserOnlineController 结构体
+var NewSysUserOnline = &SysUserOnlineController{
+ sysUserOnlineService: service.NewSysUserOnlineImpl,
+}
+
+// 在线用户监控
+//
+// PATH /monitor/online
+type SysUserOnlineController struct {
+ // 在线用户服务
+ sysUserOnlineService service.ISysUserOnline
+}
+
+// 在线用户列表
+//
+// GET /list
+func (s *SysUserOnlineController) List(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ ipaddr := c.Query("ipaddr")
+ userName := c.Query("userName")
+
+ // 获取所有在线用户key
+ keys, _ := redis.GetKeys("", cachekey.LOGIN_TOKEN_KEY+"*")
+
+ // 分批获取
+ arr := make([]string, 0)
+ for i := 0; i < len(keys); i += 20 {
+ end := i + 20
+ if end > len(keys) {
+ end = len(keys)
+ }
+ chunk := keys[i:end]
+ values, _ := redis.GetBatch("", chunk)
+ for _, v := range values {
+ arr = append(arr, v.(string))
+ }
+ }
+
+ // 遍历字符串信息解析组合可用对象
+ userOnlines := make([]model.SysUserOnline, 0)
+ for _, str := range arr {
+ if str == "" {
+ continue
+ }
+
+ var loginUser vo.LoginUser
+ err := json.Unmarshal([]byte(str), &loginUser)
+ if err != nil {
+ continue
+ }
+
+ onlineUser := s.sysUserOnlineService.LoginUserToUserOnline(loginUser)
+ if onlineUser.TokenID != "" {
+ onlineUser.LoginLocation = i18n.TKey(language, onlineUser.LoginLocation)
+ userOnlines = append(userOnlines, onlineUser)
+ }
+ }
+
+ // 根据查询条件过滤
+ filteredUserOnlines := make([]model.SysUserOnline, 0)
+ if ipaddr != "" && userName != "" {
+ for _, o := range userOnlines {
+ if strings.Contains(o.IPAddr, ipaddr) && strings.Contains(o.UserName, userName) {
+ filteredUserOnlines = append(filteredUserOnlines, o)
+ }
+ }
+ } else if ipaddr != "" {
+ for _, o := range userOnlines {
+ if strings.Contains(o.IPAddr, ipaddr) {
+ filteredUserOnlines = append(filteredUserOnlines, o)
+ }
+ }
+ } else if userName != "" {
+ for _, o := range userOnlines {
+ if strings.Contains(o.UserName, userName) {
+ filteredUserOnlines = append(filteredUserOnlines, o)
+ }
+ }
+ } else {
+ filteredUserOnlines = userOnlines
+ }
+
+ // 按登录时间排序
+ sort.Slice(filteredUserOnlines, func(i, j int) bool {
+ return filteredUserOnlines[j].LoginTime > filteredUserOnlines[i].LoginTime
+ })
+
+ c.JSON(200, result.Ok(map[string]any{
+ "total": len(filteredUserOnlines),
+ "rows": filteredUserOnlines,
+ }))
+}
+
+// 在线用户强制退出
+//
+// DELETE /:tokenId
+func (s *SysUserOnlineController) ForceLogout(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ tokenId := c.Param("tokenId")
+ if tokenId == "" || tokenId == "*" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 删除token
+ ok, _ := redis.Del("", cachekey.LOGIN_TOKEN_KEY+tokenId)
+ if ok {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
diff --git a/src/modules/monitor/controller/system_info.go b/src/modules/monitor/controller/system_info.go
new file mode 100644
index 0000000..009f1ab
--- /dev/null
+++ b/src/modules/monitor/controller/system_info.go
@@ -0,0 +1,35 @@
+package controller
+
+import (
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/monitor/service"
+
+ "github.com/gin-gonic/gin"
+)
+
+// 实例化控制层 SystemInfoController 结构体
+var NewSystemInfo = &SystemInfoController{
+ systemInfogService: service.NewSystemInfoImpl,
+}
+
+// 服务器监控信息
+//
+// PATH /monitor/system-info
+type SystemInfoController struct {
+ // 服务器系统相关信息服务
+ systemInfogService service.ISystemInfo
+}
+
+// 服务器信息
+//
+// GET /
+func (s *SystemInfoController) Info(c *gin.Context) {
+ c.JSON(200, result.OkData(map[string]any{
+ "cpu": s.systemInfogService.CPUInfo(),
+ "memory": s.systemInfogService.MemoryInfo(),
+ "network": s.systemInfogService.NetworkInfo(),
+ "time": s.systemInfogService.TimeInfo(),
+ "system": s.systemInfogService.SystemInfo(),
+ "disk": s.systemInfogService.DiskInfo(),
+ }))
+}
diff --git a/src/modules/monitor/model/monitor_base.go b/src/modules/monitor/model/monitor_base.go
new file mode 100644
index 0000000..7669afa
--- /dev/null
+++ b/src/modules/monitor/model/monitor_base.go
@@ -0,0 +1,29 @@
+package model
+
+// MonitorBase 监控_基本信息 monitor_base
+type MonitorBase struct {
+ // id
+ ID int64 `json:"id" gorm:"primaryKey"`
+ // 创建时间
+ CreateTime int64 `json:"createTime"`
+ // cpu使用率
+ CPU float64 `json:"cpu"`
+ // cpu平均使用率
+ LoadUsage float64 `json:"loadUsage"`
+ // cpu使用1分钟
+ CPULoad1 float64 `json:"cpuLoad1"`
+ // cpu使用5分钟
+ CPULoad5 float64 `json:"cpuLoad5"`
+ // cpu使用15分钟
+ CPULoad15 float64 `json:"cpuLoad15"`
+ // 内存使用率
+ Memory float64 `json:"memory"`
+ // 网元ID
+ NeType string `json:"neType"`
+ // 网元类型
+ NeID string `json:"neId"`
+}
+
+func (MonitorBase) TableName() string {
+ return "monitor_base"
+}
diff --git a/src/modules/monitor/model/monitor_io.go b/src/modules/monitor/model/monitor_io.go
new file mode 100644
index 0000000..cc52b72
--- /dev/null
+++ b/src/modules/monitor/model/monitor_io.go
@@ -0,0 +1,27 @@
+package model
+
+// MonitorIO 监控_磁盘IO monitor_io
+type MonitorIO struct {
+ // id
+ ID int64 `json:"id" gorm:"primaryKey"`
+ // 创建时间
+ CreateTime int64 `json:"createTime"`
+ // 磁盘名
+ Name string `json:"name"`
+ // 读取K
+ Read int64 `json:"read"`
+ // 写入K
+ Write int64 `json:"write"`
+ // 次数
+ Count int64 `json:"count"`
+ // 耗时
+ Time int64 `json:"time"`
+ // 网元ID
+ NeType string `json:"neType"`
+ // 网元类型
+ NeID string `json:"neId"`
+}
+
+func (MonitorIO) TableName() string {
+ return "monitor_io"
+}
diff --git a/src/modules/monitor/model/monitor_network.go b/src/modules/monitor/model/monitor_network.go
new file mode 100644
index 0000000..a2063f1
--- /dev/null
+++ b/src/modules/monitor/model/monitor_network.go
@@ -0,0 +1,23 @@
+package model
+
+// MonitorNetwork 监控_网络IO monitor_network
+type MonitorNetwork struct {
+ // id
+ ID int64 `json:"id" gorm:"primaryKey"`
+ // 创建时间
+ CreateTime int64 `json:"createTime"`
+ // 网卡名
+ Name string `json:"name"`
+ // 上行
+ Up float64 `json:"up"`
+ // 下行
+ Down float64 `json:"down"`
+ // 网元ID 本机#号
+ NeType string `json:"neType"`
+ // 网元类型 本机#号
+ NeID string `json:"neId"`
+}
+
+func (MonitorNetwork) TableName() string {
+ return "monitor_network"
+}
diff --git a/src/modules/monitor/model/sys_cache.go b/src/modules/monitor/model/sys_cache.go
new file mode 100644
index 0000000..29e79bc
--- /dev/null
+++ b/src/modules/monitor/model/sys_cache.go
@@ -0,0 +1,41 @@
+package model
+
+import "strings"
+
+// SysCache 缓存信息对象
+type SysCache struct {
+ CacheName string `json:"cacheName"` // 缓存名称
+ CacheKey string `json:"cacheKey"` // 缓存键名
+ CacheValue string `json:"cacheValue"` // 缓存内容
+ Remark string `json:"remark"` // 备注
+}
+
+// NewSysCacheNames 创建新的缓存名称列表项实例
+func NewSysCacheNames(cacheName string, cacheKey string) SysCache {
+ return SysCache{
+ CacheName: cacheKey[:len(cacheKey)-1],
+ CacheKey: "",
+ CacheValue: "",
+ Remark: cacheName,
+ }
+}
+
+// NewSysCacheKeys 创建新的缓存键名列表项实例
+func NewSysCacheKeys(cacheName string, cacheKey string) SysCache {
+ return SysCache{
+ CacheName: cacheName,
+ CacheKey: strings.Replace(cacheKey, cacheName+":", "", 1),
+ CacheValue: "",
+ Remark: "",
+ }
+}
+
+// NewSysCacheValue 创建新的缓存键名内容项实例
+func NewSysCacheValue(cacheName string, cacheKey string, cacheValue string) SysCache {
+ return SysCache{
+ CacheName: cacheName,
+ CacheKey: cacheKey,
+ CacheValue: cacheValue,
+ Remark: "",
+ }
+}
diff --git a/src/modules/monitor/model/sys_job.go b/src/modules/monitor/model/sys_job.go
new file mode 100644
index 0000000..a7f910f
--- /dev/null
+++ b/src/modules/monitor/model/sys_job.go
@@ -0,0 +1,35 @@
+package model
+
+// SysJob 调度任务信息表 sys_job
+type SysJob struct {
+ // 任务ID
+ JobID string `json:"jobId"`
+ // 任务名称
+ JobName string `json:"jobName" binding:"required"`
+ // 任务组名
+ JobGroup string `json:"jobGroup" binding:"required"`
+ // 调用目标字符串
+ InvokeTarget string `json:"invokeTarget" binding:"required"`
+ // 调用目标传入参数
+ TargetParams string `json:"targetParams"`
+ // cron执行表达式
+ CronExpression string `json:"cronExpression" binding:"required"`
+ // 计划执行错误策略(1立即执行 2执行一次 3放弃执行)
+ MisfirePolicy string `json:"misfirePolicy"`
+ // 是否并发执行(0禁止 1允许)
+ Concurrent string `json:"concurrent"`
+ // 任务状态(0暂停 1正常)
+ Status string `json:"status"`
+ // 是否记录任务日志
+ SaveLog string `json:"saveLog"`
+ // 创建者
+ CreateBy string `json:"createBy"`
+ // 创建时间
+ CreateTime int64 `json:"createTime"`
+ // 更新者
+ UpdateBy string `json:"updateBy"`
+ // 更新时间
+ UpdateTime int64 `json:"updateTime"`
+ // 备注
+ Remark string `json:"remark"`
+}
diff --git a/src/modules/monitor/model/sys_job_log.go b/src/modules/monitor/model/sys_job_log.go
new file mode 100644
index 0000000..c9a27bc
--- /dev/null
+++ b/src/modules/monitor/model/sys_job_log.go
@@ -0,0 +1,23 @@
+package model
+
+// SysJobLog 定时任务调度日志表 sys_job_log
+type SysJobLog struct {
+ // 日志序号
+ JobLogID string `json:"jobLogId"`
+ // 任务名称
+ JobName string `json:"jobName"`
+ // 任务组名
+ JobGroup string `json:"jobGroup"`
+ // 调用目标字符串
+ InvokeTarget string `json:"invokeTarget"`
+ // 调用目标传入参数
+ TargetParams string `json:"targetParams"`
+ // 日志信息
+ JobMsg string `json:"jobMsg"`
+ // 执行状态(0失败 1正常)
+ Status string `json:"status"`
+ // 创建时间
+ CreateTime int64 `json:"createTime"`
+ // 消耗时间(毫秒)
+ CostTime int64 `json:"costTime"`
+}
diff --git a/src/modules/monitor/model/sys_user_online.go b/src/modules/monitor/model/sys_user_online.go
new file mode 100644
index 0000000..2c8a1b2
--- /dev/null
+++ b/src/modules/monitor/model/sys_user_online.go
@@ -0,0 +1,21 @@
+package model
+
+// SysUserOnline 当前在线会话对象
+type SysUserOnline struct {
+ // 会话编号
+ TokenID string `json:"tokenId"`
+ // 部门名称
+ DeptName string `json:"deptName"`
+ // 用户名称
+ UserName string `json:"userName"`
+ // 登录IP地址
+ IPAddr string `json:"ipaddr"`
+ // 登录地址
+ LoginLocation string `json:"loginLocation"`
+ // 浏览器类型
+ Browser string `json:"browser"`
+ // 操作系统
+ OS string `json:"os"`
+ // 登录时间
+ LoginTime int64 `json:"loginTime"`
+}
diff --git a/src/modules/monitor/monitor.go b/src/modules/monitor/monitor.go
new file mode 100644
index 0000000..db4248f
--- /dev/null
+++ b/src/modules/monitor/monitor.go
@@ -0,0 +1,169 @@
+package monitor
+
+import (
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/middleware"
+ "nms_nbi/src/framework/middleware/collectlogs"
+ "nms_nbi/src/framework/middleware/repeat"
+ "nms_nbi/src/modules/monitor/controller"
+ "nms_nbi/src/modules/monitor/processor"
+ "nms_nbi/src/modules/monitor/service"
+
+ "github.com/gin-gonic/gin"
+)
+
+// Setup 模块路由注册
+func Setup(router *gin.Engine) {
+ logger.Infof("开始加载 ====> monitor 模块路由")
+
+ // 启动时需要的初始参数
+ InitLoad()
+
+ // 服务器资源监控信息
+ monitorGroup := router.Group("/monitor")
+ {
+ monitorGroup.GET("/load",
+ // middleware.PreAuthorize(nil),
+ controller.NewMonitor.Load,
+ )
+ }
+
+ // 服务器服务信息
+ router.GET("/monitor/system-info",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:system:info"}}),
+ controller.NewSystemInfo.Info,
+ )
+
+ // 缓存服务信息
+ sysCacheGroup := router.Group("/monitor/cache")
+ {
+ sysCacheGroup.GET("",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:info"}}),
+ controller.NewSysCache.Info,
+ )
+ sysCacheGroup.GET("/getNames",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:list"}}),
+ controller.NewSysCache.Names,
+ )
+ sysCacheGroup.GET("/getKeys/:cacheName",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:list"}}),
+ controller.NewSysCache.Keys,
+ )
+ sysCacheGroup.GET("/getValue/:cacheName/:cacheKey",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:query"}}),
+ controller.NewSysCache.Value,
+ )
+ sysCacheGroup.DELETE("/clearCacheName/:cacheName",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:remove"}}),
+ controller.NewSysCache.ClearCacheName,
+ )
+ sysCacheGroup.DELETE("/clearCacheKey/:cacheName/:cacheKey",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:remove"}}),
+ controller.NewSysCache.ClearCacheKey,
+ )
+ sysCacheGroup.DELETE("/clearCacheSafe",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:remove"}}),
+ controller.NewSysCache.ClearCacheSafe,
+ )
+ }
+
+ // 调度任务日志信息
+ sysJobLogGroup := router.Group("/monitor/jobLog")
+ {
+ sysJobLogGroup.GET("/list",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:list"}}),
+ controller.NewSysJobLog.List,
+ )
+ sysJobLogGroup.GET("/:jobLogId",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:query"}}),
+ controller.NewSysJobLog.Info,
+ )
+ sysJobLogGroup.DELETE("/:jobLogIds",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:remove"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysJobLog", collectlogs.BUSINESS_TYPE_DELETE)),
+ controller.NewSysJobLog.Remove,
+ )
+ sysJobLogGroup.DELETE("/clean",
+ repeat.RepeatSubmit(5),
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:remove"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysJobLog", collectlogs.BUSINESS_TYPE_CLEAN)),
+ controller.NewSysJobLog.Clean,
+ )
+ sysJobLogGroup.POST("/export",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:export"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysJobLog", collectlogs.BUSINESS_TYPE_EXPORT)),
+ controller.NewSysJobLog.Export,
+ )
+ }
+
+ // 调度任务信息
+ sysJobGroup := router.Group("/monitor/job")
+ {
+ sysJobGroup.GET("/list",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:list"}}),
+ controller.NewSysJob.List,
+ )
+ sysJobGroup.GET("/:jobId",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:query"}}),
+ controller.NewSysJob.Info,
+ )
+ sysJobGroup.POST("",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:add"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysJob", collectlogs.BUSINESS_TYPE_INSERT)),
+ controller.NewSysJob.Add,
+ )
+ sysJobGroup.PUT("",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:edit"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysJob", collectlogs.BUSINESS_TYPE_UPDATE)),
+ controller.NewSysJob.Edit,
+ )
+ sysJobGroup.DELETE("/:jobIds",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:remove"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysJob", collectlogs.BUSINESS_TYPE_DELETE)),
+ controller.NewSysJob.Remove,
+ )
+ sysJobGroup.PUT("/changeStatus",
+ repeat.RepeatSubmit(5),
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:changeStatus"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysJob", collectlogs.BUSINESS_TYPE_UPDATE)),
+ controller.NewSysJob.Status,
+ )
+ sysJobGroup.PUT("/run/:jobId",
+ repeat.RepeatSubmit(10),
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:changeStatus"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysJob", collectlogs.BUSINESS_TYPE_UPDATE)),
+ controller.NewSysJob.Run,
+ )
+ sysJobGroup.PUT("/resetQueueJob",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:changeStatus"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysJob", collectlogs.BUSINESS_TYPE_CLEAN)),
+ controller.NewSysJob.ResetQueueJob,
+ )
+ sysJobGroup.POST("/export",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:export"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysJob", collectlogs.BUSINESS_TYPE_EXPORT)),
+ controller.NewSysJob.Export,
+ )
+ }
+
+ // 在线用户监控
+ sysUserOnlineGroup := router.Group("/monitor/online")
+ {
+ sysUserOnlineGroup.GET("/list",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:online:list"}}),
+ controller.NewSysUserOnline.List,
+ )
+ sysUserOnlineGroup.DELETE("/:tokenId",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:online:forceLogout"}}),
+ controller.NewSysUserOnline.ForceLogout,
+ )
+ }
+}
+
+// InitLoad 初始参数
+func InitLoad() {
+ // 初始化定时任务处理
+ processor.InitCronQueue()
+ // 启动时,初始化调度任务
+ service.NewSysJobImpl.ResetQueueJob()
+}
diff --git a/src/modules/monitor/processor/bar/bar.go b/src/modules/monitor/processor/bar/bar.go
new file mode 100644
index 0000000..fed56c9
--- /dev/null
+++ b/src/modules/monitor/processor/bar/bar.go
@@ -0,0 +1,61 @@
+package bar
+
+import (
+ "time"
+
+ "nms_nbi/src/framework/cron"
+ "nms_nbi/src/framework/logger"
+)
+
+var NewProcessor = &BarProcessor{
+ progress: 0,
+ count: 0,
+}
+
+// bar 队列任务处理
+type BarProcessor struct {
+ // 任务进度
+ progress int
+ // 执行次数
+ count int
+}
+
+func (s *BarProcessor) Execute(data any) (any, error) {
+ logger.Infof("执行 %d 次,上次进度: %d ", s.count, s.progress)
+ s.count++
+
+ options := data.(cron.JobData)
+ sysJob := options.SysJob
+ logger.Infof("重复 %v 任务ID %s", options.Repeat, sysJob.JobID)
+
+ // 实现任务处理逻辑
+ i := 0
+ s.progress = i
+ for i < 5 {
+ // 获取任务进度
+ progress := s.progress
+ logger.Infof("jonId: %s => 任务进度:%d", sysJob.JobID, progress)
+ // 延迟响应
+ time.Sleep(time.Second * 2)
+ // 程序中途执行错误
+ if i == 3 {
+ // arr := [1]int{1}
+ // arr[i] = 3
+ // fmt.Println(arr)
+ // return "i = 3"
+ panic("程序中途执行错误")
+ }
+ i++
+ // 改变任务进度
+ s.progress = i
+ }
+
+ // 返回结果,用于记录执行结果
+ result := map[string]any{
+ "repeat": options.Repeat,
+ "jobName": sysJob.JobName,
+ "invokeTarget": sysJob.InvokeTarget,
+ "targetParams": sysJob.TargetParams,
+ }
+ return result, nil
+}
diff --git a/src/modules/monitor/processor/foo/foo.go b/src/modules/monitor/processor/foo/foo.go
new file mode 100644
index 0000000..795c8e4
--- /dev/null
+++ b/src/modules/monitor/processor/foo/foo.go
@@ -0,0 +1,53 @@
+package foo
+
+import (
+ "time"
+
+ "nms_nbi/src/framework/cron"
+ "nms_nbi/src/framework/logger"
+)
+
+var NewProcessor = &FooProcessor{
+ progress: 0,
+ count: 0,
+}
+
+// foo 队列任务处理
+type FooProcessor struct {
+ // 任务进度
+ progress int
+ // 执行次数
+ count int
+}
+
+func (s *FooProcessor) Execute(data any) (any, error) {
+ logger.Infof("执行 %d 次,上次进度: %d ", s.count, s.progress)
+ s.count++
+
+ options := data.(cron.JobData)
+ sysJob := options.SysJob
+ logger.Infof("重复 %v 任务ID %s", options.Repeat, sysJob.JobID)
+
+ // 实现任务处理逻辑
+ i := 0
+ s.progress = i
+ for i < 20 {
+ // 获取任务进度
+ progress := s.progress
+ logger.Infof("jonId: %s => 任务进度:%d", sysJob.JobID, progress)
+ // 延迟响应
+ time.Sleep(time.Second * 2)
+ i++
+ // 改变任务进度
+ s.progress = i
+ }
+
+ // 返回结果,用于记录执行结果
+ result := map[string]any{
+ "repeat": options.Repeat,
+ "jobName": sysJob.JobName,
+ "invokeTarget": sysJob.InvokeTarget,
+ "targetParams": sysJob.TargetParams,
+ }
+ return result, nil
+}
diff --git a/src/modules/monitor/processor/processor.go b/src/modules/monitor/processor/processor.go
new file mode 100644
index 0000000..f006925
--- /dev/null
+++ b/src/modules/monitor/processor/processor.go
@@ -0,0 +1,15 @@
+package processor
+
+import (
+ "nms_nbi/src/framework/cron"
+ "nms_nbi/src/modules/monitor/processor/bar"
+ "nms_nbi/src/modules/monitor/processor/foo"
+ "nms_nbi/src/modules/monitor/processor/simple"
+)
+
+// InitCronQueue 初始定时任务队列
+func InitCronQueue() {
+ cron.CreateQueue("simple", simple.NewProcessor)
+ cron.CreateQueue("foo", foo.NewProcessor)
+ cron.CreateQueue("bar", bar.NewProcessor)
+}
diff --git a/src/modules/monitor/processor/simple/simple.go b/src/modules/monitor/processor/simple/simple.go
new file mode 100644
index 0000000..f5c0c56
--- /dev/null
+++ b/src/modules/monitor/processor/simple/simple.go
@@ -0,0 +1,27 @@
+package simple
+
+import (
+ "nms_nbi/src/framework/cron"
+ "nms_nbi/src/framework/logger"
+)
+
+var NewProcessor = &simpleProcessor{}
+
+// simple 队列任务处理
+type simpleProcessor struct{}
+
+func (s *simpleProcessor) Execute(data any) (any, error) {
+ options := data.(cron.JobData)
+
+ sysJob := options.SysJob
+ logger.Infof("重复 %v 任务ID %s", options.Repeat, sysJob.JobID)
+
+ // 返回结果,用于记录执行结果
+ result := map[string]any{
+ "repeat": options.Repeat,
+ "jobName": sysJob.JobName,
+ "invokeTarget": sysJob.InvokeTarget,
+ "targetParams": sysJob.TargetParams,
+ }
+ return result, nil
+}
diff --git a/src/modules/monitor/repository/monitor.go b/src/modules/monitor/repository/monitor.go
new file mode 100644
index 0000000..59f3315
--- /dev/null
+++ b/src/modules/monitor/repository/monitor.go
@@ -0,0 +1,33 @@
+package repository
+
+import "nms_nbi/src/modules/monitor/model"
+
+// IMonitor 监控服务资源相关信息 数据接口
+type IMonitor interface {
+ // CreateMonitorBase 创建监控_基本信息
+ CreateMonitorBase(m model.MonitorBase) error
+
+ // DelMonitorBase 删除监控_基本信息
+ DelMonitorBase(ltTime int64) error
+
+ // SelectMonitorBase 查询监控_基本信息
+ SelectMonitorBase(query map[string]any) []model.MonitorBase
+
+ // BatchCreateMonitorIO 批量创建监控_IO
+ BatchCreateMonitorIO(ioList []model.MonitorIO) error
+
+ // DelMonitorIO 删除监控_IO
+ DelMonitorIO(ltTime int64) error
+
+ // SelectMonitorIO 查询监控_IO
+ SelectMonitorIO(query map[string]any) []model.MonitorIO
+
+ // BatchCreateMonitorNet 批量创建监控_网络
+ BatchCreateMonitorNet(netList []model.MonitorNetwork) error
+
+ // DelMonitorNet 删除监控_网络
+ DelMonitorNet(ltTime int64) error
+
+ // SelectMonitorNetwork 查询监控_网络
+ SelectMonitorNetwork(query map[string]any) []model.MonitorNetwork
+}
diff --git a/src/modules/monitor/repository/monitor.impl.go b/src/modules/monitor/repository/monitor.impl.go
new file mode 100644
index 0000000..9fd4ca7
--- /dev/null
+++ b/src/modules/monitor/repository/monitor.impl.go
@@ -0,0 +1,106 @@
+package repository
+
+import (
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/modules/monitor/model"
+
+ "gorm.io/gorm"
+)
+
+// 实例化数据层 MonitorImpl 结构体
+var NewMonitorImpl = &MonitorImpl{
+ db: datasource.DefaultDB,
+}
+
+// MonitorImpl 监控服务资源相关信息 数据层处理
+type MonitorImpl struct {
+ // 数据库实例
+ db func() *gorm.DB
+}
+
+// CreateMonitorBase 创建监控_基本信息
+func (r *MonitorImpl) CreateMonitorBase(m model.MonitorBase) error {
+ return r.db().Create(&m).Error
+}
+
+// DelMonitorBase 删除监控_基本信息
+func (r *MonitorImpl) DelMonitorBase(ltTime int64) error {
+ return r.db().Where("create_time < ?", ltTime).Delete(&model.MonitorBase{}).Error
+}
+
+// SelectMonitorBase 查询监控_基本信息
+func (r *MonitorImpl) SelectMonitorBase(query map[string]any) []model.MonitorBase {
+ var bases []model.MonitorBase
+ dbConn := r.db()
+ if query["neType"] != "" && query["neId"] != "" {
+ dbConn = dbConn.Where("ne_type = ? and ne_id = ?", query["neType"], query["neId"])
+ }
+ dbConn = dbConn.Where("create_time >= ? and create_time <= ?", query["startTime"], query["endTime"])
+ err := dbConn.Order("create_time asc").Find(&bases).Error
+ if err != nil {
+ logger.Errorf("SelectMonitorBase %v", err)
+ return bases
+ }
+ return bases
+}
+
+// BatchCreateMonitorIO 批量创建监控_IO
+func (r *MonitorImpl) BatchCreateMonitorIO(ioList []model.MonitorIO) error {
+ return r.db().CreateInBatches(ioList, len(ioList)).Error
+}
+
+// DelMonitorIO 删除监控_IO
+func (r *MonitorImpl) DelMonitorIO(ltTime int64) error {
+ return r.db().Where("create_time < ?", ltTime).Delete(&model.MonitorIO{}).Error
+}
+
+// SelectMonitorIO 查询监控_IO
+func (r *MonitorImpl) SelectMonitorIO(query map[string]any) []model.MonitorIO {
+ var ios []model.MonitorIO
+ dbConn := r.db()
+ if query["name"] != "" {
+ dbConn = dbConn.Where("name = ?", query["name"])
+ }
+ if query["neType"] != "" && query["neId"] != "" {
+ dbConn = dbConn.Where("ne_type = ? and ne_id = ?", query["neType"], query["neId"])
+ }
+ dbConn = dbConn.Where("create_time >= ? and create_time <= ?", query["startTime"], query["endTime"])
+ err := dbConn.Order("create_time asc").Find(&ios).Error
+ if err != nil {
+ logger.Errorf("SelectMonitorIO %v", err)
+ return ios
+ }
+ return ios
+}
+
+// BatchCreateMonitorNet 批量创建监控_网络
+func (r *MonitorImpl) BatchCreateMonitorNet(netList []model.MonitorNetwork) error {
+ return r.db().CreateInBatches(netList, len(netList)).Error
+}
+
+// DelMonitorNet 删除监控_网络
+func (r *MonitorImpl) DelMonitorNet(ltTime int64) error {
+ return r.db().Where("create_time < ?", ltTime).Delete(&model.MonitorNetwork{}).Error
+}
+
+// SelectMonitorNetwork 查询监控_网络
+func (r *MonitorImpl) SelectMonitorNetwork(query map[string]any) []model.MonitorNetwork {
+ var networks []model.MonitorNetwork
+ dbConn := r.db()
+ if query["name"] != "" {
+ dbConn = dbConn.Where("name = ?", query["name"])
+ } else {
+ dbConn = dbConn.Where("name = ?", "all")
+ }
+ if query["neType"] != "" && query["neId"] != "" {
+ dbConn = dbConn.Where("ne_type = ? and ne_id = ?", query["neType"], query["neId"])
+ }
+ dbConn = dbConn.Where("create_time >= ? and create_time <= ?", query["startTime"], query["endTime"])
+ err := dbConn.Order("create_time asc").Find(&networks).Error
+ if err != nil {
+ logger.Errorf("SelectMonitorNetwork %v", err)
+ return networks
+ }
+ return networks
+}
diff --git a/src/modules/monitor/repository/sys_job.go b/src/modules/monitor/repository/sys_job.go
new file mode 100644
index 0000000..3bebce4
--- /dev/null
+++ b/src/modules/monitor/repository/sys_job.go
@@ -0,0 +1,29 @@
+package repository
+
+import (
+ "nms_nbi/src/modules/monitor/model"
+)
+
+// ISysJob 调度任务表 数据层接口
+type ISysJob interface {
+ // SelectJobPage 分页查询调度任务集合
+ SelectJobPage(query map[string]any) map[string]any
+
+ // SelectJobList 查询调度任务集合
+ SelectJobList(sysJob model.SysJob) []model.SysJob
+
+ // SelectJobByIds 通过调度ID查询调度任务信息
+ SelectJobByIds(jobIds []string) []model.SysJob
+
+ // CheckUniqueJob 校验调度任务是否唯一
+ CheckUniqueJob(sysJob model.SysJob) string
+
+ // InsertJob 新增调度任务信息
+ InsertJob(sysJob model.SysJob) string
+
+ // UpdateJob 修改调度任务信息
+ UpdateJob(sysJob model.SysJob) int64
+
+ // DeleteJobByIds 批量删除调度任务信息
+ DeleteJobByIds(jobIds []string) int64
+}
diff --git a/src/modules/monitor/repository/sys_job.impl.go b/src/modules/monitor/repository/sys_job.impl.go
new file mode 100644
index 0000000..7aa8794
--- /dev/null
+++ b/src/modules/monitor/repository/sys_job.impl.go
@@ -0,0 +1,343 @@
+package repository
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/repo"
+ "nms_nbi/src/modules/monitor/model"
+)
+
+// 实例化数据层 SysJobImpl 结构体
+var NewSysJobImpl = &SysJobImpl{
+ selectSql: `select job_id, job_name, job_group, invoke_target, target_params, cron_expression,
+ misfire_policy, concurrent, status, save_log, create_by, create_time, remark from sys_job`,
+
+ resultMap: map[string]string{
+ "job_id": "JobID",
+ "job_name": "JobName",
+ "job_group": "JobGroup",
+ "invoke_target": "InvokeTarget",
+ "target_params": "TargetParams",
+ "cron_expression": "CronExpression",
+ "misfire_policy": "MisfirePolicy",
+ "concurrent": "Concurrent",
+ "status": "Status",
+ "save_log": "SaveLog",
+ "create_by": "CreateBy",
+ "create_time": "CreateTime",
+ "update_by": "UpdateBy",
+ "update_time": "UpdateTime",
+ "remark": "Remark",
+ },
+}
+
+// SysJobImpl 调度任务表 数据层处理
+type SysJobImpl struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *SysJobImpl) convertResultRows(rows []map[string]any) []model.SysJob {
+ arr := make([]model.SysJob, 0)
+ for _, row := range rows {
+ sysJob := model.SysJob{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ repo.SetFieldValue(&sysJob, keyMapper, value)
+ }
+ }
+ arr = append(arr, sysJob)
+ }
+ return arr
+}
+
+// SelectJobPage 分页查询调度任务集合
+func (r *SysJobImpl) SelectJobPage(query map[string]any) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if v, ok := query["jobName"]; ok && v != "" {
+ conditions = append(conditions, "job_name like concat(?, '%')")
+ params = append(params, v)
+ }
+ if v, ok := query["jobGroup"]; ok && v != "" {
+ conditions = append(conditions, "job_group = ?")
+ params = append(params, v)
+ }
+ if v, ok := query["invokeTarget"]; ok && v != "" {
+ conditions = append(conditions, "invoke_target like concat(?, '%')")
+ params = append(params, v)
+ }
+ if v, ok := query["status"]; ok && v != "" {
+ conditions = append(conditions, "status = ?")
+ params = append(params, v)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询结果
+ result := map[string]any{
+ "total": 0,
+ "rows": []model.SysJob{},
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := "select count(1) as 'total' from sys_job"
+ totalRows, err := datasource.RawDB("", totalSql+whereSql, params)
+ if err != nil {
+ logger.Errorf("total err => %v", err)
+ return result
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return result
+ } else {
+ result["total"] = total
+ }
+
+ // 分页
+ pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return result
+ }
+
+ // 转换实体
+ result["rows"] = r.convertResultRows(results)
+ return result
+}
+
+// SelectJobList 查询调度任务集合
+func (r *SysJobImpl) SelectJobList(sysJob model.SysJob) []model.SysJob {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysJob.JobName != "" {
+ conditions = append(conditions, "job_name like concat(?, '%')")
+ params = append(params, sysJob.JobName)
+ }
+ if sysJob.JobGroup != "" {
+ conditions = append(conditions, "job_group = ?")
+ params = append(params, sysJob.JobGroup)
+ }
+ if sysJob.InvokeTarget != "" {
+ conditions = append(conditions, "invoke_target like concat(?, '%')")
+ params = append(params, sysJob.InvokeTarget)
+ }
+ if sysJob.Status != "" {
+ conditions = append(conditions, "status = ?")
+ params = append(params, sysJob.Status)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ querySql := r.selectSql + whereSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.SysJob{}
+ }
+
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// SelectJobByIds 通过调度ID查询调度任务信息
+func (r *SysJobImpl) SelectJobByIds(jobIds []string) []model.SysJob {
+ placeholder := repo.KeyPlaceholderByQuery(len(jobIds))
+ querySql := r.selectSql + " where job_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(jobIds)
+ results, err := datasource.RawDB("", querySql, parameters)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.SysJob{}
+ }
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// CheckUniqueJob 校验调度任务是否唯一
+func (r *SysJobImpl) CheckUniqueJob(sysJob model.SysJob) string {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysJob.JobName != "" {
+ conditions = append(conditions, "job_name = ?")
+ params = append(params, sysJob.JobName)
+ }
+ if sysJob.JobGroup != "" {
+ conditions = append(conditions, "job_group = ?")
+ params = append(params, sysJob.JobGroup)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ } else {
+ return ""
+ }
+
+ // 查询数据
+ querySql := "select job_id as 'str' from sys_job " + whereSql + " limit 1"
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err %v", err)
+ return ""
+ }
+ if len(results) > 0 {
+ return fmt.Sprint(results[0]["str"])
+ }
+ return ""
+}
+
+// InsertJob 新增调度任务信息
+func (r *SysJobImpl) InsertJob(sysJob model.SysJob) string {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysJob.JobID != "" {
+ params["job_id"] = sysJob.JobID
+ }
+ if sysJob.JobName != "" {
+ params["job_name"] = sysJob.JobName
+ }
+ if sysJob.JobGroup != "" {
+ params["job_group"] = sysJob.JobGroup
+ }
+ if sysJob.InvokeTarget != "" {
+ params["invoke_target"] = sysJob.InvokeTarget
+ }
+ params["target_params"] = sysJob.TargetParams
+ if sysJob.CronExpression != "" {
+ params["cron_expression"] = sysJob.CronExpression
+ }
+ if sysJob.MisfirePolicy != "" {
+ params["misfire_policy"] = sysJob.MisfirePolicy
+ }
+ if sysJob.Concurrent != "" {
+ params["concurrent"] = sysJob.Concurrent
+ }
+ if sysJob.Status != "" {
+ params["status"] = sysJob.Status
+ }
+ if sysJob.SaveLog != "" {
+ params["save_log"] = sysJob.SaveLog
+ }
+ params["remark"] = sysJob.Remark
+ if sysJob.CreateBy != "" {
+ params["create_by"] = sysJob.CreateBy
+ params["create_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params)
+ sql := "insert into sys_job (" + strings.Join(keys, ",") + ")values(" + placeholder + ")"
+
+ db := datasource.DefaultDB()
+ // 开启事务
+ tx := db.Begin()
+ // 执行插入
+ err := tx.Exec(sql, values...).Error
+ if err != nil {
+ logger.Errorf("insert row : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 获取生成的自增 ID
+ var insertedID string
+ err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID)
+ if err != nil {
+ logger.Errorf("insert last id : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 提交事务
+ tx.Commit()
+ return insertedID
+}
+
+// UpdateJob 修改调度任务信息
+func (r *SysJobImpl) UpdateJob(sysJob model.SysJob) int64 {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysJob.JobName != "" {
+ params["job_name"] = sysJob.JobName
+ }
+ if sysJob.JobGroup != "" {
+ params["job_group"] = sysJob.JobGroup
+ }
+ if sysJob.InvokeTarget != "" {
+ params["invoke_target"] = sysJob.InvokeTarget
+ }
+ params["target_params"] = sysJob.TargetParams
+ if sysJob.CronExpression != "" {
+ params["cron_expression"] = sysJob.CronExpression
+ }
+ if sysJob.MisfirePolicy != "" {
+ params["misfire_policy"] = sysJob.MisfirePolicy
+ }
+ if sysJob.Concurrent != "" {
+ params["concurrent"] = sysJob.Concurrent
+ }
+ if sysJob.Status != "" {
+ params["status"] = sysJob.Status
+ }
+ if sysJob.SaveLog != "" {
+ params["save_log"] = sysJob.SaveLog
+ }
+ params["remark"] = sysJob.Remark
+ if sysJob.UpdateBy != "" {
+ params["update_by"] = sysJob.UpdateBy
+ params["update_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, values := repo.KeyValueByUpdate(params)
+ sql := "update sys_job set " + strings.Join(keys, ",") + " where job_id = ?"
+
+ // 执行更新
+ values = append(values, sysJob.JobID)
+ rows, err := datasource.ExecDB("", sql, values)
+ if err != nil {
+ logger.Errorf("update row : %v", err.Error())
+ return 0
+ }
+ return rows
+}
+
+// DeleteJobByIds 批量删除调度任务信息
+func (r *SysJobImpl) DeleteJobByIds(jobIds []string) int64 {
+ placeholder := repo.KeyPlaceholderByQuery(len(jobIds))
+ sql := "delete from sys_job where job_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(jobIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
diff --git a/src/modules/monitor/repository/sys_job_log.go b/src/modules/monitor/repository/sys_job_log.go
new file mode 100644
index 0000000..74b86e5
--- /dev/null
+++ b/src/modules/monitor/repository/sys_job_log.go
@@ -0,0 +1,26 @@
+package repository
+
+import (
+ "nms_nbi/src/modules/monitor/model"
+)
+
+// 调度任务日志表 数据层接口
+type ISysJobLog interface {
+ // 分页查询调度任务日志集合
+ SelectJobLogPage(query map[string]any) map[string]any
+
+ // 查询调度任务日志集合
+ SelectJobLogList(sysJobLog model.SysJobLog) []model.SysJobLog
+
+ // 通过调度ID查询调度任务日志信息
+ SelectJobLogById(jobLogId string) model.SysJobLog
+
+ // 新增调度任务日志信息
+ InsertJobLog(sysJobLog model.SysJobLog) string
+
+ // 批量删除调度任务日志信息
+ DeleteJobLogByIds(jobLogId []string) int64
+
+ // 清空调度任务日志
+ CleanJobLog() error
+}
diff --git a/src/modules/monitor/repository/sys_job_log.impl.go b/src/modules/monitor/repository/sys_job_log.impl.go
new file mode 100644
index 0000000..fd6b475
--- /dev/null
+++ b/src/modules/monitor/repository/sys_job_log.impl.go
@@ -0,0 +1,272 @@
+package repository
+
+import (
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/date"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/repo"
+ "nms_nbi/src/modules/monitor/model"
+)
+
+// 实例化数据层 SysJobLogImpl 结构体
+var NewSysJobLogImpl = &SysJobLogImpl{
+ selectSql: `select job_log_id, job_name, job_group, invoke_target,
+ target_params, job_msg, status, create_time, cost_time from sys_job_log`,
+
+ resultMap: map[string]string{
+ "job_log_id": "JobLogID",
+ "job_name": "JobName",
+ "job_group": "JobGroup",
+ "invoke_target": "InvokeTarget",
+ "target_params": "TargetParams",
+ "job_msg": "JobMsg",
+ "status": "Status",
+ "create_time": "CreateTime",
+ "cost_time": "CostTime",
+ },
+}
+
+// SysJobLogImpl 调度任务日志表 数据层处理
+type SysJobLogImpl struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *SysJobLogImpl) convertResultRows(rows []map[string]any) []model.SysJobLog {
+ arr := make([]model.SysJobLog, 0)
+ for _, row := range rows {
+ sysJobLog := model.SysJobLog{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ repo.SetFieldValue(&sysJobLog, keyMapper, value)
+ }
+ }
+ arr = append(arr, sysJobLog)
+ }
+ return arr
+}
+
+// 分页查询调度任务日志集合
+func (r *SysJobLogImpl) SelectJobLogPage(query map[string]any) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if v, ok := query["jobName"]; ok && v != "" {
+ conditions = append(conditions, "job_name = ?")
+ params = append(params, v)
+ }
+ if v, ok := query["jobGroup"]; ok && v != "" {
+ conditions = append(conditions, "job_group = ?")
+ params = append(params, v)
+ }
+ if v, ok := query["status"]; ok && v != "" {
+ conditions = append(conditions, "status = ?")
+ params = append(params, v)
+ }
+ if v, ok := query["invokeTarget"]; ok && v != "" {
+ conditions = append(conditions, "invoke_target like concat(?, '%')")
+ params = append(params, v)
+ }
+ beginTime, ok := query["beginTime"]
+ if !ok {
+ beginTime, ok = query["params[beginTime]"]
+ }
+ if ok && beginTime != "" {
+ conditions = append(conditions, "create_time >= ?")
+ beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD)
+ params = append(params, beginDate.UnixMilli())
+ }
+ endTime, ok := query["endTime"]
+ if !ok {
+ endTime, ok = query["params[endTime]"]
+ }
+ if ok && endTime != "" {
+ conditions = append(conditions, "create_time <= ?")
+ endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD)
+ params = append(params, endDate.UnixMilli())
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询结果
+ result := map[string]any{
+ "total": 0,
+ "rows": []model.SysJobLog{},
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := "select count(1) as 'total' from sys_job_log"
+ totalRows, err := datasource.RawDB("", totalSql+whereSql, params)
+ if err != nil {
+ logger.Errorf("total err => %v", err)
+ return result
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return result
+ } else {
+ result["total"] = total
+ }
+
+ // 分页
+ pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " order by job_log_id desc limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return result
+ }
+
+ // 转换实体
+ result["rows"] = r.convertResultRows(results)
+ return result
+}
+
+// 查询调度任务日志集合
+func (r *SysJobLogImpl) SelectJobLogList(sysJobLog model.SysJobLog) []model.SysJobLog {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysJobLog.JobName != "" {
+ conditions = append(conditions, "job_name like concat(?, '%')")
+ params = append(params, sysJobLog.JobName)
+ }
+ if sysJobLog.JobGroup != "" {
+ conditions = append(conditions, "job_group = ?")
+ params = append(params, sysJobLog.JobGroup)
+ }
+ if sysJobLog.Status != "" {
+ conditions = append(conditions, "status = ?")
+ params = append(params, sysJobLog.Status)
+ }
+ if sysJobLog.InvokeTarget != "" {
+ conditions = append(conditions, "invoke_target like concat(?, '%')")
+ params = append(params, sysJobLog.InvokeTarget)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ querySql := r.selectSql + whereSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.SysJobLog{}
+ }
+
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// 通过调度ID查询调度任务日志信息
+func (r *SysJobLogImpl) SelectJobLogById(jobLogId string) model.SysJobLog {
+ querySql := r.selectSql + " where job_log_id = ?"
+ results, err := datasource.RawDB("", querySql, []any{jobLogId})
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return model.SysJobLog{}
+ }
+ // 转换实体
+ rows := r.convertResultRows(results)
+ if len(rows) > 0 {
+ return rows[0]
+ }
+ return model.SysJobLog{}
+}
+
+// 新增调度任务日志信息
+func (r *SysJobLogImpl) InsertJobLog(sysJobLog model.SysJobLog) string {
+ // 参数拼接
+ params := make(map[string]any)
+ params["create_time"] = time.Now().UnixMilli()
+ if sysJobLog.JobLogID != "" {
+ params["job_log_id"] = sysJobLog.JobLogID
+ }
+ if sysJobLog.JobName != "" {
+ params["job_name"] = sysJobLog.JobName
+ }
+ if sysJobLog.JobGroup != "" {
+ params["job_group"] = sysJobLog.JobGroup
+ }
+ if sysJobLog.InvokeTarget != "" {
+ params["invoke_target"] = sysJobLog.InvokeTarget
+ }
+ if sysJobLog.TargetParams != "" {
+ params["target_params"] = sysJobLog.TargetParams
+ }
+ if sysJobLog.JobMsg != "" {
+ params["job_msg"] = sysJobLog.JobMsg
+ }
+ if sysJobLog.Status != "" {
+ params["status"] = sysJobLog.Status
+ }
+ if sysJobLog.CostTime > 0 {
+ params["cost_time"] = sysJobLog.CostTime
+ }
+
+ // 构建执行语句
+ keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params)
+ sql := "insert into sys_job_log (" + strings.Join(keys, ",") + ")values(" + placeholder + ")"
+
+ db := datasource.DefaultDB()
+ // 开启事务
+ tx := db.Begin()
+ // 执行插入
+ err := tx.Exec(sql, values...).Error
+ if err != nil {
+ logger.Errorf("insert row : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 获取生成的自增 ID
+ var insertedID string
+ err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID)
+ if err != nil {
+ logger.Errorf("insert last id : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 提交事务
+ tx.Commit()
+ return insertedID
+}
+
+// 批量删除调度任务日志信息
+func (r *SysJobLogImpl) DeleteJobLogByIds(jobLogIds []string) int64 {
+ placeholder := repo.KeyPlaceholderByQuery(len(jobLogIds))
+ sql := "delete from sys_job_log where job_log_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(jobLogIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
+
+// 清空调度任务日志
+func (r *SysJobLogImpl) CleanJobLog() error {
+ sql := "truncate table sys_job_log"
+ _, err := datasource.ExecDB("", sql, []any{})
+ return err
+}
diff --git a/src/modules/monitor/service/monitor.go b/src/modules/monitor/service/monitor.go
new file mode 100644
index 0000000..5e836cc
--- /dev/null
+++ b/src/modules/monitor/service/monitor.go
@@ -0,0 +1,14 @@
+package service
+
+// IMonitor 服务器系统相关信息 服务层接口
+type IMonitor interface {
+ // RunMonitor 执行资源监控
+ RunMonitor()
+
+ // RunMonitorDataCancel 启动资源监控数据存储io/network通道 移除之前的chan上下文后在设置新的均值
+ // interval 采集的平均值(分钟)
+ RunMonitorDataCancel(removeBefore bool, interval float64)
+
+ // SelectMonitorInfo 查询监控资源信息
+ SelectMonitorInfo(query map[string]any) map[string]any
+}
diff --git a/src/modules/monitor/service/monitor.impl.go b/src/modules/monitor/service/monitor.impl.go
new file mode 100644
index 0000000..d001307
--- /dev/null
+++ b/src/modules/monitor/service/monitor.impl.go
@@ -0,0 +1,272 @@
+package service
+
+import (
+ "context"
+ "strconv"
+ "time"
+
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/modules/monitor/model"
+ "nms_nbi/src/modules/monitor/repository"
+ systemService "nms_nbi/src/modules/system/service"
+
+ "github.com/shirou/gopsutil/v3/cpu"
+ "github.com/shirou/gopsutil/v3/disk"
+ "github.com/shirou/gopsutil/v3/load"
+ "github.com/shirou/gopsutil/v3/mem"
+ "github.com/shirou/gopsutil/v3/net"
+)
+
+// 实例化服务层 MonitorImpl 结构体
+var NewMonitorImpl = &MonitorImpl{
+ sysConfigService: systemService.NewSysConfigImpl,
+ monitorRepository: repository.NewMonitorImpl,
+ diskIO: make(chan []disk.IOCountersStat, 2),
+ netIO: make(chan []net.IOCountersStat, 2),
+}
+
+// MonitorImpl 服务器系统相关信息 服务层处理
+type MonitorImpl struct {
+ // 参数配置服务
+ sysConfigService systemService.ISysConfig
+ // 监控服务资源数据信息
+ monitorRepository repository.IMonitor
+ // 磁盘网络IO 数据通道
+ diskIO chan ([]disk.IOCountersStat)
+ netIO chan ([]net.IOCountersStat)
+}
+
+// RunMonitor 执行资源监控
+func (s *MonitorImpl) RunMonitor() {
+ var itemBase model.MonitorBase
+ itemBase.CreateTime = time.Now().UnixMilli()
+ itemBase.NeType = "#"
+ itemBase.NeID = "#"
+ totalPercent, _ := cpu.Percent(3*time.Second, false)
+ if len(totalPercent) == 1 {
+ itemBase.CPU = totalPercent[0]
+ }
+ cpuCount, _ := cpu.Counts(false)
+
+ loadInfo, _ := load.Avg()
+ itemBase.CPULoad1 = loadInfo.Load1
+ itemBase.CPULoad5 = loadInfo.Load5
+ itemBase.CPULoad15 = loadInfo.Load15
+ itemBase.LoadUsage = loadInfo.Load1 / (float64(cpuCount*2) * 0.75) * 100
+
+ memoryInfo, _ := mem.VirtualMemory()
+ itemBase.Memory = memoryInfo.UsedPercent
+
+ if err := s.monitorRepository.CreateMonitorBase(itemBase); err != nil {
+ logger.Errorf("CreateMonitorBase err: %v", err)
+ }
+
+ // 将当前资源发送到chan中处理保存
+ s.loadDiskIO()
+ s.loadNetIO()
+
+ // 监控系统资源-保留天数
+ storeDays := s.sysConfigService.SelectConfigValueByKey("monitor.sysResource.storeDays")
+ if storeDays != "" {
+ storeDays, _ := strconv.Atoi(storeDays)
+ ltTime := time.Now().AddDate(0, 0, -storeDays).UnixMilli()
+ _ = s.monitorRepository.DelMonitorBase(ltTime)
+ _ = s.monitorRepository.DelMonitorIO(ltTime)
+ _ = s.monitorRepository.DelMonitorNet(ltTime)
+ }
+}
+
+func (s *MonitorImpl) loadDiskIO() {
+ ioStat, _ := disk.IOCounters()
+ var diskIOList []disk.IOCountersStat
+ for _, io := range ioStat {
+ diskIOList = append(diskIOList, io)
+ }
+ s.diskIO <- diskIOList
+}
+
+func (s *MonitorImpl) loadNetIO() {
+ netStat, _ := net.IOCounters(true)
+ netStatAll, _ := net.IOCounters(false)
+ var netList []net.IOCountersStat
+ netList = append(netList, netStat...)
+ netList = append(netList, netStatAll...)
+ s.netIO <- netList
+}
+
+// monitorCancel 监控搜集IO/Network上下文
+var monitorCancel context.CancelFunc
+
+// RunMonitorDataCancel 启动资源监控数据存储io/network通道 移除之前的chan上下文后在设置新的均值
+// interval 采集的平均值(分钟)
+func (s *MonitorImpl) RunMonitorDataCancel(removeBefore bool, interval float64) {
+ // 是否取消之前的
+ if removeBefore {
+ monitorCancel()
+ }
+
+ // 上下文控制
+ ctx, cancel := context.WithCancel(context.Background())
+ monitorCancel = cancel
+
+ // chanl 通道进行存储数据
+ go s.saveIODataToDB(ctx, interval)
+ go s.saveNetDataToDB(ctx, interval)
+}
+
+func (s *MonitorImpl) saveIODataToDB(ctx context.Context, interval float64) {
+ defer close(s.diskIO)
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ case ioStat := <-s.diskIO:
+ select {
+ case <-ctx.Done():
+ return
+ case ioStat2 := <-s.diskIO:
+ var ioList []model.MonitorIO
+ timeMilli := time.Now().UnixMilli()
+ for _, io2 := range ioStat2 {
+ for _, io1 := range ioStat {
+ if io2.Name == io1.Name {
+ var itemIO model.MonitorIO
+ itemIO.CreateTime = timeMilli
+ itemIO.NeType = "#"
+ itemIO.NeID = "#"
+ itemIO.Name = io1.Name
+
+ if io2.ReadBytes != 0 && io1.ReadBytes != 0 && io2.ReadBytes > io1.ReadBytes {
+ itemIO.Read = int64(float64(io2.ReadBytes-io1.ReadBytes) / interval / 60)
+ }
+ if io2.WriteBytes != 0 && io1.WriteBytes != 0 && io2.WriteBytes > io1.WriteBytes {
+ itemIO.Write = int64(float64(io2.WriteBytes-io1.WriteBytes) / interval / 60)
+ }
+
+ if io2.ReadCount != 0 && io1.ReadCount != 0 && io2.ReadCount > io1.ReadCount {
+ itemIO.Count = int64(float64(io2.ReadCount-io1.ReadCount) / interval / 60)
+ }
+ writeCount := int64(0)
+ if io2.WriteCount != 0 && io1.WriteCount != 0 && io2.WriteCount > io1.WriteCount {
+ writeCount = int64(float64(io2.WriteCount-io1.WriteCount) / interval * 60)
+ }
+ if writeCount > itemIO.Count {
+ itemIO.Count = writeCount
+ }
+
+ if io2.ReadTime != 0 && io1.ReadTime != 0 && io2.ReadTime > io1.ReadTime {
+ itemIO.Time = int64(float64(io2.ReadTime-io1.ReadTime) / interval / 60)
+ }
+ writeTime := int64(0)
+ if io2.WriteTime != 0 && io1.WriteTime != 0 && io2.WriteTime > io1.WriteTime {
+ writeTime = int64(float64(io2.WriteTime-io1.WriteTime) / interval / 60)
+ }
+ if writeTime > itemIO.Time {
+ itemIO.Time = writeTime
+ }
+ ioList = append(ioList, itemIO)
+ break
+ }
+ }
+ }
+ if err := s.monitorRepository.BatchCreateMonitorIO(ioList); err != nil {
+ logger.Errorf("BatchCreateMonitorIO err: %v", err)
+ }
+ s.diskIO <- ioStat2
+ }
+ }
+ }
+}
+
+func (s *MonitorImpl) saveNetDataToDB(ctx context.Context, interval float64) {
+ defer close(s.netIO)
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ case netStat := <-s.netIO:
+ select {
+ case <-ctx.Done():
+ return
+ case netStat2 := <-s.netIO:
+ var netList []model.MonitorNetwork
+ timeMilli := time.Now().UnixMilli()
+ for _, net2 := range netStat2 {
+ for _, net1 := range netStat {
+ if net2.Name == net1.Name {
+ var itemNet model.MonitorNetwork
+ itemNet.CreateTime = timeMilli
+ itemNet.NeType = "#"
+ itemNet.NeID = "#"
+ itemNet.Name = net1.Name
+
+ if net2.BytesSent != 0 && net1.BytesSent != 0 && net2.BytesSent > net1.BytesSent {
+ itemNet.Up = float64(net2.BytesSent-net1.BytesSent) / 1024 / interval / 60
+ }
+ if net2.BytesRecv != 0 && net1.BytesRecv != 0 && net2.BytesRecv > net1.BytesRecv {
+ itemNet.Down = float64(net2.BytesRecv-net1.BytesRecv) / 1024 / interval / 60
+ }
+ netList = append(netList, itemNet)
+ break
+ }
+ }
+ }
+
+ if err := s.monitorRepository.BatchCreateMonitorNet(netList); err != nil {
+ logger.Errorf("BatchCreateMonitorNet err: %v", err)
+ }
+ s.netIO <- netStat2
+ }
+ }
+ }
+}
+
+// SelectMonitorInfo 查询监控资源信息
+func (s *MonitorImpl) SelectMonitorInfo(query map[string]any) map[string]any {
+ infoType := query["type"]
+ startTimeMilli := query["startTime"]
+ endTimeMilli := query["endTime"]
+ neType := query["neType"]
+ neId := query["neId"]
+ name := query["name"]
+
+ // 返回数据
+ backDatas := map[string]any{}
+
+ // 基本信息
+ if infoType == "all" || infoType == "load" || infoType == "cpu" || infoType == "memory" {
+ rows := s.monitorRepository.SelectMonitorBase(map[string]any{
+ "startTime": startTimeMilli,
+ "endTime": endTimeMilli,
+ "neType": neType,
+ "neId": neId,
+ })
+ backDatas["base"] = rows
+ }
+
+ // 磁盘IO
+ if infoType == "all" || infoType == "io" {
+ rows := s.monitorRepository.SelectMonitorIO(map[string]any{
+ "startTime": startTimeMilli,
+ "endTime": endTimeMilli,
+ "neType": neType,
+ "neId": neId,
+ "name": name,
+ })
+ backDatas["io"] = rows
+ }
+
+ // 网络
+ if infoType == "all" || infoType == "network" {
+ rows := s.monitorRepository.SelectMonitorNetwork(map[string]any{
+ "startTime": startTimeMilli,
+ "endTime": endTimeMilli,
+ "neType": neType,
+ "neId": neId,
+ "name": name,
+ })
+ backDatas["network"] = rows
+ }
+
+ return backDatas
+}
diff --git a/src/modules/monitor/service/monitor_test.go b/src/modules/monitor/service/monitor_test.go
new file mode 100644
index 0000000..11a87a8
--- /dev/null
+++ b/src/modules/monitor/service/monitor_test.go
@@ -0,0 +1,183 @@
+package service
+
+import (
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/shirou/gopsutil/v3/cpu"
+ "github.com/shirou/gopsutil/v3/disk"
+ "github.com/shirou/gopsutil/v3/load"
+ "github.com/shirou/gopsutil/v3/mem"
+ "github.com/shirou/gopsutil/v3/net"
+)
+
+func init() {
+
+}
+
+func TestInfo(t *testing.T) {
+ s := MonitorInfo{}
+ s.load(0.5) // 0.5 半分钟
+
+ fmt.Println(s)
+ select {}
+}
+
+// MonitorInfo 机器资源信息
+type MonitorInfo struct {
+ MonitorBase MonitorBase `json:"base"` // 监控_基本信息
+ MonitorIO []MonitorIO `json:"io"` // 监控_磁盘IO
+ MonitorNetwork []MonitorNetwork `json:"network"` // 监控_网络IO
+}
+
+// load 执行资源获取
+func (m *MonitorInfo) load(interval float64) {
+ var itemBase MonitorBase
+ itemBase.CreateTime = time.Now().UnixMilli()
+
+ totalPercent, _ := cpu.Percent(3*time.Second, false)
+ if len(totalPercent) == 1 {
+ itemBase.CPU = totalPercent[0]
+ }
+ cpuCount, _ := cpu.Counts(false)
+
+ loadInfo, _ := load.Avg()
+ itemBase.CPULoad1 = loadInfo.Load1
+ itemBase.CPULoad5 = loadInfo.Load5
+ itemBase.CPULoad15 = loadInfo.Load15
+ itemBase.LoadUsage = loadInfo.Load1 / (float64(cpuCount*2) * 0.75) * 100
+
+ memoryInfo, _ := mem.VirtualMemory()
+ itemBase.Memory = memoryInfo.UsedPercent
+
+ m.MonitorBase = itemBase
+
+ // 求平均
+ m.MonitorIO = loadDiskIO(interval)
+ m.MonitorNetwork = loadNetIO(interval)
+}
+
+// MonitorBase 监控_基本信息 monitor_base
+type MonitorBase struct {
+ CreateTime int64 `json:"createTime"` // 创建时间
+ CPU float64 `json:"cpu"` // cpu使用率
+ LoadUsage float64 `json:"loadUsage"` // cpu平均使用率
+ CPULoad1 float64 `json:"cpuLoad1"` // cpu使用1分钟
+ CPULoad5 float64 `json:"cpuLoad5"` // cpu使用5分钟
+ CPULoad15 float64 `json:"cpuLoad15"` // cpu使用15分钟
+ Memory float64 `json:"memory"` // 内存使用率
+}
+
+// MonitorIO 监控_磁盘IO monitor_io
+type MonitorIO struct {
+ CreateTime int64 `json:"createTime"` // 创建时间
+ Name string `json:"name"` // 磁盘名
+ Read int64 `json:"read"` // 读取K
+ Write int64 `json:"write"` // 写入K
+ Count int64 `json:"count"` // 次数
+ Time int64 `json:"time"` // 耗时
+}
+
+// MonitorNetwork 监控_网络IO monitor_network
+type MonitorNetwork struct {
+ CreateTime int64 `json:"createTime"` // 创建时间
+ Name string `json:"name"` // 网卡名
+ Up float64 `json:"up"` // 上行
+ Down float64 `json:"down"` // 下行
+}
+
+// loadDiskIO 磁盘读写,interval表示采集的平均值(分钟)
+func loadDiskIO(interval float64) []MonitorIO {
+ ioStat, _ := disk.IOCounters()
+
+ time.Sleep(time.Duration(interval) * time.Minute)
+
+ ioStat2, _ := disk.IOCounters()
+ var ioList []MonitorIO
+ timeMilli := time.Now().UnixMilli()
+ for _, io2 := range ioStat2 {
+ for _, io1 := range ioStat {
+ if io2.Name == io1.Name {
+ var itemIO MonitorIO
+ itemIO.CreateTime = timeMilli
+ itemIO.Name = io1.Name
+
+ if io2.ReadBytes != 0 && io1.ReadBytes != 0 && io2.ReadBytes > io1.ReadBytes {
+ itemIO.Read = int64(float64(io2.ReadBytes-io1.ReadBytes) / interval / 60)
+ }
+ if io2.WriteBytes != 0 && io1.WriteBytes != 0 && io2.WriteBytes > io1.WriteBytes {
+ itemIO.Write = int64(float64(io2.WriteBytes-io1.WriteBytes) / interval / 60)
+ }
+
+ if io2.ReadCount != 0 && io1.ReadCount != 0 && io2.ReadCount > io1.ReadCount {
+ itemIO.Count = int64(float64(io2.ReadCount-io1.ReadCount) / interval / 60)
+ }
+ writeCount := int64(0)
+ if io2.WriteCount != 0 && io1.WriteCount != 0 && io2.WriteCount > io1.WriteCount {
+ writeCount = int64(float64(io2.WriteCount-io1.WriteCount) / interval * 60)
+ }
+ if writeCount > itemIO.Count {
+ itemIO.Count = writeCount
+ }
+
+ if io2.ReadTime != 0 && io1.ReadTime != 0 && io2.ReadTime > io1.ReadTime {
+ itemIO.Time = int64(float64(io2.ReadTime-io1.ReadTime) / interval / 60)
+ }
+ writeTime := int64(0)
+ if io2.WriteTime != 0 && io1.WriteTime != 0 && io2.WriteTime > io1.WriteTime {
+ writeTime = int64(float64(io2.WriteTime-io1.WriteTime) / interval / 60)
+ }
+ if writeTime > itemIO.Time {
+ itemIO.Time = writeTime
+ }
+ ioList = append(ioList, itemIO)
+ break
+ }
+ }
+ }
+ return ioList
+}
+
+// loadNetIO 网络接口(包括虚拟接口),interval表示采集的平均值(分钟)
+func loadNetIO(interval float64) []MonitorNetwork {
+ // 获取当前时刻
+ netStat, _ := net.IOCounters(true)
+ netStatAll, _ := net.IOCounters(false)
+ var netStatList []net.IOCountersStat
+ netStatList = append(netStatList, netStat...)
+ netStatList = append(netStatList, netStatAll...)
+
+ time.Sleep(time.Duration(interval) * time.Minute)
+
+ // 获取结束时刻
+ netStat2, _ := net.IOCounters(true)
+ netStat2All, _ := net.IOCounters(false)
+ var netStat2List []net.IOCountersStat
+ netStat2List = append(netStat2List, netStat2...)
+ netStat2List = append(netStat2List, netStat2All...)
+
+ var netList []MonitorNetwork
+ timeMilli := time.Now().UnixMilli()
+ for _, net2 := range netStat2List {
+ for _, net1 := range netStatList {
+ if net2.Name == net1.Name {
+ var itemNet MonitorNetwork
+ itemNet.CreateTime = timeMilli
+ itemNet.Name = net1.Name
+
+ // 如果结束时刻发送字节数和当前时刻发送字节数都不为零,并且结束时刻发送字节数大于当前时刻发送字节数
+ if net2.BytesSent != 0 && net1.BytesSent != 0 && net2.BytesSent > net1.BytesSent {
+ itemNet.Up = float64(net2.BytesSent-net1.BytesSent) / 1024 / interval / 60
+ }
+ if net2.BytesRecv != 0 && net1.BytesRecv != 0 && net2.BytesRecv > net1.BytesRecv {
+ itemNet.Down = float64(net2.BytesRecv-net1.BytesRecv) / 1024 / interval / 60
+ }
+ netList = append(netList, itemNet)
+ break
+ }
+ }
+ }
+
+ return netList
+}
diff --git a/src/modules/monitor/service/sys_job.go b/src/modules/monitor/service/sys_job.go
new file mode 100644
index 0000000..71fe196
--- /dev/null
+++ b/src/modules/monitor/service/sys_job.go
@@ -0,0 +1,35 @@
+package service
+
+import (
+ "nms_nbi/src/modules/monitor/model"
+)
+
+// ISysJob 调度任务信息 服务层接口
+type ISysJob interface {
+ // SelectJobPage 分页查询调度任务集合
+ SelectJobPage(query map[string]any) map[string]any
+
+ // SelectJobList 查询调度任务集合
+ SelectJobList(sysJob model.SysJob) []model.SysJob
+
+ // SelectJobById 通过调度ID查询调度任务信息
+ SelectJobById(jobId string) model.SysJob
+
+ // CheckUniqueJobName 校验调度任务名称和组是否唯一
+ CheckUniqueJobName(jobName, jobGroup, jobId string) bool
+
+ // InsertJob 新增调度任务信息
+ InsertJob(sysJob model.SysJob) string
+
+ // UpdateJob 修改调度任务信息
+ UpdateJob(sysJob model.SysJob) int64
+
+ // DeleteJobByIds 批量删除调度任务信息
+ DeleteJobByIds(jobIds []string) (int64, error)
+
+ // RunQueueJob 立即运行一次调度任务
+ RunQueueJob(sysJob model.SysJob) bool
+
+ // ResetQueueJob 重置初始调度任务
+ ResetQueueJob()
+}
diff --git a/src/modules/monitor/service/sys_job.impl.go b/src/modules/monitor/service/sys_job.impl.go
new file mode 100644
index 0000000..5a63241
--- /dev/null
+++ b/src/modules/monitor/service/sys_job.impl.go
@@ -0,0 +1,169 @@
+package service
+
+import (
+ "fmt"
+
+ "nms_nbi/src/framework/constants/common"
+ "nms_nbi/src/framework/cron"
+ "nms_nbi/src/modules/monitor/model"
+ "nms_nbi/src/modules/monitor/repository"
+)
+
+// 实例化服务层 SysJobImpl 结构体
+var NewSysJobImpl = &SysJobImpl{
+ sysJobRepository: repository.NewSysJobImpl,
+}
+
+// SysJobImpl 调度任务 服务层处理
+type SysJobImpl struct {
+ // 调度任务数据信息
+ sysJobRepository repository.ISysJob
+}
+
+// SelectJobPage 分页查询调度任务集合
+func (r *SysJobImpl) SelectJobPage(query map[string]any) map[string]any {
+ return r.sysJobRepository.SelectJobPage(query)
+}
+
+// SelectJobList 查询调度任务集合
+func (r *SysJobImpl) SelectJobList(sysJob model.SysJob) []model.SysJob {
+ return r.sysJobRepository.SelectJobList(sysJob)
+}
+
+// SelectJobById 通过调度ID查询调度任务信息
+func (r *SysJobImpl) SelectJobById(jobId string) model.SysJob {
+ if jobId == "" {
+ return model.SysJob{}
+ }
+ jobs := r.sysJobRepository.SelectJobByIds([]string{jobId})
+ if len(jobs) > 0 {
+ return jobs[0]
+ }
+ return model.SysJob{}
+}
+
+// CheckUniqueJobName 校验调度任务名称和组是否唯一
+func (r *SysJobImpl) CheckUniqueJobName(jobName, jobGroup, jobId string) bool {
+ uniqueId := r.sysJobRepository.CheckUniqueJob(model.SysJob{
+ JobName: jobName,
+ JobGroup: jobGroup,
+ })
+ if uniqueId == jobId {
+ return true
+ }
+ return uniqueId == ""
+}
+
+// InsertJob 新增调度任务信息
+func (r *SysJobImpl) InsertJob(sysJob model.SysJob) string {
+ insertId := r.sysJobRepository.InsertJob(sysJob)
+ if insertId == "" && sysJob.Status == common.STATUS_YES {
+ sysJob.JobID = insertId
+ r.insertQueueJob(sysJob, true)
+ }
+ return insertId
+}
+
+// UpdateJob 修改调度任务信息
+func (r *SysJobImpl) UpdateJob(sysJob model.SysJob) int64 {
+ rows := r.sysJobRepository.UpdateJob(sysJob)
+ if rows > 0 {
+ //状态正常添加队列任务
+ if sysJob.Status == common.STATUS_YES {
+ r.insertQueueJob(sysJob, true)
+ }
+ // 状态禁用删除队列任务
+ if sysJob.Status == common.STATUS_NO {
+ r.deleteQueueJob(sysJob)
+ }
+ }
+ return rows
+}
+
+// DeleteJobByIds 批量删除调度任务信息
+func (r *SysJobImpl) DeleteJobByIds(jobIds []string) (int64, error) {
+ // 检查是否存在
+ jobs := r.sysJobRepository.SelectJobByIds(jobIds)
+ if len(jobs) <= 0 {
+ // 没有可访问调度任务数据!
+ return 0, fmt.Errorf("there is no accessible scheduling task data")
+ }
+ if len(jobs) == len(jobIds) {
+ // 清除任务
+ for _, job := range jobs {
+ r.deleteQueueJob(job)
+ }
+ rows := r.sysJobRepository.DeleteJobByIds(jobIds)
+ return rows, nil
+ }
+ // 删除调度任务信息失败!
+ return 0, fmt.Errorf("failed to delete scheduling task information")
+}
+
+// ResetQueueJob 重置初始调度任务
+func (r *SysJobImpl) ResetQueueJob() {
+ // 获取注册的队列名称
+ queueNames := cron.QueueNames()
+ if len(queueNames) == 0 {
+ return
+ }
+ // 查询系统中定义状态为正常启用的任务
+ sysJobs := r.sysJobRepository.SelectJobList(model.SysJob{
+ Status: common.STATUS_YES,
+ })
+ for _, sysJob := range sysJobs {
+ for _, name := range queueNames {
+ if name == sysJob.InvokeTarget {
+ r.insertQueueJob(sysJob, true)
+ }
+ }
+ }
+}
+
+// RunQueueJob 立即运行一次调度任务
+func (r *SysJobImpl) RunQueueJob(sysJob model.SysJob) bool {
+ return r.insertQueueJob(sysJob, false)
+}
+
+// insertQueueJob 添加调度任务
+func (r *SysJobImpl) insertQueueJob(sysJob model.SysJob, repeat bool) bool {
+ // 获取队列 Processor
+ queue := cron.GetQueue(sysJob.InvokeTarget)
+ if queue.Name != sysJob.InvokeTarget {
+ return false
+ }
+
+ // 给执行任务数据参数
+ options := cron.JobData{
+ Repeat: repeat,
+ SysJob: sysJob,
+ }
+
+ // 不是重复任务的情况,立即执行一次
+ if !repeat {
+ // 执行单次任务
+ status := queue.RunJob(options, cron.JobOptions{
+ JobId: sysJob.JobID,
+ })
+ // 执行中或等待中的都返回正常
+ return status == cron.Active || status == cron.Waiting
+ }
+
+ // 执行重复任务
+ queue.RunJob(options, cron.JobOptions{
+ JobId: sysJob.JobID,
+ Cron: sysJob.CronExpression,
+ })
+
+ return true
+}
+
+// deleteQueueJob 删除调度任务
+func (r *SysJobImpl) deleteQueueJob(sysJob model.SysJob) bool {
+ // 获取队列 Processor
+ queue := cron.GetQueue(sysJob.InvokeTarget)
+ if queue.Name != sysJob.InvokeTarget {
+ return false
+ }
+ return queue.RemoveJob(sysJob.JobID)
+}
diff --git a/src/modules/monitor/service/sys_job_log.go b/src/modules/monitor/service/sys_job_log.go
new file mode 100644
index 0000000..227a4ef
--- /dev/null
+++ b/src/modules/monitor/service/sys_job_log.go
@@ -0,0 +1,23 @@
+package service
+
+import (
+ "nms_nbi/src/modules/monitor/model"
+)
+
+// ISysJobLog 调度任务日志 服务层接口
+type ISysJobLog interface {
+ // SelectJobLogPage 分页查询调度任务日志集合
+ SelectJobLogPage(query map[string]any) map[string]any
+
+ // SelectJobLogList 查询调度任务日志集合
+ SelectJobLogList(sysJobLog model.SysJobLog) []model.SysJobLog
+
+ // SelectJobLogById 通过调度ID查询调度任务日志信息
+ SelectJobLogById(jobLogId string) model.SysJobLog
+
+ // DeleteJobLogByIds 批量删除调度任务日志信息
+ DeleteJobLogByIds(jobLogIds []string) int64
+
+ // CleanJobLog 清空调度任务日志
+ CleanJobLog() error
+}
diff --git a/src/modules/monitor/service/sys_job_log.impl.go b/src/modules/monitor/service/sys_job_log.impl.go
new file mode 100644
index 0000000..d9ff155
--- /dev/null
+++ b/src/modules/monitor/service/sys_job_log.impl.go
@@ -0,0 +1,42 @@
+package service
+
+import (
+ "nms_nbi/src/modules/monitor/model"
+ "nms_nbi/src/modules/monitor/repository"
+)
+
+// 实例化服务层 SysJobLogImpl 结构体
+var NewSysJobLogImpl = &SysJobLogImpl{
+ sysJobLogRepository: repository.NewSysJobLogImpl,
+}
+
+// SysJobLogImpl 调度任务日志 服务层处理
+type SysJobLogImpl struct {
+ // 调度任务日志数据信息
+ sysJobLogRepository repository.ISysJobLog
+}
+
+// SelectJobLogPage 分页查询调度任务日志集合
+func (s *SysJobLogImpl) SelectJobLogPage(query map[string]any) map[string]any {
+ return s.sysJobLogRepository.SelectJobLogPage(query)
+}
+
+// SelectJobLogList 查询调度任务日志集合
+func (s *SysJobLogImpl) SelectJobLogList(sysJobLog model.SysJobLog) []model.SysJobLog {
+ return s.sysJobLogRepository.SelectJobLogList(sysJobLog)
+}
+
+// SelectJobLogById 通过调度ID查询调度任务日志信息
+func (s *SysJobLogImpl) SelectJobLogById(jobLogId string) model.SysJobLog {
+ return s.sysJobLogRepository.SelectJobLogById(jobLogId)
+}
+
+// DeleteJobLogByIds 批量删除调度任务日志信息
+func (s *SysJobLogImpl) DeleteJobLogByIds(jobLogIds []string) int64 {
+ return s.sysJobLogRepository.DeleteJobLogByIds(jobLogIds)
+}
+
+// CleanJobLog 清空调度任务日志
+func (s *SysJobLogImpl) CleanJobLog() error {
+ return s.sysJobLogRepository.CleanJobLog()
+}
diff --git a/src/modules/monitor/service/sys_user_online.go b/src/modules/monitor/service/sys_user_online.go
new file mode 100644
index 0000000..c26a83e
--- /dev/null
+++ b/src/modules/monitor/service/sys_user_online.go
@@ -0,0 +1,12 @@
+package service
+
+import (
+ "nms_nbi/src/framework/vo"
+ "nms_nbi/src/modules/monitor/model"
+)
+
+// ISysUserOnline 在线用户 服务层接口
+type ISysUserOnline interface {
+ // LoginUserToUserOnline 设置在线用户信息
+ LoginUserToUserOnline(loginUser vo.LoginUser) model.SysUserOnline
+}
diff --git a/src/modules/monitor/service/sys_user_online.impl.go b/src/modules/monitor/service/sys_user_online.impl.go
new file mode 100644
index 0000000..75b4f73
--- /dev/null
+++ b/src/modules/monitor/service/sys_user_online.impl.go
@@ -0,0 +1,33 @@
+package service
+
+import (
+ "nms_nbi/src/framework/vo"
+ "nms_nbi/src/modules/monitor/model"
+)
+
+// 实例化服务层 SysUserOnlineImpl 结构体
+var NewSysUserOnlineImpl = &SysUserOnlineImpl{}
+
+// SysUserOnlineImpl 在线用户 服务层处理
+type SysUserOnlineImpl struct{}
+
+// LoginUserToUserOnline 设置在线用户信息
+func (r *SysUserOnlineImpl) LoginUserToUserOnline(loginUser vo.LoginUser) model.SysUserOnline {
+ if loginUser.UserID == "" {
+ return model.SysUserOnline{}
+ }
+
+ sysUserOnline := model.SysUserOnline{
+ TokenID: loginUser.UUID,
+ UserName: loginUser.User.UserName,
+ IPAddr: loginUser.IPAddr,
+ LoginLocation: loginUser.LoginLocation,
+ Browser: loginUser.Browser,
+ OS: loginUser.OS,
+ LoginTime: loginUser.LoginTime,
+ }
+ if loginUser.User.DeptID != "" {
+ sysUserOnline.DeptName = loginUser.User.Dept.DeptName
+ }
+ return sysUserOnline
+}
diff --git a/src/modules/monitor/service/system_info.go b/src/modules/monitor/service/system_info.go
new file mode 100644
index 0000000..a6e9a51
--- /dev/null
+++ b/src/modules/monitor/service/system_info.go
@@ -0,0 +1,22 @@
+package service
+
+// ISystemInfo 服务器系统相关信息 服务层接口
+type ISystemInfo interface {
+ // SystemInfo 系统信息
+ SystemInfo() map[string]any
+
+ // TimeInfo 系统时间信息
+ TimeInfo() map[string]string
+
+ // MemoryInfo 内存信息
+ MemoryInfo() map[string]any
+
+ // CPUInfo CPU信息
+ CPUInfo() map[string]any
+
+ // NetworkInfo 网络信息
+ NetworkInfo() map[string]string
+
+ // DiskInfo 磁盘信息
+ DiskInfo() []map[string]string
+}
diff --git a/src/modules/monitor/service/system_info.impl.go b/src/modules/monitor/service/system_info.impl.go
new file mode 100644
index 0000000..ef31b3e
--- /dev/null
+++ b/src/modules/monitor/service/system_info.impl.go
@@ -0,0 +1,173 @@
+package service
+
+import (
+ "fmt"
+ "os"
+ "runtime"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/config"
+ "nms_nbi/src/framework/utils/parse"
+
+ "github.com/shirou/gopsutil/v3/cpu"
+ "github.com/shirou/gopsutil/v3/disk"
+ "github.com/shirou/gopsutil/v3/host"
+ "github.com/shirou/gopsutil/v3/mem"
+ "github.com/shirou/gopsutil/v3/net"
+)
+
+// 实例化服务层 SystemInfoImpl 结构体
+var NewSystemInfoImpl = &SystemInfoImpl{}
+
+// SystemInfoImpl 服务器系统相关信息 服务层处理
+type SystemInfoImpl struct{}
+
+// SystemInfo 系统信息
+func (s *SystemInfoImpl) SystemInfo() map[string]any {
+ info, err := host.Info()
+ if err != nil {
+ info.Platform = err.Error()
+ }
+ // 获取主机运行时间
+ bootTime := time.Since(time.Unix(int64(info.BootTime), 0)).Seconds()
+ // 获取程序运行时间
+ runTime := time.Since(config.RunTime()).Abs().Seconds()
+ return map[string]any{
+ "platform": info.Platform,
+ "platformVersion": info.PlatformVersion,
+ "arch": info.KernelArch,
+ "archVersion": info.KernelVersion,
+ "os": info.OS,
+ "hostname": info.Hostname,
+ "bootTime": int64(bootTime),
+ "processId": os.Getpid(),
+ "runArch": runtime.GOARCH,
+ "runVersion": runtime.Version(),
+ "runTime": int64(runTime),
+ }
+}
+
+// TimeInfo 系统时间信息
+func (s *SystemInfoImpl) TimeInfo() map[string]string {
+ now := time.Now()
+ // 获取当前时间
+ current := now.Format("2006-01-02 15:04:05")
+ // 获取时区
+ timezone := now.Format("-0700 MST")
+ // 获取时区名称
+ timezoneName := now.Format("MST")
+
+ return map[string]string{
+ "current": current,
+ "timezone": timezone,
+ "timezoneName": timezoneName,
+ }
+}
+
+// MemoryInfo 内存信息
+func (s *SystemInfoImpl) MemoryInfo() map[string]any {
+ memInfo, err := mem.VirtualMemory()
+ if err != nil {
+ memInfo.UsedPercent = 0
+ memInfo.Available = 0
+ memInfo.Total = 0
+ }
+
+ var memStats runtime.MemStats
+ runtime.ReadMemStats(&memStats)
+
+ return map[string]any{
+ "usage": fmt.Sprintf("%.2f", memInfo.UsedPercent), // 内存利用率
+ "freemem": parse.Bit(float64(memInfo.Available)), // 可用内存大小(GB)
+ "totalmem": parse.Bit(float64(memInfo.Total)), // 总内存大小(GB)
+ "rss": parse.Bit(float64(memStats.Sys)), // 常驻内存大小(RSS)
+ "heapTotal": parse.Bit(float64(memStats.HeapSys)), // 堆总大小
+ "heapUsed": parse.Bit(float64(memStats.HeapAlloc)), // 堆已使用大小
+ "external": parse.Bit(float64(memStats.Sys - memStats.HeapSys)), // 外部内存大小(非堆)
+ }
+}
+
+// CPUInfo CPU信息
+func (s *SystemInfoImpl) CPUInfo() map[string]any {
+ var core int = 0
+ var speed string = "未知"
+ var model string = "未知"
+ cpuInfo, err := cpu.Info()
+ if err == nil {
+ core = runtime.NumCPU()
+ speed = fmt.Sprintf("%.0fMHz", cpuInfo[0].Mhz)
+ model = strings.TrimSpace(cpuInfo[0].ModelName)
+ }
+
+ useds := []string{}
+ cpuPercent, err := cpu.Percent(0, true)
+ if err == nil {
+ for _, v := range cpuPercent {
+ useds = append(useds, fmt.Sprintf("%.2f", v))
+ }
+ }
+
+ return map[string]any{
+ "model": model,
+ "speed": speed,
+ "core": core,
+ "coreUsed": useds,
+ }
+}
+
+// NetworkInfo 网络信息
+func (s *SystemInfoImpl) NetworkInfo() map[string]string {
+ ipAddrs := make(map[string]string)
+ interfaces, err := net.Interfaces()
+ if err == nil {
+ for _, iface := range interfaces {
+ name := iface.Name
+ if name[len(name)-1] == '0' {
+ name = name[0 : len(name)-1]
+ name = strings.Trim(name, "")
+ }
+ // ignore localhost
+ if name == "lo" {
+ continue
+ }
+ var addrs []string
+ for _, v := range iface.Addrs {
+ prefix := strings.Split(v.Addr, "/")[0]
+ if strings.Contains(prefix, "::") {
+ addrs = append(addrs, fmt.Sprintf("IPv6 %s", prefix))
+ }
+ if strings.Contains(prefix, ".") {
+ addrs = append(addrs, fmt.Sprintf("IPv4 %s", prefix))
+ }
+ }
+ ipAddrs[name] = strings.Join(addrs, " / ")
+ }
+ }
+ return ipAddrs
+}
+
+// DiskInfo 磁盘信息
+func (s *SystemInfoImpl) DiskInfo() []map[string]string {
+ disks := make([]map[string]string, 0)
+
+ partitions, err := disk.Partitions(false)
+ if err != nil {
+ return disks
+ }
+
+ for _, partition := range partitions {
+ usage, err := disk.Usage(partition.Mountpoint)
+ if err != nil {
+ continue
+ }
+ disks = append(disks, map[string]string{
+ "size": parse.Bit(float64(usage.Total)),
+ "used": parse.Bit(float64(usage.Used)),
+ "avail": parse.Bit(float64(usage.Free)),
+ "pcent": fmt.Sprintf("%.1f%%", usage.UsedPercent),
+ "target": partition.Device,
+ })
+ }
+ return disks
+}
diff --git a/src/modules/network_data/controller/amf.go b/src/modules/network_data/controller/amf.go
new file mode 100644
index 0000000..7fd225b
--- /dev/null
+++ b/src/modules/network_data/controller/amf.go
@@ -0,0 +1,81 @@
+package controller
+
+import (
+ "strings"
+
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/network_data/model"
+ neDataService "nms_nbi/src/modules/network_data/service"
+ neService "nms_nbi/src/modules/network_element/service"
+
+ "github.com/gin-gonic/gin"
+)
+
+// 实例化控制层 AMFController 结构体
+var NewAMFController = &AMFController{
+ neInfoService: neService.NewNeInfoImpl,
+ ueEventService: neDataService.NewUEEventImpl,
+}
+
+// 网元AMF
+//
+// PATH /amf
+type AMFController struct {
+ // 网元信息服务
+ neInfoService neService.INeInfo
+ // CDR会话事件服务
+ ueEventService neDataService.IUEEvent
+}
+
+// UE会话列表
+//
+// GET /ue/list
+func (s *AMFController) UEList(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var querys model.UEEventQuery
+ if err := c.ShouldBindQuery(&querys); err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 查询网元获取IP
+ neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID(querys.NeType, querys.NeID)
+ if neInfo.NeId != querys.NeID || neInfo.IP == "" {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
+ return
+ }
+ querys.RmUID = neInfo.RmUID
+
+ // 查询数据
+ data := s.ueEventService.SelectPage(querys)
+ c.JSON(200, result.Ok(data))
+}
+
+// UE会话删除
+//
+// DELETE /ue/:ueIds
+func (s *AMFController) UERemove(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ ueIds := c.Param("ueIds")
+ if ueIds == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ // 处理字符转id数组后去重
+ ids := strings.Split(ueIds, ",")
+ uniqueIDs := parse.RemoveDuplicates(ids)
+ if len(uniqueIDs) <= 0 {
+ c.JSON(200, result.Err(nil))
+ return
+ }
+ rows, err := s.ueEventService.DeleteByIds(uniqueIDs)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error())))
+ return
+ }
+ msg := i18n.TTemplate(language, "app.common.deleteSuccess", map[string]any{"num": rows})
+ c.JSON(200, result.OkMsg(msg))
+}
diff --git a/src/modules/network_data/controller/ims.go b/src/modules/network_data/controller/ims.go
new file mode 100644
index 0000000..6655521
--- /dev/null
+++ b/src/modules/network_data/controller/ims.go
@@ -0,0 +1,81 @@
+package controller
+
+import (
+ "strings"
+
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/network_data/model"
+ neDataService "nms_nbi/src/modules/network_data/service"
+ neService "nms_nbi/src/modules/network_element/service"
+
+ "github.com/gin-gonic/gin"
+)
+
+// 实例化控制层 IMSController 结构体
+var NewIMSController = &IMSController{
+ neInfoService: neService.NewNeInfoImpl,
+ cdrEventService: neDataService.NewCDREventImpl,
+}
+
+// 网元IMS
+//
+// PATH /ims
+type IMSController struct {
+ // 网元信息服务
+ neInfoService neService.INeInfo
+ // CDR会话事件服务
+ cdrEventService neDataService.ICDREvent
+}
+
+// CDR会话列表
+//
+// GET /cdr/list
+func (s *IMSController) CDRList(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var querys model.CDREventQuery
+ if err := c.ShouldBindQuery(&querys); err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 查询网元获取IP
+ neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID(querys.NeType, querys.NeID)
+ if neInfo.NeId != querys.NeID || neInfo.IP == "" {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
+ return
+ }
+ querys.RmUID = neInfo.RmUID
+
+ // 查询数据
+ data := s.cdrEventService.SelectPage(querys)
+ c.JSON(200, result.Ok(data))
+}
+
+// CDR会话删除
+//
+// DELETE /cdr/:cdrIds
+func (s *IMSController) CDRRemove(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ cdrIds := c.Param("cdrIds")
+ if cdrIds == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ // 处理字符转id数组后去重
+ ids := strings.Split(cdrIds, ",")
+ uniqueIDs := parse.RemoveDuplicates(ids)
+ if len(uniqueIDs) <= 0 {
+ c.JSON(200, result.Err(nil))
+ return
+ }
+ rows, err := s.cdrEventService.DeleteByIds(uniqueIDs)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error())))
+ return
+ }
+ msg := i18n.TTemplate(language, "app.common.deleteSuccess", map[string]any{"num": rows})
+ c.JSON(200, result.OkMsg(msg))
+}
diff --git a/src/modules/network_data/controller/perf_kpi.go b/src/modules/network_data/controller/perf_kpi.go
new file mode 100644
index 0000000..2d7f5f0
--- /dev/null
+++ b/src/modules/network_data/controller/perf_kpi.go
@@ -0,0 +1,85 @@
+package controller
+
+import (
+ "nms_nbi/lib/core/utils/date"
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/network_data/model"
+ neDataService "nms_nbi/src/modules/network_data/service"
+ neService "nms_nbi/src/modules/network_element/service"
+
+ "github.com/gin-gonic/gin"
+)
+
+// 实例化控制层 PerfKPIController 结构体
+var NewPerfKPIController = &PerfKPIController{
+ neInfoService: neService.NewNeInfoImpl,
+ perfKPIService: neDataService.NewPerfKPIImpl,
+}
+
+// 性能统计
+//
+// PATH /kpi
+type PerfKPIController struct {
+ // 网元信息服务
+ neInfoService neService.INeInfo
+ // 统计信息服务
+ perfKPIService neDataService.IPerfKPI
+}
+
+// 获取统计数据
+//
+// GET /data
+func (s *PerfKPIController) GoldKPI(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var querys model.GoldKPIQuery
+ if err := c.ShouldBindQuery(&querys); err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 时间格式校验
+ startTime := date.ParseStrToDate(querys.StartTime, date.YYYY_MM_DD_HH_MM_SS)
+ if startTime.IsZero() {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ endTime := date.ParseStrToDate(querys.EndTime, date.YYYY_MM_DD_HH_MM_SS)
+ if endTime.IsZero() {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ if querys.Interval < 5 || querys.Interval > 3600 {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 查询网元获取IP
+ neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID(querys.NeType, querys.NeID)
+ if neInfo.NeId != querys.NeID || neInfo.IP == "" {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
+ return
+ }
+ querys.RmUID = neInfo.RmUID
+
+ // 查询数据
+ kpiData := s.perfKPIService.SelectGoldKPI(querys)
+ c.JSON(200, result.OkData(kpiData))
+}
+
+// 获取统计标题
+//
+// GET /title
+func (s *PerfKPIController) Title(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ neType := c.Query("neType")
+ if neType == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ kpiTitles := s.perfKPIService.SelectGoldKPITitle(neType)
+
+ c.JSON(200, result.OkData(kpiTitles))
+}
diff --git a/src/modules/network_data/controller/upf.go b/src/modules/network_data/controller/upf.go
new file mode 100644
index 0000000..05ceabb
--- /dev/null
+++ b/src/modules/network_data/controller/upf.go
@@ -0,0 +1,55 @@
+package controller
+
+import (
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/vo/result"
+ neDataService "nms_nbi/src/modules/network_data/service"
+ neService "nms_nbi/src/modules/network_element/service"
+
+ "github.com/gin-gonic/gin"
+)
+
+// 实例化控制层 UPFController 结构体
+var NewUPFController = &UPFController{
+ neInfoService: neService.NewNeInfoImpl,
+ perfKPIService: neDataService.NewPerfKPIImpl,
+}
+
+// 网元UPF
+//
+// PATH /upf
+type UPFController struct {
+ // 网元信息服务
+ neInfoService neService.INeInfo
+ // 统计信息服务
+ perfKPIService neDataService.IPerfKPI
+}
+
+// 总流量数 N3上行 N6下行
+// 单位 比特(bit)
+//
+// GET /totalFlow
+func (s *UPFController) TotalFlow(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var querys struct {
+ NeType string `json:"neType" form:"neType" binding:"required"`
+ NeID string `form:"neId" binding:"required"`
+ Day int `form:"day" binding:"required"`
+ }
+ if err := c.ShouldBindQuery(&querys); querys.Day < 0 || err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 查询网元获取IP
+ neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID(querys.NeType, querys.NeID)
+ if neInfo.NeId != querys.NeID || neInfo.IP == "" {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
+ return
+ }
+
+ data := s.perfKPIService.SelectUPFTotalFlow(neInfo.NeType, neInfo.RmUID, querys.Day)
+
+ c.JSON(200, result.OkData(data))
+}
diff --git a/src/modules/network_data/model/cdr_event.go b/src/modules/network_data/model/cdr_event.go
new file mode 100644
index 0000000..a4721d3
--- /dev/null
+++ b/src/modules/network_data/model/cdr_event.go
@@ -0,0 +1,28 @@
+package model
+
+import "time"
+
+// CDREvent CDR会话对象 cdr_event
+type CDREvent 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"`
+ 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"`
+}
+
+// CDREventQuery CDR会话对象查询参数结构体
+type CDREventQuery 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
+ StartTime string `json:"startTime" form:"startTime"`
+ EndTime string `json:"endTime" form:"endTime"`
+ SortField string `json:"sortField" form:"sortField" binding:"omitempty,oneof=timestamp"` // 排序字段,填写结果字段
+ SortOrder string `json:"sortOrder" form:"sortOrder" binding:"omitempty,oneof=asc desc"` // 排序升降序,asc desc
+ PageNum int64 `json:"pageNum" form:"pageNum" binding:"required"`
+ PageSize int64 `json:"pageSize" form:"pageSize" binding:"required"`
+}
diff --git a/src/modules/network_data/model/perf_kpi.go b/src/modules/network_data/model/perf_kpi.go
new file mode 100644
index 0000000..108d81f
--- /dev/null
+++ b/src/modules/network_data/model/perf_kpi.go
@@ -0,0 +1,23 @@
+package model
+
+// GoldKPITitle 黄金指标标题信息对象 kpi_title
+type GoldKPITitle struct {
+ ID string `json:"id" gorm:"column:id;primaryKey;autoIncrement"`
+ NeType string `json:"neType" gorm:"column:ne_type"`
+ KPIID string `json:"kpiId" gorm:"column:kpi_id"`
+ TitleJson string `json:"titleJson" gorm:"column:title_json"`
+ CnTitle string `json:"cnTitle" gorm:"column:cn_title"`
+ EnTitle string `json:"enTitle" gorm:"column:en_title"`
+}
+
+// GoldKPIQuery 黄金指标查询参数结构体
+type GoldKPIQuery struct {
+ NeType string `form:"neType" binding:"required"`
+ NeID string `form:"neId" binding:"required"`
+ StartTime string `form:"startTime" binding:"required"`
+ EndTime string `form:"endTime" binding:"required"`
+ Interval int64 `form:"interval" binding:"required"`
+ RmUID string `form:"rmUID"`
+ SortField string `form:"sortField" binding:"omitempty,oneof=timeGroup"`
+ SortOrder string `form:"sortOrder" binding:"omitempty,oneof=asc desc"`
+}
diff --git a/src/modules/network_data/model/ue_event.go b/src/modules/network_data/model/ue_event.go
new file mode 100644
index 0000000..d39dd63
--- /dev/null
+++ b/src/modules/network_data/model/ue_event.go
@@ -0,0 +1,29 @@
+package model
+
+import "time"
+
+// UEEvent UE会话对象 ue_event
+type UEEvent 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"`
+ Timestamp int64 `json:"timestamp" gorm:"column:timestamp"`
+ EventType string `json:"eventType" gorm:"column:event_type"`
+ EventJSONStr string `json:"eventJSON" gorm:"column:event_json"`
+ CreatedAt time.Time `json:"createdAt" gorm:"column:created_at;default:CURRENT_TIMESTAMP"`
+}
+
+// UEEventQuery UE会话对象查询参数结构体
+type UEEventQuery struct {
+ NeType string `json:"neType" form:"neType" binding:"required"` // 网元类型, 暂时支持AMF
+ NeID string `json:"neId" form:"neId" binding:"required"`
+ RmUID string `json:"rmUID" form:"rmUID"`
+ EventType string `json:"eventType" form:"eventType"` // 事件类型 auth-result detach cm-state
+ StartTime string `json:"startTime" form:"startTime"`
+ EndTime string `json:"endTime" form:"endTime"`
+ SortField string `json:"sortField" form:"sortField" binding:"omitempty,oneof=timestamp"` // 排序字段,填写结果字段
+ SortOrder string `json:"sortOrder" form:"sortOrder" binding:"omitempty,oneof=asc desc"` // 排序升降序,asc desc
+ PageNum int64 `json:"pageNum" form:"pageNum" binding:"required"`
+ PageSize int64 `json:"pageSize" form:"pageSize" binding:"required"`
+}
diff --git a/src/modules/network_data/network_data.go b/src/modules/network_data/network_data.go
new file mode 100644
index 0000000..10e8df7
--- /dev/null
+++ b/src/modules/network_data/network_data.go
@@ -0,0 +1,72 @@
+package networkdata
+
+import (
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/middleware"
+ "nms_nbi/src/framework/middleware/collectlogs"
+ "nms_nbi/src/modules/network_data/controller"
+
+ "github.com/gin-gonic/gin"
+)
+
+// 模块路由注册
+func Setup(router *gin.Engine) {
+ logger.Infof("开始加载 ====> network_data 模块路由")
+
+ neDataGroup := router.Group("/neData")
+
+ // 性能统计信息
+ kpiGroup := neDataGroup.Group("/kpi")
+ {
+ kpiGroup.GET("/title",
+ middleware.PreAuthorize(nil),
+ controller.NewPerfKPIController.Title,
+ )
+ kpiGroup.GET("/data",
+ middleware.PreAuthorize(nil),
+ controller.NewPerfKPIController.GoldKPI,
+ )
+ }
+
+ // 网元IMS
+ imsGroup := neDataGroup.Group("/ims")
+ {
+ // CDR会话事件列表
+ imsGroup.GET("/cdr/list",
+ middleware.PreAuthorize(nil),
+ controller.NewIMSController.CDRList,
+ )
+ // CDR会话删除
+ imsGroup.DELETE("/cdr/:cdrIds",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.imsCDR", collectlogs.BUSINESS_TYPE_DELETE)),
+ controller.NewIMSController.CDRRemove,
+ )
+ }
+
+ // 网元AMF
+ amfGroup := neDataGroup.Group("/amf")
+ {
+ // UE会话事件
+ amfGroup.GET("/ue/list",
+ middleware.PreAuthorize(nil),
+ controller.NewAMFController.UEList,
+ )
+ // UE会话删除
+ amfGroup.DELETE("/ue/:ueIds",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.amfUE", collectlogs.BUSINESS_TYPE_DELETE)),
+ controller.NewAMFController.UERemove,
+ )
+ }
+
+ // 网元UPF
+ upfGroup := neDataGroup.Group("/upf")
+ {
+ upfGroup.GET("/totalFlow",
+ middleware.PreAuthorize(nil),
+ controller.NewUPFController.TotalFlow,
+ )
+ }
+
+}
diff --git a/src/modules/network_data/repository/cdr_event.go b/src/modules/network_data/repository/cdr_event.go
new file mode 100644
index 0000000..534903a
--- /dev/null
+++ b/src/modules/network_data/repository/cdr_event.go
@@ -0,0 +1,15 @@
+package repository
+
+import "nms_nbi/src/modules/network_data/model"
+
+// CDR会话事件 数据层接口
+type ICDREvent interface {
+ // SelectPage 根据条件分页查询
+ SelectPage(querys model.CDREventQuery) map[string]any
+
+ // SelectByIds 通过ID查询
+ SelectByIds(cdrIds []string) []model.CDREvent
+
+ // DeleteByIds 批量删除信息
+ DeleteByIds(cdrIds []string) int64
+}
diff --git a/src/modules/network_data/repository/cdr_event.impl.go b/src/modules/network_data/repository/cdr_event.impl.go
new file mode 100644
index 0000000..3fa5605
--- /dev/null
+++ b/src/modules/network_data/repository/cdr_event.impl.go
@@ -0,0 +1,171 @@
+package repository
+
+import (
+ "fmt"
+ "strings"
+
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/date"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/repo"
+ "nms_nbi/src/modules/network_data/model"
+)
+
+// 实例化数据层 CDREventImpl 结构体
+var NewCDREventImpl = &CDREventImpl{
+ selectSql: `select id, ne_type, ne_name, rm_uid, timestamp, cdr_json, created_at from cdr_event`,
+
+ resultMap: map[string]string{
+ "id": "ID",
+ "ne_type": "NeType",
+ "ne_name": "NeName",
+ "rm_uid": "RmUID",
+ "timestamp": "Timestamp",
+ "cdr_json": "CDRJSONStr",
+ "created_at": "CreatedAt",
+ },
+}
+
+// CDREventImpl CDR会话事件 数据层处理
+type CDREventImpl struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *CDREventImpl) convertResultRows(rows []map[string]any) []model.CDREvent {
+ arr := make([]model.CDREvent, 0)
+ for _, row := range rows {
+ item := model.CDREvent{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ repo.SetFieldValue(&item, keyMapper, value)
+ }
+ }
+ arr = append(arr, item)
+ }
+ return arr
+}
+
+// SelectPage 根据条件分页查询
+func (r *CDREventImpl) SelectPage(querys model.CDREventQuery) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if querys.NeType != "" {
+ conditions = append(conditions, "ne_type = ?")
+ params = append(params, querys.NeType)
+ }
+ if querys.RmUID != "" {
+ conditions = append(conditions, "rm_uid = ?")
+ params = append(params, querys.RmUID)
+ }
+ if querys.RmUID != "" {
+ conditions = append(conditions, "rm_uid = ?")
+ params = append(params, querys.RmUID)
+ }
+ if querys.StartTime != "" {
+ conditions = append(conditions, "timestamp >= ?")
+ beginDate := date.ParseStrToDate(querys.StartTime, date.YYYY_MM_DD_HH_MM_SS)
+ params = append(params, beginDate.Unix())
+ }
+ if querys.EndTime != "" {
+ conditions = append(conditions, "timestamp <= ?")
+ endDate := date.ParseStrToDate(querys.EndTime, date.YYYY_MM_DD_HH_MM_SS)
+ params = append(params, endDate.Unix())
+ }
+ if querys.RecordType != "" {
+ recordTypes := strings.Split(querys.RecordType, ",")
+ placeholder := repo.KeyPlaceholderByQuery(len(recordTypes))
+ conditions = append(conditions, fmt.Sprintf("JSON_EXTRACT(cdr_json, '$.recordType') in (%s)", placeholder))
+ for _, recordType := range recordTypes {
+ params = append(params, recordType)
+ }
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ result := map[string]any{
+ "total": 0,
+ "rows": []model.CDREvent{},
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := "select count(1) as 'total' from cdr_event"
+ totalRows, err := datasource.RawDB("", totalSql+whereSql, params)
+ if err != nil {
+ logger.Errorf("total err => %v", err)
+ return result
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return result
+ } else {
+ result["total"] = total
+ }
+
+ // 分页
+ pageNum, pageSize := repo.PageNumSize(querys.PageNum, querys.PageSize)
+ pageSql := " limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 排序
+ orderSql := ""
+ if querys.SortField != "" {
+ sortSql := querys.SortField
+ if querys.SortOrder != "" {
+ if querys.SortOrder == "desc" {
+ sortSql += " desc "
+ } else {
+ sortSql += " asc "
+ }
+ }
+ orderSql = fmt.Sprintf(" order by id desc, %s ", sortSql)
+ }
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + orderSql + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ }
+
+ // 转换实体
+ result["rows"] = r.convertResultRows(results)
+ return result
+}
+
+// SelectByIds 通过ID查询
+func (r *CDREventImpl) SelectByIds(cdrIds []string) []model.CDREvent {
+ placeholder := repo.KeyPlaceholderByQuery(len(cdrIds))
+ querySql := r.selectSql + " where id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(cdrIds)
+ results, err := datasource.RawDB("", querySql, parameters)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.CDREvent{}
+ }
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// DeleteByIds 批量删除信息
+func (r *CDREventImpl) DeleteByIds(cdrIds []string) int64 {
+ placeholder := repo.KeyPlaceholderByQuery(len(cdrIds))
+ sql := "delete from cdr_event where id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(cdrIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
diff --git a/src/modules/network_data/repository/perf_kpi.go b/src/modules/network_data/repository/perf_kpi.go
new file mode 100644
index 0000000..914704d
--- /dev/null
+++ b/src/modules/network_data/repository/perf_kpi.go
@@ -0,0 +1,15 @@
+package repository
+
+import "nms_nbi/src/modules/network_data/model"
+
+// 性能统计 数据层接口
+type IPerfKPI interface {
+ // SelectGoldKPI 通过网元指标数据信息
+ SelectGoldKPI(query model.GoldKPIQuery, kpiIds []string) []map[string]any
+
+ // SelectGoldKPITitle 网元对应的指标名称
+ SelectGoldKPITitle(neType string) []model.GoldKPITitle
+
+ // SelectUPFTotalFlow 查询UPF总流量 N3上行 N6下行
+ SelectUPFTotalFlow(neType, rmUID, startDate, endDate string) map[string]any
+}
diff --git a/src/modules/network_data/repository/perf_kpi.impl.go b/src/modules/network_data/repository/perf_kpi.impl.go
new file mode 100644
index 0000000..2272ead
--- /dev/null
+++ b/src/modules/network_data/repository/perf_kpi.impl.go
@@ -0,0 +1,133 @@
+package repository
+
+import (
+ "fmt"
+ "strings"
+
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/modules/network_data/model"
+)
+
+// 实例化数据层 PerfKPIImpl 结构体
+var NewPerfKPIImpl = &PerfKPIImpl{}
+
+// PerfKPIImpl 性能统计 数据层处理
+type PerfKPIImpl struct{}
+
+// SelectGoldKPI 通过网元指标数据信息
+func (r *PerfKPIImpl) SelectGoldKPI(query model.GoldKPIQuery, kpiIds []string) []map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if query.RmUID != "" {
+ conditions = append(conditions, "gk.rm_uid = ?")
+ params = append(params, query.RmUID)
+ }
+ if query.NeType != "" {
+ conditions = append(conditions, "gk.ne_type = ?")
+ params = append(params, query.NeType)
+ }
+ if query.StartTime != "" {
+ conditions = append(conditions, "gk.start_time >= ?")
+ params = append(params, query.StartTime)
+ }
+ if query.EndTime != "" {
+ conditions = append(conditions, "gk.start_time <= ?")
+ params = append(params, query.EndTime)
+ }
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询字段列
+ timeFormat := "DATE_FORMAT(gk.start_time, '%Y-%m-%d %H:%i:')"
+ secondGroup := fmt.Sprintf("LPAD(FLOOR(SECOND(gk.start_time) / %d) * %d, 2, '0')", query.Interval, query.Interval)
+ groupByField := fmt.Sprintf("CONCAT( %s, %s ) AS timeGroup", timeFormat, secondGroup)
+ if query.Interval > 60 {
+ minute := query.Interval / 60
+ timeFormat = "DATE_FORMAT(gk.start_time, '%Y-%m-%d %H:')"
+ minuteGroup := fmt.Sprintf("LPAD(FLOOR(MINUTE(gk.start_time) / %d) * %d, 2, '0')", minute, minute)
+ groupByField = fmt.Sprintf("CONCAT( %s, %s ) AS timeGroup", timeFormat, minuteGroup)
+ }
+ var fields = []string{
+ groupByField,
+ "min(CASE WHEN gk.index != '' THEN gk.index ELSE 0 END) AS startIndex",
+ "min(CASE WHEN gk.ne_type != '' THEN gk.ne_type ELSE 0 END) AS neType",
+ "min(CASE WHEN gk.ne_name != '' THEN gk.ne_name ELSE 0 END) AS neName",
+ }
+ for _, kid := range kpiIds {
+ // 特殊字段,只取最后一次收到的非0值
+ if kid == "AMF.01" || kid == "UDM.01" || kid == "UDM.02" || kid == "UDM.03" || kid == "SMF.01" {
+ str := fmt.Sprintf("IFNULL(SUBSTRING_INDEX(GROUP_CONCAT( CASE WHEN gk.kpi_id = '%s' and gk.VALUE != 0 THEN gk.VALUE END ), ',', 1), 0) AS '%s'", kid, kid)
+ fields = append(fields, str)
+ } else {
+ str := fmt.Sprintf("sum(CASE WHEN gk.kpi_id = '%s' THEN gk.value ELSE 0 END) AS '%s'", kid, kid)
+ fields = append(fields, str)
+ }
+ }
+ fieldsSql := strings.Join(fields, ",")
+
+ // 查询数据
+ if query.SortField == "" {
+ query.SortField = "timeGroup"
+ }
+ if query.SortOrder == "" {
+ query.SortOrder = "desc"
+ }
+ orderSql := fmt.Sprintf(" order by %s %s", query.SortField, query.SortOrder)
+ querySql := fmt.Sprintf("SELECT %s FROM gold_kpi gk %s GROUP BY timeGroup %s", fieldsSql, whereSql, orderSql)
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ }
+ return results
+}
+
+// SelectGoldKPITitle 网元对应的指标名称
+func (r *PerfKPIImpl) SelectGoldKPITitle(neType string) []model.GoldKPITitle {
+ result := []model.GoldKPITitle{}
+ tx := datasource.DefaultDB().Table("kpi_title").Where("ne_type = ?", neType).Find(&result)
+ if err := tx.Error; err != nil {
+ logger.Errorf("Find err => %v", err)
+ }
+ return result
+}
+
+// SelectUPFTotalFlow 查询UPF总流量 N3上行 N6下行
+func (r *PerfKPIImpl) SelectUPFTotalFlow(neType, rmUID, startDate, endDate string) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if neType != "" {
+ conditions = append(conditions, "gk.ne_type = ?")
+ params = append(params, neType)
+ }
+ if rmUID != "" {
+ conditions = append(conditions, "gk.rm_uid = ?")
+ params = append(params, rmUID)
+ }
+ if startDate != "" {
+ conditions = append(conditions, "gk.date >= ?")
+ params = append(params, startDate)
+ }
+ if endDate != "" {
+ conditions = append(conditions, "gk.date <= ?")
+ params = append(params, endDate)
+ }
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ querySql := fmt.Sprintf("SELECT sum( CASE WHEN gk.kpi_id = 'UPF.03' THEN gk.VALUE ELSE 0 END ) AS 'up', sum( CASE WHEN gk.kpi_id = 'UPF.06' THEN gk.VALUE ELSE 0 END ) AS 'down' FROM gold_kpi gk %s", whereSql)
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ }
+ return results[0]
+}
diff --git a/src/modules/network_data/repository/ue_event.go b/src/modules/network_data/repository/ue_event.go
new file mode 100644
index 0000000..5457af5
--- /dev/null
+++ b/src/modules/network_data/repository/ue_event.go
@@ -0,0 +1,15 @@
+package repository
+
+import "nms_nbi/src/modules/network_data/model"
+
+// UE会话事件 数据层接口
+type IUEEvent interface {
+ // SelectPage 根据条件分页查询
+ SelectPage(querys model.UEEventQuery) map[string]any
+
+ // SelectByIds 通过ID查询
+ SelectByIds(ueIds []string) []model.UEEvent
+
+ // DeleteByIds 批量删除信息
+ DeleteByIds(ueIds []string) int64
+}
diff --git a/src/modules/network_data/repository/ue_event.impl.go b/src/modules/network_data/repository/ue_event.impl.go
new file mode 100644
index 0000000..3630f1f
--- /dev/null
+++ b/src/modules/network_data/repository/ue_event.impl.go
@@ -0,0 +1,168 @@
+package repository
+
+import (
+ "fmt"
+ "strings"
+
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/date"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/repo"
+ "nms_nbi/src/modules/network_data/model"
+)
+
+// 实例化数据层 UEEventImpl 结构体
+var NewUEEventImpl = &UEEventImpl{
+ selectSql: `select id, ne_type, ne_name, rm_uid, timestamp, event_type, event_json, created_at from ue_event`,
+
+ resultMap: map[string]string{
+ "id": "ID",
+ "ne_type": "NeType",
+ "ne_name": "NeName",
+ "rm_uid": "RmUID",
+ "timestamp": "Timestamp",
+ "event_type": "EventType",
+ "event_json": "EventJSONStr",
+ "created_at": "CreatedAt",
+ },
+}
+
+// UEEventImpl UE会话事件 数据层处理
+type UEEventImpl struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *UEEventImpl) convertResultRows(rows []map[string]any) []model.UEEvent {
+ arr := make([]model.UEEvent, 0)
+ for _, row := range rows {
+ item := model.UEEvent{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ repo.SetFieldValue(&item, keyMapper, value)
+ }
+ }
+ arr = append(arr, item)
+ }
+ return arr
+}
+
+// SelectPage 根据条件分页查询
+func (r *UEEventImpl) SelectPage(querys model.UEEventQuery) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if querys.NeType != "" {
+ conditions = append(conditions, "ne_type = ?")
+ params = append(params, querys.NeType)
+ }
+ if querys.RmUID != "" {
+ conditions = append(conditions, "rm_uid = ?")
+ params = append(params, querys.RmUID)
+ }
+ if querys.StartTime != "" {
+ conditions = append(conditions, "timestamp >= ?")
+ beginDate := date.ParseStrToDate(querys.StartTime, date.YYYY_MM_DD_HH_MM_SS)
+ params = append(params, beginDate.Unix())
+ }
+ if querys.EndTime != "" {
+ conditions = append(conditions, "timestamp <= ?")
+ endDate := date.ParseStrToDate(querys.EndTime, date.YYYY_MM_DD_HH_MM_SS)
+ params = append(params, endDate.Unix())
+ }
+ if querys.EventType != "" {
+ eventTypes := strings.Split(querys.EventType, ",")
+ placeholder := repo.KeyPlaceholderByQuery(len(eventTypes))
+ conditions = append(conditions, fmt.Sprintf("event_type in (%s)", placeholder))
+ for _, eventType := range eventTypes {
+ params = append(params, eventType)
+ }
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ result := map[string]any{
+ "total": 0,
+ "rows": []model.CDREvent{},
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := "select count(1) as 'total' from ue_event"
+ totalRows, err := datasource.RawDB("", totalSql+whereSql, params)
+ if err != nil {
+ logger.Errorf("total err => %v", err)
+ return result
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return result
+ } else {
+ result["total"] = total
+ }
+
+ // 分页
+ pageNum, pageSize := repo.PageNumSize(querys.PageNum, querys.PageSize)
+ pageSql := " limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 排序
+ orderSql := ""
+ if querys.SortField != "" {
+ sortSql := querys.SortField
+ if querys.SortOrder != "" {
+ if querys.SortOrder == "desc" {
+ sortSql += " desc "
+ } else {
+ sortSql += " asc "
+ }
+ }
+ orderSql = fmt.Sprintf(" order by id desc, %s ", sortSql)
+ }
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + orderSql + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ }
+
+ // 转换实体
+ result["rows"] = r.convertResultRows(results)
+ return result
+}
+
+// SelectByIds 通过ID查询
+func (r *UEEventImpl) SelectByIds(ueIds []string) []model.UEEvent {
+ placeholder := repo.KeyPlaceholderByQuery(len(ueIds))
+ querySql := r.selectSql + " where id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(ueIds)
+ results, err := datasource.RawDB("", querySql, parameters)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.UEEvent{}
+ }
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// DeleteByIds 批量删除信息
+func (r *UEEventImpl) DeleteByIds(ueIds []string) int64 {
+ placeholder := repo.KeyPlaceholderByQuery(len(ueIds))
+ sql := "delete from ue_event where id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(ueIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
diff --git a/src/modules/network_data/service/cdr_event.go b/src/modules/network_data/service/cdr_event.go
new file mode 100644
index 0000000..6889d6b
--- /dev/null
+++ b/src/modules/network_data/service/cdr_event.go
@@ -0,0 +1,12 @@
+package service
+
+import "nms_nbi/src/modules/network_data/model"
+
+// CDR会话事件 服务层接口
+type ICDREvent interface {
+ // SelectPage 根据条件分页查询
+ SelectPage(querys model.CDREventQuery) map[string]any
+
+ // DeleteByIds 批量删除信息
+ DeleteByIds(cdrIds []string) (int64, error)
+}
diff --git a/src/modules/network_data/service/cdr_event.impl.go b/src/modules/network_data/service/cdr_event.impl.go
new file mode 100644
index 0000000..4aac465
--- /dev/null
+++ b/src/modules/network_data/service/cdr_event.impl.go
@@ -0,0 +1,40 @@
+package service
+
+import (
+ "fmt"
+
+ "nms_nbi/src/modules/network_data/model"
+ "nms_nbi/src/modules/network_data/repository"
+)
+
+// 实例化数据层 CDREventImpl 结构体
+var NewCDREventImpl = &CDREventImpl{
+ cdrEventRepository: repository.NewCDREventImpl,
+}
+
+// CDREventImpl CDR会话事件 服务层处理
+type CDREventImpl struct {
+ // CDR会话事件数据信息
+ cdrEventRepository repository.ICDREvent
+}
+
+// SelectPage 根据条件分页查询
+func (r *CDREventImpl) SelectPage(querys model.CDREventQuery) map[string]any {
+ return r.cdrEventRepository.SelectPage(querys)
+}
+
+// DeleteByIds 批量删除信息
+func (r *CDREventImpl) DeleteByIds(cdrIds []string) (int64, error) {
+ // 检查是否存在
+ ids := r.cdrEventRepository.SelectByIds(cdrIds)
+ if len(ids) <= 0 {
+ return 0, fmt.Errorf("not data")
+ }
+
+ if len(ids) == len(cdrIds) {
+ rows := r.cdrEventRepository.DeleteByIds(cdrIds)
+ return rows, nil
+ }
+ // 删除信息失败!
+ return 0, fmt.Errorf("delete fail")
+}
diff --git a/src/modules/network_data/service/perf_kpi.go b/src/modules/network_data/service/perf_kpi.go
new file mode 100644
index 0000000..a8b6919
--- /dev/null
+++ b/src/modules/network_data/service/perf_kpi.go
@@ -0,0 +1,15 @@
+package service
+
+import "nms_nbi/src/modules/network_data/model"
+
+// 性能统计 服务层接口
+type IPerfKPI interface {
+ // SelectGoldKPI 通过网元指标数据信息
+ SelectGoldKPI(query model.GoldKPIQuery) []map[string]any
+
+ // SelectGoldKPITitle 网元对应的指标名称
+ SelectGoldKPITitle(neType string) []model.GoldKPITitle
+
+ // SelectUPFTotalFlow 查询UPF总流量 N3上行 N6下行
+ SelectUPFTotalFlow(neType, rmUID string, day int) map[string]any
+}
diff --git a/src/modules/network_data/service/perf_kpi.impl.go b/src/modules/network_data/service/perf_kpi.impl.go
new file mode 100644
index 0000000..e56ddba
--- /dev/null
+++ b/src/modules/network_data/service/perf_kpi.impl.go
@@ -0,0 +1,76 @@
+package service
+
+import (
+ "encoding/json"
+ "fmt"
+ "time"
+
+ "nms_nbi/src/framework/constants/cachekey"
+ "nms_nbi/src/framework/redis"
+ "nms_nbi/src/modules/network_data/model"
+ "nms_nbi/src/modules/network_data/repository"
+)
+
+// 实例化数据层 PerfKPIImpl 结构体
+var NewPerfKPIImpl = &PerfKPIImpl{
+ perfKPIRepository: repository.NewPerfKPIImpl,
+}
+
+// PerfKPIImpl 性能统计 服务层处理
+type PerfKPIImpl struct {
+ // 性能统计数据信息
+ perfKPIRepository repository.IPerfKPI
+}
+
+// SelectGoldKPI 通过网元指标数据信息
+func (r *PerfKPIImpl) SelectGoldKPI(query model.GoldKPIQuery) []map[string]any {
+ // 获取数据指标id
+ var kpiIds []string
+ kpiTitles := r.perfKPIRepository.SelectGoldKPITitle(query.NeType)
+ for _, kpiId := range kpiTitles {
+ kpiIds = append(kpiIds, kpiId.KPIID)
+ }
+
+ data := r.perfKPIRepository.SelectGoldKPI(query, kpiIds)
+ if data == nil {
+ return []map[string]any{}
+ }
+ return data
+}
+
+// SelectGoldKPITitle 网元对应的指标名称
+func (r *PerfKPIImpl) SelectGoldKPITitle(neType string) []model.GoldKPITitle {
+ return r.perfKPIRepository.SelectGoldKPITitle(neType)
+}
+
+// SelectUPFTotalFlow 查询UPF总流量 N3上行 N6下行
+func (r *PerfKPIImpl) SelectUPFTotalFlow(neType, rmUID string, day int) map[string]any {
+ // 获取当前日期
+ now := time.Now()
+ endDate := now.Format("2006-01-02")
+ // 将当前日期前几天数
+ afterDays := now.AddDate(0, 0, -day)
+ startDate := afterDays.Format("2006-01-02")
+
+ var info map[string]any
+
+ // 读取缓存数据 小于2分钟重新缓存
+ key := fmt.Sprintf("%sUPF:totalFlow:%s_%d", cachekey.NE_DATA_KEY, rmUID, day)
+ infoStr, _ := redis.Get("", key)
+ if infoStr != "" {
+ json.Unmarshal([]byte(infoStr), &info)
+ expireSecond, _ := redis.GetExpire("", key)
+ expireMinute := (time.Duration(int64(expireSecond)) * time.Second)
+ if expireMinute > 2*time.Minute {
+ return info
+ }
+ }
+
+ info = r.perfKPIRepository.SelectUPFTotalFlow(neType, rmUID, startDate, endDate)
+
+ // 保存到缓存
+ infoJSON, _ := json.Marshal(info)
+ redis.SetByExpire("", key, string(infoJSON), time.Duration(10)*time.Minute)
+
+ return info
+}
diff --git a/src/modules/network_data/service/ue_event.go b/src/modules/network_data/service/ue_event.go
new file mode 100644
index 0000000..cbab248
--- /dev/null
+++ b/src/modules/network_data/service/ue_event.go
@@ -0,0 +1,12 @@
+package service
+
+import "nms_nbi/src/modules/network_data/model"
+
+// UE会话事件 服务层接口
+type IUEEvent interface {
+ // SelectPage 根据条件分页查询
+ SelectPage(querys model.UEEventQuery) map[string]any
+
+ // DeleteByIds 批量删除信息
+ DeleteByIds(ueIds []string) (int64, error)
+}
diff --git a/src/modules/network_data/service/ue_event.impl.go b/src/modules/network_data/service/ue_event.impl.go
new file mode 100644
index 0000000..4443898
--- /dev/null
+++ b/src/modules/network_data/service/ue_event.impl.go
@@ -0,0 +1,40 @@
+package service
+
+import (
+ "fmt"
+
+ "nms_nbi/src/modules/network_data/model"
+ "nms_nbi/src/modules/network_data/repository"
+)
+
+// 实例化数据层 UEEventImpl 结构体
+var NewUEEventImpl = &UEEventImpl{
+ ueEventRepository: repository.NewUEEventImpl,
+}
+
+// UEEventImpl UE会话事件 服务层处理
+type UEEventImpl struct {
+ // UE会话事件数据信息
+ ueEventRepository repository.IUEEvent
+}
+
+// SelectPage 根据条件分页查询
+func (r *UEEventImpl) SelectPage(querys model.UEEventQuery) map[string]any {
+ return r.ueEventRepository.SelectPage(querys)
+}
+
+// DeleteByIds 批量删除信息
+func (r *UEEventImpl) DeleteByIds(ueIds []string) (int64, error) {
+ // 检查是否存在
+ ids := r.ueEventRepository.SelectByIds(ueIds)
+ if len(ids) <= 0 {
+ return 0, fmt.Errorf("no data")
+ }
+
+ if len(ids) == len(ueIds) {
+ rows := r.ueEventRepository.DeleteByIds(ueIds)
+ return rows, nil
+ }
+ // 删除信息失败!
+ return 0, fmt.Errorf("delete fail")
+}
diff --git a/src/modules/network_element/controller/ne_action.go b/src/modules/network_element/controller/ne_action.go
new file mode 100644
index 0000000..22b9eae
--- /dev/null
+++ b/src/modules/network_element/controller/ne_action.go
@@ -0,0 +1,158 @@
+package controller
+
+import (
+ "fmt"
+ "path/filepath"
+ "strings"
+
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/file"
+ "nms_nbi/src/framework/utils/ssh"
+ "nms_nbi/src/framework/vo/result"
+ neService "nms_nbi/src/modules/network_element/service"
+
+ "github.com/gin-gonic/gin"
+ "github.com/gin-gonic/gin/binding"
+)
+
+// 实例化控制层 NeActionController 结构体
+var NewNeAction = &NeActionController{
+ neInfoService: neService.NewNeInfoImpl,
+}
+
+// 网元处理请求
+//
+// PATH /action
+type NeActionController struct {
+ // 网元信息服务
+ neInfoService neService.INeInfo
+}
+
+// 发送文件到网元端
+//
+// POST /pushFile
+func (s *NeActionController) PushFile(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body struct {
+ NeType string `json:"neType" binding:"required"`
+ NeID string `json:"neId" binding:"required"`
+ UploadPath string `json:"uploadPath" binding:"required"`
+ }
+ if err := c.ShouldBindBodyWith(&body, binding.JSON); err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 查询网元获取IP
+ neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID(body.NeType, body.NeID)
+ if neInfo.NeId != body.NeID || neInfo.IP == "" {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
+ return
+ }
+
+ // 本地文件
+ localPath := file.ParseUploadFilePath(body.UploadPath)
+ nePath := "/tmp" //config.Get("mml.upload").(string)
+ // 复制到远程
+ err := ssh.FileSCPLocalToNe(neInfo.IP, localPath, nePath)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 网元端文件路径
+ fileName := localPath[strings.LastIndex(localPath, "/")+1:]
+ neFilePath := fmt.Sprintf("%s/%s", nePath, fileName)
+ c.JSON(200, result.OkData(filepath.ToSlash(neFilePath)))
+}
+
+// 获取文件从网元到本地
+//
+// GET /pullFile
+func (s *NeActionController) PullFile(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var querys struct {
+ NeType string `form:"neType" binding:"required"`
+ NeID string `form:"neId" binding:"required"`
+ Path string `form:"path" binding:"required"`
+ FileName string `form:"fileName" binding:"required"`
+ }
+ if err := c.ShouldBindQuery(&querys); err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 查询网元获取IP
+ neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID(querys.NeType, querys.NeID)
+ if neInfo.NeId != querys.NeID || neInfo.IP == "" {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
+ return
+ }
+
+ nePath := fmt.Sprintf("%s/%s", querys.Path, querys.FileName)
+ localPath := fmt.Sprintf("/tmp/omc/pullFile/%s", querys.FileName)
+ err := ssh.FileSCPNeToLocal(neInfo.IP, nePath, localPath)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+ c.FileAttachment(localPath, querys.FileName)
+}
+
+// 网元端文件列表
+//
+// GET /files
+func (s *NeActionController) Files(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var querys struct {
+ NeType string `form:"neType" binding:"required"`
+ NeID string `form:"neId" binding:"required"`
+ Path string `form:"path" binding:"required"`
+ PageNum int64 `form:"pageNum" binding:"required"`
+ PageSize int64 `form:"pageSize" binding:"required"`
+ Search string `form:"search"`
+ }
+ if err := c.ShouldBindQuery(&querys); err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 查询网元获取IP
+ neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID(querys.NeType, querys.NeID)
+ if neInfo.NeId != querys.NeID || neInfo.IP == "" {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
+ return
+ }
+
+ totalSize, rows, err := ssh.FileList(querys.Path, neInfo.IP, querys.Search)
+ if err != nil {
+ c.JSON(200, result.Ok(map[string]any{
+ "path": querys.Path,
+ "totalSize": totalSize,
+ "total": len(rows),
+ "rows": []ssh.FileListRow{},
+ }))
+ return
+ }
+
+ // 对数组进行切片分页
+ lenNum := int64(len(rows))
+ start := (querys.PageNum - 1) * querys.PageSize
+ end := start + querys.PageSize
+ var splitRows []ssh.FileListRow
+ if start >= lenNum {
+ splitRows = []ssh.FileListRow{}
+ } else if end >= lenNum {
+ splitRows = rows[start:]
+ } else {
+ splitRows = rows[start:end]
+ }
+
+ c.JSON(200, result.Ok(map[string]any{
+ "path": querys.Path,
+ "totalSize": totalSize,
+ "total": lenNum,
+ "rows": splitRows,
+ }))
+}
diff --git a/src/modules/network_element/controller/ne_host.go b/src/modules/network_element/controller/ne_host.go
new file mode 100644
index 0000000..cdce60c
--- /dev/null
+++ b/src/modules/network_element/controller/ne_host.go
@@ -0,0 +1,357 @@
+package controller
+
+import (
+ "strings"
+
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/ssh"
+ "nms_nbi/src/framework/utils/telnet"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/network_element/model"
+ neService "nms_nbi/src/modules/network_element/service"
+
+ "github.com/gin-gonic/gin"
+ "github.com/gin-gonic/gin/binding"
+)
+
+// 实例化控制层 NeHostController 结构体
+var NewNeHost = &NeHostController{
+ neHostService: neService.NewNeHostImpl,
+}
+
+// 网元主机连接请求
+//
+// PATH /host
+type NeHostController struct {
+ // 网元主机连接服务
+ neHostService neService.INeHost
+}
+
+// 网元主机列表
+//
+// GET /list
+func (s *NeHostController) List(c *gin.Context) {
+ querys := ctx.QueryMap(c)
+ data := s.neHostService.SelectPage(querys)
+
+ rows := data["rows"].([]model.NeHost)
+ arr := &rows
+ for i := range *arr {
+ (*arr)[i].Password = "-"
+ (*arr)[i].PrivateKey = "-"
+ (*arr)[i].PassPhrase = "-"
+ }
+
+ c.JSON(200, result.Ok(data))
+}
+
+// 网元主机信息
+//
+// GET /:hostId
+func (s *NeHostController) Info(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ hostId := c.Param("hostId")
+ if hostId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ neHost := s.neHostService.SelectById(hostId)
+ if neHost.HostID != hostId {
+ // 没有可访问主机信息数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "neHost.noData")))
+ return
+ }
+
+ c.JSON(200, result.OkData(neHost))
+}
+
+// 网元主机新增
+//
+// POST /
+func (s *NeHostController) Add(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.NeHost
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.HostID != "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查属性值唯一
+ uniqueHost := s.neHostService.CheckUniqueHostTitle(body.GroupID, body.Title, body.HostType, "")
+ if !uniqueHost {
+ // 主机信息操作【%s】失败,同组内名称已存在
+ msg := i18n.TTemplate(language, "neHost.errKeyExists", map[string]any{"name": body.Title})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ body.CreateBy = ctx.LoginUserToUserName(c)
+ insertId := s.neHostService.Insert(body)
+ if insertId != "" {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 网元主机修改
+//
+// PUT /
+func (s *NeHostController) Edit(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.NeHost
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.HostID == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查属性值唯一
+ uniqueHost := s.neHostService.CheckUniqueHostTitle(body.GroupID, body.Title, body.HostType, body.HostID)
+ if !uniqueHost {
+ // 主机信息操作【%s】失败,同组内名称已存在
+ msg := i18n.TTemplate(language, "neHost.errKeyExists", map[string]any{"name": body.Title})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 检查是否存在
+ neHost := s.neHostService.SelectById(body.HostID)
+ if neHost.HostID != body.HostID {
+ // 没有可访问主机信息数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "neHost.noData")))
+ return
+ }
+
+ body.UpdateBy = ctx.LoginUserToUserName(c)
+ rows := s.neHostService.Update(body)
+ if rows > 0 {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 网元主机删除
+//
+// DELETE /:hostIds
+func (s *NeHostController) Remove(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ hostIds := c.Param("hostIds")
+ if hostIds == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ // 处理字符转id数组后去重
+ ids := strings.Split(hostIds, ",")
+ uniqueIDs := parse.RemoveDuplicates(ids)
+ if len(uniqueIDs) <= 0 {
+ c.JSON(200, result.Err(nil))
+ return
+ }
+ rows, err := s.neHostService.DeleteByIds(uniqueIDs)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error())))
+ return
+ }
+ msg := i18n.TTemplate(language, "app.common.deleteSuccess", map[string]any{"num": rows})
+ c.JSON(200, result.OkMsg(msg))
+}
+
+// 网元主机测试连接
+//
+// POST /test
+func (s *NeHostController) Test(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.NeHost
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ if body.HostType == "ssh" {
+ var connSSH ssh.ConnSSH
+ body.CopyTo(&connSSH)
+
+ client, err := connSSH.NewClient()
+ if err != nil {
+ // 连接主机失败,请检查连接参数后重试
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "neHost.errByHostInfo")))
+ return
+ }
+ defer client.Close()
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+
+ if body.HostType == "telnet" {
+ var connTelnet telnet.ConnTelnet
+ body.CopyTo(&connTelnet)
+
+ client, err := connTelnet.NewClient()
+ if err != nil {
+ // 连接主机失败,请检查连接参数后重试
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "neHost.errByHostInfo")))
+ return
+ }
+ defer client.Close()
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+}
+
+// 网元主机发送命令
+//
+// POST /cmd
+func (s *NeHostController) Cmd(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body struct {
+ HostID string `json:"hostId" binding:"required"` // 主机ID
+ Cmd string `json:"cmd" binding:"required"` // 执行命令
+ }
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查是否存在
+ neHost := s.neHostService.SelectById(body.HostID)
+ if neHost.HostID != body.HostID {
+ // 没有可访问主机信息数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "neHost.noData")))
+ return
+ }
+
+ if neHost.HostType == "ssh" {
+ var connSSH ssh.ConnSSH
+ neHost.CopyTo(&connSSH)
+
+ client, err := connSSH.NewClient()
+ if err != nil {
+ // 连接主机失败,请检查连接参数后重试
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "neHost.errByHostInfo")))
+ return
+ }
+ defer client.Close()
+
+ // 执行命令
+ output, err := client.RunCMD(body.Cmd)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+ c.JSON(200, result.OkData(output))
+ return
+ }
+
+ if neHost.HostType == "telnet" {
+ var connTelnet telnet.ConnTelnet
+ neHost.CopyTo(&connTelnet)
+
+ client, err := connTelnet.NewClient()
+ if err != nil {
+ // 连接主机失败,请检查连接参数后重试
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "neHost.errByHostInfo")))
+ return
+ }
+ defer client.Close()
+
+ // 执行命令
+ output, err := client.RunCMD(body.Cmd)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+ c.JSON(200, result.OkData(output))
+ return
+ }
+}
+
+// 网元主机SSH方式检查服务器环境
+//
+// POST /checkBySSH
+func (s *NeHostController) CheckBySSH(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.NeHost
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ var connSSH ssh.ConnSSH
+ body.CopyTo(&connSSH)
+
+ // 创建链接SSH客户端
+ client, err := connSSH.NewClient()
+ if err != nil {
+ // 连接主机失败,请检查连接参数后重试
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "neHost.errByHostInfo")))
+ return
+ }
+ defer client.Close()
+
+ // 结果信息数据
+ data := map[string]any{
+ "addr": body.Addr, // 服务器地址
+ "kernelName": "-", // 内核名称 -s
+ "nodename": "-", // 网络节点主机名 -n
+ "kernelRelease": "-", // 内核发布版本 -r
+ "machine": "-", // 机器硬件名称 -m
+ "prettyName": "-", // 系统发行版本
+ "sudo": false, // 可提权
+ "sshLink": false, // 可直连
+ }
+
+ // 执行命令 检查系统环境
+ output, err := client.RunCMD("uname -snrm && cat /etc/os-release | grep PRETTY_NAME")
+ if err != nil {
+ c.JSON(200, result.OkData(data))
+ return
+ }
+ output = strings.TrimSuffix(output, "\n")
+ sysInfoArr := strings.SplitN(output, "\n", 2)
+ if len(sysInfoArr) == 2 {
+ // uname -snrm
+ baseInfoArr := strings.SplitN(sysInfoArr[0], " ", 4)
+ data["kernelName"] = baseInfoArr[0]
+ data["nodename"] = baseInfoArr[1]
+ data["kernelRelease"] = baseInfoArr[2]
+ data["machine"] = baseInfoArr[3]
+ // cat /etc/os-release | grep PRETTY_NAME
+ prettyName := sysInfoArr[1]
+ index := strings.Index(prettyName, `"`)
+ if index != -1 {
+ data["prettyName"] = prettyName[index+1 : len(prettyName)-1]
+ }
+ }
+ // 执行命令 检查sudo权限
+ _, err = client.RunCMD("sudo -n uname")
+ if err == nil {
+ data["sudo"] = true
+ } else {
+ data["sudo"] = false
+ }
+
+ // 本地免密创建链接直连
+ lcoalConnSSH := ssh.ConnSSH{
+ User: body.User,
+ Addr: body.Addr,
+ Port: body.Port,
+ }
+ lcoalClient, err := lcoalConnSSH.NewClientByLocalPrivate()
+ if err == nil {
+ data["sshLink"] = true
+ } else {
+ data["sshLink"] = false
+ }
+ defer lcoalClient.Close()
+
+ c.JSON(200, result.OkData(data))
+}
diff --git a/src/modules/network_element/controller/ne_host_cmd.go b/src/modules/network_element/controller/ne_host_cmd.go
new file mode 100644
index 0000000..8aa8dd1
--- /dev/null
+++ b/src/modules/network_element/controller/ne_host_cmd.go
@@ -0,0 +1,153 @@
+package controller
+
+import (
+ "strings"
+
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/network_element/model"
+ neService "nms_nbi/src/modules/network_element/service"
+
+ "github.com/gin-gonic/gin"
+ "github.com/gin-gonic/gin/binding"
+)
+
+// 实例化控制层 NeHostCmdController 结构体
+var NewNeHostCmd = &NeHostCmdController{
+ neHostCmdService: neService.NewNeHostCmdImpl,
+}
+
+// 网元主机命令请求
+//
+// PATH /hostCmd
+type NeHostCmdController struct {
+ // 网元主机命令服务
+ neHostCmdService neService.INeHostCmd
+}
+
+// 网元主机命令列表
+//
+// GET /list
+func (s *NeHostCmdController) List(c *gin.Context) {
+ querys := ctx.QueryMap(c)
+ data := s.neHostCmdService.SelectPage(querys)
+
+ c.JSON(200, result.Ok(data))
+}
+
+// 网元主机命令信息
+//
+// GET /:cmdId
+func (s *NeHostCmdController) Info(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ cmdId := c.Param("cmdId")
+ if cmdId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ neHost := s.neHostCmdService.SelectById(cmdId)
+ if neHost.CmdID != cmdId {
+ // 没有可访问主机命令数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "neHostCmd.noData")))
+ return
+ }
+
+ c.JSON(200, result.OkData(neHost))
+}
+
+// 网元主机命令新增
+//
+// POST /
+func (s *NeHostCmdController) Add(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.NeHostCmd
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.CmdID != "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查属性值唯一
+ uniqueHostCmd := s.neHostCmdService.CheckUniqueGroupTitle(body.GroupID, body.Title, body.CmdType, "")
+ if !uniqueHostCmd {
+ // 主机命令操作【%s】失败,同组内名称已存在
+ msg := i18n.TTemplate(language, "neHostCmd.errKeyExists", map[string]any{"name": body.Title})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ body.CreateBy = ctx.LoginUserToUserName(c)
+ insertId := s.neHostCmdService.Insert(body)
+ if insertId != "" {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 网元主机命令修改
+//
+// PUT /
+func (s *NeHostCmdController) Edit(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.NeHostCmd
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.CmdID == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查属性值唯一
+ uniqueHostCmd := s.neHostCmdService.CheckUniqueGroupTitle(body.GroupID, body.Title, body.CmdType, body.CmdID)
+ if !uniqueHostCmd {
+ // 主机命令操作【%s】失败,同组内名称已存在
+ msg := i18n.TTemplate(language, "neHostCmd.errKeyExists", map[string]any{"name": body.Title})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 检查是否存在
+ neHost := s.neHostCmdService.SelectById(body.CmdID)
+ if neHost.CmdID != body.CmdID {
+ // 没有可访问主机命令数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "neHostCmd.noData")))
+ return
+ }
+
+ body.UpdateBy = ctx.LoginUserToUserName(c)
+ rows := s.neHostCmdService.Update(body)
+ if rows > 0 {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 网元主机命令删除
+//
+// DELETE /:cmdIds
+func (s *NeHostCmdController) Remove(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ cmdIds := c.Param("cmdIds")
+ if cmdIds == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ // 处理字符转id数组后去重
+ ids := strings.Split(cmdIds, ",")
+ uniqueIDs := parse.RemoveDuplicates(ids)
+ if len(uniqueIDs) <= 0 {
+ c.JSON(200, result.Err(nil))
+ return
+ }
+ rows, err := s.neHostCmdService.DeleteByIds(uniqueIDs)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error())))
+ return
+ }
+ msg := i18n.TTemplate(language, "app.common.deleteSuccess", map[string]any{"num": rows})
+ c.JSON(200, result.OkMsg(msg))
+}
diff --git a/src/modules/network_element/controller/ne_info.go b/src/modules/network_element/controller/ne_info.go
new file mode 100644
index 0000000..5843147
--- /dev/null
+++ b/src/modules/network_element/controller/ne_info.go
@@ -0,0 +1,294 @@
+package controller
+
+import (
+ "fmt"
+ "strings"
+ "sync"
+
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/network_element/model"
+ neService "nms_nbi/src/modules/network_element/service"
+
+ "github.com/gin-gonic/gin"
+ "github.com/gin-gonic/gin/binding"
+)
+
+// 实例化控制层 NeInfoController 结构体
+var NewNeInfo = &NeInfoController{
+ neInfoService: neService.NewNeInfoImpl,
+}
+
+// 网元信息请求
+//
+// PATH /
+type NeInfoController struct {
+ // 网元信息服务
+ neInfoService neService.INeInfo
+}
+
+// neStateCacheMap 网元状态缓存最后一次成功的信息
+var neStateCacheMap sync.Map
+var mutex sync.Mutex
+
+// 网元信息状态
+//
+// GET /state
+func (s *NeInfoController) State(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var querys struct {
+ NeType string `form:"neType" binding:"required"`
+ NeID string `form:"neId" binding:"required"`
+ }
+ if err := c.ShouldBindQuery(&querys); err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID(querys.NeType, querys.NeID)
+ if neInfo.NeId != querys.NeID || neInfo.IP == "" {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
+ return
+ }
+ neKey := fmt.Sprintf("%s_%s", neInfo.NeType, neInfo.NeId)
+
+ // 网元直连
+ resData, err := neService.NeState(neInfo)
+ if err != nil {
+ mutex.Lock()
+ // 异常取上次缓存
+ resDataCache, ok := neStateCacheMap.Load(neKey)
+ if ok && resDataCache != nil {
+ resDataCache.(map[string]any)["online"] = false
+ } else {
+ resDataCache = map[string]any{
+ "online": false,
+ "neId": neInfo.NeId,
+ "neName": neInfo.NeName,
+ "neType": neInfo.NeType,
+ "neIP": neInfo.IP,
+ }
+ }
+ neStateCacheMap.Store(neKey, resDataCache)
+ mutex.Unlock()
+ c.JSON(200, result.OkData(resDataCache))
+ return
+ }
+
+ // 存入缓存
+ resData["online"] = true
+ mutex.Lock()
+ neStateCacheMap.Store(neKey, resData)
+ mutex.Unlock()
+ c.JSON(200, result.OkData(resData))
+}
+
+// 网元信息列表
+//
+// GET /list
+func (s *NeInfoController) List(c *gin.Context) {
+ querys := ctx.QueryMap(c)
+ bandStatus := false
+ if v, ok := querys["bandStatus"]; ok && v != nil {
+ bandStatus = parse.Boolean(v)
+ }
+ data := s.neInfoService.SelectPage(querys, bandStatus)
+ c.JSON(200, result.Ok(data))
+}
+
+// 网元信息列表全部无分页
+//
+// GET /listAll
+func (s *NeInfoController) ListAll(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var querys struct {
+ NeType string `form:"neType"`
+ NeId string `form:"neId"`
+ BandStatus string `form:"bandStatus"`
+ }
+ if err := c.ShouldBindQuery(&querys); err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 查询实体参数
+ ne := model.NeInfo{}
+ if querys.NeType != "" {
+ ne.NeType = querys.NeType
+ }
+ if querys.NeId != "" {
+ ne.NeId = querys.NeId
+ }
+ bandStatus := parse.Boolean(querys.BandStatus)
+ neList := s.neInfoService.SelectList(ne, bandStatus)
+ if len(neList) == 0 {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
+ return
+ }
+ c.JSON(200, result.OkData(neList))
+}
+
+// 网元信息
+//
+// GET /:infoId
+func (s *NeInfoController) Info(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ infoId := c.Param("infoId")
+ if infoId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ neHost := s.neInfoService.SelectById(infoId, true)
+ if neHost.ID != infoId {
+ // 没有可访问网元信息数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "neInfo.noData")))
+ return
+ }
+
+ c.JSON(200, result.OkData(neHost))
+}
+
+// 网元neType和neID查询
+//
+// GET /
+func (s *NeInfoController) NeTypeAndID(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var querys struct {
+ NeType string `form:"neType" binding:"required"`
+ NeID string `form:"neId" binding:"required"`
+ }
+ if err := c.ShouldBindQuery(&querys); err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID(querys.NeType, querys.NeID)
+ if neInfo.NeId != querys.NeID || neInfo.IP == "" {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
+ return
+ }
+ c.JSON(200, result.OkData(neInfo))
+}
+
+// 网元信息新增
+//
+// POST /
+func (s *NeInfoController) Add(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.NeInfo
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.ID != "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查属性值唯一
+ uniqueInfo := s.neInfoService.CheckUniqueNeTypeAndNeId(body.NeType, body.NeId, "")
+ if !uniqueInfo {
+ // 网元信息操作【%s】失败,同类型下标识已存在
+ msg := i18n.TTemplate(language, "neInfo.errKeyExists", map[string]any{"key": body.NeId})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 获取网元状态是否正常
+ _, err = neService.NeState(body)
+ if err != nil {
+ body.Status = "1"
+ } else {
+ // 下发网管配置信息给网元
+ _, err = neService.NeConfigOMC(body)
+ if err == nil {
+ body.Status = "0"
+ } else {
+ body.Status = "3"
+ }
+ }
+
+ insertId := s.neInfoService.Insert(body)
+ if insertId != "" {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 网元信息修改
+//
+// PUT /
+func (s *NeInfoController) Edit(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.NeInfo
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.ID == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查属性值唯一
+ uniqueHostCmd := s.neInfoService.CheckUniqueNeTypeAndNeId(body.NeType, body.NeId, body.ID)
+ if !uniqueHostCmd {
+ // 网元信息操作【%s】失败,同类型下标识已存在
+ msg := i18n.TTemplate(language, "neInfo.errKeyExists", map[string]any{"key": body.NeId})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 检查是否存在
+ neInfo := s.neInfoService.SelectById(body.ID, false)
+ if neInfo.ID != body.ID {
+ // 没有可访问网元信息数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "neInfo.noData")))
+ return
+ }
+
+ // 获取网元状态是否正常
+ _, err = neService.NeState(body)
+ if err != nil {
+ body.Status = "1"
+ } else {
+ // 下发网管配置信息给网元
+ _, err = neService.NeConfigOMC(body)
+ if err == nil {
+ body.Status = "0"
+ } else {
+ body.Status = "3"
+ }
+ }
+
+ rows := s.neInfoService.Update(body)
+ if rows > 0 {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 网元信息删除
+//
+// DELETE /:infoIds
+func (s *NeInfoController) Remove(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ infoIds := c.Param("infoIds")
+ if infoIds == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ // 处理字符转id数组后去重
+ ids := strings.Split(infoIds, ",")
+ uniqueIDs := parse.RemoveDuplicates(ids)
+ if len(uniqueIDs) <= 0 {
+ c.JSON(200, result.Err(nil))
+ return
+ }
+ rows, err := s.neInfoService.DeleteByIds(uniqueIDs)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error())))
+ return
+ }
+ msg := i18n.TTemplate(language, "app.common.deleteSuccess", map[string]any{"num": rows})
+ c.JSON(200, result.OkMsg(msg))
+}
diff --git a/src/modules/network_element/controller/ne_software.go b/src/modules/network_element/controller/ne_software.go
new file mode 100644
index 0000000..ecf8386
--- /dev/null
+++ b/src/modules/network_element/controller/ne_software.go
@@ -0,0 +1,181 @@
+package controller
+
+import (
+ "strings"
+
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/network_element/model"
+ neService "nms_nbi/src/modules/network_element/service"
+
+ "github.com/gin-gonic/gin"
+ "github.com/gin-gonic/gin/binding"
+)
+
+// 实例化控制层 NeSoftwareController 结构体
+var NewNeSoftware = &NeSoftwareController{
+ neSoftwareService: neService.NewNeSoftwareImpl,
+}
+
+// 网元软件包信息请求
+//
+// PATH /software
+type NeSoftwareController struct {
+ // 网元软件包信息服务
+ neSoftwareService neService.INeSoftware
+}
+
+// 网元软件包信息列表
+//
+// GET /list
+func (s *NeSoftwareController) List(c *gin.Context) {
+ querys := ctx.QueryMap(c)
+ data := s.neSoftwareService.SelectPage(querys)
+
+ c.JSON(200, result.Ok(data))
+}
+
+// 网元软件包信息信息
+//
+// GET /:softwareId
+func (s *NeSoftwareController) Info(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ softwareId := c.Param("softwareId")
+ if softwareId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ neSoftware := s.neSoftwareService.SelectById(softwareId)
+ if neSoftware.ID != softwareId {
+ // 没有可访问网元版本数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "neSoftware.noData")))
+ return
+ }
+
+ c.JSON(200, result.OkData(neSoftware))
+}
+
+// 网元软件包信息新增
+//
+// POST /
+func (s *NeSoftwareController) Add(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.NeSoftware
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.ID != "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查属性值唯一
+ uniqueSoftware := s.neSoftwareService.CheckUniqueTypeAndFileNameAndVersion(body.NeType, body.FileName, body.Version, "")
+ if !uniqueSoftware {
+ // 网元软件包操作【%s】失败,网元类型与文件名版本已存在
+ msg := i18n.TTemplate(language, "neSoftware.errKeyExists", map[string]any{"name": body.FileName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ insertId := s.neSoftwareService.Insert(body)
+ if insertId != "" {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 网元软件包信息修改
+//
+// PUT /
+func (s *NeSoftwareController) Edit(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.NeSoftware
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.ID == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查属性值唯一
+ uniqueSoftware := s.neSoftwareService.CheckUniqueTypeAndFileNameAndVersion(body.NeType, body.FileName, body.Version, body.ID)
+ if !uniqueSoftware {
+ // 网元软件包操作【%s】失败,网元类型与文件名版本已存在
+ msg := i18n.TTemplate(language, "neSoftware.errKeyExists", map[string]any{"name": body.FileName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 检查是否存在
+ neSoftware := s.neSoftwareService.SelectById(body.ID)
+ if neSoftware.ID != body.ID {
+ // 没有可访问网元版本数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "neSoftware.noData")))
+ return
+ }
+
+ rows := s.neSoftwareService.Update(body)
+ if rows > 0 {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 网元软件包信息删除
+//
+// DELETE /:softwareIds
+func (s *NeSoftwareController) Remove(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ softwareIds := c.Param("softwareIds")
+ if softwareIds == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ // 处理字符转id数组后去重
+ ids := strings.Split(softwareIds, ",")
+ uniqueIDs := parse.RemoveDuplicates(ids)
+ if len(uniqueIDs) <= 0 {
+ c.JSON(200, result.Err(nil))
+ return
+ }
+ rows, err := s.neSoftwareService.DeleteByIds(uniqueIDs)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error())))
+ return
+ }
+ msg := i18n.TTemplate(language, "app.common.deleteSuccess", map[string]any{"num": rows})
+ c.JSON(200, result.OkMsg(msg))
+}
+
+// 网元软件包安装
+//
+// POST /install
+func (s *NeSoftwareController) Install(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.NeSoftware
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查是否存在
+ neSoftwares := s.neSoftwareService.SelectList(body)
+ if len(neSoftwares) == 0 {
+ // 没有可访问网元版本数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "neSoftware.noData")))
+ return
+ }
+ neSoftware := neSoftwares[0]
+
+ // 进行安装
+ output, err := s.neSoftwareService.Install(neSoftware)
+ if err != nil {
+ c.JSON(200, result.OkData(output))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
diff --git a/src/modules/network_element/controller/ne_version.go b/src/modules/network_element/controller/ne_version.go
new file mode 100644
index 0000000..9923405
--- /dev/null
+++ b/src/modules/network_element/controller/ne_version.go
@@ -0,0 +1,151 @@
+package controller
+
+import (
+ "strings"
+
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/network_element/model"
+ neService "nms_nbi/src/modules/network_element/service"
+
+ "github.com/gin-gonic/gin"
+ "github.com/gin-gonic/gin/binding"
+)
+
+// 实例化控制层 NeVersionController 结构体
+var NewNeVersion = &NeVersionController{
+ neVersionService: neService.NewNeVersionImpl,
+}
+
+// 网元版本信息请求
+//
+// PATH /version
+type NeVersionController struct {
+ // 网元版本信息服务
+ neVersionService neService.INeVersion
+}
+
+// 网元版本信息列表
+//
+// GET /list
+func (s *NeVersionController) List(c *gin.Context) {
+ querys := ctx.QueryMap(c)
+ data := s.neVersionService.SelectPage(querys)
+
+ c.JSON(200, result.Ok(data))
+}
+
+// 网元版本信息信息
+//
+// GET /:versionId
+func (s *NeVersionController) Info(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ versionId := c.Param("versionId")
+ if versionId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ neVersion := s.neVersionService.SelectById(versionId)
+ if neVersion.ID != versionId {
+ // 没有可访问网元版本数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "neVersion.noData")))
+ return
+ }
+
+ c.JSON(200, result.OkData(neVersion))
+}
+
+// 网元版本信息新增
+//
+// POST /
+func (s *NeVersionController) Add(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.NeVersion
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.ID != "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查属性值唯一
+ uniqueInfo := s.neVersionService.CheckUniqueTypeAndID(body.NeType, body.NeId, "")
+ if !uniqueInfo {
+ // 网元版本操作【%s】失败,网元类型信息已存在
+ msg := i18n.TTemplate(language, "neVersion.errKeyExists", map[string]any{"name": body.NeType})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ insertId := s.neVersionService.Insert(body)
+ if insertId != "" {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 网元版本信息修改
+//
+// PUT /
+func (s *NeVersionController) Edit(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.NeVersion
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.ID == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查属性值唯一
+ uniqueInfo := s.neVersionService.CheckUniqueTypeAndID(body.NeType, body.NeId, body.ID)
+ if !uniqueInfo {
+ // 网元版本操作【%s】失败,网元类型信息已存在
+ msg := i18n.TTemplate(language, "neVersion.errKeyExists", map[string]any{"name": body.NeType})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 检查是否存在
+ neVersion := s.neVersionService.SelectById(body.ID)
+ if neVersion.ID != body.ID {
+ // 没有可访问网元版本数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "neVersion.noData")))
+ return
+ }
+
+ rows := s.neVersionService.Update(body)
+ if rows > 0 {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 网元版本信息删除
+//
+// DELETE /:versionIds
+func (s *NeVersionController) Remove(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ versionIds := c.Param("versionIds")
+ if versionIds == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ // 处理字符转id数组后去重
+ ids := strings.Split(versionIds, ",")
+ uniqueIDs := parse.RemoveDuplicates(ids)
+ if len(uniqueIDs) <= 0 {
+ c.JSON(200, result.Err(nil))
+ return
+ }
+ rows, err := s.neVersionService.DeleteByIds(uniqueIDs)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error())))
+ return
+ }
+ msg := i18n.TTemplate(language, "app.common.deleteSuccess", map[string]any{"num": rows})
+ c.JSON(200, result.OkMsg(msg))
+}
diff --git a/src/modules/network_element/controller/udm_auth.go b/src/modules/network_element/controller/udm_auth.go
new file mode 100644
index 0000000..52035ec
--- /dev/null
+++ b/src/modules/network_element/controller/udm_auth.go
@@ -0,0 +1,472 @@
+package controller
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ mmlclient "nms_nbi/lib/core/mml_client"
+ "nms_nbi/src/framework/constants/uploadsubpath"
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/file"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/ssh"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/network_element/model"
+ neService "nms_nbi/src/modules/network_element/service"
+
+ "github.com/gin-gonic/gin"
+ "github.com/gin-gonic/gin/binding"
+)
+
+// 实例化控制层 UDMAuthController 结构体
+var NewUDMAuth = &UDMAuthController{
+ udmAuthService: neService.NewUDMAuthImpl,
+ neInfoService: neService.NewNeInfoImpl,
+}
+
+// UDM鉴权用户请求
+//
+// PATH /udm/auth
+type UDMAuthController struct {
+ // UDM鉴权信息服务
+ udmAuthService neService.IUDMAuth
+ // 网元信息服务
+ neInfoService neService.INeInfo
+}
+
+// UDM鉴权用户-获取全部保存数据库
+//
+// POST /resetData/:neId
+func (s *UDMAuthController) ResetData(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ neId := c.Param("neId")
+ if neId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ neId = ""
+ data := s.udmAuthService.Save(neId)
+ c.JSON(200, result.OkData(data))
+}
+
+// UDM鉴权用户
+//
+// GET /list
+func (s *UDMAuthController) List(c *gin.Context) {
+ querys := ctx.QueryMap(c)
+ querys["neId"] = ""
+ data := s.udmAuthService.Page(querys)
+ c.JSON(200, result.Ok(data))
+}
+
+// UDM鉴权用户-信息
+//
+// GET /:neId/:imsi
+func (s *UDMAuthController) Info(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ neId := c.Param("neId")
+ imsi := c.Param("imsi")
+ if neId == "" || imsi == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 查询网元获取IP
+ neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId)
+ if neInfo.NeId != neId || neInfo.IP == "" {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
+ return
+ }
+
+ msg := fmt.Sprintf("dsp authdat:imsi=%s", imsi)
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToMap(neInfo.IP, msg)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ if len(data) == 0 {
+ c.JSON(200, result.ErrMsg("No Auth Data"))
+ return
+ }
+
+ // 查询数据库是否存在并存入
+ neId = ""
+ var userInfo model.UDMAuth
+ list := s.udmAuthService.List(model.UDMAuth{NeID: neId, Imsi: imsi})
+ if len(list) > 0 {
+ userInfo = list[0]
+ // 返回查询的用户信息
+ userInfo.Amf = data["amf"]
+ userInfo.AlgoIndex = data["algo"]
+ userInfo.Opc = data["opc"]
+ userInfo.Ki = data["ki"]
+ } else {
+ userInfo := model.UDMAuth{
+ Imsi: imsi,
+ Amf: data["amf"],
+ AlgoIndex: data["algo"],
+ Opc: data["opc"],
+ Ki: data["ki"],
+ }
+ s.udmAuthService.Insert(neId, userInfo)
+ }
+ c.JSON(200, result.OkData(userInfo))
+}
+
+// UDM鉴权用户-增加
+//
+// POST /:neId
+func (s *UDMAuthController) Add(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ neId := c.Param("neId")
+ if neId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ var body model.UDMAuth
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.Imsi == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 查询网元获取IP
+ neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId)
+ if neInfo.NeId != neId || neInfo.IP == "" {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
+ return
+ }
+
+ msg := fmt.Sprintf("add authdat:imsi=%s,ki=%s,amf=%s,algo=%s,opc=%s", body.Imsi, body.Ki, body.Amf, body.AlgoIndex, body.Opc)
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.IP, msg)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ neId = ""
+ s.udmAuthService.Insert(neId, body)
+ }
+ c.JSON(200, result.OkData(data))
+}
+
+// UDM鉴权用户-批量添加
+//
+// POST /:neId/:num
+func (s *UDMAuthController) Adds(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ neId := c.Param("neId")
+ num := c.Param("num")
+ if neId == "" || num == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ var body model.UDMAuth
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.Imsi == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 查询网元获取IP
+ neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId)
+ if neInfo.NeId != neId || neInfo.IP == "" {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
+ return
+ }
+
+ msg := fmt.Sprintf("baa authdat:start_imsi=%s,sub_num=%s,ki=%s,amf=%s,algo=%s,opc=%s", body.Imsi, num, body.Ki, body.Amf, body.AlgoIndex, body.Opc)
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.IP, msg)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ neId = ""
+ s.udmAuthService.Inserts(neId, body, num)
+ }
+ c.JSON(200, result.OkData(data))
+}
+
+// UDM鉴权用户-修改
+//
+// PUT /:neId
+func (s *UDMAuthController) Edit(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ neId := c.Param("neId")
+ if neId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ var body model.UDMAuth
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.Imsi == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 查询网元获取IP
+ neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId)
+ if neInfo.NeId != neId || neInfo.IP == "" {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
+ return
+ }
+
+ msg := fmt.Sprintf("mod authdata:imsi=%s", body.Imsi)
+ // 修改的参数名称
+ if body.Ki != "" {
+ msg += fmt.Sprintf(",ki=%s", body.Ki)
+ }
+ if body.Amf != "" {
+ msg += fmt.Sprintf(",amf=%s", body.Amf)
+ }
+ if body.AlgoIndex != "" {
+ msg += fmt.Sprintf(",algo=%s", body.AlgoIndex)
+ }
+ if body.Opc != "" {
+ msg += fmt.Sprintf(",opc=%s", body.Opc)
+ }
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.IP, msg)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ neId = ""
+ s.udmAuthService.Update(neId, body)
+ }
+ c.JSON(200, result.OkData(data))
+}
+
+// UDM鉴权用户-删除
+//
+// DELETE /:neId/:imsi
+func (s *UDMAuthController) Remove(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ neId := c.Param("neId")
+ imsi := c.Param("imsi")
+ if neId == "" || imsi == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 处理字符转id数组后去重
+ imsiArr := strings.Split(imsi, ",")
+ uniqueIDs := parse.RemoveDuplicates(imsiArr)
+ if len(uniqueIDs) <= 0 {
+ c.JSON(200, result.Err(nil))
+ return
+ }
+
+ // 查询网元获取IP
+ neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId)
+ if neInfo.NeId != neId || neInfo.IP == "" {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
+ return
+ }
+
+ resultData := map[string]string{}
+ for _, imsi := range uniqueIDs {
+ msg := fmt.Sprintf("del authdat:imsi=%s", imsi)
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.IP, msg)
+ if err != nil {
+ resultData[imsi] = err.Error()
+ }
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ neId = ""
+ s.udmAuthService.Delete(neId, imsi)
+ resultData[imsi] = data
+ }
+ }
+
+ c.JSON(200, result.OkData(resultData))
+}
+
+// UDM鉴权用户-批量删除
+//
+// DELETE /:neId/:imsi/:num
+func (s *UDMAuthController) Removes(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ neId := c.Param("neId")
+ imsi := c.Param("imsi")
+ num := c.Param("num")
+ if neId == "" || imsi == "" || num == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 查询网元获取IP
+ neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId)
+ if neInfo.NeId != neId || neInfo.IP == "" {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
+ return
+ }
+
+ msg := fmt.Sprintf("bde authdat:start_imsi=%s,sub_num=%s", imsi, num)
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.IP, msg)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ neId = ""
+ s.udmAuthService.Deletes(neId, imsi, num)
+ }
+ c.JSON(200, result.OkData(data))
+}
+
+// UDM鉴权用户-导出
+//
+// POST /export
+func (s *UDMAuthController) Export(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body struct {
+ NeId string `json:"neId" binding:"required"`
+ Type string `json:"type" binding:"required"`
+ }
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ if !(body.Type == "csv" || body.Type == "txt") {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "ne.udm.errImportUserAuthFileFormat")))
+ return
+ }
+
+ neId := ""
+ list := s.udmAuthService.List(model.UDMAuth{NeID: neId})
+ // 文件名
+ fileName := fmt.Sprintf("OMC_AUTH_USER_EXPORT_%s_%d.%s", neId, time.Now().UnixMilli(), body.Type)
+ filePath := fmt.Sprintf("%s/%s", file.ParseUploadFileDir(uploadsubpath.EXPORT), fileName)
+
+ if body.Type == "csv" {
+ // 转换数据
+ data := [][]string{}
+ data = append(data, []string{"imsi", "ki", "algo", "amf", "opc"})
+ for _, v := range list {
+ data = append(data, []string{v.Imsi, v.Ki, v.AlgoIndex, v.Amf, v.Opc})
+ }
+ // 输出到文件
+ err := file.WriterCSVFile(data, filePath)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+ }
+
+ if body.Type == "txt" {
+ // 转换数据
+ data := [][]string{}
+ for _, v := range list {
+ data = append(data, []string{v.Imsi, v.Ki, v.AlgoIndex, v.Amf, v.Opc})
+ }
+ // 输出到文件
+ err = file.WriterTxtFile(data, filePath)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+ }
+
+ c.FileAttachment(filePath, fileName)
+}
+
+// UDM鉴权用户-导入
+//
+// POST /import
+func (s *UDMAuthController) Import(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ neId := c.PostForm("neId")
+ if neId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ formFile, err := c.FormFile("file")
+ if err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ // 获取文件名
+ if !(strings.HasSuffix(formFile.Filename, ".csv") || strings.HasSuffix(formFile.Filename, ".txt")) {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "ne.udm.errImportUserAuthFileFormat")))
+ return
+ }
+ // 上传文件转存
+ upFilePath, err := file.TransferUploadFile(formFile, uploadsubpath.IMPORT, nil)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 查询网元获取IP
+ neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId)
+ if neInfo.NeId != neId || neInfo.IP == "" {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
+ return
+ }
+
+ // 本地文件
+ localPath := file.ParseUploadFilePath(upFilePath)
+ nePath := "/tmp" //config.Get("mml.upload").(string)
+ // 复制到远程
+ err = ssh.FileSCPLocalToNe(neInfo.IP, localPath, nePath)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ fileName := localPath[strings.LastIndex(localPath, "/")+1:]
+ msg := fmt.Sprintf("import authdat:path=%s", fmt.Sprintf("%s/%s", nePath, fileName))
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.IP, msg)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ if strings.HasSuffix(fileName, ".csv") {
+ data := file.ReadCSVFile(localPath)
+ neId = ""
+ go s.udmAuthService.InsertCSV(neId, data)
+ }
+ if strings.HasSuffix(fileName, ".txt") {
+ data := file.ReadTxtFile(localPath)
+ neId = ""
+ go s.udmAuthService.InsertTxt(neId, data)
+ }
+ }
+ c.JSON(200, result.OkMsg(data))
+}
diff --git a/src/modules/network_element/controller/udm_sub.go b/src/modules/network_element/controller/udm_sub.go
new file mode 100644
index 0000000..91dc5d8
--- /dev/null
+++ b/src/modules/network_element/controller/udm_sub.go
@@ -0,0 +1,550 @@
+package controller
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+
+ mmlclient "nms_nbi/lib/core/mml_client"
+ "nms_nbi/src/framework/constants/uploadsubpath"
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/file"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/ssh"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/network_element/model"
+ neService "nms_nbi/src/modules/network_element/service"
+
+ "github.com/gin-gonic/gin"
+ "github.com/gin-gonic/gin/binding"
+)
+
+// 实例化控制层 UDMSubController 结构体
+var NewUDMSub = &UDMSubController{
+ udmSubService: neService.NewUDMSubImpl,
+ neInfoService: neService.NewNeInfoImpl,
+}
+
+// UDM签约用户请求
+//
+// PATH /udm/sub
+type UDMSubController struct {
+ // UDM鉴权信息服务
+ udmSubService neService.IUDMSub
+ // 网元信息服务
+ neInfoService neService.INeInfo
+}
+
+// UDM签约用户-获取全部保存数据库
+//
+// POST /resetData/:neId
+func (s *UDMSubController) ResetData(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ neId := c.Param("neId")
+ if neId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ neId = ""
+ data := s.udmSubService.Save(neId)
+ c.JSON(200, result.OkData(data))
+}
+
+// UDM签约用户
+//
+// GET /list
+func (s *UDMSubController) List(c *gin.Context) {
+ querys := ctx.QueryMap(c)
+ querys["neId"] = ""
+ data := s.udmSubService.Page(querys)
+ c.JSON(200, result.Ok(data))
+}
+
+// UDM签约用户-信息
+//
+// GET /:neId/:imsi
+func (s *UDMSubController) Info(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ neId := c.Param("neId")
+ imsi := c.Param("imsi")
+ if neId == "" || imsi == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 查询网元获取IP
+ neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId)
+ if neInfo.NeId != neId || neInfo.IP == "" {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
+ return
+ }
+
+ msg := fmt.Sprintf("dsp udmuser:imsi=%s", imsi)
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToMap(neInfo.IP, msg)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ if len(data) == 0 {
+ c.JSON(200, result.ErrMsg("No Subs Data"))
+ return
+ }
+
+ // 解析返回的数据
+ cnType, _ := strconv.ParseInt(data["CNType"][:4], 0, 64)
+ rat, _ := strconv.ParseInt(data["RAT"][:4], 0, 64)
+ msisdn := data["MSISDN"]
+ imsMsisdnLen := strings.Index(msisdn, ",")
+ if imsMsisdnLen != -1 {
+ msisdn = msisdn[:imsMsisdnLen]
+ }
+ userInfo := model.UDMSub{
+ Imsi: imsi,
+ Msisdn: msisdn,
+ Ambr: data["AMBR"],
+ Arfb: data["AreaForbidden"],
+ Cn: fmt.Sprint(cnType),
+ SmData: data["SM-Data(snssai+dnn[1..n])"],
+ Sar: data["ServiceAreaRestriction"],
+ Nssai: data["NSSAI"],
+ SmfSel: data["Smf-Selection"],
+ Rat: fmt.Sprint(rat),
+ }
+ // 1,64,24,65,def_eps,1,2,010200000000,-
+ if v, ok := data["EPS-Data"]; ok {
+ userInfo.EpsDat = v
+ arr := strings.Split(v, ",")
+ userInfo.EpsFlag = arr[0]
+ userInfo.EpsOdb = arr[1]
+ userInfo.HplmnOdb = arr[2]
+ userInfo.Ard = arr[3]
+ userInfo.Epstpl = arr[4]
+ userInfo.ContextId = arr[5]
+ userInfo.ApnContext = arr[7]
+ userInfo.StaticIp = arr[8]
+ }
+
+ // 查询数据库是否存在并存入更新
+ neId = ""
+ list := s.udmSubService.List(model.UDMSub{NeID: neId, Imsi: imsi})
+ if len(list) > 0 {
+ listItme := list[0]
+ userInfo.ID = listItme.ID
+ s.udmSubService.Update(neId, userInfo)
+ } else {
+ s.udmSubService.Insert(neId, userInfo)
+ }
+ c.JSON(200, result.OkData(userInfo))
+}
+
+// UDM签约用户-增加
+//
+// POST /:neId
+func (s *UDMSubController) Add(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ neId := c.Param("neId")
+ if neId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ var body model.UDMSub
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.Imsi == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 查询网元获取IP
+ neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId)
+ if neInfo.NeId != neId || neInfo.IP == "" {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
+ return
+ }
+
+ msg := fmt.Sprintf("add udmuser:imsi=%s,msisdn=%s,ambr=%s,nssai=%s,arfb=%s,sar=%s,rat=%s,cn=%s,smf_sel=%s,sm_data=%s,eps_flag=%s,eps_odb=%s,hplmn_odb=%s,ard=%s,epstpl=%s,context_id=%s,apn_context=%s",
+ body.Imsi, body.Msisdn, body.Ambr, body.Nssai, body.Arfb, body.Sar, body.Rat, body.Cn, body.SmfSel, body.SmData, body.EpsFlag, body.EpsOdb, body.HplmnOdb, body.Ard, body.Epstpl, body.ContextId, body.ApnContext)
+ // static_ip指给4G UE分配的静态IP,没有可不带此字段名,批量添加IP会自动递增
+ if body.StaticIp != "" {
+ msg += fmt.Sprintf(",static_ip=%s", body.StaticIp)
+ }
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.IP, msg)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ neId = ""
+ s.udmSubService.Insert(neId, body)
+ }
+ c.JSON(200, result.OkData(data))
+}
+
+// UDM签约用户-批量添加
+//
+// POST /:neId/:num
+func (s *UDMSubController) Adds(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ neId := c.Param("neId")
+ num := c.Param("num")
+ if neId == "" || num == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ var body model.UDMSub
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.Imsi == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 查询网元获取IP
+ neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId)
+ if neInfo.NeId != neId || neInfo.IP == "" {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
+ return
+ }
+
+ msg := fmt.Sprintf("baa udmuser:start_imsi=%s,start_msisdn=%s,sub_num=%s,ambr=%s,nssai=%s,arfb=%s,sar=%s,rat=%s,cn=%s,smf_sel=%s,sm_data=%s,eps_flag=%s,eps_odb=%s,hplmn_odb=%s,ard=%s,epstpl=%s,context_id=%s,apn_context=%s",
+ body.Imsi, body.Msisdn, num, body.Ambr, body.Nssai, body.Arfb, body.Sar, body.Rat, body.Cn, body.SmfSel, body.SmData, body.EpsFlag, body.EpsOdb, body.HplmnOdb, body.Ard, body.Epstpl, body.ContextId, body.ApnContext)
+ // static_ip指给4G UE分配的静态IP,没有可不带此字段名,批量添加IP会自动递增
+ if body.StaticIp != "" {
+ msg += fmt.Sprintf(",static_ip=%s", body.StaticIp)
+ }
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.IP, msg)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ neId = ""
+ s.udmSubService.Inserts(neId, body, num)
+ }
+ c.JSON(200, result.OkData(data))
+}
+
+// UDM签约用户-修改
+//
+// PUT /:neId
+func (s *UDMSubController) Edit(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ neId := c.Param("neId")
+ if neId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ var body model.UDMSub
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.Imsi == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 查询网元获取IP
+ neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId)
+ if neInfo.NeId != neId || neInfo.IP == "" {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
+ return
+ }
+
+ msg := fmt.Sprintf("mod udmuser:imsi=%s", body.Imsi)
+ // 修改的参数名称
+ if body.Msisdn != "" {
+ msg += fmt.Sprintf(",msisdn=%s", body.Msisdn)
+ }
+ if body.Ambr != "" {
+ msg += fmt.Sprintf(",ambr=%s", body.Ambr)
+ }
+ if body.Nssai != "" {
+ msg += fmt.Sprintf(",nssai=%s", body.Nssai)
+ }
+ if body.Arfb != "" {
+ msg += fmt.Sprintf(",arfb=%s", body.Arfb)
+ }
+ if body.Sar != "" {
+ msg += fmt.Sprintf(",sar=%s", body.Sar)
+ }
+ if body.Rat != "" {
+ msg += fmt.Sprintf(",rat=%s", body.Rat)
+ }
+ if body.Cn != "" {
+ msg += fmt.Sprintf(",cn=%s", body.Cn)
+ }
+ if body.SmfSel != "" {
+ msg += fmt.Sprintf(",smf_sel=%s", body.SmfSel)
+ }
+ if body.SmData != "" {
+ msg += fmt.Sprintf(",sm_data=%s", body.SmData)
+ }
+ if body.EpsDat != "" {
+ msg += fmt.Sprintf(",eps_dat=%s", body.EpsDat)
+ }
+ if body.EpsFlag != "" {
+ msg += fmt.Sprintf(",eps_flag=%s", body.EpsFlag)
+ }
+ if body.EpsOdb != "" {
+ msg += fmt.Sprintf(",eps_odb=%s", body.EpsOdb)
+ }
+ if body.HplmnOdb != "" {
+ msg += fmt.Sprintf(",hplmn_odb=%s", body.HplmnOdb)
+ }
+ if body.Epstpl != "" {
+ msg += fmt.Sprintf(",epstpl=%s", body.Epstpl)
+ }
+ if body.Ard != "" {
+ msg += fmt.Sprintf(",ard=%s", body.Ard)
+ }
+ if body.ContextId != "" {
+ msg += fmt.Sprintf(",context_id=%s", body.ContextId)
+ }
+ if body.ApnContext != "" {
+ msg += fmt.Sprintf(",apn_context=%s", body.ApnContext)
+ }
+ if body.StaticIp != "" {
+ msg += fmt.Sprintf(",static_ip=%s", body.StaticIp)
+ }
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.IP, msg)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ neId = ""
+ s.udmSubService.Update(neId, body)
+ }
+ c.JSON(200, result.OkData(data))
+}
+
+// UDM签约用户-删除
+//
+// DELETE /:neId/:imsi
+func (s *UDMSubController) Remove(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ neId := c.Param("neId")
+ imsi := c.Param("imsi")
+ if neId == "" || imsi == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 处理字符转id数组后去重
+ imsiArr := strings.Split(imsi, ",")
+ uniqueIDs := parse.RemoveDuplicates(imsiArr)
+ if len(uniqueIDs) <= 0 {
+ c.JSON(200, result.Err(nil))
+ return
+ }
+
+ // 查询网元获取IP
+ neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId)
+ if neInfo.NeId != neId || neInfo.IP == "" {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
+ return
+ }
+
+ resultData := map[string]string{}
+ for _, imsi := range uniqueIDs {
+ msg := fmt.Sprintf("del udmuser:imsi=%s", imsi)
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.IP, msg)
+ if err != nil {
+ resultData[imsi] = err.Error()
+ }
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ neId = ""
+ s.udmSubService.Delete(neId, imsi)
+ resultData[imsi] = data
+ }
+ }
+
+ c.JSON(200, result.OkData(resultData))
+}
+
+// UDM签约用户-批量删除
+//
+// DELETE /:neId/:imsi/:num
+func (s *UDMSubController) Removes(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ neId := c.Param("neId")
+ imsi := c.Param("imsi")
+ num := c.Param("num")
+ if neId == "" || imsi == "" || num == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 查询网元获取IP
+ neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId)
+ if neInfo.NeId != neId || neInfo.IP == "" {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
+ return
+ }
+
+ msg := fmt.Sprintf("bde udmuser:start_imsi=%s,sub_num=%s", imsi, num)
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.IP, msg)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ neId = ""
+ s.udmSubService.Deletes(neId, imsi, num)
+ }
+ c.JSON(200, result.OkData(data))
+}
+
+// UDM签约用户-导出
+//
+// POST /export
+func (s *UDMSubController) Export(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body struct {
+ NeId string `json:"neId" binding:"required"`
+ Type string `json:"type" binding:"required"`
+ }
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ if !(body.Type == "csv" || body.Type == "txt") {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "ne.udm.errImportUserSubFileFormat")))
+ return
+ }
+
+ neId := ""
+ list := s.udmSubService.List(model.UDMSub{NeID: neId})
+ // 文件名
+ fileName := fmt.Sprintf("OMC_SUB_USER_EXPORT_%s_%d.%s", neId, time.Now().UnixMilli(), body.Type)
+ filePath := fmt.Sprintf("%s/%s", file.ParseUploadFileDir(uploadsubpath.EXPORT), fileName)
+
+ if body.Type == "csv" {
+ // 转换数据
+ data := [][]string{}
+ data = append(data, []string{"imsi", "msisdn", "ambr", "nssai", "arfb", "sar", "rat", "cn", "smf_sel", "sm_dat", "eps_dat"})
+ for _, v := range list {
+ epsDat := fmt.Sprintf("%s,%s,%s,%s,%s,%s,%s,%s", v.EpsFlag, v.EpsOdb, v.HplmnOdb, v.Ard, v.Epstpl, v.ContextId, v.ApnContext, v.StaticIp)
+ data = append(data, []string{v.Imsi, v.Msisdn, v.Ambr, v.Nssai, v.Arfb, v.Sar, v.Rat, v.Cn, v.SmfSel, v.SmData, epsDat})
+ }
+ // 输出到文件
+ err = file.WriterCSVFile(data, filePath)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+ }
+
+ if body.Type == "txt" {
+ // 转换数据
+ data := [][]string{}
+ for _, v := range list {
+ epsDat := fmt.Sprintf("%s,%s,%s,%s,%s,%s,%s,%s", v.EpsFlag, v.EpsOdb, v.HplmnOdb, v.Ard, v.Epstpl, v.ContextId, v.ApnContext, v.StaticIp)
+ data = append(data, []string{v.Imsi, v.Msisdn, v.Ambr, v.Nssai, v.Arfb, v.Sar, v.Rat, v.Cn, v.SmfSel, v.SmData, epsDat})
+ }
+ // 输出到文件
+ err = file.WriterTxtFile(data, filePath)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+ }
+
+ c.FileAttachment(filePath, fileName)
+}
+
+// UDM签约用户-导入
+//
+// POST /import
+func (s *UDMSubController) Import(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ neId := c.PostForm("neId")
+ if neId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ formFile, err := c.FormFile("file")
+ if err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ // 获取文件名
+ if !(strings.HasSuffix(formFile.Filename, ".csv") || strings.HasSuffix(formFile.Filename, ".txt")) {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "ne.udm.errImportUserSubFileFormat")))
+ return
+ }
+ // 上传文件转存
+ upFilePath, err := file.TransferUploadFile(formFile, uploadsubpath.IMPORT, nil)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 查询网元获取IP
+ neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId)
+ if neInfo.NeId != neId || neInfo.IP == "" {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
+ return
+ }
+
+ // 本地文件
+ localPath := file.ParseUploadFilePath(upFilePath)
+ nePath := "/tmp" //config.Get("mml.upload").(string)
+ // 复制到远程
+ err = ssh.FileSCPLocalToNe(neInfo.IP, localPath, nePath)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ fileName := localPath[strings.LastIndex(localPath, "/")+1:]
+ msg := fmt.Sprintf("import udmuser:path=%s", fmt.Sprintf("%s/%s", nePath, fileName))
+
+ // 发送MML
+ data, err := mmlclient.MMLSendMsgToString(neInfo.IP, msg)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 命令ok时
+ if strings.Contains(data, "ok") {
+ if strings.HasSuffix(fileName, ".csv") {
+ data := file.ReadCSVFile(localPath)
+ neId = ""
+ go s.udmSubService.InsertCSV(neId, data)
+ }
+ if strings.HasSuffix(fileName, ".txt") {
+ data := file.ReadTxtFile(localPath)
+ neId = ""
+ go s.udmSubService.InsertTxt(neId, data)
+ }
+ }
+ c.JSON(200, result.OkMsg(data))
+}
diff --git a/src/modules/network_element/model/ne_host.go b/src/modules/network_element/model/ne_host.go
new file mode 100644
index 0000000..a479635
--- /dev/null
+++ b/src/modules/network_element/model/ne_host.go
@@ -0,0 +1,40 @@
+package model
+
+import "encoding/json"
+
+// NeHost 网元主机表 ne_host
+type NeHost struct {
+ HostID string `json:"hostId" gorm:"column:host_id"` // 主机主键
+ HostType string `json:"hostType" gorm:"column:host_type" binding:"oneof=ssh telnet"` // 主机类型 ssh telnet
+ GroupID string `json:"groupId" gorm:"column:group_id"` // 分组(0默认 1网元 2系统)
+ Title string `json:"title" gorm:"column:title"` // 标题名称
+ Addr string `json:"addr" gorm:"column:addr" binding:"required"` // 主机地址
+ Port int64 `json:"port" gorm:"column:port" binding:"required,number,max=65535,min=1"` // SSH端口
+ User string `json:"user" gorm:"column:user" binding:"required"` // 主机用户名
+ AuthMode string `json:"authMode" gorm:"column:auth_mode" binding:"oneof=0 1"` // 认证模式(0密码 1主机私钥)
+ Password string `json:"password" gorm:"column:password"` // 认证密码
+ PrivateKey string `json:"privateKey" gorm:"column:private_key"` // 认证私钥
+ PassPhrase string `json:"passPhrase" gorm:"column:pass_phrase"` // 认证私钥密码
+ Remark string `json:"remark" gorm:"column:remark"` // 备注
+ CreateBy string `json:"createBy" gorm:"column:create_by"` // 创建者
+ CreateTime int64 `json:"createTime" gorm:"column:create_time"` // 创建时间
+ UpdateBy string `json:"updateBy" gorm:"column:update_by"` // 更新者
+ UpdateTime int64 `json:"updateTime" gorm:"column:update_time"` // 更新时间
+}
+
+// TableName 表名称
+func (NeHost) TableName() string {
+ return "ne_host"
+}
+
+// CopyTo 网元主机信息将josn同key名的结构体复制给另一个结构体
+func (m *NeHost) CopyTo(to interface{}) error {
+ b, err := json.Marshal(m)
+ if err != nil {
+ return err
+ }
+ if err = json.Unmarshal(b, to); err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/src/modules/network_element/model/ne_host_cmd.go b/src/modules/network_element/model/ne_host_cmd.go
new file mode 100644
index 0000000..82aad31
--- /dev/null
+++ b/src/modules/network_element/model/ne_host_cmd.go
@@ -0,0 +1,20 @@
+package model
+
+// NeHostCmd 网元主机命令表 ne_host_cmd
+type NeHostCmd struct {
+ CmdID string `json:"cmdId" gorm:"column:cmd_id"` // 命令主键
+ CmdType string `json:"cmdType" gorm:"column:cmd_type"` // 命令类型
+ GroupID string `json:"groupId" gorm:"column:group_id"` // 分组(0默认 1快速命令)
+ Title string `json:"title" gorm:"column:title" binding:"required"` // 标题名称
+ Command string `json:"command" gorm:"column:command" binding:"required"` // 命令字符串
+ Remark string `json:"remark" gorm:"column:remark"` // 备注
+ CreateBy string `json:"createBy" gorm:"column:create_by"` // 创建者
+ CreateTime int64 `json:"createTime" gorm:"column:create_time"` // 创建时间
+ UpdateBy string `json:"updateBy" gorm:"column:update_by"` // 更新者
+ UpdateTime int64 `json:"updateTime" gorm:"column:update_time"` // 更新时间
+}
+
+// TableName 表名称
+func (*NeHostCmd) TableName() string {
+ return "ne_host_cmd"
+}
diff --git a/src/modules/network_element/model/ne_info.go b/src/modules/network_element/model/ne_info.go
new file mode 100644
index 0000000..c08e71a
--- /dev/null
+++ b/src/modules/network_element/model/ne_info.go
@@ -0,0 +1,28 @@
+package model
+
+// NeInfo 网元信息对象 ne_info
+type NeInfo struct {
+ ID string `json:"id"`
+ NeType string `json:"neType" binding:"required"`
+ NeId string `json:"neId" binding:"required"`
+ RmUID string `json:"rmUid"`
+ NeName string `json:"neName"`
+ IP string `json:"ip" binding:"required"`
+ Port int64 `json:"port" binding:"required,number,max=65535,min=1"`
+ PvFlag string `json:"pvFlag" binding:"oneof=PNF VNF"` // 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"`
+ HostIDs string `json:"hostIds"` // 网元主机ID组 数据格式(ssh,telnet,telnet)
+
+ // ====== 非数据库字段属性 ======
+
+ // 服务状态
+ ServerState map[string]any `json:"serverState,omitempty"`
+
+ // 主机对象组
+ Hosts []NeHost `json:"hosts,omitempty"`
+}
diff --git a/src/modules/network_element/model/ne_software.go b/src/modules/network_element/model/ne_software.go
new file mode 100644
index 0000000..afb6dff
--- /dev/null
+++ b/src/modules/network_element/model/ne_software.go
@@ -0,0 +1,21 @@
+package model
+
+import "time"
+
+// NeSoftware 网元软件包 ne_software
+type NeSoftware struct {
+ ID string `json:"id" gorm:"id"`
+ NeType string `json:"neType" gorm:"ne_type" binding:"required"` // 网元类型
+ FileName string `json:"fileName" gorm:"file_name" binding:"required"` // 包名称
+ Path string `json:"path" gorm:"path"` // 包路径
+ Version string `json:"version" gorm:"version" binding:"required"` // 包版本
+ Md5Sum string `json:"md5Sum" gorm:"md5_sum"` // --无使用 md5签名
+ Status string `json:"status" gorm:"status"` // --无使用
+ Comment string `json:"comment" gorm:"comment"` // 包说明
+ UpdateTime time.Time `json:"updateTime" gorm:"update_time"` // 上传时间
+}
+
+// TableName 表名称
+func (*NeSoftware) TableName() string {
+ return "ne_software"
+}
diff --git a/src/modules/network_element/model/ne_version.go b/src/modules/network_element/model/ne_version.go
new file mode 100644
index 0000000..11a00e6
--- /dev/null
+++ b/src/modules/network_element/model/ne_version.go
@@ -0,0 +1,23 @@
+package model
+
+import "time"
+
+// NeVersion 网元版本信息 ne_version
+type NeVersion struct {
+ ID string `json:"id" gorm:"id"`
+ NeType string `json:"neType" gorm:"ne_type" binding:"required"` // 网元类型
+ NeId string `json:"neId" gorm:"ne_id" binding:"required"` // 网元ID
+ Version string `json:"version" gorm:"version"` // 当前版本
+ FilePath string `json:"filePath" gorm:"file_path"` // 当前软件包
+ PreVersion string `json:"preVersion" gorm:"pre_version"` // 上一版本
+ PreFile string `json:"preFile" gorm:"pre_file"` // 上一版本软件包
+ NewVersion string `json:"newVersion" gorm:"new_version"` // 下一版本
+ NewFile string `json:"newFile" gorm:"new_file"` // 下一版本软件包
+ Status string `json:"status" gorm:"status" binding:"oneof=Uploaded Inactive Active"` // 当前状态 (Uploaded下一版本上传 Inactive下一版本待激活 Active当前已激活)
+ UpdateTime time.Time `json:"updateTime" gorm:"update_time"` // 更新时间
+}
+
+// TableName 表名称
+func (*NeVersion) TableName() string {
+ return "ne_version"
+}
diff --git a/src/modules/network_element/model/udm_auth.go b/src/modules/network_element/model/udm_auth.go
new file mode 100644
index 0000000..2fe70e2
--- /dev/null
+++ b/src/modules/network_element/model/udm_auth.go
@@ -0,0 +1,17 @@
+package model
+
+// UDMAuth UDM鉴权用户对象 u_auth_user
+type UDMAuth struct {
+ ID string `json:"id" gorm:"column:id;primaryKey;autoIncrement"`
+ Imsi string `json:"imsi" gorm:"column:imsi"` // SIM卡号
+ Amf string `json:"amf" gorm:"column:amf"` // ANF
+ Status string `json:"status" gorm:"column:status"` // 状态
+ Ki string `json:"ki" gorm:"column:ki"` // ki
+ AlgoIndex string `json:"algoIndex" gorm:"column:algo_index"` // AlgoIndex
+ Opc string `json:"opc" gorm:"column:opc"`
+ NeID string `json:"neId" gorm:"column:ne_id"` // UDM网元标识-子系统
+}
+
+func (UDMAuth) TableName() string {
+ return "u_auth_user"
+}
diff --git a/src/modules/network_element/model/udm_sub.go b/src/modules/network_element/model/udm_sub.go
new file mode 100644
index 0000000..1aa345c
--- /dev/null
+++ b/src/modules/network_element/model/udm_sub.go
@@ -0,0 +1,33 @@
+package model
+
+// UDMSub UDM签约用户对象 u_sub_user
+type UDMSub struct {
+ ID string `json:"id" gorm:"column:id;primaryKey;autoIncrement"`
+ Msisdn string `json:"msisdn" gorm:"column:msisdn"` // 相当手机号
+ Imsi string `json:"imsi" gorm:"column:imsi"` // SIM卡号
+ Ambr string `json:"ambr" gorm:"column:ambr"`
+ Nssai string `json:"nssai" gorm:"column:nssai"`
+ Rat string `json:"rat" gorm:"column:rat"`
+ Arfb string `json:"arfb" gorm:"column:arfb"`
+ Sar string `json:"sar" gorm:"column:sar"`
+ Cn string `json:"cn" gorm:"column:cn"`
+ SmData string `json:"smData" gorm:"column:sm_data"`
+ SmfSel string `json:"smfSel" gorm:"column:smf_sel"`
+ EpsDat string `json:"epsDat" gorm:"column:eps_dat"`
+ NeID string `json:"neId" gorm:"column:ne_id"` // UDM网元标识-子系统
+
+ EpsFlag string `json:"epsFlag" gorm:"column:eps_flag"`
+ EpsOdb string `json:"epsOdb" gorm:"column:eps_odb"`
+ HplmnOdb string `json:"hplmnOdb" gorm:"column:hplmn_odb"`
+ Ard string `json:"ard" gorm:"column:ard"`
+ Epstpl string `json:"epstpl" gorm:"column:epstpl"`
+ ContextId string `json:"contextId" gorm:"column:context_id"`
+ ApnContext string `json:"apnContext" gorm:"column:apn_context"`
+ StaticIp string `json:"staticIp" gorm:"column:static_ip"`
+
+ SubNum string `json:"subNum,omitempty" gorm:"-"` // 批量数
+}
+
+func (UDMSub) TableName() string {
+ return "u_sub_user"
+}
diff --git a/src/modules/network_element/network_element.go b/src/modules/network_element/network_element.go
new file mode 100644
index 0000000..caba089
--- /dev/null
+++ b/src/modules/network_element/network_element.go
@@ -0,0 +1,326 @@
+package networkelement
+
+import (
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/middleware"
+ "nms_nbi/src/framework/middleware/collectlogs"
+ "nms_nbi/src/framework/middleware/repeat"
+ "nms_nbi/src/modules/network_element/controller"
+ "nms_nbi/src/modules/network_element/service"
+
+ "github.com/gin-gonic/gin"
+)
+
+// 模块路由注册
+func Setup(router *gin.Engine) {
+ logger.Infof("开始加载 ====> network_element 模块路由")
+
+ // 启动时需要的初始参数
+ InitLoad()
+
+ neGroup := router.Group("/ne")
+
+ // 网元操作处理
+ neActionGroup := neGroup.Group("/action")
+ {
+ neActionGroup.GET("/files",
+ middleware.PreAuthorize(nil),
+ controller.NewNeAction.Files,
+ )
+ neActionGroup.GET("/pullFile",
+ middleware.PreAuthorize(nil),
+ controller.NewNeAction.PullFile,
+ )
+ neActionGroup.POST("/pushFile",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.neAction", collectlogs.BUSINESS_TYPE_IMPORT)),
+ controller.NewNeAction.PushFile,
+ )
+ }
+
+ // 网元信息
+ neInfoGroup := neGroup.Group("/info")
+ {
+ neInfoGroup.GET("/listAll",
+ middleware.PreAuthorize(nil),
+ controller.NewNeInfo.ListAll,
+ )
+ neInfoGroup.GET("/list",
+ middleware.PreAuthorize(nil),
+ controller.NewNeInfo.List,
+ )
+ neInfoGroup.GET("/:infoId",
+ middleware.PreAuthorize(nil),
+ controller.NewNeInfo.Info,
+ )
+ neInfoGroup.POST("",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.neInfo", collectlogs.BUSINESS_TYPE_INSERT)),
+ controller.NewNeInfo.Add,
+ )
+ neInfoGroup.PUT("",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.neInfo", collectlogs.BUSINESS_TYPE_UPDATE)),
+ controller.NewNeInfo.Edit,
+ )
+ neInfoGroup.DELETE("/:infoIds",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.neInfo", collectlogs.BUSINESS_TYPE_DELETE)),
+ controller.NewNeInfo.Remove,
+ )
+ neInfoGroup.GET("/state",
+ middleware.PreAuthorize(nil),
+ controller.NewNeInfo.State,
+ )
+ neInfoGroup.GET("/byTypeAndID",
+ middleware.PreAuthorize(nil),
+ controller.NewNeInfo.NeTypeAndID,
+ )
+ }
+
+ // 网元主机
+ neHostGroup := neGroup.Group("/host")
+ {
+ neHostGroup.GET("/list",
+ middleware.PreAuthorize(nil),
+ controller.NewNeHost.List,
+ )
+ neHostGroup.GET("/:hostId",
+ middleware.PreAuthorize(nil),
+ controller.NewNeHost.Info,
+ )
+ neHostGroup.POST("",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.neHost", collectlogs.BUSINESS_TYPE_INSERT)),
+ controller.NewNeHost.Add,
+ )
+ neHostGroup.PUT("",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.neHost", collectlogs.BUSINESS_TYPE_UPDATE)),
+ controller.NewNeHost.Edit,
+ )
+ neHostGroup.DELETE("/:hostIds",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.neHost", collectlogs.BUSINESS_TYPE_DELETE)),
+ controller.NewNeHost.Remove,
+ )
+ neHostGroup.POST("/test",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.neHost", collectlogs.BUSINESS_TYPE_OTHER)),
+ controller.NewNeHost.Test,
+ )
+ neHostGroup.POST("/cmd",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.neHost", collectlogs.BUSINESS_TYPE_OTHER)),
+ controller.NewNeHost.Cmd,
+ )
+ neHostGroup.POST("/checkBySSH",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.neHost", collectlogs.BUSINESS_TYPE_OTHER)),
+ controller.NewNeHost.CheckBySSH,
+ )
+ }
+
+ // 网元主机命令
+ neHostCmdGroup := neGroup.Group("/hostCmd")
+ {
+ neHostCmdGroup.GET("/list",
+ middleware.PreAuthorize(nil),
+ controller.NewNeHostCmd.List,
+ )
+ neHostCmdGroup.GET("/:cmdId",
+ middleware.PreAuthorize(nil),
+ controller.NewNeHostCmd.Info,
+ )
+ neHostCmdGroup.POST("",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.neHostCmd", collectlogs.BUSINESS_TYPE_INSERT)),
+ controller.NewNeHostCmd.Add,
+ )
+ neHostCmdGroup.PUT("",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.neHostCmd", collectlogs.BUSINESS_TYPE_UPDATE)),
+ controller.NewNeHostCmd.Edit,
+ )
+ neHostCmdGroup.DELETE("/:cmdIds",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.neHostCmd", collectlogs.BUSINESS_TYPE_DELETE)),
+ controller.NewNeHostCmd.Remove,
+ )
+ }
+
+ // 网元版本信息
+ neVersionGroup := neGroup.Group("/version")
+ {
+ neVersionGroup.GET("/list",
+ middleware.PreAuthorize(nil),
+ controller.NewNeVersion.List,
+ )
+ neVersionGroup.GET("/:versionId",
+ middleware.PreAuthorize(nil),
+ controller.NewNeVersion.Info,
+ )
+ neVersionGroup.POST("",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.neVersion", collectlogs.BUSINESS_TYPE_INSERT)),
+ controller.NewNeVersion.Add,
+ )
+ neVersionGroup.PUT("",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.neVersion", collectlogs.BUSINESS_TYPE_UPDATE)),
+ controller.NewNeVersion.Edit,
+ )
+ neVersionGroup.DELETE("/:versionIds",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.neVersion", collectlogs.BUSINESS_TYPE_DELETE)),
+ controller.NewNeVersion.Remove,
+ )
+ }
+
+ // 网元软件包信息
+ neSoftwareGroup := neGroup.Group("/software")
+ {
+ neSoftwareGroup.GET("/list",
+ middleware.PreAuthorize(nil),
+ controller.NewNeSoftware.List,
+ )
+ neSoftwareGroup.GET("/:softwareId",
+ middleware.PreAuthorize(nil),
+ controller.NewNeSoftware.Info,
+ )
+ neSoftwareGroup.POST("",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.neSoftware", collectlogs.BUSINESS_TYPE_INSERT)),
+ controller.NewNeSoftware.Add,
+ )
+ neSoftwareGroup.PUT("",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.neSoftware", collectlogs.BUSINESS_TYPE_UPDATE)),
+ controller.NewNeSoftware.Edit,
+ )
+ neSoftwareGroup.DELETE("/:softwareIds",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.neSoftware", collectlogs.BUSINESS_TYPE_DELETE)),
+ controller.NewNeSoftware.Remove,
+ )
+ neSoftwareGroup.POST("/install",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.neSoftware", collectlogs.BUSINESS_TYPE_DELETE)),
+ controller.NewNeSoftware.Install,
+ )
+ }
+
+ // UDM鉴权用户信息
+ udmAuthGroup := neGroup.Group("/udm/auth")
+ {
+ udmAuthGroup.PUT("/resetData/:neId",
+ repeat.RepeatSubmit(5),
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmAuth", collectlogs.BUSINESS_TYPE_CLEAN)),
+ controller.NewUDMAuth.ResetData,
+ )
+ udmAuthGroup.GET("/list",
+ middleware.PreAuthorize(nil),
+ controller.NewUDMAuth.List,
+ )
+ udmAuthGroup.GET("/:neId/:imsi",
+ middleware.PreAuthorize(nil),
+ controller.NewUDMAuth.Info,
+ )
+ udmAuthGroup.POST("/:neId",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmAuth", collectlogs.BUSINESS_TYPE_INSERT)),
+ controller.NewUDMAuth.Add,
+ )
+ udmAuthGroup.POST("/:neId/:num",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmAuth", collectlogs.BUSINESS_TYPE_INSERT)),
+ controller.NewUDMAuth.Adds,
+ )
+ udmAuthGroup.PUT("/:neId",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmAuth", collectlogs.BUSINESS_TYPE_UPDATE)),
+ controller.NewUDMAuth.Edit,
+ )
+ udmAuthGroup.DELETE("/:neId/:imsi",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmAuth", collectlogs.BUSINESS_TYPE_DELETE)),
+ controller.NewUDMAuth.Remove,
+ )
+ udmAuthGroup.DELETE("/:neId/:imsi/:num",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmAuth", collectlogs.BUSINESS_TYPE_DELETE)),
+ controller.NewUDMAuth.Removes,
+ )
+ udmAuthGroup.POST("/export",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmAuth", collectlogs.BUSINESS_TYPE_EXPORT)),
+ controller.NewUDMAuth.Export,
+ )
+ udmAuthGroup.POST("/import",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmAuth", collectlogs.BUSINESS_TYPE_IMPORT)),
+ controller.NewUDMAuth.Import,
+ )
+ }
+
+ // UDM签约用户信息
+ udmSubGroup := neGroup.Group("/udm/sub")
+ {
+ udmSubGroup.PUT("/resetData/:neId",
+ repeat.RepeatSubmit(5),
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmSub", collectlogs.BUSINESS_TYPE_CLEAN)),
+ controller.NewUDMSub.ResetData,
+ )
+ udmSubGroup.GET("/list",
+ middleware.PreAuthorize(nil),
+ controller.NewUDMSub.List,
+ )
+ udmSubGroup.GET("/:neId/:imsi",
+ middleware.PreAuthorize(nil),
+ controller.NewUDMSub.Info,
+ )
+ udmSubGroup.POST("/:neId",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmSub", collectlogs.BUSINESS_TYPE_INSERT)),
+ controller.NewUDMSub.Add,
+ )
+ udmSubGroup.POST("/:neId/:num",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmSub", collectlogs.BUSINESS_TYPE_INSERT)),
+ controller.NewUDMSub.Adds,
+ )
+ udmSubGroup.PUT("/:neId",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmSub", collectlogs.BUSINESS_TYPE_UPDATE)),
+ controller.NewUDMSub.Edit,
+ )
+ udmSubGroup.DELETE("/:neId/:imsi",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmSub", collectlogs.BUSINESS_TYPE_DELETE)),
+ controller.NewUDMSub.Remove,
+ )
+ udmSubGroup.DELETE("/:neId/:imsi/:num",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmSub", collectlogs.BUSINESS_TYPE_DELETE)),
+ controller.NewUDMSub.Removes,
+ )
+ udmSubGroup.POST("/export",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmSub", collectlogs.BUSINESS_TYPE_EXPORT)),
+ controller.NewUDMSub.Export,
+ )
+ udmSubGroup.POST("/import",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmSub", collectlogs.BUSINESS_TYPE_IMPORT)),
+ controller.NewUDMSub.Import,
+ )
+ }
+}
+
+// InitLoad 初始参数
+func InitLoad() {
+ // 启动时,清除缓存-网元类型
+ service.NewNeInfoImpl.ClearNeCacheByNeType("*")
+}
diff --git a/src/modules/network_element/repository/ne_host.go b/src/modules/network_element/repository/ne_host.go
new file mode 100644
index 0000000..d4ee631
--- /dev/null
+++ b/src/modules/network_element/repository/ne_host.go
@@ -0,0 +1,27 @@
+package repository
+
+import "nms_nbi/src/modules/network_element/model"
+
+// INeHost 网元主机连接 数据层接口
+type INeHost interface {
+ // SelectPage 根据条件分页查询字典类型
+ SelectPage(query map[string]any) map[string]any
+
+ // SelectList 根据实体查询
+ SelectList(neHost model.NeHost) []model.NeHost
+
+ // SelectByIds 通过ID查询
+ SelectByIds(hostIds []string) []model.NeHost
+
+ // Insert 新增信息
+ Insert(neHost model.NeHost) string
+
+ // Update 修改信息
+ Update(neHost model.NeHost) int64
+
+ // DeleteByIds 批量删除网元主机连接信息
+ DeleteByIds(hostIds []string) int64
+
+ // CheckUniqueNeHost 校验主机是否唯一
+ CheckUniqueNeHost(neHost model.NeHost) string
+}
diff --git a/src/modules/network_element/repository/ne_host.impl.go b/src/modules/network_element/repository/ne_host.impl.go
new file mode 100644
index 0000000..6f42ee5
--- /dev/null
+++ b/src/modules/network_element/repository/ne_host.impl.go
@@ -0,0 +1,404 @@
+package repository
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/crypto"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/repo"
+ "nms_nbi/src/modules/network_element/model"
+)
+
+// 实例化数据层 NewNeHostImpl 结构体
+var NewNeHostImpl = &NeHostImpl{
+ selectSql: `select
+ host_id, host_type, group_id, title, addr, port, user, auth_mode, password, private_key, pass_phrase, remark, create_by, create_time, update_by, update_time
+ from ne_host`,
+
+ resultMap: map[string]string{
+ "host_id": "HostID",
+ "host_type": "HostType",
+ "group_id": "GroupID",
+ "title": "Title",
+ "addr": "Addr",
+ "port": "Port",
+ "user": "User",
+ "auth_mode": "AuthMode",
+ "password": "Password",
+ "private_key": "PrivateKey",
+ "private_password": "PassPhrase",
+ "remark": "Remark",
+ "create_by": "CreateBy",
+ "create_time": "CreateTime",
+ "update_by": "UpdateBy",
+ "update_time": "UpdateTime",
+ },
+}
+
+// NeHostImpl 网元主机连接 数据层处理
+type NeHostImpl struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *NeHostImpl) convertResultRows(rows []map[string]any) []model.NeHost {
+ arr := make([]model.NeHost, 0)
+ for _, row := range rows {
+ item := model.NeHost{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ repo.SetFieldValue(&item, keyMapper, value)
+ }
+ }
+ arr = append(arr, item)
+ }
+ return arr
+}
+
+// SelectPage 根据条件分页查询字典类型
+func (r *NeHostImpl) SelectPage(query map[string]any) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if v, ok := query["hostType"]; ok && v != "" {
+ conditions = append(conditions, "host_type = ?")
+ params = append(params, strings.Trim(v.(string), " "))
+ }
+ if v, ok := query["groupId"]; ok && v != "" {
+ conditions = append(conditions, "group_id = ?")
+ params = append(params, strings.Trim(v.(string), " "))
+ }
+ if v, ok := query["title"]; ok && v != "" {
+ conditions = append(conditions, "title like concat(?, '%')")
+ params = append(params, strings.Trim(v.(string), " "))
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ result := map[string]any{
+ "total": 0,
+ "rows": []model.NeHost{},
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := "select count(1) as 'total' from ne_host"
+ totalRows, err := datasource.RawDB("", totalSql+whereSql, params)
+ if err != nil {
+ logger.Errorf("total err => %v", err)
+ return result
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return result
+ } else {
+ result["total"] = total
+ }
+
+ // 分页
+ pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return result
+ }
+
+ // 转换实体
+ result["rows"] = r.convertResultRows(results)
+ return result
+}
+
+// SelectList 根据实体查询
+func (r *NeHostImpl) SelectList(neHost model.NeHost) []model.NeHost {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if neHost.HostType != "" {
+ conditions = append(conditions, "host_type = ?")
+ params = append(params, neHost.HostType)
+ }
+ if neHost.GroupID != "" {
+ conditions = append(conditions, "group_id = ?")
+ params = append(params, neHost.GroupID)
+ }
+ if neHost.Title != "" {
+ conditions = append(conditions, "title like concat(?, '%')")
+ params = append(params, neHost.Title)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + " order by update_time asc "
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ }
+
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// SelectByIds 通过ID查询
+func (r *NeHostImpl) SelectByIds(hostIds []string) []model.NeHost {
+ placeholder := repo.KeyPlaceholderByQuery(len(hostIds))
+ querySql := r.selectSql + " where host_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(hostIds)
+ results, err := datasource.RawDB("", querySql, parameters)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.NeHost{}
+ }
+ // 转换实体
+ rows := r.convertResultRows(results)
+ arr := &rows
+ for i := range *arr {
+ passwordDe, err := crypto.StringDecryptByAES((*arr)[i].Password)
+ if err != nil {
+ logger.Errorf("selectById %s StringDecryptByAES : %v", (*arr)[i].HostID, err.Error())
+ (*arr)[i].Password = ""
+ } else {
+ (*arr)[i].Password = passwordDe
+ }
+ privateKeyDe, err := crypto.StringDecryptByAES((*arr)[i].PrivateKey)
+ if err != nil {
+ logger.Errorf("selectById %s StringDecryptByAES : %v", (*arr)[i].HostID, err.Error())
+ (*arr)[i].PrivateKey = ""
+ } else {
+ (*arr)[i].PrivateKey = privateKeyDe
+ }
+ passPhraseDe, err := crypto.StringDecryptByAES((*arr)[i].PassPhrase)
+ if err != nil {
+ logger.Errorf("selectById %s StringDecryptByAES : %v", (*arr)[i].HostID, err.Error())
+ (*arr)[i].PassPhrase = ""
+ } else {
+ (*arr)[i].PassPhrase = passPhraseDe
+ }
+ }
+ return rows
+}
+
+// CheckUniqueNeHost 校验主机是否唯一
+func (r *NeHostImpl) CheckUniqueNeHost(neHost model.NeHost) string {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if neHost.HostType != "" {
+ conditions = append(conditions, "host_type = ?")
+ params = append(params, neHost.HostType)
+ }
+ if neHost.GroupID != "" {
+ conditions = append(conditions, "group_id = ?")
+ params = append(params, neHost.GroupID)
+ }
+ if neHost.Title != "" {
+ conditions = append(conditions, "title = ?")
+ params = append(params, neHost.Title)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ } else {
+ return ""
+ }
+
+ // 查询数据
+ querySql := "select host_id as 'str' from ne_host " + whereSql + " limit 1"
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err %v", err)
+ return ""
+ }
+ if len(results) > 0 {
+ return fmt.Sprint(results[0]["str"])
+ }
+ return ""
+}
+
+// Insert 新增信息
+func (r *NeHostImpl) Insert(neHost model.NeHost) string {
+ // 参数拼接
+ params := make(map[string]any)
+ if neHost.HostType != "" {
+ params["host_type"] = neHost.HostType
+ }
+ if neHost.GroupID != "" {
+ params["group_id"] = neHost.GroupID
+ }
+ if neHost.Title != "" {
+ params["title"] = neHost.Title
+ }
+ if neHost.Addr != "" {
+ params["addr"] = neHost.Addr
+ }
+ if neHost.Port > 0 {
+ params["port"] = neHost.Port
+ }
+ if neHost.User != "" {
+ params["user"] = neHost.User
+ }
+ if neHost.AuthMode != "" {
+ params["auth_mode"] = neHost.AuthMode
+ }
+ if neHost.Password != "" {
+ passwordEn, err := crypto.StringEncryptByAES(neHost.Password)
+ if err != nil {
+ logger.Errorf("insert StringEncryptByAES : %v", err.Error())
+ return ""
+ }
+ params["password"] = passwordEn
+ }
+ if neHost.PrivateKey != "" {
+ privateKeyEn, err := crypto.StringEncryptByAES(neHost.PrivateKey)
+ if err != nil {
+ logger.Errorf("insert StringEncryptByAES : %v", err.Error())
+ return ""
+ }
+ params["private_key"] = privateKeyEn
+ }
+ if neHost.PassPhrase != "" {
+ passPhraseEn, err := crypto.StringEncryptByAES(neHost.PassPhrase)
+ if err != nil {
+ logger.Errorf("insert StringEncryptByAES : %v", err.Error())
+ return ""
+ }
+ params["pass_phrase"] = passPhraseEn
+ }
+ if neHost.Remark != "" {
+ params["remark"] = neHost.Remark
+ }
+ if neHost.CreateBy != "" {
+ params["create_by"] = neHost.CreateBy
+ params["create_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params)
+ sql := "insert into ne_host (" + strings.Join(keys, ",") + ")values(" + placeholder + ")"
+
+ db := datasource.DefaultDB()
+ // 开启事务
+ tx := db.Begin()
+ // 执行插入
+ err := tx.Exec(sql, values...).Error
+ if err != nil {
+ logger.Errorf("insert row : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 获取生成的自增 ID
+ var insertedID string
+ err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID)
+ if err != nil {
+ logger.Errorf("insert last id : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 提交事务
+ tx.Commit()
+ return insertedID
+}
+
+// Update 修改信息
+func (r *NeHostImpl) Update(neHost model.NeHost) int64 {
+ // 参数拼接
+ params := make(map[string]any)
+ if neHost.HostType != "" {
+ params["host_type"] = neHost.HostType
+ }
+ if neHost.GroupID != "" {
+ params["group_id"] = neHost.GroupID
+ }
+ if neHost.Title != "" {
+ params["title"] = neHost.Title
+ }
+ if neHost.Addr != "" {
+ params["addr"] = neHost.Addr
+ }
+ if neHost.Port > 0 {
+ params["port"] = neHost.Port
+ }
+ if neHost.User != "" {
+ params["user"] = neHost.User
+ }
+ if neHost.AuthMode != "" {
+ params["auth_mode"] = neHost.AuthMode
+ }
+ if neHost.Password != "" {
+ passwordEn, err := crypto.StringEncryptByAES(neHost.Password)
+ if err != nil {
+ logger.Errorf("update StringEncryptByAES : %v", err.Error())
+ return 0
+ }
+ params["password"] = passwordEn
+ }
+ if neHost.PrivateKey != "" {
+ privateKeyEn, err := crypto.StringEncryptByAES(neHost.PrivateKey)
+ if err != nil {
+ logger.Errorf("update StringEncryptByAES : %v", err.Error())
+ return 0
+ }
+ params["private_key"] = privateKeyEn
+ }
+ if neHost.PassPhrase != "" {
+ passPhraseEn, err := crypto.StringEncryptByAES(neHost.PassPhrase)
+ if err != nil {
+ logger.Errorf("update StringEncryptByAES : %v", err.Error())
+ return 0
+ }
+ params["pass_phrase"] = passPhraseEn
+ }
+ params["remark"] = neHost.Remark
+ if neHost.UpdateBy != "" {
+ params["update_by"] = neHost.UpdateBy
+ params["update_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, values := repo.KeyValueByUpdate(params)
+ sql := "update ne_host set " + strings.Join(keys, ",") + " where host_id = ?"
+
+ // 执行更新
+ values = append(values, neHost.HostID)
+ rows, err := datasource.ExecDB("", sql, values)
+ if err != nil {
+ logger.Errorf("update row : %v", err.Error())
+ return 0
+ }
+ return rows
+}
+
+// DeleteByIds 批量删除网元主机连接信息
+func (r *NeHostImpl) DeleteByIds(hostIds []string) int64 {
+ placeholder := repo.KeyPlaceholderByQuery(len(hostIds))
+ sql := "delete from ne_host where host_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(hostIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
diff --git a/src/modules/network_element/repository/ne_host_cmd.go b/src/modules/network_element/repository/ne_host_cmd.go
new file mode 100644
index 0000000..437e66b
--- /dev/null
+++ b/src/modules/network_element/repository/ne_host_cmd.go
@@ -0,0 +1,27 @@
+package repository
+
+import "nms_nbi/src/modules/network_element/model"
+
+// INeHostCmd 网元主机命令 数据层接口
+type INeHostCmd interface {
+ // SelectPage 根据条件分页查询字典类型
+ SelectPage(query map[string]any) map[string]any
+
+ // SelectList 根据实体查询
+ SelectList(neHostCmd model.NeHostCmd) []model.NeHostCmd
+
+ // SelectByIds 通过ID查询
+ SelectByIds(cmdIds []string) []model.NeHostCmd
+
+ // Insert 新增信息
+ Insert(neHostCmd model.NeHostCmd) string
+
+ // Update 修改信息
+ Update(neHostCmd model.NeHostCmd) int64
+
+ // DeleteByIds 批量删除信息
+ DeleteByIds(cmdIds []string) int64
+
+ // CheckUniqueGroupTitle 校验同类型组内是否唯一
+ CheckUniqueGroupTitle(neHostCmd model.NeHostCmd) string
+}
diff --git a/src/modules/network_element/repository/ne_host_cmd.impl.go b/src/modules/network_element/repository/ne_host_cmd.impl.go
new file mode 100644
index 0000000..84b3302
--- /dev/null
+++ b/src/modules/network_element/repository/ne_host_cmd.impl.go
@@ -0,0 +1,306 @@
+package repository
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/repo"
+ "nms_nbi/src/modules/network_element/model"
+)
+
+// 实例化数据层 NewNeHostCmdImpl 结构体
+var NewNeHostCmdImpl = &NeHostCmdImpl{
+ selectSql: `select
+ cmd_id, cmd_type, group_id, title, command, remark, create_by, create_time, update_by, update_time
+ from ne_host_cmd`,
+
+ resultMap: map[string]string{
+ "cmd_id": "CmdID",
+ "cmd_type": "CmdType",
+ "group_id": "GroupID",
+ "title": "Title",
+ "command": "Command",
+ "remark": "Remark",
+ "create_by": "CreateBy",
+ "create_time": "CreateTime",
+ "update_by": "UpdateBy",
+ "update_time": "UpdateTime",
+ },
+}
+
+// NeHostCmdImpl 网元主机连接 数据层处理
+type NeHostCmdImpl struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *NeHostCmdImpl) convertResultRows(rows []map[string]any) []model.NeHostCmd {
+ arr := make([]model.NeHostCmd, 0)
+ for _, row := range rows {
+ item := model.NeHostCmd{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ repo.SetFieldValue(&item, keyMapper, value)
+ }
+ }
+ arr = append(arr, item)
+ }
+ return arr
+}
+
+// SelectPage 根据条件分页查询字典类型
+func (r *NeHostCmdImpl) SelectPage(query map[string]any) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if v, ok := query["cmdType"]; ok && v != "" {
+ conditions = append(conditions, "cmd_type = ?")
+ params = append(params, strings.Trim(v.(string), " "))
+ }
+ if v, ok := query["groupId"]; ok && v != "" {
+ conditions = append(conditions, "group_id = ?")
+ params = append(params, strings.Trim(v.(string), " "))
+ }
+ if v, ok := query["title"]; ok && v != "" {
+ conditions = append(conditions, "title like concat(?, '%')")
+ params = append(params, strings.Trim(v.(string), " "))
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ result := map[string]any{
+ "total": 0,
+ "rows": []model.NeHost{},
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := "select count(1) as 'total' from ne_host_cmd"
+ totalRows, err := datasource.RawDB("", totalSql+whereSql, params)
+ if err != nil {
+ logger.Errorf("total err => %v", err)
+ return result
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return result
+ } else {
+ result["total"] = total
+ }
+
+ // 分页
+ pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return result
+ }
+
+ // 转换实体
+ result["rows"] = r.convertResultRows(results)
+ return result
+}
+
+// SelectList 根据实体查询
+func (r *NeHostCmdImpl) SelectList(neHostCmd model.NeHostCmd) []model.NeHostCmd {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if neHostCmd.CmdType != "" {
+ conditions = append(conditions, "cmd_type = ?")
+ params = append(params, neHostCmd.CmdType)
+ }
+ if neHostCmd.GroupID != "" {
+ conditions = append(conditions, "group_id = ?")
+ params = append(params, neHostCmd.GroupID)
+ }
+ if neHostCmd.Title != "" {
+ conditions = append(conditions, "title like concat(?, '%')")
+ params = append(params, neHostCmd.Title)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + " order by update_time asc "
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ }
+
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// SelectByIds 通过ID查询
+func (r *NeHostCmdImpl) SelectByIds(cmdIds []string) []model.NeHostCmd {
+ placeholder := repo.KeyPlaceholderByQuery(len(cmdIds))
+ querySql := r.selectSql + " where cmd_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(cmdIds)
+ results, err := datasource.RawDB("", querySql, parameters)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.NeHostCmd{}
+ }
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// CheckUniqueGroupTitle 校验同类型组内是否唯一
+func (r *NeHostCmdImpl) CheckUniqueGroupTitle(neHostCmd model.NeHostCmd) string {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if neHostCmd.CmdType != "" {
+ conditions = append(conditions, "cmd_type = ?")
+ params = append(params, neHostCmd.CmdType)
+ }
+ if neHostCmd.GroupID != "" {
+ conditions = append(conditions, "group_id = ?")
+ params = append(params, neHostCmd.GroupID)
+ }
+ if neHostCmd.Title != "" {
+ conditions = append(conditions, "title = ?")
+ params = append(params, neHostCmd.Title)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ } else {
+ return ""
+ }
+
+ // 查询数据
+ querySql := "select host_id as 'str' from ne_host " + whereSql + " limit 1"
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err %v", err)
+ return ""
+ }
+ if len(results) > 0 {
+ return fmt.Sprint(results[0]["str"])
+ }
+ return ""
+}
+
+// Insert 新增信息
+func (r *NeHostCmdImpl) Insert(neHostCmd model.NeHostCmd) string {
+ // 参数拼接
+ params := make(map[string]any)
+ if neHostCmd.CmdType != "" {
+ params["cmd_type"] = neHostCmd.CmdType
+ }
+ if neHostCmd.GroupID != "" {
+ params["group_id"] = neHostCmd.GroupID
+ }
+ if neHostCmd.Title != "" {
+ params["title"] = neHostCmd.Title
+ }
+ if neHostCmd.Command != "" {
+ params["command"] = neHostCmd.Command
+ }
+ if neHostCmd.Remark != "" {
+ params["remark"] = neHostCmd.Remark
+ }
+ if neHostCmd.CreateBy != "" {
+ params["create_by"] = neHostCmd.CreateBy
+ params["create_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params)
+ sql := "insert into ne_host_cmd (" + strings.Join(keys, ",") + ")values(" + placeholder + ")"
+
+ db := datasource.DefaultDB()
+ // 开启事务
+ tx := db.Begin()
+ // 执行插入
+ err := tx.Exec(sql, values...).Error
+ if err != nil {
+ logger.Errorf("insert row : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 获取生成的自增 ID
+ var insertedID string
+ err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID)
+ if err != nil {
+ logger.Errorf("insert last id : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 提交事务
+ tx.Commit()
+ return insertedID
+}
+
+// Update 修改信息
+func (r *NeHostCmdImpl) Update(neHostCmd model.NeHostCmd) int64 {
+ // 参数拼接
+ params := make(map[string]any)
+ if neHostCmd.CmdType != "" {
+ params["cmd_type"] = neHostCmd.CmdType
+ }
+ if neHostCmd.GroupID != "" {
+ params["group_id"] = neHostCmd.GroupID
+ }
+ if neHostCmd.Title != "" {
+ params["title"] = neHostCmd.Title
+ }
+ if neHostCmd.Command != "" {
+ params["command"] = neHostCmd.Command
+ }
+ params["remark"] = neHostCmd.Remark
+ if neHostCmd.UpdateBy != "" {
+ params["update_by"] = neHostCmd.UpdateBy
+ params["update_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, values := repo.KeyValueByUpdate(params)
+ sql := "update ne_host_cmd set " + strings.Join(keys, ",") + " where cmd_id = ?"
+
+ // 执行更新
+ values = append(values, neHostCmd.CmdID)
+ rows, err := datasource.ExecDB("", sql, values)
+ if err != nil {
+ logger.Errorf("update row : %v", err.Error())
+ return 0
+ }
+ return rows
+}
+
+// DeleteByIds 批量删除信息
+func (r *NeHostCmdImpl) DeleteByIds(cmdIds []string) int64 {
+ placeholder := repo.KeyPlaceholderByQuery(len(cmdIds))
+ sql := "delete from ne_host_cmd where cmd_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(cmdIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
diff --git a/src/modules/network_element/repository/ne_info.go b/src/modules/network_element/repository/ne_info.go
new file mode 100644
index 0000000..01d2cbd
--- /dev/null
+++ b/src/modules/network_element/repository/ne_info.go
@@ -0,0 +1,32 @@
+package repository
+
+import (
+ "nms_nbi/src/modules/network_element/model"
+)
+
+// 网元信息 数据层接口
+type INeInfo interface {
+ // SelectNeInfoByNeTypeAndNeID 通过ne_type和ne_id查询网元信息
+ SelectNeInfoByNeTypeAndNeID(neType, neID string) model.NeInfo
+
+ // SelectPage 根据条件分页查询
+ SelectPage(query map[string]any) map[string]any
+
+ // SelectList 查询列表
+ SelectList(neInfo model.NeInfo) []model.NeInfo
+
+ // SelectByIds 通过ID查询
+ SelectByIds(infoIds []string) []model.NeInfo
+
+ // Insert 新增信息
+ Insert(neInfo model.NeInfo) string
+
+ // Update 修改信息
+ Update(neInfo model.NeInfo) int64
+
+ // DeleteByIds 批量删除网元信息
+ DeleteByIds(infoIds []string) int64
+
+ // CheckUniqueNeTypeAndNeId 校验同类型下标识是否唯一
+ CheckUniqueNeTypeAndNeId(neInfo model.NeInfo) string
+}
diff --git a/src/modules/network_element/repository/ne_info.impl.go b/src/modules/network_element/repository/ne_info.impl.go
new file mode 100644
index 0000000..4c212a0
--- /dev/null
+++ b/src/modules/network_element/repository/ne_info.impl.go
@@ -0,0 +1,393 @@
+package repository
+
+import (
+ "fmt"
+ "sort"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/repo"
+ "nms_nbi/src/modules/network_element/model"
+)
+
+// neListSort 网元列表预设排序
+var neListSort = []string{
+ "OMC",
+ "MME",
+ "AMF",
+ "AUSF",
+ "UDM",
+ "SMF",
+ "PCF",
+ "UPF",
+ "NRF",
+ "NSSF",
+ "IMS",
+ "N3IWF",
+ "NEF",
+ "LMF",
+}
+
+// 实例化数据层 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, host_ids 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",
+ "host_ids": "HostIDs",
+ },
+}
+
+// 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)
+ }
+
+ // 排序
+ sort.Slice(arr, func(i, j int) bool {
+ // 前一个
+ after := arr[i]
+ afterIndex := 0
+ for i, v := range neListSort {
+ if after.NeType == v {
+ afterIndex = i
+ break
+ }
+ }
+ // 后一个
+ befter := arr[j]
+ befterIndex := 0
+ for i, v := range neListSort {
+ if befter.NeType == v {
+ befterIndex = i
+ break
+ }
+ }
+ // 升序
+ return afterIndex < befterIndex
+ })
+
+ 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{}
+}
+
+// SelectPage 根据条件分页查询
+func (r *NeInfoImpl) SelectPage(query map[string]any) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if v, ok := query["neType"]; ok && v != "" {
+ conditions = append(conditions, "ne_type = ?")
+ params = append(params, strings.Trim(v.(string), " "))
+ }
+ if v, ok := query["neId"]; ok && v != "" {
+ conditions = append(conditions, "ne_id = ?")
+ params = append(params, strings.Trim(v.(string), " "))
+ }
+ if v, ok := query["rmUid"]; ok && v != "" {
+ conditions = append(conditions, "rmUid like concat(?, '%')")
+ params = append(params, strings.Trim(v.(string), " "))
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ result := map[string]any{
+ "total": 0,
+ "rows": []model.NeInfo{},
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := "select count(1) as 'total' from ne_info"
+ totalRows, err := datasource.RawDB("", totalSql+whereSql, params)
+ if err != nil {
+ logger.Errorf("total err => %v", err)
+ return result
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return result
+ } else {
+ result["total"] = total
+ }
+
+ // 分页
+ pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + " order by ne_type asc, ne_id desc " + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return result
+ }
+
+ // 转换实体
+ result["rows"] = r.convertResultRows(results)
+ return result
+}
+
+// SelectList 查询列表
+func (r *NeInfoImpl) SelectList(neInfo model.NeInfo) []model.NeInfo {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if neInfo.NeType != "" {
+ conditions = append(conditions, "ne_type = ?")
+ params = append(params, neInfo.NeType)
+ }
+ if neInfo.NeId != "" {
+ conditions = append(conditions, "ne_id = ?")
+ params = append(params, neInfo.NeId)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + " order by ne_type asc, ne_id desc "
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ }
+
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// SelectByIds 通过ID查询
+func (r *NeInfoImpl) SelectByIds(infoIds []string) []model.NeInfo {
+ placeholder := repo.KeyPlaceholderByQuery(len(infoIds))
+ querySql := r.selectSql + " where id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(infoIds)
+ results, err := datasource.RawDB("", querySql, parameters)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.NeInfo{}
+ }
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// CheckUniqueNeTypeAndNeId 校验同类型下标识是否唯一
+func (r *NeInfoImpl) CheckUniqueNeTypeAndNeId(neInfo model.NeInfo) string {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if neInfo.NeType != "" {
+ conditions = append(conditions, "ne_type = ?")
+ params = append(params, neInfo.NeType)
+ }
+ if neInfo.NeId != "" {
+ conditions = append(conditions, "ne_id = ?")
+ params = append(params, neInfo.NeId)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ } else {
+ return ""
+ }
+
+ // 查询数据
+ querySql := "select id as 'str' from ne_info " + whereSql + " limit 1"
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err %v", err)
+ return ""
+ }
+ if len(results) > 0 {
+ return fmt.Sprint(results[0]["str"])
+ }
+ return ""
+}
+
+// Insert 新增信息
+func (r *NeInfoImpl) Insert(neInfo model.NeInfo) string {
+ // 参数拼接
+ params := make(map[string]any)
+ if neInfo.NeType != "" {
+ params["ne_type"] = neInfo.NeType
+ }
+ if neInfo.NeId != "" {
+ params["ne_id"] = neInfo.NeId
+ }
+ if neInfo.RmUID != "" {
+ params["rm_uid"] = neInfo.RmUID
+ }
+ if neInfo.NeName != "" {
+ params["ne_name"] = neInfo.NeName
+ }
+ if neInfo.IP != "" {
+ params["ip"] = neInfo.IP
+ }
+ if neInfo.Port > 0 {
+ params["port"] = neInfo.Port
+ }
+ if neInfo.PvFlag != "" {
+ params["pv_flag"] = neInfo.PvFlag
+ }
+ if neInfo.Province != "" {
+ params["province"] = neInfo.Province
+ }
+ if neInfo.VendorName != "" {
+ params["vendor_name"] = neInfo.VendorName
+ }
+ if neInfo.Dn != "" {
+ params["dn"] = neInfo.Dn
+ }
+ if neInfo.NeAddress != "" {
+ params["ne_address"] = neInfo.NeAddress
+ }
+ params["status"] = neInfo.Status
+ params["update_time"] = time.Now()
+ if neInfo.HostIDs != "" {
+ params["host_ids"] = neInfo.HostIDs
+ }
+
+ // 构建执行语句
+ keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params)
+ sql := "insert into ne_info (" + strings.Join(keys, ",") + ")values(" + placeholder + ")"
+
+ db := datasource.DefaultDB()
+ // 开启事务
+ tx := db.Begin()
+ // 执行插入
+ err := tx.Exec(sql, values...).Error
+ if err != nil {
+ logger.Errorf("insert row : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 获取生成的自增 ID
+ var insertedID string
+ err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID)
+ if err != nil {
+ logger.Errorf("insert last id : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 提交事务
+ tx.Commit()
+ return insertedID
+}
+
+// Update 修改信息
+func (r *NeInfoImpl) Update(neInfo model.NeInfo) int64 {
+ // 参数拼接
+ params := make(map[string]any)
+ if neInfo.NeType != "" {
+ params["ne_type"] = neInfo.NeType
+ }
+ if neInfo.NeId != "" {
+ params["ne_id"] = neInfo.NeId
+ }
+ if neInfo.RmUID != "" {
+ params["rm_uid"] = neInfo.RmUID
+ }
+ if neInfo.NeName != "" {
+ params["ne_name"] = neInfo.NeName
+ }
+ if neInfo.IP != "" {
+ params["ip"] = neInfo.IP
+ }
+ if neInfo.Port > 0 {
+ params["port"] = neInfo.Port
+ }
+ if neInfo.PvFlag != "" {
+ params["pv_flag"] = neInfo.PvFlag
+ }
+ params["province"] = neInfo.Province
+ params["vendor_name"] = neInfo.VendorName
+ params["dn"] = neInfo.Dn
+ params["ne_address"] = neInfo.NeAddress
+ params["status"] = neInfo.Status
+ params["update_time"] = time.Now()
+ if neInfo.HostIDs != "" {
+ params["host_ids"] = neInfo.HostIDs
+ }
+
+ // 构建执行语句
+ keys, values := repo.KeyValueByUpdate(params)
+ sql := "update ne_info set " + strings.Join(keys, ",") + " where id = ?"
+
+ // 执行更新
+ values = append(values, neInfo.ID)
+ rows, err := datasource.ExecDB("", sql, values)
+ if err != nil {
+ logger.Errorf("update row : %v", err.Error())
+ return 0
+ }
+ return rows
+}
+
+// DeleteByIds 批量删除网元信息
+func (r *NeInfoImpl) DeleteByIds(infoIds []string) int64 {
+ placeholder := repo.KeyPlaceholderByQuery(len(infoIds))
+ sql := "delete from ne_info where id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(infoIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
diff --git a/src/modules/network_element/repository/ne_software.go b/src/modules/network_element/repository/ne_software.go
new file mode 100644
index 0000000..83d50f4
--- /dev/null
+++ b/src/modules/network_element/repository/ne_software.go
@@ -0,0 +1,27 @@
+package repository
+
+import "nms_nbi/src/modules/network_element/model"
+
+// INeSoftware 网元软件包信息 数据层接口
+type INeSoftware interface {
+ // SelectPage 根据条件分页查询字典类型
+ SelectPage(query map[string]any) map[string]any
+
+ // SelectList 根据实体查询
+ SelectList(neSoftware model.NeSoftware) []model.NeSoftware
+
+ // SelectByIds 通过ID查询
+ SelectByIds(ids []string) []model.NeSoftware
+
+ // Insert 新增信息
+ Insert(neSoftware model.NeSoftware) string
+
+ // Update 修改信息
+ Update(neSoftware model.NeSoftware) int64
+
+ // DeleteByIds 批量删除信息
+ DeleteByIds(ids []string) int64
+
+ // CheckUniqueTypeAndFileNameAndVersion 校验网元类型和文件名版本是否唯一
+ CheckUniqueTypeAndFileNameAndVersion(neSoftware model.NeSoftware) string
+}
diff --git a/src/modules/network_element/repository/ne_software.impl.go b/src/modules/network_element/repository/ne_software.impl.go
new file mode 100644
index 0000000..593c6dd
--- /dev/null
+++ b/src/modules/network_element/repository/ne_software.impl.go
@@ -0,0 +1,319 @@
+package repository
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/repo"
+ "nms_nbi/src/modules/network_element/model"
+)
+
+// 实例化数据层 NewNeSoftware 结构体
+var NewNeSoftwareImpl = &NeSoftwareImpl{
+ selectSql: `select
+ id, ne_type, file_name, path, version, md5_sum, status, comment, update_time
+ from ne_software`,
+
+ resultMap: map[string]string{
+ "id": "ID",
+ "ne_type": "NeType",
+ "file_name": "FileName",
+ "path": "Path",
+ "version": "Version",
+ "md5_sum": "Md5Sum",
+ "status": "Status",
+ "comment": "Comment",
+ "update_time": "UpdateTime",
+ },
+}
+
+// NeSoftwareImpl 网元软件包信息 数据层处理
+type NeSoftwareImpl struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *NeSoftwareImpl) convertResultRows(rows []map[string]any) []model.NeSoftware {
+ arr := make([]model.NeSoftware, 0)
+ for _, row := range rows {
+ item := model.NeSoftware{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ repo.SetFieldValue(&item, keyMapper, value)
+ }
+ }
+ arr = append(arr, item)
+ }
+ return arr
+}
+
+// SelectPage 根据条件分页查询字典类型
+func (r *NeSoftwareImpl) SelectPage(query map[string]any) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if v, ok := query["neType"]; ok && v != "" {
+ conditions = append(conditions, "ne_type = ?")
+ params = append(params, strings.Trim(v.(string), " "))
+ }
+ if v, ok := query["fileName"]; ok && v != "" {
+ conditions = append(conditions, "file_name like concat(?, '%')")
+ params = append(params, strings.Trim(v.(string), " "))
+ }
+ if v, ok := query["version"]; ok && v != "" {
+ conditions = append(conditions, "version like concat(?, '%')")
+ params = append(params, strings.Trim(v.(string), " "))
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ result := map[string]any{
+ "total": 0,
+ "rows": []model.NeHost{},
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := "select count(1) as 'total' from ne_software"
+ totalRows, err := datasource.RawDB("", totalSql+whereSql, params)
+ if err != nil {
+ logger.Errorf("total err => %v", err)
+ return result
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return result
+ } else {
+ result["total"] = total
+ }
+
+ // 分页
+ pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " order by id desc limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return result
+ }
+
+ // 转换实体
+ result["rows"] = r.convertResultRows(results)
+ return result
+}
+
+// SelectList 根据实体查询
+func (r *NeSoftwareImpl) SelectList(neSoftware model.NeSoftware) []model.NeSoftware {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if neSoftware.NeType != "" {
+ conditions = append(conditions, "ne_type = ?")
+ params = append(params, neSoftware.NeType)
+ }
+ if neSoftware.Version != "" {
+ conditions = append(conditions, "version = ?")
+ params = append(params, neSoftware.Version)
+ }
+ if neSoftware.FileName != "" {
+ conditions = append(conditions, "file_name like concat(?, '%')")
+ params = append(params, neSoftware.FileName)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + " order by id desc "
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ }
+
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// SelectByIds 通过ID查询
+func (r *NeSoftwareImpl) SelectByIds(cmdIds []string) []model.NeSoftware {
+ placeholder := repo.KeyPlaceholderByQuery(len(cmdIds))
+ querySql := r.selectSql + " where id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(cmdIds)
+ results, err := datasource.RawDB("", querySql, parameters)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.NeSoftware{}
+ }
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// CheckUniqueTypeAndFileNameAndVersion 校验网元类型和文件名版本是否唯一
+func (r *NeSoftwareImpl) CheckUniqueTypeAndFileNameAndVersion(neSoftware model.NeSoftware) string {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if neSoftware.NeType != "" {
+ conditions = append(conditions, "ne_type = ?")
+ params = append(params, neSoftware.NeType)
+ }
+ if neSoftware.Version != "" {
+ conditions = append(conditions, "version = ?")
+ params = append(params, neSoftware.Version)
+ }
+ if neSoftware.FileName != "" {
+ conditions = append(conditions, "file_name = ?")
+ params = append(params, neSoftware.FileName)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ } else {
+ return ""
+ }
+
+ // 查询数据
+ querySql := "select id as 'str' from ne_software " + whereSql + " limit 1"
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err %v", err)
+ return ""
+ }
+ if len(results) > 0 {
+ return fmt.Sprint(results[0]["str"])
+ }
+ return ""
+}
+
+// Insert 新增信息
+func (r *NeSoftwareImpl) Insert(neSoftware model.NeSoftware) string {
+ // 参数拼接
+ params := make(map[string]any)
+ if neSoftware.NeType != "" {
+ params["ne_type"] = neSoftware.NeType
+ }
+ if neSoftware.FileName != "" {
+ params["file_name"] = neSoftware.FileName
+ }
+ if neSoftware.Path != "" {
+ params["path"] = neSoftware.Path
+ }
+ if neSoftware.Version != "" {
+ params["version"] = neSoftware.Version
+ }
+ if neSoftware.Md5Sum != "" {
+ params["md5_sum"] = neSoftware.Md5Sum
+ }
+ if neSoftware.Status != "" {
+ params["status"] = neSoftware.Status
+ }
+ if neSoftware.Comment != "" {
+ params["comment"] = neSoftware.Comment
+ }
+ if neSoftware.Status != "" {
+ params["status"] = neSoftware.Status
+ }
+ params["update_time"] = time.Now()
+
+ // 构建执行语句
+ keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params)
+ sql := "insert into ne_software (" + strings.Join(keys, ",") + ")values(" + placeholder + ")"
+
+ db := datasource.DefaultDB()
+ // 开启事务
+ tx := db.Begin()
+ // 执行插入
+ err := tx.Exec(sql, values...).Error
+ if err != nil {
+ logger.Errorf("insert row : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 获取生成的自增 ID
+ var insertedID string
+ err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID)
+ if err != nil {
+ logger.Errorf("insert last id : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 提交事务
+ tx.Commit()
+ return insertedID
+}
+
+// Update 修改信息
+func (r *NeSoftwareImpl) Update(neSoftware model.NeSoftware) int64 {
+ // 参数拼接
+ params := make(map[string]any)
+ if neSoftware.NeType != "" {
+ params["ne_type"] = neSoftware.NeType
+ }
+ if neSoftware.FileName != "" {
+ params["file_name"] = neSoftware.FileName
+ }
+ if neSoftware.Path != "" {
+ params["path"] = neSoftware.Path
+ }
+ if neSoftware.Version != "" {
+ params["version"] = neSoftware.Version
+ }
+ if neSoftware.Md5Sum != "" {
+ params["md5_sum"] = neSoftware.Md5Sum
+ }
+ if neSoftware.Status != "" {
+ params["status"] = neSoftware.Status
+ }
+ if neSoftware.Comment != "" {
+ params["comment"] = neSoftware.Comment
+ }
+ if neSoftware.Status != "" {
+ params["status"] = neSoftware.Status
+ }
+ params["update_time"] = time.Now()
+
+ // 构建执行语句
+ keys, values := repo.KeyValueByUpdate(params)
+ sql := "update ne_software set " + strings.Join(keys, ",") + " where id = ?"
+
+ // 执行更新
+ values = append(values, neSoftware.ID)
+ rows, err := datasource.ExecDB("", sql, values)
+ if err != nil {
+ logger.Errorf("update row : %v", err.Error())
+ return 0
+ }
+ return rows
+}
+
+// DeleteByIds 批量删除信息
+func (r *NeSoftwareImpl) DeleteByIds(cmdIds []string) int64 {
+ placeholder := repo.KeyPlaceholderByQuery(len(cmdIds))
+ sql := "delete from ne_software where id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(cmdIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
diff --git a/src/modules/network_element/repository/ne_version.go b/src/modules/network_element/repository/ne_version.go
new file mode 100644
index 0000000..134d7e1
--- /dev/null
+++ b/src/modules/network_element/repository/ne_version.go
@@ -0,0 +1,27 @@
+package repository
+
+import "nms_nbi/src/modules/network_element/model"
+
+// INeVersion 网元版本信息 数据层接口
+type INeVersion interface {
+ // SelectPage 根据条件分页查询字典类型
+ SelectPage(query map[string]any) map[string]any
+
+ // SelectList 根据实体查询
+ SelectList(neVersion model.NeVersion) []model.NeVersion
+
+ // SelectByIds 通过ID查询
+ SelectByIds(ids []string) []model.NeVersion
+
+ // Insert 新增信息
+ Insert(neVersion model.NeVersion) string
+
+ // Update 修改信息
+ Update(neVersion model.NeVersion) int64
+
+ // DeleteByIds 批量删除信息
+ DeleteByIds(ids []string) int64
+
+ // CheckUniqueTypeAndID 校验网元类型和网元ID是否唯一
+ CheckUniqueTypeAndID(neVersion model.NeVersion) string
+}
diff --git a/src/modules/network_element/repository/ne_version.impl.go b/src/modules/network_element/repository/ne_version.impl.go
new file mode 100644
index 0000000..70fe59b
--- /dev/null
+++ b/src/modules/network_element/repository/ne_version.impl.go
@@ -0,0 +1,331 @@
+package repository
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/repo"
+ "nms_nbi/src/modules/network_element/model"
+)
+
+// 实例化数据层 NewNeVersion 结构体
+var NewNeVersionImpl = &NeVersionImpl{
+ selectSql: `select
+ id, ne_type, ne_id, version, file_path, pre_version, pre_file, new_version, new_file, status, update_time
+ from ne_version`,
+
+ resultMap: map[string]string{
+ "id": "ID",
+ "ne_type": "NeType",
+ "ne_id": "NeId",
+ "version": "Version",
+ "file_path": "FilePath",
+ "pre_version": "PreVersion",
+ "pre_file": "PreFile",
+ "new_version": "NewVersion",
+ "new_file": "NewFile",
+ "status": "Status",
+ "update_time": "UpdateTime",
+ },
+}
+
+// NeVersionImpl 网元版本信息 数据层处理
+type NeVersionImpl struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *NeVersionImpl) convertResultRows(rows []map[string]any) []model.NeVersion {
+ arr := make([]model.NeVersion, 0)
+ for _, row := range rows {
+ item := model.NeVersion{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ repo.SetFieldValue(&item, keyMapper, value)
+ }
+ }
+ arr = append(arr, item)
+ }
+ return arr
+}
+
+// SelectPage 根据条件分页查询字典类型
+func (r *NeVersionImpl) SelectPage(query map[string]any) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if v, ok := query["neType"]; ok && v != "" {
+ conditions = append(conditions, "ne_type = ?")
+ params = append(params, strings.Trim(v.(string), " "))
+ }
+ if v, ok := query["neId"]; ok && v != "" {
+ conditions = append(conditions, "ne_id = ?")
+ params = append(params, strings.Trim(v.(string), " "))
+ }
+ if v, ok := query["version"]; ok && v != "" {
+ conditions = append(conditions, "version like concat(?, '%')")
+ params = append(params, strings.Trim(v.(string), " "))
+ }
+ if v, ok := query["filePath"]; ok && v != "" {
+ conditions = append(conditions, "file_path like concat(?, '%')")
+ params = append(params, strings.Trim(v.(string), " "))
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ result := map[string]any{
+ "total": 0,
+ "rows": []model.NeHost{},
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := "select count(1) as 'total' from ne_version"
+ totalRows, err := datasource.RawDB("", totalSql+whereSql, params)
+ if err != nil {
+ logger.Errorf("total err => %v", err)
+ return result
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return result
+ } else {
+ result["total"] = total
+ }
+
+ // 分页
+ pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return result
+ }
+
+ // 转换实体
+ result["rows"] = r.convertResultRows(results)
+ return result
+}
+
+// SelectList 根据实体查询
+func (r *NeVersionImpl) SelectList(neVersion model.NeVersion) []model.NeVersion {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if neVersion.NeType != "" {
+ conditions = append(conditions, "ne_type = ?")
+ params = append(params, neVersion.NeType)
+ }
+ if neVersion.NeId != "" {
+ conditions = append(conditions, "ne_id = ?")
+ params = append(params, neVersion.NeId)
+ }
+ if neVersion.Version != "" {
+ conditions = append(conditions, "version like concat(?, '%')")
+ params = append(params, neVersion.Version)
+ }
+ if neVersion.FilePath != "" {
+ conditions = append(conditions, "file_path like concat(?, '%')")
+ params = append(params, neVersion.FilePath)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + " order by update_time asc "
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ }
+
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// SelectByIds 通过ID查询
+func (r *NeVersionImpl) SelectByIds(cmdIds []string) []model.NeVersion {
+ placeholder := repo.KeyPlaceholderByQuery(len(cmdIds))
+ querySql := r.selectSql + " where id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(cmdIds)
+ results, err := datasource.RawDB("", querySql, parameters)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.NeVersion{}
+ }
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// CheckUniqueTypeAndID 校验网元类型和网元ID是否唯一
+func (r *NeVersionImpl) CheckUniqueTypeAndID(neVersion model.NeVersion) string {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if neVersion.NeType != "" {
+ conditions = append(conditions, "ne_type = ?")
+ params = append(params, neVersion.NeType)
+ }
+ if neVersion.NeId != "" {
+ conditions = append(conditions, "ne_id = ?")
+ params = append(params, neVersion.NeId)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ } else {
+ return ""
+ }
+
+ // 查询数据
+ querySql := "select id as 'str' from ne_version " + whereSql + " limit 1"
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err %v", err)
+ return ""
+ }
+ if len(results) > 0 {
+ return fmt.Sprint(results[0]["str"])
+ }
+ return ""
+}
+
+// Insert 新增信息
+func (r *NeVersionImpl) Insert(neVersion model.NeVersion) string {
+ // 参数拼接
+ params := make(map[string]any)
+ if neVersion.NeType != "" {
+ params["ne_type"] = neVersion.NeType
+ }
+ if neVersion.NeId != "" {
+ params["ne_id"] = neVersion.NeId
+ }
+ if neVersion.Version != "" {
+ params["version"] = neVersion.Version
+ }
+ if neVersion.FilePath != "" {
+ params["file_path"] = neVersion.FilePath
+ }
+ if neVersion.PreVersion != "" {
+ params["pre_version"] = neVersion.PreVersion
+ }
+ if neVersion.PreFile != "" {
+ params["pre_file"] = neVersion.PreFile
+ }
+ if neVersion.NewVersion != "" {
+ params["new_version"] = neVersion.NewVersion
+ }
+ if neVersion.NewFile != "" {
+ params["new_file"] = neVersion.NewFile
+ }
+ if neVersion.Status != "" {
+ params["status"] = neVersion.Status
+ }
+ params["update_time"] = time.Now()
+
+ // 构建执行语句
+ keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params)
+ sql := "insert into ne_version (" + strings.Join(keys, ",") + ")values(" + placeholder + ")"
+
+ db := datasource.DefaultDB()
+ // 开启事务
+ tx := db.Begin()
+ // 执行插入
+ err := tx.Exec(sql, values...).Error
+ if err != nil {
+ logger.Errorf("insert row : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 获取生成的自增 ID
+ var insertedID string
+ err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID)
+ if err != nil {
+ logger.Errorf("insert last id : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 提交事务
+ tx.Commit()
+ return insertedID
+}
+
+// Update 修改信息
+func (r *NeVersionImpl) Update(neVersion model.NeVersion) int64 {
+ // 参数拼接
+ params := make(map[string]any)
+ if neVersion.NeType != "" {
+ params["ne_type"] = neVersion.NeType
+ }
+ if neVersion.NeId != "" {
+ params["ne_id"] = neVersion.NeId
+ }
+ if neVersion.Version != "" {
+ params["version"] = neVersion.Version
+ }
+ if neVersion.FilePath != "" {
+ params["file_path"] = neVersion.FilePath
+ }
+ if neVersion.PreVersion != "" {
+ params["pre_version"] = neVersion.PreVersion
+ }
+ if neVersion.PreFile != "" {
+ params["pre_file"] = neVersion.PreFile
+ }
+ if neVersion.NewVersion != "" {
+ params["new_version"] = neVersion.NewVersion
+ }
+ if neVersion.NewFile != "" {
+ params["new_file"] = neVersion.NewFile
+ }
+ if neVersion.Status != "" {
+ params["status"] = neVersion.Status
+ }
+ params["update_time"] = time.Now()
+
+ // 构建执行语句
+ keys, values := repo.KeyValueByUpdate(params)
+ sql := "update ne_version set " + strings.Join(keys, ",") + " where id = ?"
+
+ // 执行更新
+ values = append(values, neVersion.ID)
+ rows, err := datasource.ExecDB("", sql, values)
+ if err != nil {
+ logger.Errorf("update row : %v", err.Error())
+ return 0
+ }
+ return rows
+}
+
+// DeleteByIds 批量删除信息
+func (r *NeVersionImpl) DeleteByIds(cmdIds []string) int64 {
+ placeholder := repo.KeyPlaceholderByQuery(len(cmdIds))
+ sql := "delete from ne_version where id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(cmdIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
diff --git a/src/modules/network_element/repository/udm_auth.go b/src/modules/network_element/repository/udm_auth.go
new file mode 100644
index 0000000..96f14ab
--- /dev/null
+++ b/src/modules/network_element/repository/udm_auth.go
@@ -0,0 +1,26 @@
+package repository
+
+import (
+ "nms_nbi/src/modules/network_element/model"
+)
+
+// UDM鉴权信息 数据层接口
+type IUDMAuth interface {
+ // ClearAndInsert 清空ne_id后新增实体
+ ClearAndInsert(neID string, authArr []model.UDMAuth) int64
+
+ // SelectPage 根据条件分页查询
+ SelectPage(query map[string]any) map[string]any
+
+ // SelectList 根据实体查询
+ SelectList(auth model.UDMAuth) []model.UDMAuth
+
+ // Insert 批量添加
+ Inserts(authUsers []model.UDMAuth) int64
+
+ // Delete 删除实体
+ Delete(neID, imsi string) int64
+
+ // DeletePrefixImsi 删除前缀匹配的实体
+ DeletePrefixImsi(neID, imsi string) int64
+}
diff --git a/src/modules/network_element/repository/udm_auth.impl.go b/src/modules/network_element/repository/udm_auth.impl.go
new file mode 100644
index 0000000..cc13da1
--- /dev/null
+++ b/src/modules/network_element/repository/udm_auth.impl.go
@@ -0,0 +1,191 @@
+package repository
+
+import (
+ "strings"
+
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/repo"
+ "nms_nbi/src/modules/network_element/model"
+)
+
+// 实例化数据层 UDMAuthImpl 结构体
+var NewUDMAuthImpl = &UDMAuthImpl{
+ selectSql: `select id, imsi, amf, status, ki, algo_index, opc, ne_id from u_auth_user`,
+
+ resultMap: map[string]string{
+ "id": "ID",
+ "imsi": "Imsi",
+ "amf": "Amf",
+ "status": "Status",
+ "ki": "Ki",
+ "algo_index": "AlgoIndex",
+ "opc": "Opc",
+ "ne_id": "NeID",
+ },
+}
+
+// UDMAuthImpl UDM鉴权信息表 数据层处理
+type UDMAuthImpl struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *UDMAuthImpl) convertResultRows(rows []map[string]any) []model.UDMAuth {
+ arr := make([]model.UDMAuth, 0)
+ for _, row := range rows {
+ item := model.UDMAuth{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ repo.SetFieldValue(&item, keyMapper, value)
+ }
+ }
+ arr = append(arr, item)
+ }
+ return arr
+}
+
+// ClearAndInsert 清空ne_id后新增实体
+func (r *UDMAuthImpl) ClearAndInsert(neID string, authArr []model.UDMAuth) int64 {
+ // 清空指定ne_id
+ _, err := datasource.ExecDB("", "TRUNCATE TABLE u_auth_user", nil)
+ if err != nil {
+ logger.Errorf("TRUNCATE err => %v", err)
+ }
+
+ return r.Inserts(authArr)
+}
+
+// SelectPage 根据条件分页查询
+func (r *UDMAuthImpl) SelectPage(query map[string]any) map[string]any {
+ // 查询条件拼接
+ 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), " "))
+ }
+ if v, ok := query["neId"]; ok && v != "" {
+ conditions = append(conditions, "ne_id = ?")
+ params = append(params, v)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ result := map[string]any{
+ "total": 0,
+ "rows": []model.UDMAuth{},
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := "select count(1) as 'total' from u_auth_user"
+ totalRows, err := datasource.RawDB("", totalSql+whereSql, params)
+ if err != nil {
+ logger.Errorf("total err => %v", err)
+ return result
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return result
+ } else {
+ result["total"] = total
+ }
+
+ // 分页
+ pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 排序
+ sortSql := ""
+ if v, ok := query["sortField"]; ok && v != "" {
+ if v == "imsi" {
+ sortSql += " order by imsi "
+ }
+ if o, ok := query["sortOrder"]; ok && o != nil && v != "" {
+ if o == "desc" {
+ sortSql += " desc "
+ } else {
+ sortSql += " asc "
+ }
+ }
+ }
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + sortSql + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ }
+
+ // 转换实体
+ result["rows"] = r.convertResultRows(results)
+ return result
+}
+
+// SelectList 根据实体查询
+func (r *UDMAuthImpl) SelectList(auth model.UDMAuth) []model.UDMAuth {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if auth.Imsi != "" {
+ conditions = append(conditions, "imsi = ?")
+ params = append(params, auth.Imsi)
+ }
+ if auth.NeID != "" {
+ conditions = append(conditions, "ne_id = ?")
+ params = append(params, auth.NeID)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + " order by imsi asc "
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ }
+
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// Insert 批量添加
+func (r *UDMAuthImpl) Inserts(authUsers []model.UDMAuth) int64 {
+ tx := datasource.DefaultDB().CreateInBatches(authUsers, 3000)
+ if err := tx.Error; err != nil {
+ logger.Errorf("CreateInBatches err => %v", err)
+ }
+ return tx.RowsAffected
+}
+
+// Delete 删除实体
+func (r *UDMAuthImpl) Delete(neID, imsi string) int64 {
+ tx := datasource.DefaultDB().Where("imsi = ? and ne_id = ?", imsi, neID).Delete(&model.UDMAuth{})
+ if err := tx.Error; err != nil {
+ logger.Errorf("Delete err => %v", err)
+ }
+ return tx.RowsAffected
+}
+
+// DeletePrefixImsi 删除前缀匹配的实体
+func (r *UDMAuthImpl) DeletePrefixImsi(neID, imsi string) int64 {
+ tx := datasource.DefaultDB().Where("imsi like concat(?, '%') and ne_id = ?", imsi, neID).Delete(&model.UDMAuth{})
+ if err := tx.Error; err != nil {
+ logger.Errorf("DeletePrefixImsi err => %v", err)
+ }
+ return tx.RowsAffected
+}
diff --git a/src/modules/network_element/repository/udm_sub.go b/src/modules/network_element/repository/udm_sub.go
new file mode 100644
index 0000000..2b4f3e0
--- /dev/null
+++ b/src/modules/network_element/repository/udm_sub.go
@@ -0,0 +1,35 @@
+package repository
+
+import (
+ "nms_nbi/src/modules/network_element/model"
+)
+
+// UDM签约信息 数据层接口
+type IUDMSub interface {
+ // ClearAndInsert 清空ne_id后新增实体
+ ClearAndInsert(neID string, subArr []model.UDMSub) int64
+
+ // SelectPage 根据条件分页查询字典类型
+ SelectPage(query map[string]any) map[string]any
+
+ // SelectList 根据实体查询
+ SelectList(subUser model.UDMSub) []model.UDMSub
+
+ // Insert 新增实体
+ Insert(subUser model.UDMSub) string
+
+ // Insert 批量添加
+ Inserts(subUser []model.UDMSub) int64
+
+ // Update 修改更新
+ Update(neID string, subUser model.UDMSub) int64
+
+ // Delete 删除实体
+ Delete(neID, imsi string) int64
+
+ // DeletePrefixImsi 删除前缀匹配的实体
+ DeletePrefixImsi(neID, imsi string) int64
+
+ // Delete 删除范围实体
+ Deletes(neID, imsi, num string) int64
+}
diff --git a/src/modules/network_element/repository/udm_sub.impl.go b/src/modules/network_element/repository/udm_sub.impl.go
new file mode 100644
index 0000000..8761a62
--- /dev/null
+++ b/src/modules/network_element/repository/udm_sub.impl.go
@@ -0,0 +1,312 @@
+package repository
+
+import (
+ "strconv"
+ "strings"
+
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/repo"
+ "nms_nbi/src/modules/network_element/model"
+)
+
+// 实例化数据层 UDMSubImpl 结构体
+var NewUDMSubImpl = &UDMSubImpl{
+ selectSql: `select
+ id, msisdn, imsi, ambr, nssai, rat, arfb, sar, cn, sm_data, smf_sel, eps_dat, ne_id, eps_flag, eps_odb, hplmn_odb, ard, epstpl, context_id, apn_context, static_ip
+ from u_sub_user`,
+
+ resultMap: map[string]string{
+ "id": "ID",
+ "msisdn": "Msisdn",
+ "imsi": "Imsi",
+ "ambr": "Ambr",
+ "nssai": "Nssai",
+ "rat": "Rat",
+ "arfb": "Arfb",
+ "sar": "Sar",
+ "cn": "Cn",
+ "sm_data": "SmData",
+ "smf_sel": "SmfSel",
+ "eps_dat": "EpsDat",
+ "ne_id": "NeID",
+ "eps_flag": "EpsFlag",
+ "eps_odb": "EpsOdb",
+ "hplmn_odb": "HplmnOdb",
+ "ard": "Ard",
+ "epstpl": "Epstpl",
+ "context_id": "ContextId",
+ "apn_context": "ApnContext",
+ "static_ip": "StaticIp",
+ },
+}
+
+// UDMSubImpl UDM签约信息表 数据层处理
+type UDMSubImpl struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *UDMSubImpl) convertResultRows(rows []map[string]any) []model.UDMSub {
+ arr := make([]model.UDMSub, 0)
+ for _, row := range rows {
+ item := model.UDMSub{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ repo.SetFieldValue(&item, keyMapper, value)
+ }
+ }
+ arr = append(arr, item)
+ }
+ return arr
+}
+
+// ClearAndInsert 清空ne_id后新增实体
+func (r *UDMSubImpl) ClearAndInsert(neID string, subArr []model.UDMSub) int64 {
+ // 清空指定ne_id
+ _, err := datasource.ExecDB("", "TRUNCATE TABLE u_sub_user", nil)
+ if err != nil {
+ logger.Errorf("TRUNCATE err => %v", err)
+ }
+
+ return r.Inserts(subArr)
+}
+
+// SelectPage 根据条件分页查询字典类型
+func (r *UDMSubImpl) SelectPage(query map[string]any) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if v, ok := query["msisdn"]; ok && v != "" {
+ conditions = append(conditions, "msisdn like concat(concat('%', ?), '%')")
+ params = append(params, strings.Trim(v.(string), " "))
+ }
+ if v, ok := query["imsi"]; ok && v != "" {
+ conditions = append(conditions, "imsi like concat(concat('%', ?), '%')")
+ params = append(params, strings.Trim(v.(string), " "))
+ }
+ if v, ok := query["neId"]; ok && v != "" {
+ conditions = append(conditions, "ne_id = ?")
+ params = append(params, v)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ result := map[string]any{
+ "total": 0,
+ "rows": []model.UDMSub{},
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := "select count(1) as 'total' from u_sub_user"
+ totalRows, err := datasource.RawDB("", totalSql+whereSql, params)
+ if err != nil {
+ logger.Errorf("total err => %v", err)
+ return result
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return result
+ } else {
+ result["total"] = total
+ }
+
+ // 分页
+ pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 排序
+ sortSql := ""
+ if v, ok := query["sortField"]; ok && v != "" {
+ if v == "imsi" {
+ sortSql += " order by imsi "
+ }
+ if v == "msisdn" {
+ sortSql += " order by msisdn "
+ }
+ if o, ok := query["sortOrder"]; ok && o != nil && v != "" {
+ if o == "desc" {
+ sortSql += " desc "
+ } else {
+ sortSql += " asc "
+ }
+ }
+ }
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + sortSql + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return result
+ }
+
+ // 转换实体
+ result["rows"] = r.convertResultRows(results)
+ return result
+}
+
+// SelectList 根据实体查询
+func (r *UDMSubImpl) SelectList(subUser model.UDMSub) []model.UDMSub {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if subUser.Imsi != "" {
+ conditions = append(conditions, "imsi = ?")
+ params = append(params, subUser.Imsi)
+ }
+ if subUser.NeID != "" {
+ conditions = append(conditions, "ne_id = ?")
+ params = append(params, subUser.NeID)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + " order by imsi asc "
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ }
+
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// Insert 新增实体
+func (r *UDMSubImpl) Insert(subUser model.UDMSub) string {
+ err := datasource.DefaultDB().Create(&subUser).Error
+ if err != nil {
+ logger.Errorf("Create err => %v", err)
+ }
+ return subUser.ID
+}
+
+// Insert 批量添加
+func (r *UDMSubImpl) Inserts(subUser []model.UDMSub) int64 {
+ tx := datasource.DefaultDB().CreateInBatches(subUser, 2000)
+ if err := tx.Error; err != nil {
+ logger.Errorf("CreateInBatches err => %v", err)
+ }
+ return tx.RowsAffected
+}
+
+// Update 修改更新
+func (r *UDMSubImpl) Update(neID string, subUser model.UDMSub) int64 {
+ // 查询先
+ var user model.UDMSub
+ err := datasource.DefaultDB().Where("imsi = ? and ne_id = ?", subUser.Imsi, neID).First(&user).Error
+ if err != nil {
+ logger.Errorf("Update First err => %v", err)
+ }
+
+ if user.Msisdn != subUser.Msisdn {
+ user.Msisdn = subUser.Msisdn
+ }
+ if user.Ambr != subUser.Ambr {
+ user.Ambr = subUser.Ambr
+ }
+ if user.Arfb != subUser.Arfb {
+ user.Arfb = subUser.Arfb
+ }
+ if user.Sar != subUser.Sar {
+ user.Sar = subUser.Sar
+ }
+ if user.Rat != subUser.Rat {
+ user.Rat = subUser.Rat
+ }
+ if user.Cn != subUser.Cn {
+ user.Cn = subUser.Cn
+ }
+ if user.SmfSel != subUser.SmfSel {
+ user.SmfSel = subUser.SmfSel
+ }
+ if user.SmData != subUser.SmData {
+ user.SmData = subUser.SmData
+ }
+ if user.EpsDat != subUser.EpsDat {
+ user.EpsDat = subUser.EpsDat
+ }
+ if user.EpsFlag != subUser.EpsFlag {
+ user.EpsFlag = subUser.EpsFlag
+ }
+ if user.EpsDat != subUser.EpsDat {
+ user.EpsOdb = subUser.EpsOdb
+ }
+ if user.HplmnOdb != subUser.HplmnOdb {
+ user.HplmnOdb = subUser.HplmnOdb
+ }
+ if user.Epstpl != subUser.Epstpl {
+ user.Epstpl = subUser.Epstpl
+ }
+ if user.Ard != subUser.Ard {
+ user.Ard = subUser.Ard
+ }
+ if user.ContextId != subUser.ContextId {
+ user.ContextId = subUser.ContextId
+ }
+ if user.ApnContext != subUser.ApnContext {
+ user.ApnContext = subUser.ApnContext
+ }
+ if user.StaticIp != subUser.StaticIp {
+ user.StaticIp = subUser.StaticIp
+ }
+
+ tx := datasource.DefaultDB().Save(user)
+ if err := tx.Error; err != nil {
+ logger.Errorf("Update Save err => %v", err)
+ return 0
+ }
+ return tx.RowsAffected
+}
+
+// Delete 删除实体
+func (r *UDMSubImpl) Delete(neID, imsi string) int64 {
+ tx := datasource.DefaultDB().Where("imsi = ? and ne_id = ?", imsi, neID).Delete(&model.UDMSub{})
+ if err := tx.Error; err != nil {
+ logger.Errorf("Delete err => %v", err)
+ }
+ return tx.RowsAffected
+}
+
+// DeletePrefixImsi 删除前缀匹配的实体
+func (r *UDMSubImpl) DeletePrefixImsi(neID, imsi string) int64 {
+ tx := datasource.DefaultDB().Where("imsi like concat(?, '%') and ne_id = ?", imsi, neID).Delete(&model.UDMSub{})
+ if err := tx.Error; err != nil {
+ logger.Errorf("DeletePrefixImsi err => %v", err)
+ }
+ return tx.RowsAffected
+}
+
+// Delete 删除范围实体
+func (r *UDMSubImpl) Deletes(neID, imsi, num string) int64 {
+ imsiV, err := strconv.Atoi(imsi)
+ if err != nil {
+ return 0
+ }
+
+ numV, err := strconv.Atoi(num)
+ if err != nil {
+ return 0
+ }
+
+ tx := datasource.DefaultDB().Where("imsi >= ? and imsi < ? and ne_id = ?", imsiV, imsiV+numV, neID).Delete(&model.UDMSub{})
+ if err := tx.Error; err != nil {
+ logger.Errorf("Deletes err => %v", err)
+ }
+ return tx.RowsAffected
+}
diff --git a/src/modules/network_element/service/ne_direct_link.go b/src/modules/network_element/service/ne_direct_link.go
new file mode 100644
index 0000000..09b1d80
--- /dev/null
+++ b/src/modules/network_element/service/ne_direct_link.go
@@ -0,0 +1,80 @@
+package service
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/fetch"
+ "nms_nbi/src/modules/network_element/model"
+)
+
+// NeState 获取网元端服务状态
+func NeState(neInfo model.NeInfo) (map[string]any, error) {
+ // 网元状态
+ neUrl := fmt.Sprintf("http://%s:%d/api/rest/systemManagement/v1/elementType/%s/objectType/systemState", neInfo.IP, neInfo.Port, strings.ToLower(neInfo.NeType))
+ resBytes, err := fetch.Get(neUrl, nil, 250)
+ if err != nil {
+ logger.Warnf("NeState %s", err.Error())
+ return nil, err
+ }
+
+ // 序列化结果
+ var resData map[string]any
+ err = json.Unmarshal(resBytes, &resData)
+ if err != nil {
+ logger.Warnf("NeState Unmarshal %s", err.Error())
+ return nil, err
+ }
+
+ return map[string]any{
+ "neType": neInfo.NeType,
+ "neId": neInfo.NeId,
+ "neName": neInfo.NeName,
+ "neIP": neInfo.IP,
+ "refreshTime": time.Now().UnixMilli(), // 获取时间
+ "version": resData["version"],
+ "capability": resData["capability"],
+ "sn": resData["serialNum"],
+ "expire": resData["expiryDate"],
+ "cpu": resData["cpuUsage"],
+ "mem": resData["memUsage"],
+ "disk": resData["diskSpace"],
+ }, nil
+}
+
+// NeConfigOMC 网元配置对端网管信息
+func NeConfigOMC(neInfo model.NeInfo) (map[string]any, error) {
+ // 网元配置对端网管信息
+ neUrl := fmt.Sprintf("http://%s:%d/api/rest/systemManagement/v1/elementType/%s/objectType/config/omcNeConfig", neInfo.IP, neInfo.Port, strings.ToLower(neInfo.NeType))
+ resBytes, err := fetch.PutJSON(neUrl, map[string]any{
+ "neId": neInfo.NeId,
+ "neName": neInfo.NeName,
+ "port": neInfo.Port,
+ "province": neInfo.Province,
+ "pvFlag": neInfo.PvFlag,
+ "rmUID": neInfo.RmUID,
+ "vendorName": neInfo.VendorName,
+ "dn": neInfo.Dn,
+ }, nil)
+ var resData map[string]any
+ if err != nil {
+ status := err.Error()
+ logger.Warnf("NeConfigOMC %s", status)
+ if strings.HasPrefix(status, "204") {
+ return resData, nil
+ }
+ return nil, err
+ }
+
+ // 序列化结果
+ err = json.Unmarshal(resBytes, &resData)
+ if err != nil {
+ logger.Warnf("NeConfigOMC Unmarshal %s", err.Error())
+ return nil, err
+ }
+
+ return resData, nil
+}
diff --git a/src/modules/network_element/service/ne_host.go b/src/modules/network_element/service/ne_host.go
new file mode 100644
index 0000000..95e4b83
--- /dev/null
+++ b/src/modules/network_element/service/ne_host.go
@@ -0,0 +1,30 @@
+package service
+
+import "nms_nbi/src/modules/network_element/model"
+
+// INeHost 网元主机连接 服务层接口
+type INeHost interface {
+ // SelectPage 根据条件分页查询字典类型
+ SelectPage(query map[string]any) map[string]any
+
+ // SelectList 根据实体查询
+ SelectList(neHost model.NeHost) []model.NeHost
+
+ // SelectByIds 通过ID查询
+ SelectById(hostId string) model.NeHost
+
+ // CheckUniqueHostTitle 校验分组组和主机名称是否唯一
+ CheckUniqueHostTitle(groupId, title, hostType, hostId string) bool
+
+ // Insert 新增信息
+ Insert(neHost model.NeHost) string
+
+ // Update 修改信息
+ Update(neHost model.NeHost) int64
+
+ // Insert 批量添加
+ Inserts(neHosts []model.NeHost) int64
+
+ // DeleteByIds 批量删除网元主机连接信息
+ DeleteByIds(hostIds []string) (int64, error)
+}
diff --git a/src/modules/network_element/service/ne_host.impl.go b/src/modules/network_element/service/ne_host.impl.go
new file mode 100644
index 0000000..7357e79
--- /dev/null
+++ b/src/modules/network_element/service/ne_host.impl.go
@@ -0,0 +1,92 @@
+package service
+
+import (
+ "fmt"
+
+ "nms_nbi/src/modules/network_element/model"
+ "nms_nbi/src/modules/network_element/repository"
+)
+
+// 实例化服务层 NeHostImpl 结构体
+var NewNeHostImpl = &NeHostImpl{
+ neHostRepository: repository.NewNeHostImpl,
+}
+
+// NeHostImpl 网元主机连接 服务层处理
+type NeHostImpl struct {
+ // 网元主机连接表
+ neHostRepository repository.INeHost
+}
+
+// SelectNeHostPage 分页查询列表数据
+func (r *NeHostImpl) SelectPage(query map[string]any) map[string]any {
+ return r.neHostRepository.SelectPage(query)
+}
+
+// SelectConfigList 查询列表
+func (r *NeHostImpl) SelectList(neHost model.NeHost) []model.NeHost {
+ return r.neHostRepository.SelectList(neHost)
+}
+
+// SelectByIds 通过ID查询
+func (r *NeHostImpl) SelectById(hostId string) model.NeHost {
+ if hostId == "" {
+ return model.NeHost{}
+ }
+ neHosts := r.neHostRepository.SelectByIds([]string{hostId})
+ if len(neHosts) > 0 {
+ return neHosts[0]
+ }
+ return model.NeHost{}
+}
+
+// Insert 批量添加
+func (r *NeHostImpl) Inserts(neHosts []model.NeHost) int64 {
+ var num int64 = 0
+ for _, v := range neHosts {
+ hostId := r.neHostRepository.Insert(v)
+ if hostId != "" {
+ num += 1
+ }
+ }
+ return num
+}
+
+// Insert 新增信息
+func (r *NeHostImpl) Insert(neHost model.NeHost) string {
+ return r.neHostRepository.Insert(neHost)
+}
+
+// Update 修改信息
+func (r *NeHostImpl) Update(neHost model.NeHost) int64 {
+ return r.neHostRepository.Update(neHost)
+}
+
+// DeleteByIds 批量删除网元主机连接信息
+func (r *NeHostImpl) DeleteByIds(hostIds []string) (int64, error) {
+ // 检查是否存在
+ ids := r.neHostRepository.SelectByIds(hostIds)
+ if len(ids) <= 0 {
+ return 0, fmt.Errorf("neHost.noData")
+ }
+
+ if len(ids) == len(hostIds) {
+ rows := r.neHostRepository.DeleteByIds(hostIds)
+ return rows, nil
+ }
+ // 删除信息失败!
+ return 0, fmt.Errorf("delete fail")
+}
+
+// CheckUniqueHostTitle 校验分组组和主机名称是否唯一
+func (r *NeHostImpl) CheckUniqueHostTitle(groupId, title, hostType, hostId string) bool {
+ uniqueId := r.neHostRepository.CheckUniqueNeHost(model.NeHost{
+ HostType: hostType,
+ GroupID: groupId,
+ Title: title,
+ })
+ if uniqueId == hostId {
+ return true
+ }
+ return uniqueId == ""
+}
diff --git a/src/modules/network_element/service/ne_host_cmd.go b/src/modules/network_element/service/ne_host_cmd.go
new file mode 100644
index 0000000..46343a2
--- /dev/null
+++ b/src/modules/network_element/service/ne_host_cmd.go
@@ -0,0 +1,27 @@
+package service
+
+import "nms_nbi/src/modules/network_element/model"
+
+// INeHostCmd 网元主机命令 服务层接口
+type INeHostCmd interface {
+ // SelectPage 根据条件分页查询字典类型
+ SelectPage(query map[string]any) map[string]any
+
+ // SelectList 根据实体查询
+ SelectList(neHostCmd model.NeHostCmd) []model.NeHostCmd
+
+ // SelectByIds 通过ID查询
+ SelectById(cmdId string) model.NeHostCmd
+
+ // Insert 新增信息
+ Insert(neHostCmd model.NeHostCmd) string
+
+ // Update 修改信息
+ Update(neHostCmd model.NeHostCmd) int64
+
+ // DeleteByIds 批量删除信息
+ DeleteByIds(cmdIds []string) (int64, error)
+
+ // CheckUniqueGroupTitle 校验同类型组内是否唯一
+ CheckUniqueGroupTitle(groupId, title, cmdType, cmdId string) bool
+}
diff --git a/src/modules/network_element/service/ne_host_cmd.impl.go b/src/modules/network_element/service/ne_host_cmd.impl.go
new file mode 100644
index 0000000..0c195e2
--- /dev/null
+++ b/src/modules/network_element/service/ne_host_cmd.impl.go
@@ -0,0 +1,80 @@
+package service
+
+import (
+ "fmt"
+
+ "nms_nbi/src/modules/network_element/model"
+ "nms_nbi/src/modules/network_element/repository"
+)
+
+// 实例化服务层 NeHostCmdImpl 结构体
+var NewNeHostCmdImpl = &NeHostCmdImpl{
+ neHostCmdRepository: repository.NewNeHostCmdImpl,
+}
+
+// NeHostCmdImpl 网元主机命令 服务层处理
+type NeHostCmdImpl struct {
+ // 网元主机命令表
+ neHostCmdRepository repository.INeHostCmd
+}
+
+// SelectNeHostPage 分页查询列表数据
+func (r *NeHostCmdImpl) SelectPage(query map[string]any) map[string]any {
+ return r.neHostCmdRepository.SelectPage(query)
+}
+
+// SelectConfigList 查询列表
+func (r *NeHostCmdImpl) SelectList(neHostCmd model.NeHostCmd) []model.NeHostCmd {
+ return r.neHostCmdRepository.SelectList(neHostCmd)
+}
+
+// SelectByIds 通过ID查询
+func (r *NeHostCmdImpl) SelectById(cmdId string) model.NeHostCmd {
+ if cmdId == "" {
+ return model.NeHostCmd{}
+ }
+ neHosts := r.neHostCmdRepository.SelectByIds([]string{cmdId})
+ if len(neHosts) > 0 {
+ return neHosts[0]
+ }
+ return model.NeHostCmd{}
+}
+
+// Insert 新增信息
+func (r *NeHostCmdImpl) Insert(neHostCmd model.NeHostCmd) string {
+ return r.neHostCmdRepository.Insert(neHostCmd)
+}
+
+// Update 修改信息
+func (r *NeHostCmdImpl) Update(neHostCmd model.NeHostCmd) int64 {
+ return r.neHostCmdRepository.Update(neHostCmd)
+}
+
+// DeleteByIds 批量删除信息
+func (r *NeHostCmdImpl) DeleteByIds(cmdIds []string) (int64, error) {
+ // 检查是否存在
+ ids := r.neHostCmdRepository.SelectByIds(cmdIds)
+ if len(ids) <= 0 {
+ return 0, fmt.Errorf("neHostCmd.noData")
+ }
+
+ if len(ids) == len(cmdIds) {
+ rows := r.neHostCmdRepository.DeleteByIds(cmdIds)
+ return rows, nil
+ }
+ // 删除信息失败!
+ return 0, fmt.Errorf("delete fail")
+}
+
+// CheckUniqueGroupTitle 校验同类型组内是否唯一
+func (r *NeHostCmdImpl) CheckUniqueGroupTitle(groupId, title, cmdType, cmdId string) bool {
+ uniqueId := r.neHostCmdRepository.CheckUniqueGroupTitle(model.NeHostCmd{
+ CmdType: cmdType,
+ GroupID: groupId,
+ Title: title,
+ })
+ if uniqueId == cmdId {
+ return true
+ }
+ return uniqueId == ""
+}
diff --git a/src/modules/network_element/service/ne_info.go b/src/modules/network_element/service/ne_info.go
new file mode 100644
index 0000000..6919614
--- /dev/null
+++ b/src/modules/network_element/service/ne_info.go
@@ -0,0 +1,42 @@
+package service
+
+import "nms_nbi/src/modules/network_element/model"
+
+// 网元信息 服务层接口
+type INeInfo interface {
+ // SelectNeInfoByNeTypeAndNeID 通过ne_type和ne_id查询网元信息
+ SelectNeInfoByNeTypeAndNeID(neType, neID string) model.NeInfo
+
+ // RefreshByNeTypeAndNeID 通过ne_type和ne_id刷新redis中的缓存
+ RefreshByNeTypeAndNeID(neType, neID string) model.NeInfo
+
+ // ClearNeCacheByNeType 清除网元类型缓存
+ ClearNeCacheByNeType(neType string) bool
+
+ // SelectPage 根据条件分页查询
+ //
+ // bandStatus 带状态信息
+ SelectPage(query map[string]any, bandStatus bool) map[string]any
+
+ // SelectList 查询列表
+ //
+ // bandStatus 带状态信息
+ SelectList(ne model.NeInfo, bandStatus bool) []model.NeInfo
+
+ // SelectByIds 通过ID查询
+ //
+ // bandStatus 带主机信息
+ SelectById(infoId string, bandHost bool) model.NeInfo
+
+ // Insert 新增信息
+ Insert(neInfo model.NeInfo) string
+
+ // Update 修改信息
+ Update(neInfo model.NeInfo) int64
+
+ // DeleteByIds 批量删除信息
+ DeleteByIds(infoIds []string) (int64, error)
+
+ // CheckUniqueNeTypeAndNeId 校验同类型下标识是否唯一
+ CheckUniqueNeTypeAndNeId(neType, neId, infoId string) bool
+}
diff --git a/src/modules/network_element/service/ne_info.impl.go b/src/modules/network_element/service/ne_info.impl.go
new file mode 100644
index 0000000..ad1c149
--- /dev/null
+++ b/src/modules/network_element/service/ne_info.impl.go
@@ -0,0 +1,261 @@
+package service
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+
+ "nms_nbi/src/framework/constants/cachekey"
+ "nms_nbi/src/framework/redis"
+ "nms_nbi/src/modules/network_element/model"
+ "nms_nbi/src/modules/network_element/repository"
+)
+
+// 实例化服务层 NeInfoImpl 结构体
+var NewNeInfoImpl = &NeInfoImpl{
+ neInfoRepository: repository.NewNeInfoImpl,
+ neHostRepository: repository.NewNeHostImpl,
+}
+
+// 网元信息 服务层处理
+type NeInfoImpl struct {
+ // 网元信息数据信息
+ neInfoRepository repository.INeInfo
+ // 网元主机连接表
+ neHostRepository repository.INeHost
+}
+
+// SelectNeInfoByNeTypeAndNeID 通过ne_type和ne_id查询网元信息
+func (r *NeInfoImpl) SelectNeInfoByNeTypeAndNeID(neType, neID string) model.NeInfo {
+ var neInfo model.NeInfo
+ key := fmt.Sprintf("%s%s:%s", cachekey.NE_KEY, neType, neID)
+ jsonStr, _ := redis.Get("", key)
+ if len(jsonStr) > 7 {
+ err := json.Unmarshal([]byte(jsonStr), &neInfo)
+ if err != nil {
+ neInfo = model.NeInfo{}
+ }
+ } else {
+ neInfo = r.neInfoRepository.SelectNeInfoByNeTypeAndNeID(neType, neID)
+ if neInfo.NeId == neID {
+ redis.Del("", key)
+ values, _ := json.Marshal(neInfo)
+ redis.Set("", key, string(values))
+ }
+ }
+ return neInfo
+}
+
+// RefreshByNeTypeAndNeID 通过ne_type和ne_id刷新redis中的缓存
+func (r *NeInfoImpl) RefreshByNeTypeAndNeID(neType, neID string) model.NeInfo {
+ var neInfo model.NeInfo
+ key := fmt.Sprintf("%s%s:%s", cachekey.NE_KEY, neType, neID)
+ redis.Del("", key)
+ neInfo = r.neInfoRepository.SelectNeInfoByNeTypeAndNeID(neType, neID)
+ if neInfo.NeId == neID {
+ values, _ := json.Marshal(neInfo)
+ redis.Set("", key, string(values))
+ }
+ return neInfo
+}
+
+// ClearNeCacheByNeType 清除网元类型缓存
+func (r *NeInfoImpl) ClearNeCacheByNeType(neType string) bool {
+ key := fmt.Sprintf("%s*", cachekey.NE_KEY)
+ if neType != "*" {
+ key = fmt.Sprintf("%s%s*", cachekey.NE_KEY, neType)
+ }
+ keys, err := redis.GetKeys("", key)
+ if err != nil {
+ return false
+ }
+ delOk, _ := redis.DelKeys("", keys)
+ return delOk
+}
+
+// SelectPage 根据条件分页查询
+//
+// bandStatus 带状态信息
+func (r *NeInfoImpl) SelectPage(query map[string]any, bandStatus bool) map[string]any {
+ data := r.neInfoRepository.SelectPage(query)
+
+ // 网元直连读取网元服务状态
+ if bandStatus {
+ rows := data["rows"].([]model.NeInfo)
+ arr := &rows
+ for i := range *arr {
+ v := (*arr)[i]
+ result, err := NeState(v)
+ if err != nil {
+ (*arr)[i].ServerState = map[string]any{
+ "online": false,
+ }
+ // 网元状态设置为离线
+ if v.Status != "1" {
+ v.Status = "1"
+ r.neInfoRepository.Update(v)
+ }
+ continue
+ }
+ result["online"] = true
+ (*arr)[i].ServerState = result
+ // 网元状态设置为在线
+ if v.Status != "0" {
+ // 下发网管配置信息给网元
+ _, err = NeConfigOMC(v)
+ if err != nil {
+ v.Status = "3"
+ } else {
+ v.Status = "0"
+ }
+ r.neInfoRepository.Update(v)
+ }
+ }
+ }
+
+ return data
+}
+
+// SelectList 查询列表
+//
+// bandStatus 带状态信息
+func (r *NeInfoImpl) SelectList(ne model.NeInfo, bandStatus bool) []model.NeInfo {
+ list := r.neInfoRepository.SelectList(ne)
+
+ // 网元直连读取网元服务状态
+ if bandStatus {
+ neList := &list
+ for i := range *neList {
+ v := (*neList)[i]
+ result, err := NeState(v)
+ if err != nil {
+ (*neList)[i].ServerState = map[string]any{
+ "online": false,
+ }
+ // 网元状态设置为离线
+ if v.Status != "1" {
+ v.Status = "1"
+ r.neInfoRepository.Update(v)
+ }
+ continue
+ }
+ result["online"] = true
+ (*neList)[i].ServerState = result
+ // 网元状态设置为在线
+ if v.Status != "0" {
+ // 下发网管配置信息给网元
+ _, err = NeConfigOMC(v)
+ if err != nil {
+ v.Status = "3"
+ } else {
+ v.Status = "0"
+ }
+ r.neInfoRepository.Update(v)
+ }
+ }
+ }
+
+ return list
+}
+
+// SelectByIds 通过ID查询
+//
+// bandStatus 带主机信息
+func (r *NeInfoImpl) SelectById(infoId string, bandHost bool) model.NeInfo {
+ if infoId == "" {
+ return model.NeInfo{}
+ }
+ neInfos := r.neInfoRepository.SelectByIds([]string{infoId})
+ if len(neInfos) > 0 {
+ neInfo := neInfos[0]
+ // 带主机信息
+ if neInfo.HostIDs != "" && bandHost {
+ neInfo.Hosts = r.neHostRepository.SelectByIds(strings.Split(neInfo.HostIDs, ","))
+ }
+ return neInfo
+ }
+ return model.NeInfo{}
+}
+
+// Insert 新增信息
+func (r *NeInfoImpl) Insert(neInfo model.NeInfo) string {
+ // 主机信息新增
+ if neInfo.Hosts != nil {
+ var hostIDs []string
+ for _, host := range neInfo.Hosts {
+ host.Title = fmt.Sprintf("%s_%s_%d", strings.ToUpper(neInfo.NeType), neInfo.NeId, host.Port)
+ host.GroupID = "1"
+ hostId := r.neHostRepository.Insert(host)
+ if hostId != "" {
+ hostIDs = append(hostIDs, hostId)
+ }
+ }
+ neInfo.HostIDs = strings.Join(hostIDs, ",")
+ }
+
+ insertId := r.neInfoRepository.Insert(neInfo)
+ if insertId != "" {
+ // 刷新缓存
+ r.RefreshByNeTypeAndNeID(neInfo.NeType, neInfo.NeId)
+ }
+ return insertId
+}
+
+// Update 修改信息
+func (r *NeInfoImpl) Update(neInfo model.NeInfo) int64 {
+ // 主机信息更新
+ if neInfo.Hosts != nil {
+ for _, host := range neInfo.Hosts {
+ if host.HostID != "" {
+ host.Title = fmt.Sprintf("%s_%s_%d", strings.ToUpper(neInfo.NeType), neInfo.NeId, host.Port)
+ host.GroupID = "1"
+ r.neHostRepository.Update(host)
+ }
+ }
+ }
+
+ num := r.neInfoRepository.Update(neInfo)
+ if num > 0 {
+ // 刷新缓存
+ r.RefreshByNeTypeAndNeID(neInfo.NeType, neInfo.NeId)
+ }
+ return num
+}
+
+// DeleteByIds 批量删除信息
+func (r *NeInfoImpl) DeleteByIds(infoIds []string) (int64, error) {
+ // 检查是否存在
+ infos := r.neInfoRepository.SelectByIds(infoIds)
+ if len(infos) <= 0 {
+ return 0, fmt.Errorf("neHostCmd.noData")
+ }
+
+ if len(infos) == len(infoIds) {
+ for _, v := range infos {
+ // 主机信息删除
+ if v.HostIDs != "" {
+ hostIds := strings.Split(v.HostIDs, ",")
+ r.neHostRepository.DeleteByIds(hostIds)
+ }
+ // 缓存信息删除
+ key := fmt.Sprintf("%s%s:%s", cachekey.NE_KEY, v.NeType, v.NeId)
+ redis.Del("", key)
+ }
+ rows := r.neInfoRepository.DeleteByIds(infoIds)
+ return rows, nil
+ }
+ // 删除信息失败!
+ return 0, fmt.Errorf("delete fail")
+}
+
+// CheckUniqueNeTypeAndNeId 校验同类型下标识是否唯一
+func (r *NeInfoImpl) CheckUniqueNeTypeAndNeId(neType, neId, infoId string) bool {
+ uniqueId := r.neInfoRepository.CheckUniqueNeTypeAndNeId(model.NeInfo{
+ NeType: neType,
+ NeId: neId,
+ })
+ if uniqueId == infoId {
+ return true
+ }
+ return uniqueId == ""
+}
diff --git a/src/modules/network_element/service/ne_software.go b/src/modules/network_element/service/ne_software.go
new file mode 100644
index 0000000..a67efa2
--- /dev/null
+++ b/src/modules/network_element/service/ne_software.go
@@ -0,0 +1,30 @@
+package service
+
+import "nms_nbi/src/modules/network_element/model"
+
+// INeSoftware 网元软件包信息 服务层接口
+type INeSoftware interface {
+ // SelectPage 根据条件分页查询字典类型
+ SelectPage(query map[string]any) map[string]any
+
+ // SelectList 根据实体查询
+ SelectList(neSoftware model.NeSoftware) []model.NeSoftware
+
+ // SelectById 通过ID查询
+ SelectById(id string) model.NeSoftware
+
+ // Insert 新增信息
+ Insert(neSoftware model.NeSoftware) string
+
+ // Update 修改信息
+ Update(neSoftware model.NeSoftware) int64
+
+ // DeleteByIds 批量删除信息
+ DeleteByIds(ids []string) (int64, error)
+
+ // CheckUniqueTypeAndFileNameAndVersion 校验网元类型和文件名版本是否唯一
+ CheckUniqueTypeAndFileNameAndVersion(neType, fileName, version, id string) bool
+
+ // Install 安装软件包
+ Install(neSoftware model.NeSoftware) (string, error)
+}
diff --git a/src/modules/network_element/service/ne_software.impl.go b/src/modules/network_element/service/ne_software.impl.go
new file mode 100644
index 0000000..953ef66
--- /dev/null
+++ b/src/modules/network_element/service/ne_software.impl.go
@@ -0,0 +1,85 @@
+package service
+
+import (
+ "fmt"
+
+ "nms_nbi/src/modules/network_element/model"
+ "nms_nbi/src/modules/network_element/repository"
+)
+
+// 实例化服务层 NeSoftwareImpl 结构体
+var NewNeSoftwareImpl = &NeSoftwareImpl{
+ neSoftwareRepository: repository.NewNeSoftwareImpl,
+}
+
+// NeSoftwareImpl 网元软件包信息 服务层处理
+type NeSoftwareImpl struct {
+ // 网元软件包信息
+ neSoftwareRepository repository.INeSoftware
+}
+
+// SelectNeHostPage 分页查询列表数据
+func (r *NeSoftwareImpl) SelectPage(query map[string]any) map[string]any {
+ return r.neSoftwareRepository.SelectPage(query)
+}
+
+// SelectConfigList 查询列表
+func (r *NeSoftwareImpl) SelectList(neSoftware model.NeSoftware) []model.NeSoftware {
+ return r.neSoftwareRepository.SelectList(neSoftware)
+}
+
+// SelectByIds 通过ID查询
+func (r *NeSoftwareImpl) SelectById(id string) model.NeSoftware {
+ if id == "" {
+ return model.NeSoftware{}
+ }
+ neHosts := r.neSoftwareRepository.SelectByIds([]string{id})
+ if len(neHosts) > 0 {
+ return neHosts[0]
+ }
+ return model.NeSoftware{}
+}
+
+// Insert 新增信息
+func (r *NeSoftwareImpl) Insert(neSoftware model.NeSoftware) string {
+ return r.neSoftwareRepository.Insert(neSoftware)
+}
+
+// Update 修改信息
+func (r *NeSoftwareImpl) Update(neSoftware model.NeSoftware) int64 {
+ return r.neSoftwareRepository.Update(neSoftware)
+}
+
+// DeleteByIds 批量删除信息
+func (r *NeSoftwareImpl) DeleteByIds(ids []string) (int64, error) {
+ // 检查是否存在
+ rowIds := r.neSoftwareRepository.SelectByIds(ids)
+ if len(rowIds) <= 0 {
+ return 0, fmt.Errorf("neSoftware.noData")
+ }
+
+ if len(rowIds) == len(ids) {
+ rows := r.neSoftwareRepository.DeleteByIds(ids)
+ return rows, nil
+ }
+ // 删除信息失败!
+ return 0, fmt.Errorf("delete fail")
+}
+
+// CheckUniqueTypeAndFileNameAndVersion 校验网元类型和文件名版本是否唯一
+func (r *NeSoftwareImpl) CheckUniqueTypeAndFileNameAndVersion(neType, fileName, version, id string) bool {
+ uniqueId := r.neSoftwareRepository.CheckUniqueTypeAndFileNameAndVersion(model.NeSoftware{
+ NeType: neType,
+ FileName: fileName,
+ Version: version,
+ })
+ if uniqueId == id {
+ return true
+ }
+ return uniqueId == ""
+}
+
+// Install 安装软件包
+func (r *NeSoftwareImpl) Install(neSoftware model.NeSoftware) (string, error) {
+ return "", nil
+}
diff --git a/src/modules/network_element/service/ne_version.go b/src/modules/network_element/service/ne_version.go
new file mode 100644
index 0000000..31a9a24
--- /dev/null
+++ b/src/modules/network_element/service/ne_version.go
@@ -0,0 +1,27 @@
+package service
+
+import "nms_nbi/src/modules/network_element/model"
+
+// INeVersion 网元版本信息 服务层接口
+type INeVersion interface {
+ // SelectPage 根据条件分页查询字典类型
+ SelectPage(query map[string]any) map[string]any
+
+ // SelectList 根据实体查询
+ SelectList(neVersion model.NeVersion) []model.NeVersion
+
+ // SelectById 通过ID查询
+ SelectById(id string) model.NeVersion
+
+ // Insert 新增信息
+ Insert(neVersion model.NeVersion) string
+
+ // Update 修改信息
+ Update(neVersion model.NeVersion) int64
+
+ // DeleteByIds 批量删除信息
+ DeleteByIds(ids []string) (int64, error)
+
+ // CheckUniqueTypeAndID 校验网元类型和网元ID是否唯一
+ CheckUniqueTypeAndID(neType, neId, id string) bool
+}
diff --git a/src/modules/network_element/service/ne_version.impl.go b/src/modules/network_element/service/ne_version.impl.go
new file mode 100644
index 0000000..743e6f8
--- /dev/null
+++ b/src/modules/network_element/service/ne_version.impl.go
@@ -0,0 +1,79 @@
+package service
+
+import (
+ "fmt"
+
+ "nms_nbi/src/modules/network_element/model"
+ "nms_nbi/src/modules/network_element/repository"
+)
+
+// 实例化服务层 NeVersionImpl 结构体
+var NewNeVersionImpl = &NeVersionImpl{
+ neVersionRepository: repository.NewNeVersionImpl,
+}
+
+// NeVersionImpl 网元版本信息 服务层处理
+type NeVersionImpl struct {
+ // 网元版本信息表
+ neVersionRepository repository.INeVersion
+}
+
+// SelectNeHostPage 分页查询列表数据
+func (r *NeVersionImpl) SelectPage(query map[string]any) map[string]any {
+ return r.neVersionRepository.SelectPage(query)
+}
+
+// SelectConfigList 查询列表
+func (r *NeVersionImpl) SelectList(neVersion model.NeVersion) []model.NeVersion {
+ return r.neVersionRepository.SelectList(neVersion)
+}
+
+// SelectByIds 通过ID查询
+func (r *NeVersionImpl) SelectById(id string) model.NeVersion {
+ if id == "" {
+ return model.NeVersion{}
+ }
+ neHosts := r.neVersionRepository.SelectByIds([]string{id})
+ if len(neHosts) > 0 {
+ return neHosts[0]
+ }
+ return model.NeVersion{}
+}
+
+// Insert 新增信息
+func (r *NeVersionImpl) Insert(neVersion model.NeVersion) string {
+ return r.neVersionRepository.Insert(neVersion)
+}
+
+// Update 修改信息
+func (r *NeVersionImpl) Update(neVersion model.NeVersion) int64 {
+ return r.neVersionRepository.Update(neVersion)
+}
+
+// DeleteByIds 批量删除信息
+func (r *NeVersionImpl) DeleteByIds(ids []string) (int64, error) {
+ // 检查是否存在
+ rowIds := r.neVersionRepository.SelectByIds(ids)
+ if len(rowIds) <= 0 {
+ return 0, fmt.Errorf("neVersion.noData")
+ }
+
+ if len(rowIds) == len(ids) {
+ rows := r.neVersionRepository.DeleteByIds(ids)
+ return rows, nil
+ }
+ // 删除信息失败!
+ return 0, fmt.Errorf("delete fail")
+}
+
+// CheckUniqueTypeAndID 校验网元类型和网元ID是否唯一
+func (r *NeVersionImpl) CheckUniqueTypeAndID(neType, neId, id string) bool {
+ uniqueId := r.neVersionRepository.CheckUniqueTypeAndID(model.NeVersion{
+ NeType: neType,
+ NeId: neId,
+ })
+ if uniqueId == id {
+ return true
+ }
+ return uniqueId == ""
+}
diff --git a/src/modules/network_element/service/udm_auth.go b/src/modules/network_element/service/udm_auth.go
new file mode 100644
index 0000000..0cec3af
--- /dev/null
+++ b/src/modules/network_element/service/udm_auth.go
@@ -0,0 +1,37 @@
+package service
+
+import "nms_nbi/src/modules/network_element/model"
+
+// UDM鉴权信息 服务层接口
+type IUDMAuth interface {
+ // Save UDM鉴权用户-获取redis全部保存数据库
+ Save(neID string) int64
+
+ // Page UDM鉴权用户-分页查询数据库
+ Page(query map[string]any) map[string]any
+
+ // List UDM鉴权用户-查询数据库
+ List(authUser model.UDMAuth) []model.UDMAuth
+
+ // Insert UDM鉴权用户-新增单个
+ // imsi长度15,ki长度32,opc长度0或者32
+ Insert(neID string, authUser model.UDMAuth) int64
+
+ // Insert UDM鉴权用户-批量添加
+ Inserts(neID string, authUser model.UDMAuth, num string) int64
+
+ // InsertCSV UDM鉴权用户-批量添加
+ InsertCSV(neID string, data []map[string]string) int64
+
+ // InsertTxt UDM鉴权用户-批量添加
+ InsertTxt(neID string, data [][]string) int64
+
+ // Insert UDM鉴权用户-修改更新
+ Update(neID string, authUser model.UDMAuth) int64
+
+ // Insert UDM鉴权用户-删除单个
+ Delete(neID, imsi string) int64
+
+ // Insert UDM鉴权用户-删除范围
+ Deletes(neID, imsi, num string) int64
+}
diff --git a/src/modules/network_element/service/udm_auth.impl.go b/src/modules/network_element/service/udm_auth.impl.go
new file mode 100644
index 0000000..69f3ff5
--- /dev/null
+++ b/src/modules/network_element/service/udm_auth.impl.go
@@ -0,0 +1,179 @@
+package service
+
+import (
+ "fmt"
+ "strings"
+
+ "nms_nbi/src/framework/redis"
+ "nms_nbi/src/modules/network_element/model"
+ "nms_nbi/src/modules/network_element/repository"
+)
+
+// 实例化服务层 UDMAuthImpl 结构体
+var NewUDMAuthImpl = &UDMAuthImpl{
+ udmAuthRepository: repository.NewUDMAuthImpl,
+}
+
+// UDM鉴权信息 服务层处理
+type UDMAuthImpl struct {
+ // UDM鉴权信息数据信息
+ udmAuthRepository repository.IUDMAuth
+}
+
+// authDataByRedis UDM鉴权用户
+func (r *UDMAuthImpl) authDataByRedis(imsi, neID string) []model.UDMAuth {
+ arr := []model.UDMAuth{}
+ key := fmt.Sprintf("ausf:%s", imsi)
+ ausfArr, err := redis.GetKeys("udmuser", key)
+ if err != nil {
+ return arr
+ }
+ for _, key := range ausfArr {
+ m, err := redis.GetHash("udmuser", key)
+ if err != nil {
+ continue
+ }
+
+ // 跳过-号数据
+ imsi := key[5:]
+ if strings.Contains(imsi, "-") {
+ continue
+ }
+
+ status := "1" // 默认给1
+
+ amf := ""
+ if v, ok := m["amf"]; ok {
+ amf = strings.Replace(v, "\r\n", "", 1)
+ }
+ a := model.UDMAuth{
+ Imsi: imsi,
+ Amf: amf,
+ Status: status,
+ Ki: m["ki"],
+ AlgoIndex: m["algo"],
+ Opc: m["opc"],
+ NeID: neID,
+ }
+ arr = append(arr, a)
+ }
+ return arr
+}
+
+// Save UDM鉴权用户-获取redis全部保存数据库
+func (r *UDMAuthImpl) Save(neID string) int64 {
+ authArr := r.authDataByRedis("*", neID)
+ // 数据清空后添加
+ go r.udmAuthRepository.ClearAndInsert(neID, authArr)
+ return int64(len(authArr))
+}
+
+// Page UDM鉴权用户-分页查询数据库
+func (r *UDMAuthImpl) Page(query map[string]any) map[string]any {
+ return r.udmAuthRepository.SelectPage(query)
+}
+
+// List UDM鉴权用户-查询数据库
+func (r *UDMAuthImpl) List(authUser model.UDMAuth) []model.UDMAuth {
+ return r.udmAuthRepository.SelectList(authUser)
+}
+
+// Insert UDM鉴权用户-新增单个
+// imsi长度15,ki长度32,opc长度0或者32
+func (r *UDMAuthImpl) Insert(neID string, authUser model.UDMAuth) int64 {
+ authArr := r.authDataByRedis(authUser.Imsi, neID)
+ if len(authArr) > 0 {
+ r.udmAuthRepository.Delete(neID, authUser.Imsi)
+ r.udmAuthRepository.Inserts(authArr)
+ }
+ return 0
+}
+
+// Insert UDM鉴权用户-批量添加
+func (r *UDMAuthImpl) Inserts(neID string, authUser model.UDMAuth, num string) int64 {
+ startIMSI := authUser.Imsi
+ startIMSI = startIMSI[:len(startIMSI)-len(num)] + "*"
+ // keys udm-sd:4600001000004*
+ authArr := r.authDataByRedis(startIMSI, neID)
+ if len(authArr) > 0 {
+ for _, v := range authArr {
+ r.udmAuthRepository.Delete(neID, v.Imsi)
+ }
+ return r.udmAuthRepository.Inserts(authArr)
+ }
+ return 0
+}
+
+// InsertCSV UDM鉴权用户-批量添加
+func (r *UDMAuthImpl) InsertCSV(neID string, data []map[string]string) int64 {
+ prefixes := make(map[string]struct{})
+ for _, v := range data {
+ imsi := v["imsi"]
+ if len(imsi) < 5 {
+ continue
+ }
+ prefix := imsi[:len(imsi)-4]
+ prefixes[prefix] = struct{}{}
+ }
+ // 分组插入
+ var num int64 = 0
+ for prefix := range prefixes {
+ r.udmAuthRepository.DeletePrefixImsi(neID, prefix)
+ subArr := r.authDataByRedis(prefix+"*", neID)
+ if len(subArr) > 0 {
+ num += r.udmAuthRepository.Inserts(subArr)
+ }
+ }
+ return num
+}
+
+// InsertTxt UDM鉴权用户-批量添加
+func (r *UDMAuthImpl) InsertTxt(neID string, data [][]string) int64 {
+ prefixes := make(map[string]struct{})
+ for _, v := range data {
+ imsi := v[0]
+ if len(imsi) < 5 {
+ continue
+ }
+ prefix := imsi[:len(imsi)-4]
+ prefixes[prefix] = struct{}{}
+ }
+ // 分组插入
+ var num int64 = 0
+ for prefix := range prefixes {
+ r.udmAuthRepository.DeletePrefixImsi(neID, prefix)
+ subArr := r.authDataByRedis(prefix+"*", neID)
+ if len(subArr) > 0 {
+ num += r.udmAuthRepository.Inserts(subArr)
+ }
+ }
+ return num
+}
+
+// Insert UDM鉴权用户-修改更新
+func (r *UDMAuthImpl) Update(neID string, authUser model.UDMAuth) int64 {
+ authArr := r.authDataByRedis(authUser.Imsi, neID)
+ if len(authArr) > 0 {
+ r.udmAuthRepository.Delete(neID, authUser.Imsi)
+ return r.udmAuthRepository.Inserts(authArr)
+ }
+ return 0
+}
+
+// Insert UDM鉴权用户-删除单个
+func (r *UDMAuthImpl) Delete(neID, imsi string) int64 {
+ return r.udmAuthRepository.Delete(neID, imsi)
+}
+
+// Insert UDM鉴权用户-删除范围
+func (r *UDMAuthImpl) Deletes(neID, imsi, num string) int64 {
+ prefix := imsi[:len(imsi)-len(num)-1]
+ // 直接删除前缀的记录
+ r.udmAuthRepository.DeletePrefixImsi(neID, prefix)
+ // keys ausf:4600001000004*
+ authArr := r.authDataByRedis(prefix+"*", neID)
+ if len(authArr) > 0 {
+ return r.udmAuthRepository.Inserts(authArr)
+ }
+ return 0
+}
diff --git a/src/modules/network_element/service/udm_sub.go b/src/modules/network_element/service/udm_sub.go
new file mode 100644
index 0000000..b4b7dd7
--- /dev/null
+++ b/src/modules/network_element/service/udm_sub.go
@@ -0,0 +1,37 @@
+package service
+
+import "nms_nbi/src/modules/network_element/model"
+
+// UDM签约用户信息 服务层接口
+type IUDMSub interface {
+ // Save UDM签约用户-获取redis全部保存数据库
+ Save(neID string) int64
+
+ // Page UDM签约用户-分页查询数据库
+ Page(query map[string]any) map[string]any
+
+ // List UDM签约用户-查询数据库
+ List(subUser model.UDMSub) []model.UDMSub
+
+ // Insert UDM签约用户-新增单个
+ // imsi长度15,ki长度32,opc长度0或者32
+ Insert(neID string, subUser model.UDMSub) int64
+
+ // Insert UDM签约用户-批量添加
+ Inserts(neID string, subUser model.UDMSub, num string) int64
+
+ // InsertCSV UDM签约用户-批量添加
+ InsertCSV(neID string, data []map[string]string) int64
+
+ // InsertTxt UDM签约用户-批量添加
+ InsertTxt(neID string, data [][]string) int64
+
+ // Insert UDM签约用户-修改更新
+ Update(neID string, subUser model.UDMSub) int64
+
+ // Insert UDM签约用户-删除单个
+ Delete(neID, imsi string) int64
+
+ // Insert UDM签约用户-删除范围
+ Deletes(neID, imsi, num string) int64
+}
diff --git a/src/modules/network_element/service/udm_sub.impl.go b/src/modules/network_element/service/udm_sub.impl.go
new file mode 100644
index 0000000..d8646ef
--- /dev/null
+++ b/src/modules/network_element/service/udm_sub.impl.go
@@ -0,0 +1,195 @@
+package service
+
+import (
+ "fmt"
+ "strings"
+
+ "nms_nbi/src/framework/redis"
+ "nms_nbi/src/modules/network_element/model"
+ "nms_nbi/src/modules/network_element/repository"
+)
+
+// 实例化服务层 UDMSubImpl 结构体
+var NewUDMSubImpl = &UDMSubImpl{
+ udmSubRepository: repository.NewUDMSubImpl,
+}
+
+// UDM签约信息 服务层处理
+type UDMSubImpl struct {
+ // UDM签约信息数据信息
+ udmSubRepository repository.IUDMSub
+}
+
+// subDataByRedis UDM签约用户
+func (r *UDMSubImpl) subDataByRedis(imsi, neID string) []model.UDMSub {
+ arr := []model.UDMSub{}
+ key := fmt.Sprintf("udm-sd:%s", imsi)
+ udmsdArr, err := redis.GetKeys("udmuser", key)
+ if err != nil {
+ return arr
+ }
+ for _, key := range udmsdArr {
+ m, err := redis.GetHash("udmuser", key)
+ if err != nil {
+ continue
+ }
+
+ a := model.UDMSub{
+ Imsi: key[7:],
+ Msisdn: m["gpsi"], // 46003550072 strings.TrimPrefix(m["gpsi"], "86"),
+ SmfSel: m["smf-sel"],
+ SmData: m["sm-dat"], // 1-000001&cmnet&ims&3gnet
+ NeID: neID,
+ }
+
+ // def_ambr,def_nssai,0,def_arfb,def_sar,3,1,12000,1,1000,0,1,-
+ if v, ok := m["am-dat"]; ok {
+ arr := strings.Split(v, ",")
+ a.Ambr = arr[0]
+ a.Nssai = arr[1]
+ a.Rat = arr[2]
+ a.Arfb = arr[3]
+ a.Sar = arr[4]
+ a.Cn = arr[5]
+ }
+ // 1,64,24,65,def_eps,1,2,010200000000,-
+ if v, ok := m["eps-dat"]; ok {
+ arr := strings.Split(v, ",")
+ // 跳过非常规数据
+ if len(arr) > 9 {
+ continue
+ }
+ a.EpsDat = v
+ a.EpsFlag = arr[0]
+ a.EpsOdb = arr[1]
+ a.HplmnOdb = arr[2]
+ a.Ard = arr[3]
+ a.Epstpl = arr[4]
+ a.ContextId = arr[5]
+ a.ApnContext = arr[7]
+ // [6] 是不要的,导入和导出不用
+ a.StaticIp = arr[8]
+ }
+
+ arr = append(arr, a)
+ }
+ return arr
+}
+
+// Save UDM签约用户-获取redis全部保存数据库
+func (r *UDMSubImpl) Save(neID string) int64 {
+ subArr := r.subDataByRedis("*", neID)
+ // 数据清空后添加
+ go r.udmSubRepository.ClearAndInsert(neID, subArr)
+ return int64(len(subArr))
+}
+
+// Page UDM签约用户-分页查询数据库
+func (r *UDMSubImpl) Page(query map[string]any) map[string]any {
+ return r.udmSubRepository.SelectPage(query)
+}
+
+// List UDM签约用户-查询数据库
+func (r *UDMSubImpl) List(subUser model.UDMSub) []model.UDMSub {
+ return r.udmSubRepository.SelectList(subUser)
+}
+
+// Insert UDM签约用户-新增单个
+// imsi长度15,ki长度32,opc长度0或者32
+func (r *UDMSubImpl) Insert(neID string, subUser model.UDMSub) int64 {
+ authArr := r.subDataByRedis(subUser.Imsi, neID)
+ if len(authArr) > 0 {
+ r.udmSubRepository.Delete(neID, subUser.Imsi)
+ r.udmSubRepository.Inserts(authArr)
+ }
+ return 0
+}
+
+// Insert UDM签约用户-批量添加
+func (r *UDMSubImpl) Inserts(neID string, subUser model.UDMSub, num string) int64 {
+ startIMSI := subUser.Imsi
+ startIMSI = startIMSI[:len(startIMSI)-len(num)] + "*"
+ // keys udm-sd:4600001000004*
+ subArr := r.subDataByRedis(startIMSI, neID)
+ if len(subArr) > 0 {
+ for _, v := range subArr {
+ r.udmSubRepository.Delete(neID, v.Imsi)
+ }
+ return r.udmSubRepository.Inserts(subArr)
+ }
+ return 0
+}
+
+// InsertCSV UDM签约用户-批量添加
+func (r *UDMSubImpl) InsertCSV(neID string, data []map[string]string) int64 {
+ prefixes := make(map[string]struct{})
+ for _, v := range data {
+ imsi := v["imsi"]
+ if len(imsi) < 5 {
+ continue
+ }
+ prefix := imsi[:len(imsi)-4]
+ prefixes[prefix] = struct{}{}
+ }
+ // 分组插入
+ var num int64 = 0
+ for prefix := range prefixes {
+ r.udmSubRepository.DeletePrefixImsi(neID, prefix)
+ subArr := r.subDataByRedis(prefix+"*", neID)
+ if len(subArr) > 0 {
+ num += r.udmSubRepository.Inserts(subArr)
+ }
+ }
+ return num
+}
+
+// InsertTxt UDM签约用户-批量添加
+func (r *UDMSubImpl) InsertTxt(neID string, data [][]string) int64 {
+ prefixes := make(map[string]struct{})
+ for _, v := range data {
+ imsi := v[0]
+ if len(imsi) < 5 {
+ continue
+ }
+ prefix := imsi[:len(imsi)-4]
+ prefixes[prefix] = struct{}{}
+ }
+ // 分组插入
+ var num int64 = 0
+ for prefix := range prefixes {
+ r.udmSubRepository.DeletePrefixImsi(neID, prefix)
+ subArr := r.subDataByRedis(prefix+"*", neID)
+ if len(subArr) > 0 {
+ num += r.udmSubRepository.Inserts(subArr)
+ }
+ }
+ return num
+}
+
+// Insert UDM签约用户-修改更新
+func (r *UDMSubImpl) Update(neID string, subUser model.UDMSub) int64 {
+ authArr := r.subDataByRedis(subUser.Imsi, neID)
+ if len(authArr) > 0 {
+ r.udmSubRepository.Delete(neID, subUser.Imsi)
+ return r.udmSubRepository.Inserts(authArr)
+ }
+ return 0
+}
+
+// Insert UDM签约用户-删除单个
+func (r *UDMSubImpl) Delete(neID, imsi string) int64 {
+ return r.udmSubRepository.Delete(neID, imsi)
+}
+
+// Insert UDM签约用户-删除范围
+func (r *UDMSubImpl) Deletes(neID, imsi, num string) int64 {
+ prefix := imsi[:len(imsi)-len(num)-1]
+ // 直接删除前缀的记录
+ r.udmSubRepository.DeletePrefixImsi(neID, prefix)
+ // keys udm-sd:4600001000004*
+ authArr := r.subDataByRedis(prefix+"*", neID)
+ if len(authArr) > 0 {
+ return r.udmSubRepository.Inserts(authArr)
+ }
+ return 0
+}
diff --git a/src/modules/system/controller/sys_config.go b/src/modules/system/controller/sys_config.go
new file mode 100644
index 0000000..cad8782
--- /dev/null
+++ b/src/modules/system/controller/sys_config.go
@@ -0,0 +1,326 @@
+package controller
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/file"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/system/model"
+ "nms_nbi/src/modules/system/service"
+
+ "github.com/gin-gonic/gin"
+ "github.com/gin-gonic/gin/binding"
+)
+
+// 实例化控制层 SysConfigController 结构体
+var NewSysConfig = &SysConfigController{
+ sysConfigService: service.NewSysConfigImpl,
+}
+
+// 参数配置信息
+//
+// PATH /system/config
+type SysConfigController struct {
+ // 参数配置服务
+ sysConfigService service.ISysConfig
+}
+
+// 参数配置列表
+//
+// GET /list
+func (s *SysConfigController) List(c *gin.Context) {
+ querys := ctx.QueryMap(c)
+ data := s.sysConfigService.SelectConfigPage(querys)
+
+ rows := data["rows"].([]model.SysConfig)
+ // 闭包函数处理多语言
+ language := ctx.AcceptLanguage(c)
+ converI18n := func(language string, arr *[]model.SysConfig) {
+ for i := range *arr {
+ (*arr)[i].ConfigName = i18n.TKey(language, (*arr)[i].ConfigName)
+ (*arr)[i].ConfigValue = i18n.TKey(language, (*arr)[i].ConfigValue)
+ (*arr)[i].Remark = i18n.TKey(language, (*arr)[i].Remark)
+ }
+ }
+ converI18n(language, &rows)
+
+ c.JSON(200, result.Ok(data))
+}
+
+// 参数配置信息
+//
+// GET /:configId
+func (s *SysConfigController) Info(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ configId := c.Param("configId")
+ if configId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ data := s.sysConfigService.SelectConfigById(configId)
+ if data.ConfigID == configId {
+ // 处理多语言
+ data.ConfigName = i18n.TKey(language, data.ConfigName)
+ data.ConfigValue = i18n.TKey(language, data.ConfigValue)
+ data.Remark = i18n.TKey(language, data.Remark)
+ c.JSON(200, result.OkData(data))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 参数配置新增
+//
+// POST /
+func (s *SysConfigController) Add(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.SysConfig
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.ConfigID != "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查属性值唯一
+ uniqueConfigKey := s.sysConfigService.CheckUniqueConfigKey(body.ConfigKey, "")
+ if !uniqueConfigKey {
+ // 参数配置新增【%s】失败,参数键名已存在
+ msg := i18n.TTemplate(language, "config.errKeyExists", map[string]any{"name": body.ConfigKey})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ body.CreateBy = ctx.LoginUserToUserName(c)
+ insertId := s.sysConfigService.InsertConfig(body)
+ if insertId != "" {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 参数配置修改
+//
+// PUT /
+func (s *SysConfigController) Edit(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.SysConfig
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.ConfigID == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查属性值唯一
+ uniqueConfigKey := s.sysConfigService.CheckUniqueConfigKey(body.ConfigKey, body.ConfigID)
+ if !uniqueConfigKey {
+ // 参数配置修改【%s】失败,参数键名已存在
+ msg := i18n.TTemplate(language, "config.errKeyExists", map[string]any{"name": body.ConfigKey})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 检查是否存在
+ configInfo := s.sysConfigService.SelectConfigById(body.ConfigID)
+ if configInfo.ConfigID != body.ConfigID {
+ // 没有可访问参数配置数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "config.noData")))
+ return
+ }
+
+ // 多语言非原始值
+ i18nValue := i18n.TKey(language, configInfo.ConfigName)
+ if i18nValue != configInfo.ConfigName {
+ i18n.UpdateKeyValue(language, configInfo.ConfigName, body.ConfigName)
+ body.ConfigName = configInfo.ConfigName
+ }
+ // 多语言非原始值
+ i18nValue2 := i18n.TKey(language, configInfo.ConfigValue)
+ if i18nValue2 != configInfo.ConfigValue {
+ i18n.UpdateKeyValue(language, configInfo.ConfigValue, body.ConfigValue)
+ body.ConfigValue = configInfo.ConfigValue
+ }
+ // 多语言非原始值
+ i18nValue3 := i18n.TKey(language, configInfo.Remark)
+ if i18nValue3 != configInfo.Remark {
+ i18n.UpdateKeyValue(language, configInfo.Remark, body.Remark)
+ body.Remark = configInfo.Remark
+ }
+
+ body.UpdateBy = ctx.LoginUserToUserName(c)
+ rows := s.sysConfigService.UpdateConfig(body)
+ if rows > 0 {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 参数配置删除
+//
+// DELETE /:configIds
+func (s *SysConfigController) Remove(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ configIds := c.Param("configIds")
+ if configIds == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ // 处理字符转id数组后去重
+ ids := strings.Split(configIds, ",")
+ uniqueIDs := parse.RemoveDuplicates(ids)
+ if len(uniqueIDs) <= 0 {
+ c.JSON(200, result.Err(nil))
+ return
+ }
+ rows, err := s.sysConfigService.DeleteConfigByIds(uniqueIDs)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error())))
+ return
+ }
+ msg := i18n.TTemplate(language, "app.common.deleteSuccess", map[string]any{"num": rows})
+ c.JSON(200, result.OkMsg(msg))
+}
+
+// 参数配置刷新缓存
+//
+// PUT /refreshCache
+func (s *SysConfigController) RefreshCache(c *gin.Context) {
+ s.sysConfigService.ResetConfigCache()
+ c.JSON(200, result.Ok(nil))
+}
+
+// 参数配置根据参数键名
+//
+// GET /configKey/:configKey
+func (s *SysConfigController) ConfigKey(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ configKey := c.Param("configKey")
+ if configKey == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ key := s.sysConfigService.SelectConfigValueByKey(configKey)
+ if key != "" {
+ c.JSON(200, result.OkData(key))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 导出参数配置信息
+//
+// POST /export
+func (s *SysConfigController) Export(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ // 查询结果,根据查询条件结果,单页最大值限制
+ querys := ctx.BodyJSONMap(c)
+ data := s.sysConfigService.SelectConfigPage(querys)
+ if data["total"].(int64) == 0 {
+ // 导出数据记录为空
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.exportEmpty")))
+ return
+ }
+ rows := data["rows"].([]model.SysConfig)
+
+ // 闭包函数处理多语言
+ converI18n := func(language string, arr *[]model.SysConfig) {
+ for i := range *arr {
+ (*arr)[i].ConfigName = i18n.TKey(language, (*arr)[i].ConfigName)
+ (*arr)[i].ConfigValue = i18n.TKey(language, (*arr)[i].ConfigValue)
+ (*arr)[i].Remark = i18n.TKey(language, (*arr)[i].Remark)
+ }
+ }
+ converI18n(language, &rows)
+
+ // 导出文件名称
+ fileName := fmt.Sprintf("config_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli())
+ // 第一行表头标题
+ headerCells := map[string]string{
+ "A1": i18n.TKey(language, "config..export.id"),
+ "B1": i18n.TKey(language, "config..export.name"),
+ "C1": i18n.TKey(language, "config..export.key"),
+ "D1": i18n.TKey(language, "config..export.value"),
+ "E1": i18n.TKey(language, "config..export.type"),
+ "F1": i18n.TKey(language, "config..export.remark"),
+ }
+ // 从第二行开始的数据
+ dataCells := make([]map[string]any, 0)
+ for i, row := range rows {
+ idx := strconv.Itoa(i + 2)
+ typeValue := i18n.TKey(language, "dictData.no")
+ if row.ConfigType == "Y" {
+ typeValue = i18n.TKey(language, "dictData.yes")
+ }
+ dataCells = append(dataCells, map[string]any{
+ "A" + idx: row.ConfigID,
+ "B" + idx: row.ConfigName,
+ "C" + idx: row.ConfigKey,
+ "D" + idx: row.ConfigValue,
+ "E" + idx: typeValue,
+ "F" + idx: row.Remark,
+ })
+ }
+
+ // 导出数据表格
+ saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "")
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ c.FileAttachment(saveFilePath, fileName)
+}
+
+// 参数配置修改配置参数
+//
+// PUT /changeValue
+func (s *SysConfigController) ConfigValue(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body struct {
+ Key string `json:"key" binding:"required"`
+ Value string `json:"value" binding:"required"`
+ }
+ if err := c.ShouldBindBodyWith(&body, binding.JSON); err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查是否存在
+ info := s.sysConfigService.SelectConfigByKey(body.Key)
+ if info.ConfigKey != body.Key {
+ // 无效 key
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "config.errKey")))
+ return
+ }
+
+ // 与旧值相等 不变更
+ i18nValue := i18n.TKey(language, info.ConfigValue)
+ if i18nValue == body.Value {
+ // 变更状态与旧值相等!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "config.errValueEq")))
+ return
+ }
+
+ // 多语言非原始值
+ if i18nValue != info.ConfigValue {
+ i18n.UpdateKeyValue(language, info.ConfigValue, body.Value)
+ } else {
+ info.ConfigValue = body.Value
+ }
+
+ info.UpdateBy = ctx.LoginUserToUserName(c)
+ rows := s.sysConfigService.UpdateConfig(info)
+ if rows > 0 {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
diff --git a/src/modules/system/controller/sys_dept.go b/src/modules/system/controller/sys_dept.go
new file mode 100644
index 0000000..feb94be
--- /dev/null
+++ b/src/modules/system/controller/sys_dept.go
@@ -0,0 +1,376 @@
+package controller
+
+import (
+ "strings"
+
+ "nms_nbi/src/framework/constants/common"
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/vo"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/system/model"
+ "nms_nbi/src/modules/system/service"
+
+ "github.com/gin-gonic/gin"
+ "github.com/gin-gonic/gin/binding"
+)
+
+// 实例化控制层 SysDeptController 结构体
+var NewSysDept = &SysDeptController{
+ sysDeptService: service.NewSysDeptImpl,
+}
+
+// 部门信息
+//
+// PATH /system/dept
+type SysDeptController struct {
+ // 部门服务
+ sysDeptService service.ISysDept
+}
+
+// 部门列表
+//
+// GET /list
+func (s *SysDeptController) List(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var querys struct {
+ // 部门ID
+ DeptID string `json:"deptId"`
+ // 父部门ID
+ ParentID string `json:"parentId" `
+ // 部门名称
+ DeptName string `json:"deptName" `
+ // 部门状态(0正常 1停用)
+ Status string `json:"status"`
+ }
+ err := c.ShouldBindQuery(&querys)
+ if err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ SysDeptController := model.SysDept{
+ DeptID: querys.DeptID,
+ ParentID: querys.ParentID,
+ DeptName: querys.DeptName,
+ Status: querys.Status,
+ }
+ dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "")
+ data := s.sysDeptService.SelectDeptList(SysDeptController, dataScopeSQL)
+
+ // 闭包函数处理多语言
+ converI18n := func(language string, arr *[]model.SysDept) {
+ for i := range *arr {
+ (*arr)[i].DeptName = i18n.TKey(language, (*arr)[i].DeptName)
+ }
+ }
+ converI18n(language, &data)
+
+ c.JSON(200, result.OkData(data))
+}
+
+// 部门信息
+//
+// GET /:deptId
+func (s *SysDeptController) Info(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ deptId := c.Param("deptId")
+ if deptId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ data := s.sysDeptService.SelectDeptById(deptId)
+ if data.DeptID == deptId {
+ // 处理多语言
+ data.DeptName = i18n.TKey(language, data.DeptName)
+ c.JSON(200, result.OkData(data))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 部门新增
+//
+// POST /
+func (s *SysDeptController) Add(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.SysDept
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.DeptID != "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 父级ID不为0是要检查
+ if body.ParentID != "0" {
+ deptParent := s.sysDeptService.SelectDeptById(body.ParentID)
+ if deptParent.DeptID != body.ParentID {
+ // 没有可访问部门数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "dept.noData")))
+ return
+ }
+ if deptParent.Status == common.STATUS_NO {
+ // 上级部门【%s】停用,不允许新增
+ msg := i18n.TTemplate(language, "dept.errParentStatus", map[string]any{"name": deptParent.DeptName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+ if deptParent.DelFlag == common.STATUS_YES {
+ // 上级部门【%s】已删除,不允许新增
+ msg := i18n.TTemplate(language, "dept.errParentDelFlag", map[string]any{"name": deptParent.DeptName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+ body.Ancestors = deptParent.Ancestors + "," + body.ParentID
+ } else {
+ body.Ancestors = "0"
+ }
+
+ // 检查同级下名称唯一
+ uniqueDeptName := s.sysDeptService.CheckUniqueDeptName(body.DeptName, body.ParentID, "")
+ if !uniqueDeptName {
+ // 部门新增【%s】失败,部门名称已存在
+ msg := i18n.TTemplate(language, "dept.errNameExists", map[string]any{"name": body.DeptName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ body.CreateBy = ctx.LoginUserToUserName(c)
+ insertId := s.sysDeptService.InsertDept(body)
+ if insertId != "" {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 部门修改
+//
+// PUT /
+func (s *SysDeptController) Edit(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.SysDept
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.DeptID == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 上级部门不能选自己
+ if body.DeptID == body.ParentID {
+ // 部门修改【%s】失败,上级部门不能是自己
+ msg := i18n.TTemplate(language, "dept.errParentID", map[string]any{"name": body.DeptName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 检查数据是否存在
+ deptInfo := s.sysDeptService.SelectDeptById(body.DeptID)
+ if deptInfo.DeptID != body.DeptID {
+ // 没有可访问部门数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "dept.noData")))
+ return
+ }
+ // 父级ID不为0是要检查
+ if body.ParentID != "0" {
+ deptParent := s.sysDeptService.SelectDeptById(body.ParentID)
+ if deptParent.DeptID != body.ParentID {
+ // 没有可访问部门数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "dept.noData")))
+ return
+ }
+ }
+
+ // 检查同级下名称唯一
+ uniqueDeptName := s.sysDeptService.CheckUniqueDeptName(body.DeptName, body.ParentID, body.DeptID)
+ if !uniqueDeptName {
+ // 部门修改【%s】失败,部门名称已存在
+ msg := i18n.TTemplate(language, "dept.errNameExists", map[string]any{"name": body.DeptName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 上级停用需要检查下级是否有在使用
+ if body.Status == common.STATUS_NO {
+ hasChild := s.sysDeptService.HasChildByDeptId(body.DeptID)
+ if hasChild > 0 {
+ // 该部门包含未停用的子部门数量:%d
+ msg := i18n.TTemplate(language, "dept.errHasChildUse", map[string]any{"num": hasChild})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+ }
+
+ // 多语言非原始值
+ i18nValue := i18n.TKey(language, deptInfo.DeptName)
+ if i18nValue != deptInfo.DeptName {
+ i18n.UpdateKeyValue(language, deptInfo.DeptName, body.DeptName)
+ body.DeptName = deptInfo.DeptName
+ }
+
+ body.UpdateBy = ctx.LoginUserToUserName(c)
+ rows := s.sysDeptService.UpdateDept(body)
+ if rows > 0 {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 部门删除
+//
+// DELETE /:deptId
+func (s *SysDeptController) Remove(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ deptId := c.Param("deptId")
+ if deptId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查数据是否存在
+ dept := s.sysDeptService.SelectDeptById(deptId)
+ if dept.DeptID != deptId {
+ // 没有可访问部门数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "dept.noData")))
+ return
+ }
+
+ // 检查是否存在子部门
+ hasChild := s.sysDeptService.HasChildByDeptId(deptId)
+ if hasChild > 0 {
+ // 不允许删除,存在子部门数:%d
+ msg := i18n.TTemplate(language, "dept.errHasChildUse", map[string]any{"num": hasChild})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 检查是否分配给用户
+ existUser := s.sysDeptService.CheckDeptExistUser(deptId)
+ if existUser > 0 {
+ // 不允许删除,部门已分配给用户数:%d
+ msg := i18n.TTemplate(language, "dept.errHasUserUse", map[string]any{"num": existUser})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ rows := s.sysDeptService.DeleteDeptById(deptId)
+ if rows > 0 {
+ // 删除成功:%d
+ msg := i18n.TTemplate(language, "app.common.deleteSuccess", map[string]any{"num": rows})
+ c.JSON(200, result.OkMsg(msg))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 部门列表(排除节点)
+//
+// GET /list/exclude/:deptId
+func (s *SysDeptController) ExcludeChild(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ deptId := c.Param("deptId")
+ if deptId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "")
+ data := s.sysDeptService.SelectDeptList(model.SysDept{}, dataScopeSQL)
+
+ // 过滤排除节点
+ filtered := make([]model.SysDept, 0)
+ for _, dept := range data {
+ hasAncestor := false
+ ancestorList := strings.Split(dept.Ancestors, ",")
+ for _, ancestor := range ancestorList {
+ if ancestor == deptId {
+ hasAncestor = true
+ break
+ }
+ }
+ if !(dept.DeptID == deptId || hasAncestor) {
+ dept.DeptName = i18n.TKey(language, dept.DeptName)
+ filtered = append(filtered, dept)
+ }
+ }
+ c.JSON(200, result.OkData(filtered))
+}
+
+// 部门树结构列表
+//
+// GET /treeSelect
+func (s *SysDeptController) TreeSelect(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var querys struct {
+ // 部门ID
+ DeptID string `json:"deptId"`
+ // 父部门ID
+ ParentID string `json:"parentId" `
+ // 部门名称
+ DeptName string `json:"deptName" `
+ // 部门状态(0正常 1停用)
+ Status string `json:"status"`
+ }
+ err := c.ShouldBindQuery(&querys)
+ if err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ SysDeptController := model.SysDept{
+ DeptID: querys.DeptID,
+ ParentID: querys.ParentID,
+ DeptName: querys.DeptName,
+ Status: querys.Status,
+ }
+ dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "")
+ deptTreeSelect := s.sysDeptService.SelectDeptTreeSelect(SysDeptController, dataScopeSQL)
+
+ // 闭包函数处理多语言
+ var converI18n func(language string, arr *[]vo.TreeSelect)
+ converI18n = func(language string, arr *[]vo.TreeSelect) {
+ for i := range *arr {
+ (*arr)[i].Label = i18n.TKey(language, (*arr)[i].Label)
+ if len((*arr)[i].Children) > 0 {
+ converI18n(language, &(*arr)[i].Children)
+ }
+ }
+ }
+ converI18n(language, &deptTreeSelect)
+
+ c.JSON(200, result.OkData(deptTreeSelect))
+}
+
+// 部门树结构列表(指定角色)
+//
+// GET /roleDeptTreeSelect/:roleId
+func (s *SysDeptController) RoleDeptTreeSelect(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ roleId := c.Param("roleId")
+ if roleId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "")
+ deptTreeSelect := s.sysDeptService.SelectDeptTreeSelect(model.SysDept{}, dataScopeSQL)
+ checkedKeys := s.sysDeptService.SelectDeptListByRoleId(roleId)
+
+ // 闭包函数处理多语言
+ var converI18n func(language string, arr *[]vo.TreeSelect)
+ converI18n = func(language string, arr *[]vo.TreeSelect) {
+ for i := range *arr {
+ (*arr)[i].Label = i18n.TKey(language, (*arr)[i].Label)
+ if len((*arr)[i].Children) > 0 {
+ converI18n(language, &(*arr)[i].Children)
+ }
+ }
+ }
+ converI18n(language, &deptTreeSelect)
+
+ c.JSON(200, result.OkData(map[string]any{
+ "depts": deptTreeSelect,
+ "checkedKeys": checkedKeys,
+ }))
+}
diff --git a/src/modules/system/controller/sys_dict_data.go b/src/modules/system/controller/sys_dict_data.go
new file mode 100644
index 0000000..f21db26
--- /dev/null
+++ b/src/modules/system/controller/sys_dict_data.go
@@ -0,0 +1,297 @@
+package controller
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/file"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/system/model"
+ "nms_nbi/src/modules/system/service"
+
+ "github.com/gin-gonic/gin"
+ "github.com/gin-gonic/gin/binding"
+)
+
+// 实例化控制层 SysDictDataController 结构体
+var NewSysDictData = &SysDictDataController{
+ sysDictDataService: service.NewSysDictDataImpl,
+ sysDictTypeService: service.NewSysDictTypeImpl,
+}
+
+// 字典类型对应的字典数据信息
+//
+// PATH /system/dict/data
+type SysDictDataController struct {
+ // 字典数据服务
+ sysDictDataService service.ISysDictData
+ // 字典类型服务
+ sysDictTypeService service.ISysDictType
+}
+
+// 字典数据列表
+//
+// GET /list
+func (s *SysDictDataController) List(c *gin.Context) {
+ querys := ctx.QueryMap(c)
+ data := s.sysDictDataService.SelectDictDataPage(querys)
+
+ rows := data["rows"].([]model.SysDictData)
+ // 闭包函数处理多语言
+ language := ctx.AcceptLanguage(c)
+ converI18n := func(language string, arr *[]model.SysDictData) {
+ for i := range *arr {
+ if strings.Contains((*arr)[i].DictType, "i18n") {
+ continue
+ }
+ (*arr)[i].DictLabel = i18n.TKey(language, (*arr)[i].DictLabel)
+ (*arr)[i].Remark = i18n.TKey(language, (*arr)[i].Remark)
+ }
+ }
+ converI18n(language, &rows)
+
+ c.JSON(200, result.Ok(data))
+}
+
+// 字典数据详情
+//
+// GET /:dictCode
+func (s *SysDictDataController) Info(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ dictCode := c.Param("dictCode")
+ if dictCode == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ data := s.sysDictDataService.SelectDictDataByCode(dictCode)
+ if data.DictCode == dictCode {
+ // 处理多语言
+ if !strings.Contains(data.DictType, "i18n") {
+ data.DictLabel = i18n.TKey(language, data.DictLabel)
+ data.Remark = i18n.TKey(language, data.Remark)
+ }
+ c.JSON(200, result.OkData(data))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 字典数据新增
+//
+// POST /
+func (s *SysDictDataController) Add(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.SysDictData
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.DictCode != "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查字典类型是否存在
+ sysDictType := s.sysDictTypeService.SelectDictTypeByType(body.DictType)
+ if sysDictType.DictType != body.DictType {
+ // 没有可访问字典类型数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "dictType.noData")))
+ return
+ }
+
+ // 检查字典标签唯一
+ uniqueDictLabel := s.sysDictDataService.CheckUniqueDictLabel(body.DictType, body.DictLabel, "")
+ if !uniqueDictLabel {
+ // 数据新增【%s】失败,该字典类型下标签名已存在
+ msg := i18n.TTemplate(language, "dictType.errLabelExists", map[string]any{"name": body.DictLabel})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ body.CreateBy = ctx.LoginUserToUserName(c)
+ insertId := s.sysDictDataService.InsertDictData(body)
+ if insertId != "" {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 字典类型修改
+//
+// PUT /
+func (s *SysDictDataController) Edit(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.SysDictData
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.DictCode == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查字典类型是否存在
+ sysDictType := s.sysDictTypeService.SelectDictTypeByType(body.DictType)
+ if sysDictType.DictType != body.DictType {
+ // 没有可访问字典类型数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "dictType.noData")))
+ return
+ }
+
+ // 检查字典编码是否存在
+ sysDictData := s.sysDictDataService.SelectDictDataByCode(body.DictCode)
+ if sysDictData.DictCode != body.DictCode {
+ // 没有可访问字典编码数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "dictData.noData")))
+ return
+ }
+
+ // 检查字典标签唯一
+ uniqueDictLabel := s.sysDictDataService.CheckUniqueDictLabel(body.DictType, body.DictLabel, body.DictCode)
+ if !uniqueDictLabel {
+ // 数据修改【%s】失败,该字典类型下标签名已存在
+ msg := i18n.TTemplate(language, "dictType.errLabelExists", map[string]any{"name": body.DictLabel})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 多语言非原始值
+ i18nValue := i18n.TKey(language, sysDictData.DictLabel)
+ if i18nValue != sysDictData.DictLabel {
+ i18n.UpdateKeyValue(language, sysDictData.DictLabel, body.DictLabel)
+ body.DictLabel = sysDictData.DictLabel
+ }
+ // 多语言非原始值
+ i18nValue2 := i18n.TKey(language, sysDictData.Remark)
+ if i18nValue2 != sysDictData.Remark {
+ i18n.UpdateKeyValue(language, sysDictData.Remark, body.Remark)
+ body.Remark = sysDictData.Remark
+ }
+
+ body.UpdateBy = ctx.LoginUserToUserName(c)
+ rows := s.sysDictDataService.UpdateDictData(body)
+ if rows > 0 {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 字典数据删除
+//
+// DELETE /:dictCodes
+func (s *SysDictDataController) Remove(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ dictCodes := c.Param("dictCodes")
+ if dictCodes == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ // 处理字符转id数组后去重
+ ids := strings.Split(dictCodes, ",")
+ uniqueIDs := parse.RemoveDuplicates(ids)
+ if len(uniqueIDs) <= 0 {
+ c.JSON(200, result.Err(nil))
+ return
+ }
+ rows, err := s.sysDictDataService.DeleteDictDataByCodes(uniqueIDs)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+ // 删除成功:%d
+ msg := i18n.TTemplate(language, "app.common.deleteSuccess", map[string]any{"num": rows})
+ c.JSON(200, result.OkMsg(msg))
+}
+
+// 字典数据列表(指定字典类型)
+//
+// GET /type/:dictType
+func (s *SysDictDataController) DictType(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ dictType := c.Param("dictType")
+ if dictType == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ data := s.sysDictDataService.SelectDictDataByType(dictType)
+
+ // 闭包函数处理多语言
+ converI18n := func(language string, arr *[]model.SysDictData) {
+ for i := range *arr {
+ (*arr)[i].DictLabel = i18n.TKey(language, (*arr)[i].DictLabel)
+ (*arr)[i].Remark = i18n.TKey(language, (*arr)[i].Remark)
+ }
+ }
+ converI18n(language, &data)
+
+ c.JSON(200, result.OkData(data))
+}
+
+// 字典数据列表导出
+//
+// POST /export
+func (s *SysDictDataController) Export(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ // 查询结果,根据查询条件结果,单页最大值限制
+ querys := ctx.BodyJSONMap(c)
+ data := s.sysDictDataService.SelectDictDataPage(querys)
+ if data["total"].(int64) == 0 {
+ // 导出数据记录为空
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.exportEmpty")))
+ return
+ }
+ rows := data["rows"].([]model.SysDictData)
+
+ // 闭包函数处理多语言
+ converI18n := func(language string, arr *[]model.SysDictData) {
+ for i := range *arr {
+ if strings.Contains((*arr)[i].DictType, "i18n") {
+ continue
+ }
+ (*arr)[i].DictLabel = i18n.TKey(language, (*arr)[i].DictLabel)
+ (*arr)[i].Remark = i18n.TKey(language, (*arr)[i].Remark)
+ }
+ }
+ converI18n(language, &rows)
+
+ // 导出文件名称
+ fileName := fmt.Sprintf("dict_data_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli())
+ // 第一行表头标题
+ headerCells := map[string]string{
+ "A1": i18n.TKey(language, "dictData.export.code"),
+ "B1": i18n.TKey(language, "dictData.export.sort"),
+ "C1": i18n.TKey(language, "dictData.export.label"),
+ "D1": i18n.TKey(language, "dictData.export.value"),
+ "E1": i18n.TKey(language, "dictData.export.type"),
+ "F1": i18n.TKey(language, "dictData.export.status"),
+ }
+ // 从第二行开始的数据
+ dataCells := make([]map[string]any, 0)
+ for i, row := range rows {
+ idx := strconv.Itoa(i + 2)
+ statusValue := i18n.TKey(language, "dictData.disable")
+ if row.Status == "1" {
+ statusValue = i18n.TKey(language, "dictData.normal")
+ }
+ dataCells = append(dataCells, map[string]any{
+ "A" + idx: row.DictCode,
+ "B" + idx: row.DictSort,
+ "C" + idx: row.DictLabel,
+ "D" + idx: row.DictValue,
+ "E" + idx: row.DictType,
+ "F" + idx: statusValue,
+ })
+ }
+
+ // 导出数据表格
+ saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "")
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ c.FileAttachment(saveFilePath, fileName)
+}
diff --git a/src/modules/system/controller/sys_dict_type.go b/src/modules/system/controller/sys_dict_type.go
new file mode 100644
index 0000000..e75ebca
--- /dev/null
+++ b/src/modules/system/controller/sys_dict_type.go
@@ -0,0 +1,293 @@
+package controller
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/constants/common"
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/file"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/system/model"
+ "nms_nbi/src/modules/system/service"
+
+ "github.com/gin-gonic/gin"
+ "github.com/gin-gonic/gin/binding"
+)
+
+// 实例化控制层 SysDictTypeController 结构体
+var NewSysDictType = &SysDictTypeController{
+ sysDictTypeService: service.NewSysDictTypeImpl,
+}
+
+// 字典类型信息
+//
+// PATH /system/dict/type
+type SysDictTypeController struct {
+ // 字典类型服务
+ sysDictTypeService service.ISysDictType
+}
+
+// 字典类型列表
+//
+// GET /list
+func (s *SysDictTypeController) List(c *gin.Context) {
+ querys := ctx.QueryMap(c)
+ data := s.sysDictTypeService.SelectDictTypePage(querys)
+
+ rows := data["rows"].([]model.SysDictType)
+ // 闭包函数处理多语言
+ language := ctx.AcceptLanguage(c)
+ converI18n := func(language string, arr *[]model.SysDictType) {
+ for i := range *arr {
+ (*arr)[i].DictName = i18n.TKey(language, (*arr)[i].DictName)
+ (*arr)[i].Remark = i18n.TKey(language, (*arr)[i].Remark)
+ }
+ }
+ converI18n(language, &rows)
+
+ c.JSON(200, result.Ok(data))
+}
+
+// 字典类型信息
+//
+// GET /:dictId
+func (s *SysDictTypeController) Info(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ dictId := c.Param("dictId")
+ if dictId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ data := s.sysDictTypeService.SelectDictTypeByID(dictId)
+ if data.DictID == dictId {
+ // 处理多语言
+ data.DictName = i18n.TKey(language, data.DictName)
+ data.Remark = i18n.TKey(language, data.Remark)
+ c.JSON(200, result.OkData(data))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 字典类型新增
+//
+// POST /
+func (s *SysDictTypeController) Add(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.SysDictType
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.DictID != "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查字典名称唯一
+ uniqueDictName := s.sysDictTypeService.CheckUniqueDictName(body.DictName, "")
+ if !uniqueDictName {
+ // 字典新增【%s】失败,字典名称已存在
+ msg := i18n.TTemplate(language, "dictType.errNameExists", map[string]any{"name": body.DictName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 检查字典类型唯一
+ uniqueDictType := s.sysDictTypeService.CheckUniqueDictType(body.DictType, "")
+ if !uniqueDictType {
+ // 字典新增【%s】失败,字典类型已存在
+ msg := i18n.TTemplate(language, "dictType.errTypeExists", map[string]any{"name": body.DictName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ body.CreateBy = ctx.LoginUserToUserName(c)
+ insertId := s.sysDictTypeService.InsertDictType(body)
+ if insertId != "" {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 字典类型修改
+//
+// PUT /
+func (s *SysDictTypeController) Edit(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.SysDictType
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.DictID == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查数据是否存在
+ dictInfo := s.sysDictTypeService.SelectDictTypeByID(body.DictID)
+ if dictInfo.DictID != body.DictID {
+ // 没有可访问字典类型数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "dictType.noData")))
+ return
+ }
+
+ // 检查字典名称唯一
+ uniqueDictName := s.sysDictTypeService.CheckUniqueDictName(body.DictName, body.DictID)
+ if !uniqueDictName {
+ // 字典修改【%s】失败,字典名称已存在
+ msg := i18n.TTemplate(language, "dictType.errNameExists", map[string]any{"name": body.DictName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 检查字典类型唯一
+ uniqueDictType := s.sysDictTypeService.CheckUniqueDictType(body.DictType, body.DictID)
+ if !uniqueDictType {
+ // 字典修改【%s】失败,字典类型已存在
+ msg := i18n.TTemplate(language, "dictType.errTypeExists", map[string]any{"name": body.DictName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 多语言非原始值
+ i18nValue := i18n.TKey(language, dictInfo.DictName)
+ if i18nValue != dictInfo.DictName {
+ i18n.UpdateKeyValue(language, dictInfo.DictName, body.DictName)
+ body.DictName = dictInfo.DictName
+ }
+ // 多语言非原始值
+ i18nValue2 := i18n.TKey(language, dictInfo.Remark)
+ if i18nValue2 != dictInfo.Remark {
+ i18n.UpdateKeyValue(language, dictInfo.Remark, body.Remark)
+ body.Remark = dictInfo.Remark
+ }
+
+ body.UpdateBy = ctx.LoginUserToUserName(c)
+ rows := s.sysDictTypeService.UpdateDictType(body)
+ if rows > 0 {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 字典类型删除
+//
+// DELETE /:dictIds
+func (s *SysDictTypeController) Remove(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ dictIds := c.Param("dictIds")
+ if dictIds == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ // 处理字符转id数组后去重
+ ids := strings.Split(dictIds, ",")
+ uniqueIDs := parse.RemoveDuplicates(ids)
+ if len(uniqueIDs) <= 0 {
+ c.JSON(200, result.Err(nil))
+ return
+ }
+ rows, err := s.sysDictTypeService.DeleteDictTypeByIDs(uniqueIDs)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+ msg := i18n.TTemplate(language, "app.common.deleteSuccess", map[string]any{"num": rows})
+ c.JSON(200, result.OkMsg(msg))
+}
+
+// 字典类型刷新缓存
+//
+// PUT /refreshCache
+func (s *SysDictTypeController) RefreshCache(c *gin.Context) {
+ s.sysDictTypeService.ResetDictCache()
+ i18n.ClearLocaleData() // 清空国际化数据
+ c.JSON(200, result.Ok(nil))
+}
+
+// 字典类型选择框列表
+//
+// GET /getDictOptionselect
+func (s *SysDictTypeController) DictOptionselect(c *gin.Context) {
+ data := s.sysDictTypeService.SelectDictTypeList(model.SysDictType{
+ Status: common.STATUS_YES,
+ })
+
+ type labelValue struct {
+ Label string `json:"label"`
+ Value string `json:"value"`
+ }
+
+ language := ctx.AcceptLanguage(c)
+ // 数据组
+ arr := []labelValue{}
+ for _, v := range data {
+ arr = append(arr, labelValue{
+ Label: i18n.TKey(language, v.DictName),
+ Value: v.DictType,
+ })
+ }
+ c.JSON(200, result.OkData(arr))
+}
+
+// 字典类型列表导出
+//
+// POST /export
+func (s *SysDictTypeController) Export(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ // 查询结果,根据查询条件结果,单页最大值限制
+ querys := ctx.BodyJSONMap(c)
+ data := s.sysDictTypeService.SelectDictTypePage(querys)
+ if data["total"].(int64) == 0 {
+ // 导出数据记录为空
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.exportEmpty")))
+ return
+ }
+ rows := data["rows"].([]model.SysDictType)
+
+ // 闭包函数处理多语言
+ converI18n := func(language string, arr *[]model.SysDictType) {
+ for i := range *arr {
+ (*arr)[i].DictName = i18n.TKey(language, (*arr)[i].DictName)
+ (*arr)[i].Remark = i18n.TKey(language, (*arr)[i].Remark)
+ }
+ }
+ converI18n(language, &rows)
+
+ // 导出文件名称
+ fileName := fmt.Sprintf("dict_type_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli())
+ // 第一行表头标题
+ headerCells := map[string]string{
+ "A1": i18n.TKey(language, "dictType.export.id"),
+ "B1": i18n.TKey(language, "dictType.export.name"),
+ "C1": i18n.TKey(language, "dictType.export.type"),
+ "D1": i18n.TKey(language, "dictType.export.status"),
+ }
+ // 从第二行开始的数据
+ dataCells := make([]map[string]any, 0)
+ for i, row := range rows {
+ idx := strconv.Itoa(i + 2)
+ statusValue := i18n.TKey(language, "dictData.disable")
+ if row.Status == "1" {
+ statusValue = i18n.TKey(language, "dictData.normal")
+ }
+ dataCells = append(dataCells, map[string]any{
+ "A" + idx: row.DictID,
+ "B" + idx: row.DictName,
+ "C" + idx: row.DictType,
+ "D" + idx: statusValue,
+ })
+ }
+
+ // 导出数据表格
+ saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "")
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ c.FileAttachment(saveFilePath, fileName)
+}
diff --git a/src/modules/system/controller/sys_log_login.go b/src/modules/system/controller/sys_log_login.go
new file mode 100644
index 0000000..c65853a
--- /dev/null
+++ b/src/modules/system/controller/sys_log_login.go
@@ -0,0 +1,188 @@
+package controller
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/date"
+ "nms_nbi/src/framework/utils/file"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/vo/result"
+ commonService "nms_nbi/src/modules/common/service"
+ "nms_nbi/src/modules/system/model"
+ "nms_nbi/src/modules/system/service"
+
+ "github.com/gin-gonic/gin"
+)
+
+// 实例化控制层 SysLogLoginController 结构体
+var NewSysLogLogin = &SysLogLoginController{
+ sysLogLoginService: service.NewSysLogLoginImpl,
+ accountService: commonService.NewAccountImpl,
+}
+
+// 系统登录日志信息
+//
+// PATH /system/log/login
+type SysLogLoginController struct {
+ // 系统登录日志服务
+ sysLogLoginService service.ISysLogLogin
+ // 账号身份操作服务
+ accountService commonService.IAccount
+}
+
+// 系统登录日志列表
+//
+// GET /list
+func (s *SysLogLoginController) List(c *gin.Context) {
+ querys := ctx.QueryMap(c)
+ data := s.sysLogLoginService.SelectSysLogLoginPage(querys)
+
+ rows := data["rows"].([]model.SysLogLogin)
+ // 闭包函数处理多语言
+ language := ctx.AcceptLanguage(c)
+ converI18n := func(language string, arr *[]model.SysLogLogin) {
+ for i := range *arr {
+ (*arr)[i].LoginLocation = i18n.TKey(language, (*arr)[i].LoginLocation)
+ (*arr)[i].OS = i18n.TKey(language, (*arr)[i].OS)
+ (*arr)[i].Browser = i18n.TKey(language, (*arr)[i].Browser)
+ (*arr)[i].Msg = i18n.TKey(language, (*arr)[i].Msg)
+ }
+ }
+ converI18n(language, &rows)
+
+ c.JSON(200, result.Ok(data))
+}
+
+// 系统登录日志删除
+//
+// DELETE /:infoIds
+func (s *SysLogLoginController) Remove(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ infoIds := c.Param("infoIds")
+ if infoIds == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 处理字符转id数组后去重
+ ids := strings.Split(infoIds, ",")
+ uniqueIDs := parse.RemoveDuplicates(ids)
+ if len(uniqueIDs) <= 0 {
+ c.JSON(200, result.Err(nil))
+ return
+ }
+ rows := s.sysLogLoginService.DeleteSysLogLoginByIds(uniqueIDs)
+ if rows > 0 {
+ msg := i18n.TTemplate(language, "app.common.deleteSuccess", map[string]any{"num": rows})
+ c.JSON(200, result.OkMsg(msg))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 系统登录日志清空
+//
+// DELETE /clean
+func (s *SysLogLoginController) Clean(c *gin.Context) {
+ err := s.sysLogLoginService.CleanSysLogLogin()
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+ c.JSON(200, result.Ok(nil))
+}
+
+// 系统登录日志账户解锁
+//
+// PUT /unlock/:userName
+func (s *SysLogLoginController) Unlock(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ userName := c.Param("userName")
+ if userName == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ ok := s.accountService.ClearLoginRecordCache(userName)
+ if ok {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.errUnlock")))
+}
+
+// 导出系统登录日志信息
+//
+// POST /export
+func (s *SysLogLoginController) Export(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ // 查询结果,根据查询条件结果,单页最大值限制
+ querys := ctx.BodyJSONMap(c)
+ data := s.sysLogLoginService.SelectSysLogLoginPage(querys)
+ if data["total"].(int64) == 0 {
+ // 导出数据记录为空
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.exportEmpty")))
+ return
+ }
+ rows := data["rows"].([]model.SysLogLogin)
+
+ // 闭包函数处理多语言
+ converI18n := func(language string, arr *[]model.SysLogLogin) {
+ for i := range *arr {
+ (*arr)[i].LoginLocation = i18n.TKey(language, (*arr)[i].LoginLocation)
+ (*arr)[i].OS = i18n.TKey(language, (*arr)[i].OS)
+ (*arr)[i].Browser = i18n.TKey(language, (*arr)[i].Browser)
+ (*arr)[i].Msg = i18n.TKey(language, (*arr)[i].Msg)
+ }
+ }
+ converI18n(language, &rows)
+
+ // 导出文件名称
+ fileName := fmt.Sprintf("sys_log_login_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli())
+ // 第一行表头标题
+ headerCells := map[string]string{
+ "A1": i18n.TKey(language, "log.login.export.id"),
+ "B1": i18n.TKey(language, "log.login.export.userName"),
+ "C1": i18n.TKey(language, "log.login.export.status"),
+ "D1": i18n.TKey(language, "log.login.export.ip"),
+ "E1": i18n.TKey(language, "log.login.export.location"),
+ "F1": i18n.TKey(language, "log.login.export.browser"),
+ "G1": i18n.TKey(language, "log.login.export.os"),
+ "H1": i18n.TKey(language, "log.login.export.msg"),
+ "I1": i18n.TKey(language, "log.login.export.time"),
+ }
+ // 从第二行开始的数据
+ dataCells := make([]map[string]any, 0)
+ for i, row := range rows {
+ idx := strconv.Itoa(i + 2)
+ // 状态
+ statusValue := i18n.TKey(language, "dictData.fail")
+ if row.Status == "1" {
+ statusValue = i18n.TKey(language, "dictData.success")
+ }
+ dataCells = append(dataCells, map[string]any{
+ "A" + idx: row.LoginID,
+ "B" + idx: row.UserName,
+ "C" + idx: statusValue,
+ "D" + idx: row.IPAddr,
+ "E" + idx: row.LoginLocation,
+ "F" + idx: row.Browser,
+ "G" + idx: row.OS,
+ "H" + idx: row.Msg,
+ "I" + idx: date.ParseDateToStr(row.LoginTime, date.YYYY_MM_DD_HH_MM_SS),
+ })
+ }
+
+ // 导出数据表格
+ saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "")
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ c.FileAttachment(saveFilePath, fileName)
+}
diff --git a/src/modules/system/controller/sys_log_operate.go b/src/modules/system/controller/sys_log_operate.go
new file mode 100644
index 0000000..1817705
--- /dev/null
+++ b/src/modules/system/controller/sys_log_operate.go
@@ -0,0 +1,180 @@
+package controller
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/date"
+ "nms_nbi/src/framework/utils/file"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/system/model"
+ "nms_nbi/src/modules/system/service"
+
+ "github.com/gin-gonic/gin"
+)
+
+// 实例化控制层 SysLogOperateController 结构体
+var NewSysLogOperate = &SysLogOperateController{
+ SysLogOperateService: service.NewSysLogOperateImpl,
+}
+
+// 操作日志记录信息
+//
+// PATH /system/log/operate
+type SysLogOperateController struct {
+ // 操作日志服务
+ SysLogOperateService service.ISysLogOperate
+}
+
+// 操作日志列表
+//
+// GET /list
+func (s *SysLogOperateController) List(c *gin.Context) {
+ querys := ctx.QueryMap(c)
+ data := s.SysLogOperateService.SelectSysLogOperatePage(querys)
+
+ rows := data["rows"].([]model.SysLogOperate)
+ // 闭包函数处理多语言
+ language := ctx.AcceptLanguage(c)
+ converI18n := func(language string, arr *[]model.SysLogOperate) {
+ for i := range *arr {
+ (*arr)[i].Title = i18n.TKey(language, (*arr)[i].Title)
+ (*arr)[i].OperLocation = i18n.TKey(language, (*arr)[i].OperLocation)
+ }
+ }
+ converI18n(language, &rows)
+
+ c.JSON(200, result.Ok(data))
+}
+
+// 操作日志删除
+//
+// DELETE /:operIds
+func (s *SysLogOperateController) Remove(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ operIds := c.Param("operIds")
+ if operIds == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 处理字符转id数组后去重
+ ids := strings.Split(operIds, ",")
+ uniqueIDs := parse.RemoveDuplicates(ids)
+ if len(uniqueIDs) <= 0 {
+ c.JSON(200, result.Err(nil))
+ return
+ }
+ rows := s.SysLogOperateService.DeleteSysLogOperateByIds(uniqueIDs)
+ if rows > 0 {
+ msg := i18n.TTemplate(language, "app.common.deleteSuccess", map[string]any{"num": rows})
+ c.JSON(200, result.OkMsg(msg))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 操作日志清空
+//
+// DELETE /clean
+func (s *SysLogOperateController) Clean(c *gin.Context) {
+ err := s.SysLogOperateService.CleanSysLogOperate()
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+ c.JSON(200, result.Ok(nil))
+}
+
+// 导出操作日志
+//
+// POST /export
+func (s *SysLogOperateController) Export(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ // 查询结果,根据查询条件结果,单页最大值限制
+ querys := ctx.BodyJSONMap(c)
+ data := s.SysLogOperateService.SelectSysLogOperatePage(querys)
+ if data["total"].(int64) == 0 {
+ // 导出数据记录为空
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.exportEmpty")))
+ return
+ }
+ rows := data["rows"].([]model.SysLogOperate)
+
+ // 闭包函数处理多语言
+ converI18n := func(language string, arr *[]model.SysLogOperate) {
+ for i := range *arr {
+ (*arr)[i].Title = i18n.TKey(language, (*arr)[i].Title)
+ (*arr)[i].OperLocation = i18n.TKey(language, (*arr)[i].OperLocation)
+ }
+ }
+ converI18n(language, &rows)
+
+ // 导出文件名称
+ fileName := fmt.Sprintf("sys_log_operate_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli())
+ // 第一行表头标题
+ headerCells := map[string]string{
+ "A1": i18n.TKey(language, "log.operate.export.id"),
+ "B1": i18n.TKey(language, "log.operate.export.title"),
+ "C1": i18n.TKey(language, "log.operate.export.businessType"),
+ "D1": i18n.TKey(language, "log.operate.export.method"),
+ "E1": i18n.TKey(language, "log.operate.export.requestMethod"),
+ "F1": i18n.TKey(language, "log.operate.export.operatorType"),
+ "G1": i18n.TKey(language, "log.operate.export.operName"),
+ "H1": i18n.TKey(language, "log.operate.export.deptName"),
+ "I1": i18n.TKey(language, "log.operate.export.url"),
+ "J1": i18n.TKey(language, "log.operate.export.ip"),
+ "K1": i18n.TKey(language, "log.operate.export.location"),
+ "L1": i18n.TKey(language, "log.operate.export.param"),
+ "M1": i18n.TKey(language, "log.operate.export.msg"),
+ "N1": i18n.TKey(language, "log.operate.export.status"),
+ "O1": i18n.TKey(language, "log.operate.export.costTime"),
+ "P1": i18n.TKey(language, "log.operate.export.operTime"),
+ }
+ // 从第二行开始的数据
+ dataCells := make([]map[string]any, 0)
+ for i, row := range rows {
+ idx := strconv.Itoa(i + 2)
+ // 业务类型
+ businessType := ""
+ // 操作类别
+ operatorType := ""
+ // 状态
+ statusValue := i18n.TKey(language, "dictData.fail")
+ if row.Status == "1" {
+ statusValue = i18n.TKey(language, "dictData.success")
+ }
+ dataCells = append(dataCells, map[string]any{
+ "A" + idx: row.OperID,
+ "B" + idx: row.Title,
+ "C" + idx: businessType,
+ "D" + idx: row.Method,
+ "E" + idx: row.RequestMethod,
+ "F" + idx: operatorType,
+ "G" + idx: row.OperName,
+ "H" + idx: row.DeptName,
+ "I" + idx: row.OperURL,
+ "J" + idx: row.OperIP,
+ "K" + idx: row.OperLocation,
+ "L" + idx: row.OperParam,
+ "M" + idx: row.OperMsg,
+ "N" + idx: statusValue,
+ "O" + idx: row.CostTime,
+ "P" + idx: date.ParseDateToStr(row.OperTime, date.YYYY_MM_DD_HH_MM_SS),
+ })
+ }
+
+ // 导出数据表格
+ saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "")
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ c.FileAttachment(saveFilePath, fileName)
+}
diff --git a/src/modules/system/controller/sys_menu.go b/src/modules/system/controller/sys_menu.go
new file mode 100644
index 0000000..da823d2
--- /dev/null
+++ b/src/modules/system/controller/sys_menu.go
@@ -0,0 +1,364 @@
+package controller
+
+import (
+ "nms_nbi/src/framework/config"
+ "nms_nbi/src/framework/constants/common"
+ "nms_nbi/src/framework/constants/menu"
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/regular"
+ "nms_nbi/src/framework/vo"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/system/model"
+ "nms_nbi/src/modules/system/service"
+
+ "github.com/gin-gonic/gin"
+ "github.com/gin-gonic/gin/binding"
+)
+
+// 实例化控制层 SysMenuController 结构体
+var NewSysMenu = &SysMenuController{
+ sysMenuService: service.NewSysMenuImpl,
+}
+
+// 菜单信息
+//
+// PATH /system/menu
+type SysMenuController struct {
+ // 菜单服务
+ sysMenuService service.ISysMenu
+}
+
+// 菜单列表
+//
+// GET /list
+func (s *SysMenuController) List(c *gin.Context) {
+ query := model.SysMenu{}
+ if v, ok := c.GetQuery("menuName"); ok && v != "" {
+ query.MenuName = v
+ }
+ if v, ok := c.GetQuery("status"); ok && v != "" {
+ query.Status = v
+ }
+
+ userId := ctx.LoginUserToUserID(c)
+ if config.IsAdmin(userId) {
+ userId = "*"
+ }
+ data := s.sysMenuService.SelectMenuList(query, userId)
+
+ // 闭包函数处理多语言
+ language := ctx.AcceptLanguage(c)
+ var converI18n func(language string, arr *[]model.SysMenu)
+ converI18n = func(language string, arr *[]model.SysMenu) {
+ for i := range *arr {
+ (*arr)[i].MenuName = i18n.TKey(language, (*arr)[i].MenuName)
+ (*arr)[i].Remark = i18n.TKey(language, (*arr)[i].Remark)
+ if len((*arr)[i].Children) > 0 {
+ converI18n(language, &(*arr)[i].Children)
+ }
+ }
+ }
+ converI18n(language, &data)
+
+ c.JSON(200, result.OkData(data))
+}
+
+// 菜单信息
+//
+// GET /:menuId
+func (s *SysMenuController) Info(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ menuId := c.Param("menuId")
+ if menuId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ data := s.sysMenuService.SelectMenuById(menuId)
+ if data.MenuID == menuId {
+ // 处理多语言
+ data.MenuName = i18n.TKey(language, data.MenuName)
+ data.Remark = i18n.TKey(language, data.Remark)
+ c.JSON(200, result.OkData(data))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 菜单新增
+//
+// POST /
+func (s *SysMenuController) Add(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.SysMenu
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.MenuID != "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 目录和菜单检查地址唯一
+ if menu.TYPE_DIR == body.MenuType || menu.TYPE_MENU == body.MenuType {
+ uniqueNenuPath := s.sysMenuService.CheckUniqueMenuPath(body.Path, body.ParentID, "")
+ if !uniqueNenuPath {
+ // 菜单新增【%s】失败,菜单路由地址已存在
+ msg := i18n.TTemplate(language, "menu.errPathExists", map[string]any{"name": body.MenuName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+ }
+
+ // 检查名称唯一
+ uniqueNenuName := s.sysMenuService.CheckUniqueMenuName(body.MenuName, body.ParentID, "")
+ if !uniqueNenuName {
+ // 菜单新增【%s】失败,菜单名称已存在
+ msg := i18n.TTemplate(language, "menu.errNameExists", map[string]any{"name": body.MenuName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 外链菜单需要符合网站http(s)开头
+ if body.IsFrame == common.STATUS_NO && !regular.ValidHttp(body.Path) {
+ // 操作菜单【{name}】失败,非内部地址请以http(s)://开头
+ msg := i18n.TTemplate(language, "menu.errFramePath", map[string]any{"name": body.MenuName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ body.CreateBy = ctx.LoginUserToUserName(c)
+ insertId := s.sysMenuService.InsertMenu(body)
+ if insertId != "" {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 菜单修改
+//
+// PUT /
+func (s *SysMenuController) Edit(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.SysMenu
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.MenuID == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 上级菜单不能选自己
+ if body.MenuID == body.ParentID {
+ // 菜单修改【%s】失败,上级菜单不能选择自己
+ msg := i18n.TTemplate(language, "menu.errPathExists", map[string]any{"name": body.MenuName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 检查数据是否存在
+ menuInfo := s.sysMenuService.SelectMenuById(body.MenuID)
+ if menuInfo.MenuID != body.MenuID {
+ // 没有可访问菜单数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "menu.noData")))
+ return
+ }
+ // 父级ID不为0是要检查
+ if body.ParentID != "0" {
+ menuParent := s.sysMenuService.SelectMenuById(body.ParentID)
+ if menuParent.MenuID != body.ParentID {
+ // 没有可访问菜单数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "menu.noData")))
+ return
+ }
+ // 禁用菜单时检查父菜单是否使用
+ if body.Status == common.STATUS_YES && menuParent.Status == common.STATUS_NO {
+ // 上级菜单未启用!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "menu.errParentStatus")))
+ return
+ }
+ }
+
+ // 目录和菜单检查地址唯一
+ if menu.TYPE_DIR == body.MenuType || menu.TYPE_MENU == body.MenuType {
+ uniqueNenuPath := s.sysMenuService.CheckUniqueMenuPath(body.Path, body.ParentID, body.MenuID)
+ if !uniqueNenuPath {
+ // 菜单修改【%s】失败,菜单路由地址已存在
+ msg := i18n.TTemplate(language, "menu.errPathExists", map[string]any{"name": body.MenuName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+ }
+
+ // 检查名称唯一
+ uniqueNenuName := s.sysMenuService.CheckUniqueMenuName(body.MenuName, body.ParentID, body.MenuID)
+ if !uniqueNenuName {
+ // 菜单修改【%s】失败,菜单名称已存在
+ msg := i18n.TTemplate(language, "menu.errNameExists", map[string]any{"name": body.MenuName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 外链菜单需要符合网站http(s)开头
+ if body.IsFrame == common.STATUS_NO && !regular.ValidHttp(body.Path) {
+ // 菜单修改【%s】失败,非内部地址必须以http(s)://开头
+ msg := i18n.TTemplate(language, "menu.errFramePath", map[string]any{"name": body.MenuName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 禁用菜单时检查子菜单是否使用
+ if body.Status == common.STATUS_NO {
+ hasStatus := s.sysMenuService.HasChildByMenuIdAndStatus(body.MenuID, common.STATUS_YES)
+ if hasStatus > 0 {
+ // 操作菜单【%s】失败,存在使用子菜单数:%d
+ msg := i18n.TTemplate(language, "menu.errHasChildUse", map[string]any{"name": body.MenuName, "num": hasStatus})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+ }
+
+ // 多语言非原始值
+ i18nValue := i18n.TKey(language, menuInfo.MenuName)
+ if i18nValue != menuInfo.MenuName {
+ i18n.UpdateKeyValue(language, menuInfo.MenuName, body.MenuName)
+ body.MenuName = menuInfo.MenuName
+ }
+ // 多语言非原始值
+ i18nValue2 := i18n.TKey(language, menuInfo.Remark)
+ if i18nValue2 != menuInfo.Remark {
+ i18n.UpdateKeyValue(language, menuInfo.Remark, body.Remark)
+ body.Remark = menuInfo.Remark
+ }
+
+ body.UpdateBy = ctx.LoginUserToUserName(c)
+ rows := s.sysMenuService.UpdateMenu(body)
+ if rows > 0 {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 菜单删除
+//
+// DELETE /:menuId
+func (s *SysMenuController) Remove(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ menuId := c.Param("menuId")
+ if menuId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查数据是否存在
+ menu := s.sysMenuService.SelectMenuById(menuId)
+ if menu.MenuID != menuId {
+ // 没有可访问菜单数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "menu.noData")))
+ return
+ }
+
+ // 检查是否存在子菜单
+ hasChild := s.sysMenuService.HasChildByMenuIdAndStatus(menuId, "")
+ if hasChild > 0 {
+ // 不允许删除,存在子菜单数:%d
+ msg := i18n.TTemplate(language, "menu.errHasChildUse", map[string]any{"name": menu.MenuName, "num": hasChild})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 检查是否分配给角色
+ existRole := s.sysMenuService.CheckMenuExistRole(menuId)
+ if existRole > 0 {
+ // 不允许删除,菜单已分配给角色数:%d
+ msg := i18n.TTemplate(language, "menu.errHasRoleUse", map[string]any{"name": menu.MenuName, "num": existRole})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ rows := s.sysMenuService.DeleteMenuById(menuId)
+ if rows > 0 {
+ msg := i18n.TTemplate(language, "app.common.deleteSuccess", map[string]any{"num": rows})
+ c.JSON(200, result.OkMsg(msg))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 菜单树结构列表
+//
+// GET /treeSelect
+func (s *SysMenuController) TreeSelect(c *gin.Context) {
+ query := model.SysMenu{}
+ if v, ok := c.GetQuery("menuName"); ok && v != "" {
+ query.MenuName = v
+ }
+ if v, ok := c.GetQuery("status"); ok && v != "" {
+ query.Status = v
+ }
+
+ userId := ctx.LoginUserToUserID(c)
+ if config.IsAdmin(userId) {
+ userId = "*"
+ }
+ data := s.sysMenuService.SelectMenuTreeSelectByUserId(query, userId)
+
+ // 闭包函数处理多语言
+ language := ctx.AcceptLanguage(c)
+ var converI18n func(language string, arr *[]vo.TreeSelect)
+ converI18n = func(language string, arr *[]vo.TreeSelect) {
+ for i := range *arr {
+ (*arr)[i].Label = i18n.TKey(language, (*arr)[i].Label)
+ if len((*arr)[i].Children) > 0 {
+ converI18n(language, &(*arr)[i].Children)
+ }
+ }
+ }
+ converI18n(language, &data)
+
+ c.JSON(200, result.OkData(data))
+
+}
+
+// 菜单树结构列表(指定角色)
+//
+// GET /roleMenuTreeSelect/:roleId
+func (s *SysMenuController) RoleMenuTreeSelect(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ roleId := c.Param("roleId")
+ if roleId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ query := model.SysMenu{}
+ if v, ok := c.GetQuery("menuName"); ok && v != "" {
+ query.MenuName = v
+ }
+ if v, ok := c.GetQuery("status"); ok && v != "" {
+ query.Status = v
+ }
+
+ userId := ctx.LoginUserToUserID(c)
+ if config.IsAdmin(userId) {
+ userId = "*"
+ }
+ menuTreeSelect := s.sysMenuService.SelectMenuTreeSelectByUserId(query, userId)
+ checkedKeys := s.sysMenuService.SelectMenuListByRoleId(roleId)
+
+ // 闭包函数处理多语言
+ var converI18n func(language string, arr *[]vo.TreeSelect)
+ converI18n = func(language string, arr *[]vo.TreeSelect) {
+ for i := range *arr {
+ (*arr)[i].Label = i18n.TKey(language, (*arr)[i].Label)
+ if len((*arr)[i].Children) > 0 {
+ converI18n(language, &(*arr)[i].Children)
+ }
+ }
+ }
+ converI18n(language, &menuTreeSelect)
+
+ c.JSON(200, result.OkData(map[string]any{
+ "menus": menuTreeSelect,
+ "checkedKeys": checkedKeys,
+ }))
+}
diff --git a/src/modules/system/controller/sys_post.go b/src/modules/system/controller/sys_post.go
new file mode 100644
index 0000000..30a7fda
--- /dev/null
+++ b/src/modules/system/controller/sys_post.go
@@ -0,0 +1,260 @@
+package controller
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/file"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/system/model"
+ "nms_nbi/src/modules/system/service"
+
+ "github.com/gin-gonic/gin"
+ "github.com/gin-gonic/gin/binding"
+)
+
+// 实例化控制层 SysPostController 结构体
+var NewSysPost = &SysPostController{
+ sysPostService: service.NewSysPostImpl,
+}
+
+// 岗位信息
+//
+// PATH /system/post
+type SysPostController struct {
+ // 岗位服务
+ sysPostService service.ISysPost
+}
+
+// 岗位列表
+//
+// GET /list
+func (s *SysPostController) List(c *gin.Context) {
+ querys := ctx.QueryMap(c)
+ data := s.sysPostService.SelectPostPage(querys)
+
+ rows := data["rows"].([]model.SysPost)
+ // 闭包函数处理多语言
+ language := ctx.AcceptLanguage(c)
+ converI18n := func(language string, arr *[]model.SysPost) {
+ for i := range *arr {
+ (*arr)[i].PostName = i18n.TKey(language, (*arr)[i].PostName)
+ (*arr)[i].Remark = i18n.TKey(language, (*arr)[i].Remark)
+ }
+ }
+ converI18n(language, &rows)
+
+ c.JSON(200, result.Ok(data))
+}
+
+// 岗位信息
+//
+// GET /:postId
+func (s *SysPostController) Info(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ postId := c.Param("postId")
+ if postId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ data := s.sysPostService.SelectPostById(postId)
+ if data.PostID == postId {
+ // 处理多语言
+ data.PostName = i18n.TKey(language, data.PostName)
+ data.Remark = i18n.TKey(language, data.Remark)
+ c.JSON(200, result.OkData(data))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 岗位新增
+//
+// POST /
+func (s *SysPostController) Add(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.SysPost
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.PostID != "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查名称唯一
+ uniqueuPostName := s.sysPostService.CheckUniquePostName(body.PostName, "")
+ if !uniqueuPostName {
+ // 岗位新增【%s】失败,岗位名称已存在
+ msg := i18n.TTemplate(language, "post.errNameExists", map[string]any{"name": body.PostName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 检查编码属性值唯一
+ uniquePostCode := s.sysPostService.CheckUniquePostCode(body.PostCode, "")
+ if !uniquePostCode {
+ // 岗位新增【%s】失败,岗位编码已存在
+ msg := i18n.TTemplate(language, "post.errCodeExists", map[string]any{"name": body.PostCode})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ body.CreateBy = ctx.LoginUserToUserName(c)
+ insertId := s.sysPostService.InsertPost(body)
+ if insertId != "" {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 岗位修改
+//
+// PUT /
+func (s *SysPostController) Edit(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.SysPost
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.PostID == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查是否存在
+ postInfo := s.sysPostService.SelectPostById(body.PostID)
+ if postInfo.PostID != body.PostID {
+ // 没有可访问岗位数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "post.noData")))
+ return
+ }
+
+ // 检查名称唯一
+ uniqueuPostName := s.sysPostService.CheckUniquePostName(body.PostName, body.PostID)
+ if !uniqueuPostName {
+ // 岗位修改【%s】失败,岗位名称已存在
+ msg := i18n.TTemplate(language, "post.errNameExists", map[string]any{"name": body.PostName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 检查编码属性值唯一
+ uniquePostCode := s.sysPostService.CheckUniquePostCode(body.PostCode, body.PostID)
+ if !uniquePostCode {
+ // 岗位修改【%s】失败,岗位编码已存在
+ msg := i18n.TTemplate(language, "post.errCodeExists", map[string]any{"name": body.PostCode})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 多语言非原始值
+ i18nValue := i18n.TKey(language, postInfo.PostName)
+ if i18nValue != postInfo.PostName {
+ i18n.UpdateKeyValue(language, postInfo.PostName, body.PostName)
+ body.PostName = postInfo.PostName
+ }
+ // 多语言非原始值
+ i18nValue2 := i18n.TKey(language, postInfo.Remark)
+ if i18nValue2 != postInfo.Remark {
+ i18n.UpdateKeyValue(language, postInfo.Remark, body.Remark)
+ body.Remark = postInfo.Remark
+ }
+
+ body.UpdateBy = ctx.LoginUserToUserName(c)
+ rows := s.sysPostService.UpdatePost(body)
+ if rows > 0 {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 岗位删除
+//
+// DELETE /:postIds
+func (s *SysPostController) Remove(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ postIds := c.Param("postIds")
+ if postIds == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ // 处理字符转id数组后去重
+ ids := strings.Split(postIds, ",")
+ uniqueIDs := parse.RemoveDuplicates(ids)
+ if len(uniqueIDs) <= 0 {
+ c.JSON(200, result.Err(nil))
+ return
+ }
+ rows, err := s.sysPostService.DeletePostByIds(uniqueIDs)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+ msg := i18n.TTemplate(language, "app.common.deleteSuccess", map[string]any{"num": rows})
+ c.JSON(200, result.OkMsg(msg))
+}
+
+// 导出岗位信息
+//
+// POST /export
+func (s *SysPostController) Export(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ // 查询结果,根据查询条件结果,单页最大值限制
+ querys := ctx.BodyJSONMap(c)
+ data := s.sysPostService.SelectPostPage(querys)
+ if data["total"].(int64) == 0 {
+ // 导出数据记录为空
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.exportEmpty")))
+ return
+ }
+ rows := data["rows"].([]model.SysPost)
+
+ // 闭包函数处理多语言
+ converI18n := func(language string, arr *[]model.SysPost) {
+ for i := range *arr {
+ (*arr)[i].PostName = i18n.TKey(language, (*arr)[i].PostName)
+ (*arr)[i].Remark = i18n.TKey(language, (*arr)[i].Remark)
+ }
+ }
+ converI18n(language, &rows)
+
+ // 导出文件名称
+ fileName := fmt.Sprintf("post_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli())
+ // 第一行表头标题
+ headerCells := map[string]string{
+ "A1": i18n.TKey(language, "post.export.id"),
+ "B1": i18n.TKey(language, "post.export.code"),
+ "C1": i18n.TKey(language, "post.export.name"),
+ "D1": i18n.TKey(language, "post.export.sort"),
+ "E1": i18n.TKey(language, "post.export.status"),
+ }
+ // 从第二行开始的数据
+ dataCells := make([]map[string]any, 0)
+ for i, row := range rows {
+ idx := strconv.Itoa(i + 2)
+ statusValue := i18n.TKey(language, "dictData.disable")
+ if row.Status == "1" {
+ statusValue = i18n.TKey(language, "dictData.normal")
+ }
+ dataCells = append(dataCells, map[string]any{
+ "A" + idx: row.PostID,
+ "B" + idx: row.PostCode,
+ "C" + idx: row.PostName,
+ "D" + idx: row.PostSort,
+ "E" + idx: statusValue,
+ })
+ }
+
+ // 导出数据表格
+ saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "")
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ c.FileAttachment(saveFilePath, fileName)
+}
diff --git a/src/modules/system/controller/sys_profile.go b/src/modules/system/controller/sys_profile.go
new file mode 100644
index 0000000..6e72625
--- /dev/null
+++ b/src/modules/system/controller/sys_profile.go
@@ -0,0 +1,294 @@
+package controller
+
+import (
+ "nms_nbi/src/framework/config"
+ "nms_nbi/src/framework/constants/admin"
+ "nms_nbi/src/framework/constants/uploadsubpath"
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/crypto"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/file"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/regular"
+ "nms_nbi/src/framework/utils/token"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/system/model"
+ "nms_nbi/src/modules/system/service"
+
+ "github.com/gin-gonic/gin"
+ "github.com/gin-gonic/gin/binding"
+)
+
+// 实例化控制层 SysProfileController 结构体
+var NewSysProfile = &SysProfileController{
+ sysUserService: service.NewSysUserImpl,
+ sysRoleService: service.NewSysRoleImpl,
+ sysPostService: service.NewSysPostImpl,
+ sysMenuService: service.NewSysMenuImpl,
+}
+
+// 个人信息
+//
+// PATH /system/user/profile
+type SysProfileController struct {
+ // 用户服务
+ sysUserService service.ISysUser
+ // 角色服务
+ sysRoleService service.ISysRole
+ // 岗位服务
+ sysPostService service.ISysPost
+ // 菜单服务
+ sysMenuService service.ISysMenu
+}
+
+// 个人信息
+//
+// GET /
+func (s *SysProfileController) Info(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ loginUser, err := ctx.LoginUser(c)
+ if err != nil {
+ c.JSON(401, result.CodeMsg(401, i18n.TKey(language, err.Error())))
+ return
+ }
+
+ // 查询用户所属角色组
+ roleGroup := []string{}
+ roles := s.sysRoleService.SelectRoleListByUserId(loginUser.UserID)
+ for _, role := range roles {
+ roleGroup = append(roleGroup, i18n.TKey(language, role.RoleName))
+ }
+ isAdmin := config.IsAdmin(loginUser.UserID)
+ if isAdmin {
+ roleGroup = append(roleGroup, i18n.TKey(language, "role.admin"))
+ }
+
+ // 查询用户所属岗位组
+ postGroup := []string{}
+ posts := s.sysPostService.SelectPostListByUserId(loginUser.UserID)
+ for _, post := range posts {
+ postGroup = append(postGroup, i18n.TKey(language, post.PostName))
+ }
+
+ loginUser.User.NickName = i18n.TKey(language, loginUser.User.NickName)
+ loginUser.User.Remark = i18n.TKey(language, loginUser.User.Remark)
+ loginUser.User.Dept.DeptName = i18n.TKey(language, loginUser.User.Dept.DeptName)
+ for ri := range loginUser.User.Roles {
+ loginUser.User.Roles[ri].RoleName = i18n.TKey(language, loginUser.User.Roles[ri].RoleName)
+ }
+ c.JSON(200, result.OkData(map[string]any{
+ "user": loginUser.User,
+ "roleGroup": parse.RemoveDuplicates(roleGroup),
+ "postGroup": parse.RemoveDuplicates(postGroup),
+ }))
+}
+
+// 个人信息修改
+//
+// PUT /
+func (s *SysProfileController) UpdateProfile(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body struct {
+ // 昵称
+ NickName string `json:"nickName" binding:"required"`
+ // 性别
+ Sex string `json:"sex" binding:"required"`
+ // 手机号
+ PhoneNumber string `json:"phonenumber"`
+ // 邮箱
+ Email string `json:"email"`
+ }
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.Sex == "" {
+ 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
+ }
+ userId := loginUser.UserID
+ userName := loginUser.User.UserName
+
+ // 检查手机号码格式并判断是否唯一
+ if body.PhoneNumber != "" {
+ if regular.ValidMobile(body.PhoneNumber) {
+ uniquePhone := s.sysUserService.CheckUniquePhone(body.PhoneNumber, userId)
+ if !uniquePhone {
+ // 修改用户【%s】失败,手机号码已存在
+ msg := i18n.TTemplate(language, "user.errPhoneExists", map[string]any{"name": userName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+ } else {
+ // 修改用户【%s】失败,手机号码格式错误
+ msg := i18n.TTemplate(language, "user.errPhoneFormat", map[string]any{"name": userName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+ }
+
+ // 检查邮箱格式并判断是否唯一
+ if body.Email != "" {
+ if regular.ValidEmail(body.Email) {
+ uniqueEmail := s.sysUserService.CheckUniqueEmail(body.Email, userId)
+ if !uniqueEmail {
+ // 修改用户【%s】失败,邮箱已存在
+ msg := i18n.TTemplate(language, "user.errEmailExists", map[string]any{"name": userName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+ } else {
+ // 修改用户【%s】失败,邮箱格式错误
+ msg := i18n.TTemplate(language, "user.errEmailFormat", map[string]any{"name": userName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+ }
+
+ // 用户基本资料
+ sysUser := model.SysUser{
+ UserID: userId,
+ UpdateBy: userName,
+ NickName: body.NickName,
+ PhoneNumber: body.PhoneNumber,
+ Email: body.Email,
+ Sex: body.Sex,
+ }
+ rows := s.sysUserService.UpdateUser(sysUser)
+ if rows > 0 {
+ // 更新缓存用户信息
+ loginUser.User = s.sysUserService.SelectUserByUserName(userName)
+ // 用户权限组标识
+ isAdmin := config.IsAdmin(sysUser.UserID)
+ if isAdmin {
+ loginUser.Permissions = []string{admin.PERMISSION}
+ } else {
+ perms := s.sysMenuService.SelectMenuPermsByUserId(sysUser.UserID)
+ loginUser.Permissions = parse.RemoveDuplicates(perms)
+ }
+ // 刷新令牌信息
+ token.Cache(&loginUser)
+
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 个人重置密码
+//
+// PUT /updatePwd
+func (s *SysProfileController) UpdatePwd(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body struct {
+ // 旧密码
+ OldPassword string `json:"oldPassword" binding:"required"`
+ // 新密码
+ NewPassword string `json:"newPassword" binding:"required"`
+ }
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if 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
+ }
+ userId := loginUser.UserID
+ userName := loginUser.User.UserName
+
+ // 查询当前登录用户信息得到密码值
+ user := s.sysUserService.SelectUserById(userId)
+ if user.UserID != userId {
+ // 没有可访问用户数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "user.noData")))
+ return
+ }
+
+ // 检查匹配用户密码
+ oldCompare := crypto.BcryptCompare(body.OldPassword, user.Password)
+ if !oldCompare {
+ // 修改密码失败,旧密码错误
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "user.errPasswdOld")))
+ return
+ }
+ newCompare := crypto.BcryptCompare(body.NewPassword, user.Password)
+ if newCompare {
+ // 新密码不能与旧密码相同
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "user.errPasswdEqOld")))
+ return
+ }
+
+ // 修改新密码
+ sysUser := model.SysUser{
+ UserID: userId,
+ UpdateBy: userName,
+ Password: body.NewPassword,
+ }
+ rows := s.sysUserService.UpdateUser(sysUser)
+ if rows > 0 {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 个人头像上传
+//
+// POST /avatar
+func (s *SysProfileController) Avatar(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ formFile, err := c.FormFile("file")
+ if err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 上传文件转存
+ filePath, err := file.TransferUploadFile(formFile, uploadsubpath.AVATART, []string{".jpg", ".jpeg", ".png"})
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 登录用户信息
+ loginUser, err := ctx.LoginUser(c)
+ if err != nil {
+ c.JSON(401, result.CodeMsg(401, i18n.TKey(language, err.Error())))
+ return
+ }
+
+ // 更新头像地址
+ sysUser := model.SysUser{
+ UserID: loginUser.UserID,
+ UpdateBy: loginUser.User.UserName,
+ Avatar: filePath,
+ }
+ rows := s.sysUserService.UpdateUser(sysUser)
+ if rows > 0 {
+ // 更新缓存用户信息
+ loginUser.User = s.sysUserService.SelectUserByUserName(loginUser.User.UserName)
+ // 用户权限组标识
+ isAdmin := config.IsAdmin(sysUser.UserID)
+ if isAdmin {
+ loginUser.Permissions = []string{admin.PERMISSION}
+ } else {
+ perms := s.sysMenuService.SelectMenuPermsByUserId(sysUser.UserID)
+ loginUser.Permissions = parse.RemoveDuplicates(perms)
+ }
+ // 刷新令牌信息
+ token.Cache(&loginUser)
+
+ c.JSON(200, result.OkData(filePath))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
diff --git a/src/modules/system/controller/sys_role.go b/src/modules/system/controller/sys_role.go
new file mode 100644
index 0000000..283dca4
--- /dev/null
+++ b/src/modules/system/controller/sys_role.go
@@ -0,0 +1,477 @@
+package controller
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/constants/admin"
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/file"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/system/model"
+ "nms_nbi/src/modules/system/service"
+
+ "github.com/gin-gonic/gin"
+ "github.com/gin-gonic/gin/binding"
+)
+
+// 实例化控制层 SysRoleController 结构体
+var NewSysRole = &SysRoleController{
+ sysRoleService: service.NewSysRoleImpl,
+ sysUserService: service.NewSysUserImpl,
+ sysDictDataService: service.NewSysDictDataImpl,
+}
+
+// 角色信息
+//
+// PATH /system/role
+type SysRoleController struct {
+ // 角色服务
+ sysRoleService service.ISysRole
+ // 用户服务
+ sysUserService service.ISysUser
+ // 字典数据服务
+ sysDictDataService service.ISysDictData
+}
+
+// 角色列表
+//
+// GET /list
+func (s *SysRoleController) List(c *gin.Context) {
+ querys := ctx.QueryMap(c)
+ dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "")
+ data := s.sysRoleService.SelectRolePage(querys, dataScopeSQL)
+
+ rows := data["rows"].([]model.SysRole)
+ // 闭包函数处理多语言
+ language := ctx.AcceptLanguage(c)
+ converI18n := func(language string, arr *[]model.SysRole) {
+ for i := range *arr {
+ (*arr)[i].RoleName = i18n.TKey(language, (*arr)[i].RoleName)
+ (*arr)[i].Remark = i18n.TKey(language, (*arr)[i].Remark)
+ }
+ }
+ converI18n(language, &rows)
+
+ c.JSON(200, result.Ok(data))
+}
+
+// 角色信息详情
+//
+// GET /:roleId
+func (s *SysRoleController) Info(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ roleId := c.Param("roleId")
+ if roleId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ data := s.sysRoleService.SelectRoleById(roleId)
+ if data.RoleID == roleId {
+ data.RoleName = i18n.TKey(language, data.RoleName)
+ data.Remark = i18n.TKey(language, data.Remark)
+ c.JSON(200, result.OkData(data))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 角色信息新增
+//
+// POST /
+func (s *SysRoleController) Add(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.SysRole
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.RoleID != "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 判断角色名称是否唯一
+ uniqueRoleName := s.sysRoleService.CheckUniqueRoleName(body.RoleName, "")
+ if !uniqueRoleName {
+ // 角色新增【%s】失败,角色名称已存在
+ msg := i18n.TTemplate(language, "role.errNameExists", map[string]any{"name": body.RoleName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 判断角色键值是否唯一
+ uniqueRoleKey := s.sysRoleService.CheckUniqueRoleKey(body.RoleKey, "")
+ if !uniqueRoleKey {
+ // 角色新增【%s】失败,角色键值已存在
+ msg := i18n.TTemplate(language, "role.errKeyExists", map[string]any{"name": body.RoleName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ body.CreateBy = ctx.LoginUserToUserName(c)
+ insertId := s.sysRoleService.InsertRole(body)
+ if insertId != "" {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 角色信息修改
+//
+// PUT /
+func (s *SysRoleController) Edit(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.SysRole
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.RoleID == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查是否管理员角色
+ if body.RoleID == admin.ROLE_ID {
+ // 不允许操作管理员角色
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.errOperateRole")))
+ return
+ }
+
+ // 检查是否存在
+ roleInfo := s.sysRoleService.SelectRoleById(body.RoleID)
+ if roleInfo.RoleID != body.RoleID {
+ // 没有可访问角色数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "role.noData")))
+ return
+ }
+
+ // 判断角色名称是否唯一
+ uniqueRoleName := s.sysRoleService.CheckUniqueRoleName(body.RoleName, body.RoleID)
+ if !uniqueRoleName {
+ // 操作角色【%s】失败,角色名称已存在
+ // msg := fmt.Sprintf("Character modification [%s] failed, character name already exists", body.RoleName)
+ msg := i18n.TTemplate(language, "role.errNameExists", map[string]any{"name": body.RoleName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 判断角色键值是否唯一
+ uniqueRoleKey := s.sysRoleService.CheckUniqueRoleKey(body.RoleKey, body.RoleID)
+ if !uniqueRoleKey {
+ // 角色修改【%s】失败,角色键值已存在
+ // msg := fmt.Sprintf("Character modification [%s] failed, character key already exists", body.RoleName)
+ msg := i18n.TTemplate(language, "role.errKeyExists", map[string]any{"name": body.RoleName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 多语言非原始值
+ i18nValue := i18n.TKey(language, roleInfo.RoleName)
+ if i18nValue != roleInfo.RoleName {
+ i18n.UpdateKeyValue(language, roleInfo.RoleName, body.RoleName)
+ body.RoleName = roleInfo.RoleName
+ }
+ // 多语言非原始值
+ i18nValue2 := i18n.TKey(language, roleInfo.Remark)
+ if i18nValue2 != roleInfo.Remark {
+ i18n.UpdateKeyValue(language, roleInfo.Remark, body.Remark)
+ body.Remark = roleInfo.Remark
+ }
+
+ body.UpdateBy = ctx.LoginUserToUserName(c)
+ rows := s.sysRoleService.UpdateRole(body)
+ if rows > 0 {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 角色信息删除
+//
+// DELETE /:roleIds
+func (s *SysRoleController) Remove(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ roleIds := c.Param("roleIds")
+ if roleIds == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ // 处理字符转id数组后去重
+ ids := strings.Split(roleIds, ",")
+ uniqueIDs := parse.RemoveDuplicates(ids)
+ if len(uniqueIDs) <= 0 {
+ c.JSON(200, result.Err(nil))
+ return
+ }
+ // 检查是否管理员角色
+ for _, id := range uniqueIDs {
+ if id == admin.ROLE_ID {
+ // 不允许操作管理员角色
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.errOperateRole")))
+ return
+ }
+ }
+ rows, err := s.sysRoleService.DeleteRoleByIds(uniqueIDs)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+ msg := i18n.TTemplate(language, "app.common.deleteSuccess", map[string]any{"num": rows})
+ c.JSON(200, result.OkMsg(msg))
+}
+
+// 角色状态变更
+//
+// PUT /changeStatus
+func (s *SysRoleController) Status(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body struct {
+ // 角色ID
+ RoleID string `json:"roleId" binding:"required"`
+ // 状态
+ Status string `json:"status" binding:"required"`
+ }
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查是否管理员角色
+ if body.RoleID == admin.ROLE_ID {
+ // 不允许操作管理员角色
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.errOperateRole")))
+ return
+ }
+
+ // 检查是否存在
+ role := s.sysRoleService.SelectRoleById(body.RoleID)
+ if role.RoleID != body.RoleID {
+ // 没有可访问角色数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "role.noData")))
+ return
+ }
+
+ // 与旧值相等不变更
+ if role.Status == body.Status {
+ // 变更状态与旧值相等!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "role.statusEq")))
+ return
+ }
+
+ // 更新状态不刷新缓存
+ userName := ctx.LoginUserToUserName(c)
+ SysRoleController := model.SysRole{
+ RoleID: body.RoleID,
+ Status: body.Status,
+ UpdateBy: userName,
+ }
+ rows := s.sysRoleService.UpdateRole(SysRoleController)
+ if rows > 0 {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 角色数据权限修改
+//
+// PUT /dataScope
+func (s *SysRoleController) DataScope(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body struct {
+ // 角色ID
+ RoleID string `json:"roleId"`
+ // 部门组(数据权限)
+ DeptIds []string `json:"deptIds"`
+ // 数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限)
+ DataScope string `json:"dataScope"`
+ // 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示)
+ DeptCheckStrictly string `json:"deptCheckStrictly"`
+ }
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查是否管理员角色
+ if body.RoleID == admin.ROLE_ID {
+ // 不允许操作管理员角色
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.errOperateRole")))
+ return
+ }
+
+ // 检查是否存在
+ role := s.sysRoleService.SelectRoleById(body.RoleID)
+ if role.RoleID != body.RoleID {
+ // 没有可访问角色数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "role.noData")))
+ return
+ }
+
+ // 更新数据权限
+ userName := ctx.LoginUserToUserName(c)
+ SysRoleController := model.SysRole{
+ RoleID: body.RoleID,
+ DeptIds: body.DeptIds,
+ DataScope: body.DataScope,
+ DeptCheckStrictly: body.DeptCheckStrictly,
+ UpdateBy: userName,
+ }
+ rows := s.sysRoleService.AuthDataScope(SysRoleController)
+ if rows > 0 {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 角色分配用户列表
+//
+// GET /authUser/allocatedList
+func (s *SysRoleController) AuthUserAllocatedList(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ querys := ctx.QueryMap(c)
+ roleId, ok := querys["roleId"]
+ if !ok || roleId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查是否存在
+ role := s.sysRoleService.SelectRoleById(roleId.(string))
+ if role.RoleID != roleId {
+ // 没有可访问角色数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "role.noData")))
+ return
+ }
+
+ dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "u")
+ data := s.sysUserService.SelectAllocatedPage(querys, dataScopeSQL)
+ c.JSON(200, result.Ok(data))
+}
+
+// 角色分配选择授权
+//
+// PUT /authUser/checked
+func (s *SysRoleController) AuthUserChecked(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body struct {
+ // 角色ID
+ RoleID string `json:"roleId" binding:"required"`
+ // 用户ID组
+ UserIDs string `json:"userIds" binding:"required"`
+ // 选择操作 添加true 取消false
+ Checked bool `json:"checked"`
+ }
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 处理字符转id数组后去重
+ ids := strings.Split(body.UserIDs, ",")
+ uniqueIDs := parse.RemoveDuplicates(ids)
+ if len(uniqueIDs) <= 0 {
+ c.JSON(200, result.Err(nil))
+ return
+ }
+
+ // 检查是否存在
+ role := s.sysRoleService.SelectRoleById(body.RoleID)
+ if role.RoleID != body.RoleID {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "role.noData")))
+ return
+ }
+
+ var rows int64
+ if body.Checked {
+ rows = s.sysRoleService.InsertAuthUsers(body.RoleID, uniqueIDs)
+ } else {
+ rows = s.sysRoleService.DeleteAuthUsers(body.RoleID, uniqueIDs)
+ }
+ if rows > 0 {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 导出角色信息
+//
+// POST /export
+func (s *SysRoleController) Export(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ // 查询结果,根据查询条件结果,单页最大值限制
+ querys := ctx.BodyJSONMap(c)
+ dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "")
+ data := s.sysRoleService.SelectRolePage(querys, dataScopeSQL)
+ if data["total"].(int64) == 0 {
+ // 导出数据记录为空
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.exportEmpty")))
+ return
+ }
+ rows := data["rows"].([]model.SysRole)
+
+ // 闭包函数处理多语言
+ converI18n := func(language string, arr *[]model.SysRole) {
+ for i := range *arr {
+ (*arr)[i].RoleName = i18n.TKey(language, (*arr)[i].RoleName)
+ (*arr)[i].Remark = i18n.TKey(language, (*arr)[i].Remark)
+ }
+ }
+ converI18n(language, &rows)
+
+ // 导出文件名称
+ fileName := fmt.Sprintf("role_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli())
+ // 第一行表头标题
+ headerCells := map[string]string{
+ "A1": i18n.TKey(language, "role.export.id"),
+ "B1": i18n.TKey(language, "role.export.name"),
+ "C1": i18n.TKey(language, "role.export.key"),
+ "D1": i18n.TKey(language, "role.export.sort"),
+ "E1": i18n.TKey(language, "role.export.dataScope"),
+ "F1": i18n.TKey(language, "role.export.status"),
+ }
+ // 读取系统角色数据范围字典数据
+ dictSysRoleDatascope := s.sysDictDataService.SelectDictDataByType("sys_role_datascope")
+ // 从第二行开始的数据
+ dataCells := make([]map[string]any, 0)
+ for i, row := range rows {
+ idx := strconv.Itoa(i + 2)
+ // 数据范围
+ dataScope := row.DataScope
+ for _, v := range dictSysRoleDatascope {
+ if row.DataScope == v.DictValue {
+ dataScope = i18n.TKey(language, v.DictLabel)
+ break
+ }
+ }
+ // 角色状态
+ statusValue := i18n.TKey(language, "dictData.disable")
+ if row.Status == "1" {
+ statusValue = i18n.TKey(language, "dictData.normal")
+ }
+ dataCells = append(dataCells, map[string]any{
+ "A" + idx: row.RoleID,
+ "B" + idx: row.RoleName,
+ "C" + idx: row.RoleKey,
+ "D" + idx: row.RoleSort,
+ "E" + idx: dataScope,
+ "F" + idx: statusValue,
+ })
+ }
+
+ // 导出数据表格
+ saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "")
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ c.FileAttachment(saveFilePath, fileName)
+}
diff --git a/src/modules/system/controller/sys_user.go b/src/modules/system/controller/sys_user.go
new file mode 100644
index 0000000..a8493ed
--- /dev/null
+++ b/src/modules/system/controller/sys_user.go
@@ -0,0 +1,786 @@
+package controller
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/config"
+ "nms_nbi/src/framework/constants/admin"
+ "nms_nbi/src/framework/constants/common"
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/date"
+ "nms_nbi/src/framework/utils/file"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/regular"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/system/model"
+ "nms_nbi/src/modules/system/service"
+
+ "github.com/gin-gonic/gin"
+ "github.com/gin-gonic/gin/binding"
+)
+
+// 实例化控制层 SysUserController 结构体
+var NewSysUser = &SysUserController{
+ sysUserService: service.NewSysUserImpl,
+ sysRoleService: service.NewSysRoleImpl,
+ sysPostService: service.NewSysPostImpl,
+ sysDictDataService: service.NewSysDictDataImpl,
+ sysConfigService: service.NewSysConfigImpl,
+}
+
+// 用户信息
+//
+// PATH /system/user
+type SysUserController struct {
+ // 用户服务
+ sysUserService service.ISysUser
+ // 角色服务
+ sysRoleService service.ISysRole
+ // 岗位服务
+ sysPostService service.ISysPost
+ // 字典数据服务
+ sysDictDataService service.ISysDictData
+ // 参数配置服务
+ sysConfigService service.ISysConfig
+}
+
+// 用户信息列表
+//
+// GET /list
+func (s *SysUserController) List(c *gin.Context) {
+ querys := ctx.QueryMap(c)
+ dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "u")
+ data := s.sysUserService.SelectUserPage(querys, dataScopeSQL)
+
+ rows := data["rows"].([]model.SysUser)
+ // 闭包函数处理多语言
+ language := ctx.AcceptLanguage(c)
+ converI18n := func(language string, arr *[]model.SysUser) {
+ for i := range *arr {
+ (*arr)[i].NickName = i18n.TKey(language, (*arr)[i].NickName)
+ (*arr)[i].Remark = i18n.TKey(language, (*arr)[i].Remark)
+ (*arr)[i].Dept.DeptName = i18n.TKey(language, (*arr)[i].Dept.DeptName)
+ for ri := range (*arr)[i].Roles {
+ (*arr)[i].Roles[ri].RoleName = i18n.TKey(language, (*arr)[i].Roles[ri].RoleName)
+ }
+ }
+ }
+ converI18n(language, &rows)
+
+ c.JSON(200, result.Ok(data))
+}
+
+// 用户信息详情
+//
+// GET /:userId
+func (s *SysUserController) Info(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ userId := c.Param("userId")
+ if userId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 查询系统角色列表
+ dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "")
+ roles := s.sysRoleService.SelectRoleList(model.SysRole{}, dataScopeSQL)
+
+ // 不是系统指定管理员需要排除其角色
+ if !config.IsAdmin(userId) {
+ rolesFilter := make([]model.SysRole, 0)
+ for _, r := range roles {
+ if r.RoleID != admin.ROLE_ID {
+ rolesFilter = append(rolesFilter, r)
+ }
+ }
+ roles = rolesFilter
+ }
+
+ // 闭包函数处理多语言
+ converI18nRoles := func(language string, arr *[]model.SysRole) {
+ for i := range *arr {
+ (*arr)[i].RoleName = i18n.TKey(language, (*arr)[i].RoleName)
+ (*arr)[i].Remark = i18n.TKey(language, (*arr)[i].Remark)
+ }
+ }
+ converI18nRoles(language, &roles)
+
+ // 查询系统岗位列表
+ posts := s.sysPostService.SelectPostList(model.SysPost{})
+
+ // 闭包函数处理多语言
+ converI18nPosts := func(language string, arr *[]model.SysPost) {
+ for i := range *arr {
+ (*arr)[i].PostName = i18n.TKey(language, (*arr)[i].PostName)
+ (*arr)[i].Remark = i18n.TKey(language, (*arr)[i].Remark)
+ }
+ }
+ converI18nPosts(language, &posts)
+
+ // 新增用户时,用户ID为0
+ if userId == "0" {
+ c.JSON(200, result.OkData(map[string]any{
+ "user": map[string]any{},
+ "roleIds": []string{},
+ "postIds": []string{},
+ "roles": roles,
+ "posts": posts,
+ }))
+ return
+ }
+
+ // 检查用户是否存在
+ user := s.sysUserService.SelectUserById(userId)
+ if user.UserID != userId {
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "user.noData")))
+ return
+ }
+
+ // 处理多语言
+ user.NickName = i18n.TKey(language, user.NickName)
+ user.Remark = i18n.TKey(language, user.Remark)
+ user.Dept.DeptName = i18n.TKey(language, user.Dept.DeptName)
+ for ri := range user.Roles {
+ user.Roles[ri].RoleName = i18n.TKey(language, user.Roles[ri].RoleName)
+ }
+
+ // 角色ID组
+ roleIds := make([]string, 0)
+ for _, r := range user.Roles {
+ roleIds = append(roleIds, r.RoleID)
+ }
+
+ // 岗位ID组
+ postIds := make([]string, 0)
+ userPosts := s.sysPostService.SelectPostListByUserId(userId)
+ for _, p := range userPosts {
+ postIds = append(postIds, p.PostID)
+ }
+
+ c.JSON(200, result.OkData(map[string]any{
+ "user": user,
+ "roleIds": roleIds,
+ "postIds": postIds,
+ "roles": roles,
+ "posts": posts,
+ }))
+}
+
+// 用户信息新增
+//
+// POST /
+func (s *SysUserController) Add(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.SysUser
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.UserID != "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 密码单独取,避免序列化输出
+ var bodyPassword struct {
+ Password string `json:"password" binding:"required"`
+ }
+ if err := c.ShouldBindBodyWith(&bodyPassword, binding.JSON); err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ body.Password = bodyPassword.Password
+
+ // 检查用户登录账号是否唯一
+ uniqueUserName := s.sysUserService.CheckUniqueUserName(body.UserName, "")
+ if !uniqueUserName {
+ // 新增用户【%s】失败,登录账号已存在
+ // msg := fmt.Sprintf("Failed to add user [%s], login account already exists", body.UserName)
+ msg := i18n.TTemplate(language, "user.errNameExists", map[string]any{"name": body.UserName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 检查手机号码格式并判断是否唯一
+ if body.PhoneNumber != "" {
+ if regular.ValidMobile(body.PhoneNumber) {
+ uniquePhone := s.sysUserService.CheckUniquePhone(body.PhoneNumber, "")
+ if !uniquePhone {
+ // 新增用户【%s】失败,手机号码已存在
+ // msg := fmt.Sprintf("Failed to add user [%s], cell phone number already exists", body.UserName)
+ msg := i18n.TTemplate(language, "user.errPhoneExists", map[string]any{"name": body.UserName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+ } else {
+ // 新增用户【%s】失败,手机号码格式错误
+ // msg := fmt.Sprintf("Failed to add user [%s], wrong format of cell phone number", body.UserName)
+ msg := i18n.TTemplate(language, "user.errPhoneFormat", map[string]any{"name": body.UserName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+ }
+
+ // 检查邮箱格式并判断是否唯一
+ if body.Email != "" {
+ if regular.ValidEmail(body.Email) {
+ uniqueEmail := s.sysUserService.CheckUniqueEmail(body.Email, "")
+ if !uniqueEmail {
+ // 新增用户【%s】失败,邮箱已存在
+ // msg := fmt.Sprintf("Failed to add user [%s], mailbox already exists", body.UserName)
+ msg := i18n.TTemplate(language, "user.errEmailExists", map[string]any{"name": body.UserName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+ } else {
+ // 新增用户【%s】失败,邮箱格式错误
+ // msg := fmt.Sprintf("Failed to add user [%s], mailbox format error", body.UserName)
+ msg := i18n.TTemplate(language, "user.errEmailFormat", map[string]any{"name": body.UserName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+ }
+
+ body.CreateBy = ctx.LoginUserToUserName(c)
+ insertId := s.sysUserService.InsertUser(body)
+ if insertId != "" {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 用户信息修改
+//
+// POST /
+func (s *SysUserController) Edit(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body model.SysUser
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil || body.UserID == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查是否管理员用户
+ if config.IsAdmin(body.UserID) {
+ // 不允许操作管理员用户
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.errOperateAdmin")))
+ return
+ }
+
+ user := s.sysUserService.SelectUserById(body.UserID)
+ if user.UserID != body.UserID {
+ // 没有可访问用户数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "user.noData")))
+ return
+ }
+
+ // 检查用户登录账号是否唯一
+ uniqueUserName := s.sysUserService.CheckUniqueUserName(body.UserName, body.UserID)
+ if !uniqueUserName {
+ // 修改用户【%s】失败,登录账号已存在
+ // msg := fmt.Sprintf("Failed to modify user [%s], login account already exists", body.UserName)
+ msg := i18n.TTemplate(language, "user.errNameExists", map[string]any{"name": body.UserName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+
+ // 检查手机号码格式并判断是否唯一
+ if body.PhoneNumber != "" {
+ if regular.ValidMobile(body.PhoneNumber) {
+ uniquePhone := s.sysUserService.CheckUniquePhone(body.PhoneNumber, body.UserID)
+ if !uniquePhone {
+ // 修改用户【%s】失败,手机号码已存在
+ // msg := fmt.Sprintf("Failed to modify user [%s], cell phone number already exists", body.UserName)
+ msg := i18n.TTemplate(language, "user.errPhoneExists", map[string]any{"name": body.UserName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+ } else {
+ // 修改用户【%s】失败,手机号码格式错误
+ // msg := fmt.Sprintf("Failed to modify user [%s], wrong format of cell phone number", body.UserName)
+ msg := i18n.TTemplate(language, "user.errPhoneFormat", map[string]any{"name": body.UserName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+ }
+
+ // 检查邮箱格式并判断是否唯一
+ if body.Email != "" {
+ if regular.ValidEmail(body.Email) {
+ uniqueEmail := s.sysUserService.CheckUniqueEmail(body.Email, body.UserID)
+ if !uniqueEmail {
+ // 修改用户【%s】失败,邮箱已存在
+ // msg := fmt.Sprintf("Failed to modify user [%s], mailbox already exists", body.UserName)
+ msg := i18n.TTemplate(language, "user.errEmailExists", map[string]any{"name": body.UserName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+ } else {
+ // 修改用户【%s】失败,邮箱格式错误
+ // msg := fmt.Sprintf("Failed to modify user [%s], mailbox format error", body.UserName)
+ msg := i18n.TTemplate(language, "user.errEmailFormat", map[string]any{"name": body.UserName})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+ }
+
+ body.UserName = "" // 忽略修改登录用户名称
+ body.Password = "" // 忽略修改密码
+ body.LoginIP = "" // 忽略登录IP
+ body.LoginDate = 0 // 忽略登录时间
+ body.UpdateBy = ctx.LoginUserToUserName(c)
+ rows := s.sysUserService.UpdateUserAndRolePost(body)
+ if rows > 0 {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 用户信息删除
+//
+// DELETE /:userIds
+func (s *SysUserController) Remove(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ userIds := c.Param("userIds")
+ if userIds == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ // 处理字符转id数组后去重
+ ids := strings.Split(userIds, ",")
+ uniqueIDs := parse.RemoveDuplicates(ids)
+ if len(uniqueIDs) <= 0 {
+ c.JSON(200, result.Err(nil))
+ return
+ }
+
+ // 检查是否管理员用户
+ for _, id := range uniqueIDs {
+ if config.IsAdmin(id) {
+ // 不允许操作管理员用户
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.errOperateAdmin")))
+ return
+ }
+ }
+
+ rows, err := s.sysUserService.DeleteUserByIds(uniqueIDs)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+ // 删除成功:%d
+ msg := i18n.TTemplate(language, "app.common.deleteSuccess", map[string]any{"num": rows})
+ c.JSON(200, result.OkMsg(msg))
+}
+
+// 用户重置密码
+//
+// PUT /resetPwd
+func (s *SysUserController) ResetPwd(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body struct {
+ UserID string `json:"userId" binding:"required"`
+ Password string `json:"password" binding:"required"`
+ }
+ if err := c.ShouldBindBodyWith(&body, binding.JSON); err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查是否管理员用户
+ if config.IsAdmin(body.UserID) {
+ // 不允许操作内置用户
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.errOperateAdmin")))
+ return
+ }
+
+ user := s.sysUserService.SelectUserById(body.UserID)
+ if user.UserID != body.UserID {
+ // 没有可访问用户数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "user.noData")))
+ return
+ }
+ if !regular.ValidPassword(body.Password) {
+ // 登录密码至少包含大小写字母、数字、特殊符号,且不少于6位
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "user.errPasswd")))
+ return
+ }
+
+ userName := ctx.LoginUserToUserName(c)
+ info := model.SysUser{
+ UserID: body.UserID,
+ Password: body.Password,
+ UpdateBy: userName,
+ }
+ rows := s.sysUserService.UpdateUser(info)
+ if rows > 0 {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 用户状态修改
+//
+// PUT /changeStatus
+func (s *SysUserController) Status(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body struct {
+ UserID string `json:"userId" binding:"required"`
+ Status string `json:"status" binding:"required"`
+ }
+ if err := c.ShouldBindBodyWith(&body, binding.JSON); err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 检查是否存在
+ user := s.sysUserService.SelectUserById(body.UserID)
+ if user.UserID != body.UserID {
+ // 没有可访问用户数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "user.noData")))
+ return
+ }
+
+ // 与旧值相等不变更
+ if user.Status == body.Status {
+ // 变更状态与旧值相等!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "user.statusEq")))
+ return
+ }
+
+ userName := ctx.LoginUserToUserName(c)
+ info := model.SysUser{
+ UserID: body.UserID,
+ Status: body.Status,
+ UpdateBy: userName,
+ }
+ rows := s.sysUserService.UpdateUser(info)
+ if rows > 0 {
+ c.JSON(200, result.Ok(nil))
+ return
+ }
+ c.JSON(200, result.Err(nil))
+}
+
+// 用户信息列表导出
+//
+// POST /export
+func (s *SysUserController) Export(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ // 查询结果,根据查询条件结果,单页最大值限制
+ querys := ctx.BodyJSONMap(c)
+ dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "u")
+ data := s.sysUserService.SelectUserPage(querys, dataScopeSQL)
+ if data["total"].(int64) == 0 {
+ // 导出数据记录为空
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.exportEmpty")))
+ return
+ }
+ rows := data["rows"].([]model.SysUser)
+
+ // 闭包函数处理多语言
+ converI18n := func(language string, arr *[]model.SysUser) {
+ for i := range *arr {
+ (*arr)[i].NickName = i18n.TKey(language, (*arr)[i].NickName)
+ (*arr)[i].Remark = i18n.TKey(language, (*arr)[i].Remark)
+ (*arr)[i].Dept.DeptName = i18n.TKey(language, (*arr)[i].Dept.DeptName)
+ for ri := range (*arr)[i].Roles {
+ (*arr)[i].Roles[ri].RoleName = i18n.TKey(language, (*arr)[i].Roles[ri].RoleName)
+ }
+ }
+ }
+ converI18n(language, &rows)
+
+ // 导出文件名称
+ fileName := fmt.Sprintf("user_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli())
+ // 第一行表头标题
+ headerCells := map[string]string{
+ "A1": i18n.TKey(language, "user.export.id"),
+ "B1": i18n.TKey(language, "user.export.name"),
+ "C1": i18n.TKey(language, "user.export.nick"),
+ "D1": i18n.TKey(language, "user.export.email"),
+ "E1": i18n.TKey(language, "user.export.phone"),
+ "F1": i18n.TKey(language, "user.export.sex"),
+ "G1": i18n.TKey(language, "user.export.status"),
+ "H1": i18n.TKey(language, "user.export.role"),
+ "I1": i18n.TKey(language, "user.export.deptID"),
+ "J1": i18n.TKey(language, "user.export.deptName"),
+ "K1": i18n.TKey(language, "user.export.deptLeader"),
+ "L1": i18n.TKey(language, "user.export.loginIP"),
+ "M1": i18n.TKey(language, "user.export.loginDate"),
+ }
+ // 读取用户性别字典数据
+ dictSysUserSex := s.sysDictDataService.SelectDictDataByType("sys_user_sex")
+ // 从第二行开始的数据
+ dataCells := make([]map[string]any, 0)
+ for i, row := range rows {
+ idx := strconv.Itoa(i + 2)
+ // 用户性别
+ sysUserSex := row.Sex
+ for _, v := range dictSysUserSex {
+ if row.Sex == v.DictValue {
+ sysUserSex = i18n.TKey(language, v.DictLabel)
+ break
+ }
+ }
+ // 帐号状态
+ statusValue := i18n.TKey(language, "dictData.disable")
+ if row.Status == "1" {
+ statusValue = i18n.TKey(language, "dictData.normal")
+ }
+ // 用户角色, 默认导出首个
+ userRole := ""
+ if len(row.Roles) > 0 {
+ roleID := row.Roles[0].RoleID
+ roleName := i18n.TKey(language, row.Roles[0].RoleName)
+ userRole = fmt.Sprintf("%s-%s", roleID, roleName)
+ }
+ dataCells = append(dataCells, map[string]any{
+ "A" + idx: row.UserID,
+ "B" + idx: row.UserName,
+ "C" + idx: row.NickName,
+ "D" + idx: row.Email,
+ "E" + idx: row.PhoneNumber,
+ "F" + idx: sysUserSex,
+ "G" + idx: statusValue,
+ "H" + idx: userRole,
+ "I" + idx: row.Dept.DeptID,
+ "J" + idx: row.Dept.DeptName,
+ "K" + idx: row.Dept.Leader,
+ "L" + idx: row.LoginIP,
+ "M" + idx: date.ParseDateToStr(row.LoginDate, date.YYYY_MM_DD_HH_MM_SS),
+ })
+ }
+
+ // 导出数据表格
+ saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "")
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ c.FileAttachment(saveFilePath, fileName)
+}
+
+// 用户信息列表导入模板下载
+//
+// GET /importTemplate
+func (s *SysUserController) Template(c *gin.Context) {
+ fileName := fmt.Sprintf("user_import_template_%d.xlsx", time.Now().UnixMilli())
+
+ // 多语言处理
+ language := ctx.AcceptLanguage(c)
+ asserPath := fmt.Sprintf("assets/template/excel/user_import_template_%s.xlsx", language)
+
+ // 从 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)
+}
+
+// 用户信息列表导入
+//
+// POST /importData
+func (s *SysUserController) ImportData(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ // 允许进行更新
+ updateSupport := c.PostForm("updateSupport")
+ // 上传的文件
+ formFile, err := c.FormFile("file")
+ if err != nil || updateSupport == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ // 保存表格文件
+ filePath, err := file.TransferExeclUploadFile(formFile)
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 读取表格数据
+ rows, err := file.ReadSheet(filePath, "")
+ if err != nil {
+ c.JSON(200, result.ErrMsg(err.Error()))
+ return
+ }
+
+ // 获取操作人名称
+ operName := ctx.LoginUserToUserName(c)
+ isUpdateSupport := parse.Boolean(updateSupport)
+
+ // 读取默认初始密码
+ initPassword := s.sysConfigService.SelectConfigValueByKey("sys.user.initPassword")
+ // 读取用户性别字典数据
+ dictSysUserSex := s.sysDictDataService.SelectDictDataByType("sys_user_sex")
+
+ // 导入记录
+ successNum := 0
+ failureNum := 0
+ successMsgArr := []string{}
+ failureMsgArr := []string{}
+ mustItemArr := []string{"B", "C"}
+ for _, row := range rows {
+ // 检查必填列
+ ownItem := true
+ for _, item := range mustItemArr {
+ if v, ok := row[item]; !ok || v == "" {
+ ownItem = false
+ break
+ }
+ }
+ if !ownItem {
+ mustItemArrStr := strings.Join(mustItemArr, "、")
+ failureNum++
+ // 表格中必填列表项,
+ msg := i18n.TTemplate(language, "user.import.mustItem", map[string]any{"text": mustItemArrStr})
+ failureMsgArr = append(failureMsgArr, msg)
+ continue
+ }
+
+ // 用户性别转值
+ sysUserSex := "0"
+ for _, v := range dictSysUserSex {
+ label := i18n.TKey(language, v.DictLabel)
+ if row["F"] == label {
+ sysUserSex = v.DictValue
+ break
+ }
+ }
+ // 用户状态
+ sysUserStatus := common.STATUS_NO
+ if row["G"] == "正常" || row["G"] == "Normal" {
+ sysUserStatus = common.STATUS_YES
+ }
+ // 用户角色 拿编号
+ sysUserRole := ""
+ if v, ok := row["H"]; ok && v != "" {
+ sysUserRole = strings.SplitN(v, "-", 2)[0]
+ if sysUserRole == "1" {
+ sysUserRole = ""
+ }
+ }
+
+ // 构建用户实体信息
+ newSysUser := model.SysUser{
+ UserType: "sys",
+ Password: initPassword,
+ DeptID: row["I"],
+ UserName: row["B"],
+ NickName: row["C"],
+ PhoneNumber: row["E"],
+ Email: row["D"],
+ Status: sysUserStatus,
+ Sex: sysUserSex,
+ RoleIDs: []string{sysUserRole},
+ }
+
+ // 检查手机号码格式并判断是否唯一
+ if newSysUser.PhoneNumber != "" {
+ if regular.ValidMobile(newSysUser.PhoneNumber) {
+ uniquePhone := s.sysUserService.CheckUniquePhone(newSysUser.PhoneNumber, "")
+ if !uniquePhone {
+ // 用户编号:%s 手机号码 %s 已存在
+ msg := i18n.TTemplate(language, "user.import.phoneExist", map[string]any{"id": row["A"], "phone": newSysUser.PhoneNumber})
+ failureNum++
+ failureMsgArr = append(failureMsgArr, msg)
+ continue
+ }
+ } else {
+ // 用户编号:%s 手机号码 %s 格式错误
+ msg := i18n.TTemplate(language, "user.import.phoneFormat", map[string]any{"id": row["A"], "phone": newSysUser.PhoneNumber})
+ failureNum++
+ failureMsgArr = append(failureMsgArr, msg)
+ continue
+ }
+ }
+
+ // 检查邮箱格式并判断是否唯一
+ if newSysUser.Email != "" {
+ if regular.ValidEmail(newSysUser.Email) {
+ uniqueEmail := s.sysUserService.CheckUniqueEmail(newSysUser.Email, "")
+ if !uniqueEmail {
+ // 用户编号:%s 用户邮箱 %s 已存在
+ msg := i18n.TTemplate(language, "user.import.emailExist", map[string]any{"id": row["A"], "email": newSysUser.Email})
+ failureNum++
+ failureMsgArr = append(failureMsgArr, msg)
+ continue
+ }
+ } else {
+ // 用户编号:%s 用户邮箱 %s 格式错误
+ msg := i18n.TTemplate(language, "user.import.emailFormat", map[string]any{"id": row["A"], "email": newSysUser.Email})
+ failureNum++
+ failureMsgArr = append(failureMsgArr, msg)
+ continue
+ }
+ }
+
+ // 验证是否存在这个用户
+ userInfo := s.sysUserService.SelectUserByUserName(newSysUser.UserName)
+ if userInfo.UserName != newSysUser.UserName {
+ newSysUser.CreateBy = operName
+ insertId := s.sysUserService.InsertUser(newSysUser)
+ if insertId != "" {
+ // 用户编号:%s 登录名称 %s 导入成功
+ msg := i18n.TTemplate(language, "user.import.success", map[string]any{"id": row["A"], "name": newSysUser.UserName})
+ successNum++
+ successMsgArr = append(successMsgArr, msg)
+ } else {
+ // 用户编号:%s 登录名称 %s 导入失败
+ msg := i18n.TTemplate(language, "user.import.fail", map[string]any{"id": row["A"], "name": newSysUser.UserName})
+ failureNum++
+ failureMsgArr = append(failureMsgArr, msg)
+ }
+ continue
+ }
+
+ // 如果用户已存在 同时 是否更新支持
+ if userInfo.UserName == newSysUser.UserName && isUpdateSupport {
+ newSysUser.UserID = userInfo.UserID
+ newSysUser.UpdateBy = operName
+ rows := s.sysUserService.UpdateUser(newSysUser)
+ if rows > 0 {
+ // 用户编号:%s 登录名称 %s 更新成功
+ msg := i18n.TTemplate(language, "user.import.successUpdate", map[string]any{"id": row["A"], "name": newSysUser.UserName})
+ successNum++
+ successMsgArr = append(successMsgArr, msg)
+ } else {
+ // 用户编号:%s 登录名称 %s 更新失败
+ msg := i18n.TTemplate(language, "user.import.failUpdate", map[string]any{"id": row["A"], "name": newSysUser.UserName})
+ failureNum++
+ failureMsgArr = append(failureMsgArr, msg)
+ }
+ continue
+ }
+ }
+
+ message := ""
+ if failureNum > 0 {
+ // 很抱歉,导入失败!共 %d 条数据格式不正确,错误如下:
+ msg := i18n.TTemplate(language, "user.import.failTip", map[string]any{"num": failureNum})
+ message = strings.Join(append([]string{msg}, failureMsgArr...), "
")
+ } else {
+ // 恭喜您,数据已全部导入成功!共 %d 条,数据如下:
+ msg := i18n.TTemplate(language, "user.import.successTip", map[string]any{"num": successNum})
+ message = strings.Join(append([]string{msg}, successMsgArr...), "
")
+ }
+
+ c.JSON(200, result.OkMsg(message))
+}
diff --git a/src/modules/system/model/sys_config.go b/src/modules/system/model/sys_config.go
new file mode 100644
index 0000000..8949c67
--- /dev/null
+++ b/src/modules/system/model/sys_config.go
@@ -0,0 +1,25 @@
+package model
+
+// 参数配置对象 sys_config
+type SysConfig struct {
+ // 参数主键
+ ConfigID string `json:"configId"`
+ // 参数名称
+ ConfigName string `json:"configName" binding:"required"`
+ // 参数键名
+ ConfigKey string `json:"configKey" binding:"required"`
+ // 参数键值
+ ConfigValue string `json:"configValue" binding:"required"`
+ // 系统内置(Y是 N否)
+ ConfigType string `json:"configType"`
+ // 创建者
+ CreateBy string `json:"createBy"`
+ // 创建时间
+ CreateTime int64 `json:"createTime"`
+ // 更新者
+ UpdateBy string `json:"updateBy"`
+ // 更新时间
+ UpdateTime int64 `json:"updateTime"`
+ // 备注
+ Remark string `json:"remark"`
+}
diff --git a/src/modules/system/model/sys_dept.go b/src/modules/system/model/sys_dept.go
new file mode 100644
index 0000000..b7a08f1
--- /dev/null
+++ b/src/modules/system/model/sys_dept.go
@@ -0,0 +1,41 @@
+package model
+
+// SysDept 部门对象 sys_dept
+type SysDept struct {
+ // 部门ID
+ DeptID string `json:"deptId"`
+ // 父部门ID
+ ParentID string `json:"parentId" binding:"required"`
+ // 祖级列表
+ Ancestors string `json:"ancestors"`
+ // 部门名称
+ DeptName string `json:"deptName" binding:"required"`
+ // 显示顺序
+ OrderNum int `json:"orderNum"`
+ // 负责人
+ Leader string `json:"leader"`
+ // 联系电话
+ Phone string `json:"phone"`
+ // 邮箱
+ Email string `json:"email"`
+ // 部门状态(0正常 1停用)
+ Status string `json:"status"`
+ // 删除标志(0代表存在 1代表删除)
+ DelFlag string `json:"delFlag"`
+ // 创建者
+ CreateBy string `json:"createBy"`
+ // 创建时间
+ CreateTime int64 `json:"createTime"`
+ // 更新者
+ UpdateBy string `json:"updateBy"`
+ // 更新时间
+ UpdateTime int64 `json:"updateTime"`
+
+ // ====== 非数据库字段属性 ======
+
+ // 子部门列表
+ Children []SysDept `json:"children,omitempty"`
+
+ // 父部门名称
+ ParentName string `json:"parentName,omitempty"`
+}
diff --git a/src/modules/system/model/sys_dict_data.go b/src/modules/system/model/sys_dict_data.go
new file mode 100644
index 0000000..7c8cd06
--- /dev/null
+++ b/src/modules/system/model/sys_dict_data.go
@@ -0,0 +1,31 @@
+package model
+
+// SysDictData 字典数据对象 sys_dict_data
+type SysDictData struct {
+ // 字典编码
+ DictCode string `json:"dictCode"`
+ // 字典排序
+ DictSort int `json:"dictSort"`
+ // 字典标签
+ DictLabel string `json:"dictLabel" binding:"required"`
+ // 字典键值
+ DictValue string `json:"dictValue" binding:"required"`
+ // 字典类型
+ DictType string `json:"dictType" binding:"required"`
+ // 样式属性(样式扩展)
+ TagClass string `json:"tagClass"`
+ // 标签类型(预设颜色)
+ TagType string `json:"tagType"`
+ // 状态(0停用 1正常)
+ Status string `json:"status"`
+ // 创建者
+ CreateBy string `json:"createBy"`
+ // 创建时间
+ CreateTime int64 `json:"createTime"`
+ // 更新者
+ UpdateBy string `json:"updateBy"`
+ // 更新时间
+ UpdateTime int64 `json:"updateTime"`
+ // 备注
+ Remark string `json:"remark"`
+}
diff --git a/src/modules/system/model/sys_dict_type.go b/src/modules/system/model/sys_dict_type.go
new file mode 100644
index 0000000..0ad0b1a
--- /dev/null
+++ b/src/modules/system/model/sys_dict_type.go
@@ -0,0 +1,23 @@
+package model
+
+// SysDictType 字典类型对象 sys_dict_type
+type SysDictType struct {
+ // 字典主键
+ DictID string `json:"dictId"`
+ // 字典名称
+ DictName string `json:"dictName" binding:"required"`
+ // 字典类型
+ DictType string `json:"dictType" binding:"required"`
+ // 状态(0停用 1正常)
+ Status string `json:"status"`
+ // 创建者
+ CreateBy string `json:"createBy"`
+ // 创建时间
+ CreateTime int64 `json:"createTime"`
+ // 更新者
+ UpdateBy string `json:"updateBy"`
+ // 更新时间
+ UpdateTime int64 `json:"updateTime"`
+ // 备注
+ Remark string `json:"remark"`
+}
diff --git a/src/modules/system/model/sys_log_login.go b/src/modules/system/model/sys_log_login.go
new file mode 100644
index 0000000..8548f8d
--- /dev/null
+++ b/src/modules/system/model/sys_log_login.go
@@ -0,0 +1,23 @@
+package model
+
+// SysLogLogin 系统登录日志表 sys_log_login
+type SysLogLogin struct {
+ // 登录ID
+ LoginID string `json:"loginId"`
+ // 用户账号
+ UserName string `json:"userName"`
+ // 登录IP地址
+ IPAddr string `json:"ipaddr"`
+ // 登录地点
+ LoginLocation string `json:"loginLocation"`
+ // 浏览器类型
+ Browser string `json:"browser"`
+ // 操作系统
+ OS string `json:"os"`
+ // 登录状态(0失败 1成功)
+ Status string `json:"status"`
+ // 提示消息
+ Msg string `json:"msg"`
+ // 访问时间
+ LoginTime int64 `json:"loginTime"`
+}
diff --git a/src/modules/system/model/sys_log_operate.go b/src/modules/system/model/sys_log_operate.go
new file mode 100644
index 0000000..3a0b604
--- /dev/null
+++ b/src/modules/system/model/sys_log_operate.go
@@ -0,0 +1,37 @@
+package model
+
+// SysLogOperate 系统操作日志表 sys_log_operate
+type SysLogOperate struct {
+ // 日志主键
+ OperID string `json:"operId"`
+ // 模块标题
+ Title string `json:"title"`
+ // 业务类型(0其它 1新增 2修改 3删除 4授权 5导出 6导入 7强退 8清空数据)
+ BusinessType string `json:"businessType"`
+ // 方法名称
+ Method string `json:"method"`
+ // 请求方式
+ RequestMethod string `json:"requestMethod"`
+ // 操作人员类别(0其它 1后台用户 2手机端用户)
+ OperatorType string `json:"operatorType"`
+ // 操作人员
+ OperName string `json:"operName"`
+ // 部门名称
+ DeptName string `json:"deptName"`
+ // 请求URL
+ OperURL string `json:"operUrl"`
+ // 主机地址
+ OperIP string `json:"operIp"`
+ // 操作地点
+ OperLocation string `json:"operLocation"`
+ // 请求参数
+ OperParam string `json:"operParam"`
+ // 操作消息
+ OperMsg string `json:"operMsg"`
+ // 操作状态(0异常 1正常)
+ Status string `json:"status"`
+ // 操作时间
+ OperTime int64 `json:"operTime"`
+ // 消耗时间(毫秒)
+ CostTime int64 `json:"costTime"`
+}
diff --git a/src/modules/system/model/sys_menu.go b/src/modules/system/model/sys_menu.go
new file mode 100644
index 0000000..397e30e
--- /dev/null
+++ b/src/modules/system/model/sys_menu.go
@@ -0,0 +1,46 @@
+package model
+
+// SysMenu 菜单权限对象 sys_menu
+type SysMenu struct {
+ // 菜单ID
+ MenuID string `json:"menuId"`
+ // 菜单名称
+ MenuName string `json:"menuName" binding:"required"`
+ // 父菜单ID 默认0
+ ParentID string `json:"parentId" binding:"required"`
+ // 显示顺序
+ MenuSort int `json:"menuSort"`
+ // 路由地址
+ Path string `json:"path"`
+ // 组件路径
+ Component string `json:"component"`
+ // 是否内部跳转(0否 1是)
+ IsFrame string `json:"isFrame"`
+ // 是否缓存(0不缓存 1缓存)
+ IsCache string `json:"isCache"`
+ // 菜单类型(D目录 M菜单 B按钮)
+ MenuType string `json:"menuType" binding:"required"`
+ // 是否显示(0隐藏 1显示)
+ Visible string `json:"visible"`
+ // 菜单状态(0停用 1正常)
+ Status string `json:"status"`
+ // 权限标识
+ Perms string `json:"perms"`
+ // 菜单图标(#无图标)
+ Icon string `json:"icon"`
+ // 创建者
+ CreateBy string `json:"createBy"`
+ // 创建时间
+ CreateTime int64 `json:"createTime"`
+ // 更新者
+ UpdateBy string `json:"updateBy"`
+ // 更新时间
+ UpdateTime int64 `json:"updateTime"`
+ // 备注
+ Remark string `json:"remark"`
+
+ // ====== 非数据库字段属性 ======
+
+ // 子菜单
+ Children []SysMenu `json:"children,omitempty"`
+}
diff --git a/src/modules/system/model/sys_notice.go b/src/modules/system/model/sys_notice.go
new file mode 100644
index 0000000..dc33097
--- /dev/null
+++ b/src/modules/system/model/sys_notice.go
@@ -0,0 +1,27 @@
+package model
+
+// SysNotice 通知公告对象 sys_notice
+type SysNotice struct {
+ // 公告ID
+ NoticeID string `json:"noticeId"`
+ // 公告标题
+ NoticeTitle string `json:"noticeTitle" binding:"required"`
+ // 公告类型(1通知 2公告)
+ NoticeType string `json:"noticeType" binding:"required"`
+ // 公告内容
+ NoticeContent string `json:"noticeContent" binding:"required"`
+ // 公告状态(0关闭 1正常)
+ Status string `json:"status"`
+ // 删除标志(0代表存在 1代表删除)
+ DelFlag string `json:"delFlag"`
+ // 创建者
+ CreateBy string `json:"createBy"`
+ // 创建时间
+ CreateTime int64 `json:"createTime"`
+ // 更新者
+ UpdateBy string `json:"updateBy"`
+ // 更新时间
+ UpdateTime int64 `json:"updateTime"`
+ // 备注
+ Remark string `json:"remark"`
+}
diff --git a/src/modules/system/model/sys_post.go b/src/modules/system/model/sys_post.go
new file mode 100644
index 0000000..7b65197
--- /dev/null
+++ b/src/modules/system/model/sys_post.go
@@ -0,0 +1,25 @@
+package model
+
+// SysPost 岗位对象 sys_post
+type SysPost struct {
+ // 岗位ID
+ PostID string `json:"postId"`
+ // 岗位编码
+ PostCode string `json:"postCode" binding:"required"`
+ // 岗位名称
+ PostName string `json:"postName" binding:"required"`
+ // 显示顺序
+ PostSort int `json:"postSort"`
+ // 状态(0停用 1正常)
+ Status string `json:"status"`
+ // 创建者
+ CreateBy string `json:"createBy"`
+ // 创建时间
+ CreateTime int64 `json:"createTime"`
+ // 更新者
+ UpdateBy string `json:"updateBy"`
+ // 更新时间
+ UpdateTime int64 `json:"updateTime"`
+ // 备注
+ Remark string `json:"remark"`
+}
diff --git a/src/modules/system/model/sys_role.go b/src/modules/system/model/sys_role.go
new file mode 100644
index 0000000..c4a8d9e
--- /dev/null
+++ b/src/modules/system/model/sys_role.go
@@ -0,0 +1,40 @@
+package model
+
+// SysRole 角色对象 sys_role
+type SysRole struct {
+ // 角色ID
+ RoleID string `json:"roleId"`
+ // 角色名称
+ RoleName string `json:"roleName" binding:"required"`
+ // 角色键值
+ RoleKey string `json:"roleKey" binding:"required"`
+ // 显示顺序
+ RoleSort int `json:"roleSort"`
+ // 数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限)
+ DataScope string `json:"dataScope"`
+ // 菜单树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示)
+ MenuCheckStrictly string `json:"menuCheckStrictly"`
+ // 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示)
+ DeptCheckStrictly string `json:"deptCheckStrictly"`
+ // 角色状态(0停用 1正常)
+ Status string `json:"status"`
+ // 删除标志(0代表存在 1代表删除)
+ DelFlag string `json:"delFlag"`
+ // 创建者
+ CreateBy string `json:"createBy"`
+ // 创建时间
+ CreateTime int64 `json:"createTime"`
+ // 更新者
+ UpdateBy string `json:"updateBy"`
+ // 更新时间
+ UpdateTime int64 `json:"updateTime"`
+ // 备注
+ Remark string `json:"remark"`
+
+ // ====== 非数据库字段属性 ======
+
+ // 菜单组
+ MenuIds []string `json:"menuIds,omitempty"`
+ // 部门组(数据权限)
+ DeptIds []string `json:"deptIds,omitempty"`
+}
diff --git a/src/modules/system/model/sys_role_dept.go b/src/modules/system/model/sys_role_dept.go
new file mode 100644
index 0000000..29f9840
--- /dev/null
+++ b/src/modules/system/model/sys_role_dept.go
@@ -0,0 +1,15 @@
+package model
+
+// SysRoleDept 角色和部门关联对象 sys_role_dept
+type SysRoleDept struct {
+ RoleID string `json:"roleId"` // 角色ID
+ DeptID string `json:"deptId"` // 部门ID
+}
+
+// NewSysRoleDept 创建角色和部门关联对象的构造函数
+func NewSysRoleDept(roleID string, deptID string) SysRoleDept {
+ return SysRoleDept{
+ RoleID: roleID,
+ DeptID: deptID,
+ }
+}
diff --git a/src/modules/system/model/sys_role_menu.go b/src/modules/system/model/sys_role_menu.go
new file mode 100644
index 0000000..3d5f74d
--- /dev/null
+++ b/src/modules/system/model/sys_role_menu.go
@@ -0,0 +1,15 @@
+package model
+
+// SysRoleMenu 角色和菜单关联对象 sys_role_menu
+type SysRoleMenu struct {
+ RoleID string `json:"roleId"` // 角色ID
+ MenuID string `json:"menuId"` // 菜单ID
+}
+
+// NewSysRoleMenu 创建角色和菜单关联对象的构造函数
+func NewSysRoleMenu(roleID string, menuID string) SysRoleMenu {
+ return SysRoleMenu{
+ RoleID: roleID,
+ MenuID: menuID,
+ }
+}
diff --git a/src/modules/system/model/sys_user.go b/src/modules/system/model/sys_user.go
new file mode 100644
index 0000000..c580784
--- /dev/null
+++ b/src/modules/system/model/sys_user.go
@@ -0,0 +1,56 @@
+package model
+
+// SysUser 用户对象 sys_user
+type SysUser struct {
+ // 用户ID
+ UserID string `json:"userId"`
+ // 部门ID
+ DeptID string `json:"deptId"`
+ // 用户账号
+ UserName string `json:"userName" binding:"required"`
+ // 用户昵称
+ NickName string `json:"nickName" binding:"required"`
+ // 用户类型(sys系统用户)
+ UserType string `json:"userType"`
+ // 用户邮箱
+ Email string `json:"email"`
+ // 手机号码
+ PhoneNumber string `json:"phonenumber"`
+ // 用户性别(0未知 1男 2女)
+ Sex string `json:"sex"`
+ // 头像地址
+ Avatar string `json:"avatar"`
+ // 密码
+ Password string `json:"-"`
+ // 帐号状态(0停用 1正常)
+ Status string `json:"status"`
+ // 删除标志(0代表存在 1代表删除)
+ DelFlag string `json:"delFlag"`
+ // 最后登录IP
+ LoginIP string `json:"loginIp"`
+ // 最后登录时间
+ LoginDate int64 `json:"loginDate"`
+ // 创建者
+ CreateBy string `json:"createBy"`
+ // 创建时间
+ CreateTime int64 `json:"createTime"`
+ // 更新者
+ UpdateBy string `json:"updateBy"`
+ // 更新时间
+ UpdateTime int64 `json:"updateTime"`
+ // 备注
+ Remark string `json:"remark"`
+
+ // ====== 非数据库字段属性 ======
+
+ // 部门对象
+ Dept SysDept `json:"dept,omitempty" binding:"structonly"`
+ // 角色对象组
+ Roles []SysRole `json:"roles"`
+ // 角色ID
+ RoleID string `json:"roleId,omitempty"`
+ // 角色组
+ RoleIDs []string `json:"roleIds,omitempty"`
+ // 岗位组
+ PostIDs []string `json:"postIds,omitempty"`
+}
diff --git a/src/modules/system/model/sys_user_post.go b/src/modules/system/model/sys_user_post.go
new file mode 100644
index 0000000..557cd2e
--- /dev/null
+++ b/src/modules/system/model/sys_user_post.go
@@ -0,0 +1,15 @@
+package model
+
+// SysUserPost 用户和岗位关联对象 sys_user_post
+type SysUserPost struct {
+ UserID string `json:"userId"` // 用户ID
+ PostID string `json:"postId"` // 岗位ID
+}
+
+// NewSysUserPost 创建用户和岗位关联对象的构造函数
+func NewSysUserPost(userID string, postID string) SysUserPost {
+ return SysUserPost{
+ UserID: userID,
+ PostID: postID,
+ }
+}
diff --git a/src/modules/system/model/sys_user_role.go b/src/modules/system/model/sys_user_role.go
new file mode 100644
index 0000000..e2c44a7
--- /dev/null
+++ b/src/modules/system/model/sys_user_role.go
@@ -0,0 +1,15 @@
+package model
+
+// SysUserRole 用户和角色关联对象 sys_user_role
+type SysUserRole struct {
+ UserID string `json:"userId"` // 用户ID
+ RoleID string `json:"roleId"` // 角色ID
+}
+
+// NewSysUserRole 创建用户和角色关联对象的构造函数
+func NewSysUserRole(userID string, roleID string) SysUserRole {
+ return SysUserRole{
+ UserID: userID,
+ RoleID: roleID,
+ }
+}
diff --git a/src/modules/system/repository/sys_config.go b/src/modules/system/repository/sys_config.go
new file mode 100644
index 0000000..f2dbd48
--- /dev/null
+++ b/src/modules/system/repository/sys_config.go
@@ -0,0 +1,30 @@
+package repository
+
+import "nms_nbi/src/modules/system/model"
+
+// ISysConfig 参数配置表 数据层接口
+type ISysConfig interface {
+ // SelectDictDataPage 分页查询参数配置列表数据
+ SelectConfigPage(query map[string]any) map[string]any
+
+ // SelectConfigList 查询参数配置列表
+ SelectConfigList(sysConfig model.SysConfig) []model.SysConfig
+
+ // SelectConfigValueByKey 通过参数键名查询参数键值
+ SelectConfigValueByKey(configKey string) string
+
+ // SelectConfigByIds 通过配置ID查询参数配置信息
+ SelectConfigByIds(configIds []string) []model.SysConfig
+
+ // CheckUniqueConfig 校验配置参数是否唯一
+ CheckUniqueConfig(sysConfig model.SysConfig) string
+
+ // InsertConfig 新增参数配置
+ InsertConfig(sysConfig model.SysConfig) string
+
+ // UpdateConfig 修改参数配置
+ UpdateConfig(sysConfig model.SysConfig) int64
+
+ // DeleteConfigByIds 批量删除参数配置信息
+ DeleteConfigByIds(configIds []string) int64
+}
diff --git a/src/modules/system/repository/sys_config.impl.go b/src/modules/system/repository/sys_config.impl.go
new file mode 100644
index 0000000..ea71533
--- /dev/null
+++ b/src/modules/system/repository/sys_config.impl.go
@@ -0,0 +1,337 @@
+package repository
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/date"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/repo"
+ "nms_nbi/src/modules/system/model"
+)
+
+// 实例化数据层 SysConfigImpl 结构体
+var NewSysConfigImpl = &SysConfigImpl{
+ selectSql: `select
+ config_id, config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark
+ from sys_config`,
+
+ resultMap: map[string]string{
+ "config_id": "ConfigID",
+ "config_name": "ConfigName",
+ "config_key": "ConfigKey",
+ "config_value": "ConfigValue",
+ "config_type": "ConfigType",
+ "remark": "Remark",
+ "create_by": "CreateBy",
+ "create_time": "CreateTime",
+ "update_by": "UpdateBy",
+ "update_time": "UpdateTime",
+ },
+}
+
+// SysConfigImpl 参数配置表 数据层处理
+type SysConfigImpl struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *SysConfigImpl) convertResultRows(rows []map[string]any) []model.SysConfig {
+ arr := make([]model.SysConfig, 0)
+ for _, row := range rows {
+ sysConfig := model.SysConfig{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ repo.SetFieldValue(&sysConfig, keyMapper, value)
+ }
+ }
+ arr = append(arr, sysConfig)
+ }
+ return arr
+}
+
+// SelectDictDataPage 分页查询参数配置列表数据
+func (r *SysConfigImpl) SelectConfigPage(query map[string]any) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if v, ok := query["configName"]; ok && v != "" {
+ conditions = append(conditions, "config_name like concat(?, '%')")
+ params = append(params, v)
+ }
+ if v, ok := query["configType"]; ok && v != "" {
+ conditions = append(conditions, "config_type = ?")
+ params = append(params, v)
+ }
+ if v, ok := query["configKey"]; ok && v != "" {
+ conditions = append(conditions, "config_key like concat(?, '%')")
+ params = append(params, v)
+ }
+ beginTime, ok := query["beginTime"]
+ if !ok {
+ beginTime, ok = query["params[beginTime]"]
+ }
+ if ok && beginTime != "" {
+ conditions = append(conditions, "create_time >= ?")
+ beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD)
+ params = append(params, beginDate.UnixMilli())
+ }
+ endTime, ok := query["endTime"]
+ if !ok {
+ endTime, ok = query["params[endTime]"]
+ }
+ if ok && endTime != "" {
+ conditions = append(conditions, "create_time <= ?")
+ endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD)
+ params = append(params, endDate.UnixMilli())
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询结果
+ result := map[string]any{
+ "total": 0,
+ "rows": []model.SysConfig{},
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := "select count(1) as 'total' from sys_config"
+ totalRows, err := datasource.RawDB("", totalSql+whereSql, params)
+ if err != nil {
+ logger.Errorf("total err => %v", err)
+ return result
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return result
+ } else {
+ result["total"] = total
+ }
+
+ // 分页
+ pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return result
+ }
+
+ // 转换实体
+ result["rows"] = r.convertResultRows(results)
+ return result
+}
+
+// SelectConfigList 查询参数配置列表
+func (r *SysConfigImpl) SelectConfigList(sysConfig model.SysConfig) []model.SysConfig {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysConfig.ConfigName != "" {
+ conditions = append(conditions, "config_name like concat(?, '%')")
+ params = append(params, sysConfig.ConfigName)
+ }
+ if sysConfig.ConfigType != "" {
+ conditions = append(conditions, "config_type = ?")
+ params = append(params, sysConfig.ConfigType)
+ }
+ if sysConfig.ConfigKey != "" {
+ conditions = append(conditions, "config_key like concat(?, '%')")
+ params = append(params, sysConfig.ConfigKey)
+ }
+ if sysConfig.CreateTime > 0 {
+ conditions = append(conditions, "create_time >= ?")
+ params = append(params, sysConfig.CreateTime)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ querySql := r.selectSql + whereSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.SysConfig{}
+ }
+
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// SelectConfigValueByKey 通过参数键名查询参数键值
+func (r *SysConfigImpl) SelectConfigValueByKey(configKey string) string {
+ querySql := "select config_value as 'str' from sys_config where config_key = ?"
+ results, err := datasource.RawDB("", querySql, []any{configKey})
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return ""
+ }
+ if len(results) > 0 {
+ return fmt.Sprint(results[0]["str"])
+ }
+ return ""
+}
+
+// SelectConfigByIds 通过配置ID查询参数配置信息
+func (r *SysConfigImpl) SelectConfigByIds(configIds []string) []model.SysConfig {
+ placeholder := repo.KeyPlaceholderByQuery(len(configIds))
+ querySql := r.selectSql + " where config_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(configIds)
+ results, err := datasource.RawDB("", querySql, parameters)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.SysConfig{}
+ }
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// CheckUniqueConfig 校验配置参数是否唯一
+func (r *SysConfigImpl) CheckUniqueConfig(sysConfig model.SysConfig) string {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysConfig.ConfigKey != "" {
+ conditions = append(conditions, "config_key = ?")
+ params = append(params, sysConfig.ConfigKey)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ } else {
+ return ""
+ }
+
+ // 查询数据
+ querySql := "select config_id as 'str' from sys_config " + whereSql + " limit 1"
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err %v", err)
+ return ""
+ }
+ if len(results) > 0 {
+ return fmt.Sprint(results[0]["str"])
+ }
+ return ""
+}
+
+// InsertConfig 新增参数配置
+func (r *SysConfigImpl) InsertConfig(sysConfig model.SysConfig) string {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysConfig.ConfigName != "" {
+ params["config_name"] = sysConfig.ConfigName
+ }
+ if sysConfig.ConfigKey != "" {
+ params["config_key"] = sysConfig.ConfigKey
+ }
+ if sysConfig.ConfigValue != "" {
+ params["config_value"] = sysConfig.ConfigValue
+ }
+ if sysConfig.ConfigType != "" {
+ params["config_type"] = sysConfig.ConfigType
+ }
+ if sysConfig.Remark != "" {
+ params["remark"] = sysConfig.Remark
+ }
+ if sysConfig.CreateBy != "" {
+ params["create_by"] = sysConfig.CreateBy
+ params["create_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params)
+ sql := "insert into sys_config (" + strings.Join(keys, ",") + ")values(" + placeholder + ")"
+
+ db := datasource.DefaultDB()
+ // 开启事务
+ tx := db.Begin()
+ // 执行插入
+ err := tx.Exec(sql, values...).Error
+ if err != nil {
+ logger.Errorf("insert row : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 获取生成的自增 ID
+ var insertedID string
+ err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID)
+ if err != nil {
+ logger.Errorf("insert last id : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 提交事务
+ tx.Commit()
+ return insertedID
+}
+
+// UpdateConfig 修改参数配置
+func (r *SysConfigImpl) UpdateConfig(sysConfig model.SysConfig) int64 {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysConfig.ConfigName != "" {
+ params["config_name"] = sysConfig.ConfigName
+ }
+ if sysConfig.ConfigKey != "" {
+ params["config_key"] = sysConfig.ConfigKey
+ }
+ if sysConfig.ConfigValue != "" {
+ params["config_value"] = sysConfig.ConfigValue
+ }
+ if sysConfig.ConfigType != "" {
+ params["config_type"] = sysConfig.ConfigType
+ }
+ params["remark"] = sysConfig.Remark
+ if sysConfig.UpdateBy != "" {
+ params["update_by"] = sysConfig.UpdateBy
+ params["update_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, values := repo.KeyValueByUpdate(params)
+ sql := "update sys_config set " + strings.Join(keys, ",") + " where config_id = ?"
+
+ // 执行更新
+ values = append(values, sysConfig.ConfigID)
+ rows, err := datasource.ExecDB("", sql, values)
+ if err != nil {
+ logger.Errorf("update row : %v", err.Error())
+ return 0
+ }
+ return rows
+}
+
+// DeleteConfigByIds 批量删除参数配置信息
+func (r *SysConfigImpl) DeleteConfigByIds(configIds []string) int64 {
+ placeholder := repo.KeyPlaceholderByQuery(len(configIds))
+ sql := "delete from sys_config where config_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(configIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
diff --git a/src/modules/system/repository/sys_dept.go b/src/modules/system/repository/sys_dept.go
new file mode 100644
index 0000000..313ac13
--- /dev/null
+++ b/src/modules/system/repository/sys_dept.go
@@ -0,0 +1,42 @@
+package repository
+
+import "nms_nbi/src/modules/system/model"
+
+// ISysDept 部门表 数据层接口
+type ISysDept interface {
+ // SelectDeptList 查询部门管理数据
+ SelectDeptList(sysDept model.SysDept, dataScopeSQL string) []model.SysDept
+
+ // SelectDeptListByRoleId 根据角色ID查询部门树信息
+ SelectDeptListByRoleId(roleId string, deptCheckStrictly bool) []string
+
+ // SelectDeptById 根据部门ID查询信息
+ SelectDeptById(deptId string) model.SysDept
+
+ // SelectChildrenDeptById 根据ID查询所有子部门
+ SelectChildrenDeptById(deptId string) []model.SysDept
+
+ // HasChildByDeptId 是否存在子节点
+ HasChildByDeptId(deptId string) int64
+
+ // CheckDeptExistUser 查询部门是否存在用户
+ CheckDeptExistUser(deptId string) int64
+
+ // CheckUniqueDept 校验部门是否唯一
+ CheckUniqueDept(sysDept model.SysDept) string
+
+ // InsertDept 新增部门信息
+ InsertDept(sysDept model.SysDept) string
+
+ // UpdateDept 修改部门信息
+ UpdateDept(sysDept model.SysDept) int64
+
+ // UpdateDeptStatusNormal 修改所在部门正常状态
+ UpdateDeptStatusNormal(deptIds []string) int64
+
+ // UpdateDeptChildren 修改子元素关系
+ UpdateDeptChildren(sysDepts []model.SysDept) int64
+
+ // DeleteDeptById 删除部门管理信息
+ DeleteDeptById(deptId string) int64
+}
diff --git a/src/modules/system/repository/sys_dept.impl.go b/src/modules/system/repository/sys_dept.impl.go
new file mode 100644
index 0000000..718f61c
--- /dev/null
+++ b/src/modules/system/repository/sys_dept.impl.go
@@ -0,0 +1,393 @@
+package repository
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/repo"
+ "nms_nbi/src/modules/system/model"
+)
+
+// 实例化数据层 SysDeptImpl 结构体
+var NewSysDeptImpl = &SysDeptImpl{
+ selectSql: `select
+ d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time
+ from sys_dept d`,
+
+ resultMap: map[string]string{
+ "dept_id": "DeptID",
+ "parent_id": "ParentID",
+ "ancestors": "Ancestors",
+ "dept_name": "DeptName",
+ "order_num": "OrderNum",
+ "leader": "Leader",
+ "phone": "Phone",
+ "email": "Email",
+ "status": "Status",
+ "del_flag": "DelFlag",
+ "create_by": "CreateBy",
+ "create_time": "CreateTime",
+ "update_by": "UpdateBy",
+ "update_time": "UpdateTime",
+ "parent_name": "ParentName",
+ },
+}
+
+// SysDeptImpl 部门表 数据层处理
+type SysDeptImpl struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *SysDeptImpl) convertResultRows(rows []map[string]any) []model.SysDept {
+ arr := make([]model.SysDept, 0)
+ for _, row := range rows {
+ sysDept := model.SysDept{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ repo.SetFieldValue(&sysDept, keyMapper, value)
+ }
+ }
+ arr = append(arr, sysDept)
+ }
+ return arr
+}
+
+// SelectDeptList 查询部门管理数据
+func (r *SysDeptImpl) SelectDeptList(sysDept model.SysDept, dataScopeSQL string) []model.SysDept {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysDept.DeptID != "" {
+ conditions = append(conditions, "dept_id = ?")
+ params = append(params, sysDept.DeptID)
+ }
+ if sysDept.ParentID != "" {
+ conditions = append(conditions, "parent_id = ?")
+ params = append(params, sysDept.ParentID)
+ }
+ if sysDept.DeptName != "" {
+ conditions = append(conditions, "dept_name like concat(?, '%')")
+ params = append(params, sysDept.DeptName)
+ }
+ if sysDept.Status != "" {
+ conditions = append(conditions, "status = ?")
+ params = append(params, sysDept.Status)
+ }
+
+ // 构建查询条件语句
+ whereSql := " where d.del_flag = '0' "
+ if len(conditions) > 0 {
+ whereSql += " and " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ orderSql := " order by d.parent_id, d.order_num asc "
+ querySql := r.selectSql + whereSql + dataScopeSQL + orderSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.SysDept{}
+ }
+
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// SelectDeptListByRoleId 根据角色ID查询部门树信息
+func (r *SysDeptImpl) SelectDeptListByRoleId(roleId string, deptCheckStrictly bool) []string {
+ querySql := `select d.dept_id as 'str' from sys_dept d
+ left join sys_role_dept rd on d.dept_id = rd.dept_id
+ where rd.role_id = ? `
+ var params []any
+ params = append(params, roleId)
+ // 展开
+ if deptCheckStrictly {
+ querySql += ` and d.dept_id not in
+ (select d.parent_id from sys_dept d
+ inner join sys_role_dept rd on d.dept_id = rd.dept_id
+ and rd.role_id = ?) `
+ params = append(params, roleId)
+ }
+ querySql += "order by d.parent_id, d.order_num"
+
+ // 查询结果
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []string{}
+ }
+
+ if len(results) > 0 {
+ ids := make([]string, 0)
+ for _, v := range results {
+ ids = append(ids, fmt.Sprintf("%v", v["str"]))
+ }
+ return ids
+ }
+ return []string{}
+}
+
+// SelectDeptById 根据部门ID查询信息
+func (r *SysDeptImpl) SelectDeptById(deptId string) model.SysDept {
+ querySql := `select d.dept_id, d.parent_id, d.ancestors,
+ d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status,
+ (select dept_name from sys_dept where dept_id = d.parent_id) parent_name
+ from sys_dept d where d.dept_id = ?`
+ results, err := datasource.RawDB("", querySql, []any{deptId})
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return model.SysDept{}
+ }
+ // 转换实体
+ rows := r.convertResultRows(results)
+ if len(rows) > 0 {
+ return rows[0]
+ }
+ return model.SysDept{}
+}
+
+// SelectChildrenDeptById 根据ID查询所有子部门
+func (r *SysDeptImpl) SelectChildrenDeptById(deptId string) []model.SysDept {
+ querySql := r.selectSql + " where find_in_set(?, d.ancestors)"
+ results, err := datasource.RawDB("", querySql, []any{deptId})
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.SysDept{}
+ }
+
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// HasChildByDeptId 是否存在子节点
+func (r *SysDeptImpl) HasChildByDeptId(deptId string) int64 {
+ querySql := "select count(1) as 'total' from sys_dept where status = '1' and parent_id = ? limit 1"
+ results, err := datasource.RawDB("", querySql, []any{deptId})
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return 0
+ }
+ if len(results) > 0 {
+ return parse.Number(results[0]["total"])
+ }
+ return 0
+}
+
+// CheckDeptExistUser 查询部门是否存在用户
+func (r *SysDeptImpl) CheckDeptExistUser(deptId string) int64 {
+ querySql := "select count(1) as 'total' from sys_user where dept_id = ? and del_flag = '0'"
+ results, err := datasource.RawDB("", querySql, []any{deptId})
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return 0
+ }
+ if len(results) > 0 {
+ return parse.Number(results[0]["total"])
+ }
+ return 0
+}
+
+// CheckUniqueDept 校验部门是否唯一
+func (r *SysDeptImpl) CheckUniqueDept(sysDept model.SysDept) string {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysDept.DeptName != "" {
+ conditions = append(conditions, "dept_name = ?")
+ params = append(params, sysDept.DeptName)
+ }
+ if sysDept.ParentID != "" {
+ conditions = append(conditions, "parent_id = ?")
+ params = append(params, sysDept.ParentID)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+ if whereSql == "" {
+ return ""
+ }
+
+ // 查询数据
+ querySql := "select dept_id as 'str' from sys_dept " + whereSql + " and del_flag = '0' limit 1"
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err %v", err)
+ return ""
+ }
+ if len(results) > 0 {
+ return fmt.Sprint(results[0]["str"])
+ }
+ return ""
+}
+
+// InsertDept 新增部门信息
+func (r *SysDeptImpl) InsertDept(sysDept model.SysDept) string {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysDept.DeptID != "" {
+ params["dept_id"] = sysDept.DeptID
+ }
+ if sysDept.ParentID != "" {
+ params["parent_id"] = sysDept.ParentID
+ }
+ if sysDept.DeptName != "" {
+ params["dept_name"] = sysDept.DeptName
+ }
+ if sysDept.Ancestors != "" {
+ params["ancestors"] = sysDept.Ancestors
+ }
+ if sysDept.OrderNum > 0 {
+ params["order_num"] = sysDept.OrderNum
+ }
+ if sysDept.Leader != "" {
+ params["leader"] = sysDept.Leader
+ }
+ if sysDept.Phone != "" {
+ params["phone"] = sysDept.Phone
+ }
+ if sysDept.Email != "" {
+ params["email"] = sysDept.Email
+ }
+ if sysDept.Status != "" {
+ params["status"] = sysDept.Status
+ }
+ if sysDept.CreateBy != "" {
+ params["create_by"] = sysDept.CreateBy
+ params["create_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params)
+ sql := "insert into sys_dept (" + strings.Join(keys, ",") + ")values(" + placeholder + ")"
+
+ db := datasource.DefaultDB()
+ // 开启事务
+ tx := db.Begin()
+ // 执行插入
+ err := tx.Exec(sql, values...).Error
+ if err != nil {
+ logger.Errorf("insert row : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 获取生成的自增 ID
+ var insertedID string
+ err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID)
+ if err != nil {
+ logger.Errorf("insert last id : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 提交事务
+ tx.Commit()
+ return insertedID
+}
+
+// UpdateDept 修改部门信息
+func (r *SysDeptImpl) UpdateDept(sysDept model.SysDept) int64 {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysDept.ParentID != "" {
+ params["parent_id"] = sysDept.ParentID
+ }
+ if sysDept.DeptName != "" {
+ params["dept_name"] = sysDept.DeptName
+ }
+ if sysDept.Ancestors != "" {
+ params["ancestors"] = sysDept.Ancestors
+ }
+ if sysDept.OrderNum > 0 {
+ params["order_num"] = sysDept.OrderNum
+ }
+ if sysDept.Leader != "" {
+ params["leader"] = sysDept.Leader
+ }
+ if sysDept.Phone != "" {
+ params["phone"] = sysDept.Phone
+ }
+ if sysDept.Email != "" {
+ params["email"] = sysDept.Email
+ }
+ if sysDept.Status != "" {
+ params["status"] = sysDept.Status
+ }
+ if sysDept.UpdateBy != "" {
+ params["update_by"] = sysDept.UpdateBy
+ params["update_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, values := repo.KeyValueByUpdate(params)
+ sql := "update sys_dept set " + strings.Join(keys, ",") + " where dept_id = ?"
+
+ // 执行更新
+ values = append(values, sysDept.DeptID)
+ rows, err := datasource.ExecDB("", sql, values)
+ if err != nil {
+ logger.Errorf("update row : %v", err.Error())
+ return 0
+ }
+ return rows
+}
+
+// UpdateDeptStatusNormal 修改所在部门正常状态
+func (r *SysDeptImpl) UpdateDeptStatusNormal(deptIds []string) int64 {
+ placeholder := repo.KeyPlaceholderByQuery(len(deptIds))
+ sql := "update sys_dept set status = '1' where dept_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(deptIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ logger.Errorf("update err => %v", err)
+ return 0
+ }
+ return results
+}
+
+// UpdateDeptChildren 修改子元素关系
+func (r *SysDeptImpl) UpdateDeptChildren(sysDepts []model.SysDept) int64 {
+ // 无参数
+ if len(sysDepts) == 0 {
+ return 0
+ }
+
+ // 更新条件拼接
+ var conditions []string
+ var params []any
+ for _, dept := range sysDepts {
+ caseSql := fmt.Sprintf("WHEN dept_id = '%s' THEN '%s'", dept.DeptID, dept.Ancestors)
+ conditions = append(conditions, caseSql)
+ params = append(params, dept.DeptID)
+ }
+
+ cases := strings.Join(conditions, " ")
+ placeholders := repo.KeyPlaceholderByQuery(len(params))
+ sql := "update sys_dept set ancestors = CASE " + cases + " END where dept_id in (" + placeholders + ")"
+ results, err := datasource.ExecDB("", sql, params)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
+
+// DeleteDeptById 删除部门管理信息
+func (r *SysDeptImpl) DeleteDeptById(deptId string) int64 {
+ sql := "update sys_dept set status = '0', del_flag = '1' where dept_id = ?"
+ results, err := datasource.ExecDB("", sql, []any{deptId})
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
diff --git a/src/modules/system/repository/sys_dict_data.go b/src/modules/system/repository/sys_dict_data.go
new file mode 100644
index 0000000..c3a44fa
--- /dev/null
+++ b/src/modules/system/repository/sys_dict_data.go
@@ -0,0 +1,33 @@
+package repository
+
+import "nms_nbi/src/modules/system/model"
+
+// ISysDictData 字典类型数据表 数据层接口
+type ISysDictData interface {
+ // SelectDictDataPage 根据条件分页查询字典数据
+ SelectDictDataPage(query map[string]any) map[string]any
+
+ // SelectDictDataList 根据条件查询字典数据
+ SelectDictDataList(sysDictData model.SysDictData) []model.SysDictData
+
+ // SelectDictDataByCodes 根据字典数据编码查询信息
+ SelectDictDataByCodes(dictCodes []string) []model.SysDictData
+
+ // CountDictDataByType 查询字典数据
+ CountDictDataByType(dictType string) int64
+
+ // CheckUniqueDictData 校验字典数据是否唯一
+ CheckUniqueDictData(sysDictData model.SysDictData) string
+
+ // DeleteDictDataByCodes 批量删除字典数据信息
+ DeleteDictDataByCodes(dictCodes []string) int64
+
+ // InsertDictData 新增字典数据信息
+ InsertDictData(sysDictData model.SysDictData) string
+
+ // UpdateDictData 修改字典数据信息
+ UpdateDictData(sysDictData model.SysDictData) int64
+
+ // UpdateDictDataType 同步修改字典类型
+ UpdateDictDataType(oldDictType string, newDictType string) int64
+}
diff --git a/src/modules/system/repository/sys_dict_data.impl.go b/src/modules/system/repository/sys_dict_data.impl.go
new file mode 100644
index 0000000..bc682cc
--- /dev/null
+++ b/src/modules/system/repository/sys_dict_data.impl.go
@@ -0,0 +1,362 @@
+package repository
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/repo"
+ "nms_nbi/src/modules/system/model"
+)
+
+// 实例化数据层 SysDictDataImpl 结构体
+var NewSysDictDataImpl = &SysDictDataImpl{
+ selectSql: `select
+ dict_code, dict_sort, dict_label, dict_value, dict_type, tag_class, tag_type, status, create_by, create_time, remark
+ from sys_dict_data`,
+
+ resultMap: map[string]string{
+ "dict_code": "DictCode",
+ "dict_sort": "DictSort",
+ "dict_label": "DictLabel",
+ "dict_value": "DictValue",
+ "dict_type": "DictType",
+ "tag_class": "TagClass",
+ "tag_type": "TagType",
+ "status": "Status",
+ "remark": "Remark",
+ "create_by": "CreateBy",
+ "create_time": "CreateTime",
+ "update_by": "UpdateBy",
+ "update_time": "UpdateTime",
+ },
+}
+
+// SysDictDataImpl 字典类型数据表 数据层处理
+type SysDictDataImpl struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *SysDictDataImpl) convertResultRows(rows []map[string]any) []model.SysDictData {
+ arr := make([]model.SysDictData, 0)
+ for _, row := range rows {
+ sysDictData := model.SysDictData{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ repo.SetFieldValue(&sysDictData, keyMapper, value)
+ }
+ }
+ arr = append(arr, sysDictData)
+ }
+ return arr
+}
+
+// SelectDictDataPage 根据条件分页查询字典数据
+func (r *SysDictDataImpl) SelectDictDataPage(query map[string]any) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if v, ok := query["dictType"]; ok && v != "" {
+ conditions = append(conditions, "dict_type = ?")
+ params = append(params, v)
+ }
+ if v, ok := query["dictLabel"]; ok && v != "" {
+ conditions = append(conditions, "dict_label like concat(?, '%')")
+ params = append(params, v)
+ }
+ if v, ok := query["status"]; ok && v != "" {
+ conditions = append(conditions, "status = ?")
+ params = append(params, v)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询结果
+ result := map[string]any{
+ "total": 0,
+ "rows": []model.SysDictData{},
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := "select count(1) as 'total' from sys_dict_data"
+ totalRows, err := datasource.RawDB("", totalSql+whereSql, params)
+ if err != nil {
+ logger.Errorf("total err => %v", err)
+ return result
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return result
+ } else {
+ result["total"] = total
+ }
+
+ // 分页
+ pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " order by dict_sort asc limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return result
+ }
+
+ // 转换实体
+ result["rows"] = r.convertResultRows(results)
+ return result
+}
+
+// SelectDictDataList 根据条件查询字典数据
+func (r *SysDictDataImpl) SelectDictDataList(sysDictData model.SysDictData) []model.SysDictData {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysDictData.DictLabel != "" {
+ conditions = append(conditions, "dict_label like concat(?, '%')")
+ params = append(params, sysDictData.DictLabel)
+ }
+ if sysDictData.DictType != "" {
+ conditions = append(conditions, "dict_type = ?")
+ params = append(params, sysDictData.DictType)
+ }
+ if sysDictData.Status != "" {
+ conditions = append(conditions, "status = ?")
+ params = append(params, sysDictData.Status)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ orderSql := " order by dict_sort asc "
+ querySql := r.selectSql + whereSql + orderSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.SysDictData{}
+ }
+
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// SelectDictDataByCodes 根据字典数据编码查询信息
+func (r *SysDictDataImpl) SelectDictDataByCodes(dictCodes []string) []model.SysDictData {
+ placeholder := repo.KeyPlaceholderByQuery(len(dictCodes))
+ querySql := r.selectSql + " where dict_code in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(dictCodes)
+ results, err := datasource.RawDB("", querySql, parameters)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.SysDictData{}
+ }
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// CountDictDataByType 查询字典数据
+func (r *SysDictDataImpl) CountDictDataByType(dictType string) int64 {
+ querySql := "select count(1) as 'total' from sys_dict_data where dict_type = ?"
+ results, err := datasource.RawDB("", querySql, []any{dictType})
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return 0
+ }
+ if len(results) > 0 {
+ return parse.Number(results[0]["total"])
+ }
+ return 0
+}
+
+// CheckUniqueDictData 校验字典数据是否唯一
+func (r *SysDictDataImpl) CheckUniqueDictData(sysDictData model.SysDictData) string {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysDictData.DictType != "" {
+ conditions = append(conditions, "dict_type = ?")
+ params = append(params, sysDictData.DictType)
+ }
+ if sysDictData.DictLabel != "" {
+ conditions = append(conditions, "dict_label = ?")
+ params = append(params, sysDictData.DictLabel)
+ }
+ if sysDictData.DictValue != "" {
+ conditions = append(conditions, "dict_value = ?")
+ params = append(params, sysDictData.DictValue)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ } else {
+ return ""
+ }
+
+ // 查询数据
+ querySql := "select dict_code as 'str' from sys_dict_data " + whereSql + " limit 1"
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err %v", err)
+ return ""
+ }
+ if len(results) > 0 {
+ return fmt.Sprint(results[0]["str"])
+ }
+ return ""
+}
+
+// DeleteDictDataByCodes 批量删除字典数据信息
+func (r *SysDictDataImpl) DeleteDictDataByCodes(dictCodes []string) int64 {
+ placeholder := repo.KeyPlaceholderByQuery(len(dictCodes))
+ sql := "delete from sys_dict_data where dict_code in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(dictCodes)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
+
+// InsertDictData 新增字典数据信息
+func (r *SysDictDataImpl) InsertDictData(sysDictData model.SysDictData) string {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysDictData.DictSort > 0 {
+ params["dict_sort"] = sysDictData.DictSort
+ }
+ if sysDictData.DictLabel != "" {
+ params["dict_label"] = sysDictData.DictLabel
+ }
+ if sysDictData.DictValue != "" {
+ params["dict_value"] = sysDictData.DictValue
+ }
+ if sysDictData.DictType != "" {
+ params["dict_type"] = sysDictData.DictType
+ }
+ if sysDictData.TagClass != "" {
+ params["tag_class"] = sysDictData.TagClass
+ }
+ if sysDictData.TagType != "" {
+ params["tag_type"] = sysDictData.TagType
+ }
+ if sysDictData.Status != "" {
+ params["status"] = sysDictData.Status
+ }
+ if sysDictData.Remark != "" {
+ params["remark"] = sysDictData.Remark
+ }
+ if sysDictData.CreateBy != "" {
+ params["create_by"] = sysDictData.CreateBy
+ params["create_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params)
+ sql := "insert into sys_dict_data (" + strings.Join(keys, ",") + ")values(" + placeholder + ")"
+
+ db := datasource.DefaultDB()
+ // 开启事务
+ tx := db.Begin()
+ // 执行插入
+ err := tx.Exec(sql, values...).Error
+ if err != nil {
+ logger.Errorf("insert row : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 获取生成的自增 ID
+ var insertedID string
+ err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID)
+ if err != nil {
+ logger.Errorf("insert last id : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 提交事务
+ tx.Commit()
+ return insertedID
+}
+
+// UpdateDictData 修改字典数据信息
+func (r *SysDictDataImpl) UpdateDictData(sysDictData model.SysDictData) int64 {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysDictData.DictSort > 0 {
+ params["dict_sort"] = sysDictData.DictSort
+ }
+ if sysDictData.DictLabel != "" {
+ params["dict_label"] = sysDictData.DictLabel
+ }
+ if sysDictData.DictValue != "" {
+ params["dict_value"] = sysDictData.DictValue
+ }
+ if sysDictData.DictType != "" {
+ params["dict_type"] = sysDictData.DictType
+ }
+ if sysDictData.Status != "" {
+ params["status"] = sysDictData.Status
+ }
+ params["tag_class"] = sysDictData.TagClass
+ params["tag_type"] = sysDictData.TagType
+ params["remark"] = sysDictData.Remark
+ if sysDictData.UpdateBy != "" {
+ params["update_by"] = sysDictData.UpdateBy
+ params["update_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, values := repo.KeyValueByUpdate(params)
+ sql := "update sys_dict_data set " + strings.Join(keys, ",") + " where dict_code = ?"
+
+ // 执行更新
+ values = append(values, sysDictData.DictCode)
+ rows, err := datasource.ExecDB("", sql, values)
+ if err != nil {
+ logger.Errorf("update row : %v", err.Error())
+ return 0
+ }
+ return rows
+}
+
+// UpdateDictDataType 同步修改字典类型
+func (r *SysDictDataImpl) UpdateDictDataType(oldDictType string, newDictType string) int64 {
+ // 参数拼接
+ params := make([]any, 0)
+ if oldDictType == "" || newDictType == "" {
+ return 0
+ }
+ params = append(params, newDictType)
+ params = append(params, oldDictType)
+
+ // 构建执行语句
+ sql := "update sys_dict_data set dict_type = ? where dict_type = ?"
+
+ // 执行更新
+ rows, err := datasource.ExecDB("", sql, params)
+ if err != nil {
+ logger.Errorf("update row : %v", err.Error())
+ return 0
+ }
+ return rows
+}
diff --git a/src/modules/system/repository/sys_dict_type.go b/src/modules/system/repository/sys_dict_type.go
new file mode 100644
index 0000000..4e732e5
--- /dev/null
+++ b/src/modules/system/repository/sys_dict_type.go
@@ -0,0 +1,30 @@
+package repository
+
+import "nms_nbi/src/modules/system/model"
+
+// ISysDictType 字典类型表 数据层接口
+type ISysDictType interface {
+ // SelectDictTypePage 根据条件分页查询字典类型
+ SelectDictTypePage(query map[string]any) map[string]any
+
+ // SelectDictTypeList 根据条件查询字典类型
+ SelectDictTypeList(sysDictType model.SysDictType) []model.SysDictType
+
+ // SelectDictTypeByIDs 根据字典类型ID查询信息
+ SelectDictTypeByIDs(dictIDs []string) []model.SysDictType
+
+ // SelectDictTypeByType 根据字典类型查询信息
+ SelectDictTypeByType(dictType string) model.SysDictType
+
+ // CheckUniqueDictType 校验字典类型是否唯一
+ CheckUniqueDictType(sysDictType model.SysDictType) string
+
+ // InsertDictType 新增字典类型信息
+ InsertDictType(sysDictType model.SysDictType) string
+
+ // UpdateDictType 修改字典类型信息
+ UpdateDictType(sysDictType model.SysDictType) int64
+
+ // DeleteDictTypeByIDs 批量删除字典类型信息
+ DeleteDictTypeByIDs(dictIDs []string) int64
+}
diff --git a/src/modules/system/repository/sys_dict_type.impl.go b/src/modules/system/repository/sys_dict_type.impl.go
new file mode 100644
index 0000000..8bb4851
--- /dev/null
+++ b/src/modules/system/repository/sys_dict_type.impl.go
@@ -0,0 +1,332 @@
+package repository
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/date"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/repo"
+ "nms_nbi/src/modules/system/model"
+)
+
+// 实例化数据层 SysDictTypeImpl 结构体
+var NewSysDictTypeImpl = &SysDictTypeImpl{
+ selectSql: `select
+ dict_id, dict_name, dict_type, status, create_by, create_time, remark
+ from sys_dict_type`,
+
+ resultMap: map[string]string{
+ "dict_id": "DictID",
+ "dict_name": "DictName",
+ "dict_type": "DictType",
+ "remark": "Remark",
+ "status": "Status",
+ "create_by": "CreateBy",
+ "create_time": "CreateTime",
+ "update_by": "UpdateBy",
+ "update_time": "UpdateTime",
+ },
+}
+
+// SysDictTypeImpl 字典类型表 数据层处理
+type SysDictTypeImpl struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *SysDictTypeImpl) convertResultRows(rows []map[string]any) []model.SysDictType {
+ arr := make([]model.SysDictType, 0)
+ for _, row := range rows {
+ sysDictType := model.SysDictType{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ repo.SetFieldValue(&sysDictType, keyMapper, value)
+ }
+ }
+ arr = append(arr, sysDictType)
+ }
+ return arr
+}
+
+// SelectDictTypePage 根据条件分页查询字典类型
+func (r *SysDictTypeImpl) SelectDictTypePage(query map[string]any) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if v, ok := query["dictName"]; ok && v != "" {
+ conditions = append(conditions, "dict_name like concat(?, '%')")
+ params = append(params, v)
+ }
+ if v, ok := query["dictType"]; ok && v != "" {
+ conditions = append(conditions, "dict_type like concat(?, '%')")
+ params = append(params, v)
+ }
+ if v, ok := query["status"]; ok && v != "" {
+ conditions = append(conditions, "status = ?")
+ params = append(params, v)
+ }
+ beginTime, ok := query["beginTime"]
+ if !ok {
+ beginTime, ok = query["params[beginTime]"]
+ }
+ if ok && beginTime != "" {
+ conditions = append(conditions, "create_time >= ?")
+ beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD)
+ params = append(params, beginDate.UnixMilli())
+ }
+ endTime, ok := query["endTime"]
+ if !ok {
+ endTime, ok = query["params[endTime]"]
+ }
+ if ok && endTime != "" {
+ conditions = append(conditions, "create_time <= ?")
+ endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD)
+ params = append(params, endDate.UnixMilli())
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询结果
+ result := map[string]any{
+ "total": 0,
+ "rows": []model.SysDictType{},
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := "select count(1) as 'total' from sys_dict_type"
+ totalRows, err := datasource.RawDB("", totalSql+whereSql, params)
+ if err != nil {
+ logger.Errorf("total err => %v", err)
+ return result
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return result
+ } else {
+ result["total"] = total
+ }
+
+ // 分页
+ pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return result
+ }
+
+ // 转换实体
+ result["rows"] = r.convertResultRows(results)
+ return result
+}
+
+// SelectDictTypeList 根据条件查询字典类型
+func (r *SysDictTypeImpl) SelectDictTypeList(sysDictType model.SysDictType) []model.SysDictType {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysDictType.DictName != "" {
+ conditions = append(conditions, "dict_name like concat(?, '%')")
+ params = append(params, sysDictType.DictName)
+ }
+ if sysDictType.DictType != "" {
+ conditions = append(conditions, "dict_type like concat(?, '%')")
+ params = append(params, sysDictType.DictType)
+ }
+ if sysDictType.Status != "" {
+ conditions = append(conditions, "status = ?")
+ params = append(params, sysDictType.Status)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ querySql := r.selectSql + whereSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.SysDictType{}
+ }
+
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// SelectDictTypeByIDs 根据字典类型ID查询信息
+func (r *SysDictTypeImpl) SelectDictTypeByIDs(dictIDs []string) []model.SysDictType {
+ placeholder := repo.KeyPlaceholderByQuery(len(dictIDs))
+ querySql := r.selectSql + " where dict_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(dictIDs)
+ results, err := datasource.RawDB("", querySql, parameters)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.SysDictType{}
+ }
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// SelectDictTypeByType 根据字典类型查询信息
+func (r *SysDictTypeImpl) SelectDictTypeByType(dictType string) model.SysDictType {
+ querySql := r.selectSql + " where dict_type = ?"
+ results, err := datasource.RawDB("", querySql, []any{dictType})
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return model.SysDictType{}
+ }
+ // 转换实体
+ rows := r.convertResultRows(results)
+ if len(rows) > 0 {
+ return rows[0]
+ }
+ return model.SysDictType{}
+}
+
+// CheckUniqueDictType 校验字典是否唯一
+func (r *SysDictTypeImpl) CheckUniqueDictType(sysDictType model.SysDictType) string {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysDictType.DictName != "" {
+ conditions = append(conditions, "dict_name = ?")
+ params = append(params, sysDictType.DictName)
+ }
+ if sysDictType.DictType != "" {
+ conditions = append(conditions, "dict_type = ?")
+ params = append(params, sysDictType.DictType)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ } else {
+ return ""
+ }
+
+ // 查询数据
+ querySql := "select dict_id as 'str' from sys_dict_type " + whereSql + " limit 1"
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err %v", err)
+ return ""
+ }
+ if len(results) > 0 {
+ return fmt.Sprint(results[0]["str"])
+ }
+ return ""
+}
+
+// InsertDictType 新增字典类型信息
+func (r *SysDictTypeImpl) InsertDictType(sysDictType model.SysDictType) string {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysDictType.DictName != "" {
+ params["dict_name"] = sysDictType.DictName
+ }
+ if sysDictType.DictType != "" {
+ params["dict_type"] = sysDictType.DictType
+ }
+ if sysDictType.Status != "" {
+ params["status"] = sysDictType.Status
+ }
+ if sysDictType.Remark != "" {
+ params["remark"] = sysDictType.Remark
+ }
+ if sysDictType.CreateBy != "" {
+ params["create_by"] = sysDictType.CreateBy
+ params["create_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params)
+ sql := "insert into sys_dict_type (" + strings.Join(keys, ",") + ")values(" + placeholder + ")"
+
+ db := datasource.DefaultDB()
+ // 开启事务
+ tx := db.Begin()
+ // 执行插入
+ err := tx.Exec(sql, values...).Error
+ if err != nil {
+ logger.Errorf("insert row : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 获取生成的自增 ID
+ var insertedID string
+ err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID)
+ if err != nil {
+ logger.Errorf("insert last id : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 提交事务
+ tx.Commit()
+ return insertedID
+}
+
+// UpdateDictType 修改字典类型信息
+func (r *SysDictTypeImpl) UpdateDictType(sysDictType model.SysDictType) int64 {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysDictType.DictName != "" {
+ params["dict_name"] = sysDictType.DictName
+ }
+ if sysDictType.DictType != "" {
+ params["dict_type"] = sysDictType.DictType
+ }
+ if sysDictType.Status != "" {
+ params["status"] = sysDictType.Status
+ }
+ params["remark"] = sysDictType.Remark
+ if sysDictType.UpdateBy != "" {
+ params["update_by"] = sysDictType.UpdateBy
+ params["update_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, values := repo.KeyValueByUpdate(params)
+ sql := "update sys_dict_type set " + strings.Join(keys, ",") + " where dict_id = ?"
+
+ // 执行更新
+ values = append(values, sysDictType.DictID)
+ rows, err := datasource.ExecDB("", sql, values)
+ if err != nil {
+ logger.Errorf("update row : %v", err.Error())
+ return 0
+ }
+ return rows
+}
+
+// DeleteDictTypeByIDs 批量删除字典类型信息
+func (r *SysDictTypeImpl) DeleteDictTypeByIDs(dictIDs []string) int64 {
+ placeholder := repo.KeyPlaceholderByQuery(len(dictIDs))
+ sql := "delete from sys_dict_type where dict_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(dictIDs)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
diff --git a/src/modules/system/repository/sys_log_login.go b/src/modules/system/repository/sys_log_login.go
new file mode 100644
index 0000000..ec26b87
--- /dev/null
+++ b/src/modules/system/repository/sys_log_login.go
@@ -0,0 +1,21 @@
+package repository
+
+import "nms_nbi/src/modules/system/model"
+
+// ISysLogLogin 系统登录日志表 数据层接口
+type ISysLogLogin interface {
+ // SelectSysLogLoginPage 分页查询系统登录日志集合
+ SelectSysLogLoginPage(query map[string]any) map[string]any
+
+ // SelectSysLogLoginList 查询系统登录日志集合
+ SelectSysLogLoginList(sysLogLogin model.SysLogLogin) []model.SysLogLogin
+
+ // InsertSysLogLogin 新增系统登录日志
+ InsertSysLogLogin(sysLogLogin model.SysLogLogin) string
+
+ // DeleteSysLogLoginByIds 批量删除系统登录日志
+ DeleteSysLogLoginByIds(loginIds []string) int64
+
+ // CleanSysLogLogin 清空系统登录日志
+ CleanSysLogLogin() error
+}
diff --git a/src/modules/system/repository/sys_log_login.impl.go b/src/modules/system/repository/sys_log_login.impl.go
new file mode 100644
index 0000000..ff1b849
--- /dev/null
+++ b/src/modules/system/repository/sys_log_login.impl.go
@@ -0,0 +1,245 @@
+package repository
+
+import (
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/date"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/repo"
+ "nms_nbi/src/modules/system/model"
+)
+
+// 实例化数据层 SysLogLoginImpl 结构体
+var NewSysLogLoginImpl = &SysLogLoginImpl{
+ selectSql: `select login_id, user_name, ipaddr, login_location,
+ browser, os, status, msg, login_time from sys_log_login`,
+
+ resultMap: map[string]string{
+ "login_id": "LoginID",
+ "user_name": "UserName",
+ "status": "Status",
+ "ipaddr": "IPAddr",
+ "login_location": "LoginLocation",
+ "browser": "Browser",
+ "os": "OS",
+ "msg": "Msg",
+ "login_time": "LoginTime",
+ },
+}
+
+// SysLogLoginImpl 系统登录访问表 数据层处理
+type SysLogLoginImpl struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *SysLogLoginImpl) convertResultRows(rows []map[string]any) []model.SysLogLogin {
+ arr := make([]model.SysLogLogin, 0)
+ for _, row := range rows {
+ SysLogLogin := model.SysLogLogin{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ repo.SetFieldValue(&SysLogLogin, keyMapper, value)
+ }
+ }
+ arr = append(arr, SysLogLogin)
+ }
+ return arr
+}
+
+// SelectSysLogLoginPage 分页查询系统登录日志集合
+func (r *SysLogLoginImpl) SelectSysLogLoginPage(query map[string]any) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if v, ok := query["ipaddr"]; ok && v != "" {
+ conditions = append(conditions, "ipaddr like concat(?, '%')")
+ params = append(params, v)
+ }
+ if v, ok := query["userName"]; ok && v != "" {
+ conditions = append(conditions, "user_name like concat(?, '%')")
+ params = append(params, v)
+ }
+ if v, ok := query["status"]; ok && v != "" {
+ conditions = append(conditions, "status = ?")
+ params = append(params, v)
+ }
+ beginTime, ok := query["beginTime"]
+ if !ok {
+ beginTime, ok = query["params[beginTime]"]
+ }
+ if ok && beginTime != "" {
+ conditions = append(conditions, "login_time >= ?")
+ beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD)
+ params = append(params, beginDate.UnixMilli())
+ }
+ endTime, ok := query["endTime"]
+ if !ok {
+ endTime, ok = query["params[endTime]"]
+ }
+ if ok && endTime != "" {
+ conditions = append(conditions, "login_time <= ?")
+ endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD)
+ params = append(params, endDate.UnixMilli())
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询结果
+ result := map[string]any{
+ "total": 0,
+ "rows": []model.SysLogLogin{},
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := "select count(1) as 'total' from sys_log_login"
+ totalRows, err := datasource.RawDB("", totalSql+whereSql, params)
+ if err != nil {
+ logger.Errorf("total err => %v", err)
+ return result
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return result
+ } else {
+ result["total"] = total
+ }
+
+ // 分页
+ pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " order by login_id desc limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return result
+ }
+
+ // 转换实体
+ result["rows"] = r.convertResultRows(results)
+ return result
+}
+
+// SelectSysLogLoginList 查询系统登录日志集合
+func (r *SysLogLoginImpl) SelectSysLogLoginList(SysLogLogin model.SysLogLogin) []model.SysLogLogin {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if SysLogLogin.IPAddr != "" {
+ conditions = append(conditions, "title like concat(?, '%')")
+ params = append(params, SysLogLogin.IPAddr)
+ }
+ if SysLogLogin.UserName != "" {
+ conditions = append(conditions, "user_name like concat(?, '%')")
+ params = append(params, SysLogLogin.UserName)
+ }
+ if SysLogLogin.Status != "" {
+ conditions = append(conditions, "status = ?")
+ params = append(params, SysLogLogin.Status)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ querySql := r.selectSql + whereSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.SysLogLogin{}
+ }
+
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// InsertSysLogLogin 新增系统登录日志
+func (r *SysLogLoginImpl) InsertSysLogLogin(SysLogLogin model.SysLogLogin) string {
+ // 参数拼接
+ params := make(map[string]any)
+ params["login_time"] = time.Now().UnixMilli()
+ if SysLogLogin.UserName != "" {
+ params["user_name"] = SysLogLogin.UserName
+ }
+ if SysLogLogin.Status != "" {
+ params["status"] = SysLogLogin.Status
+ }
+ if SysLogLogin.IPAddr != "" {
+ params["ipaddr"] = SysLogLogin.IPAddr
+ }
+ if SysLogLogin.LoginLocation != "" {
+ params["login_location"] = SysLogLogin.LoginLocation
+ }
+ if SysLogLogin.Browser != "" {
+ params["browser"] = SysLogLogin.Browser
+ }
+ if SysLogLogin.OS != "" {
+ params["os"] = SysLogLogin.OS
+ }
+ if SysLogLogin.Msg != "" {
+ params["msg"] = SysLogLogin.Msg
+ }
+
+ // 构建执行语句
+ keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params)
+ sql := "insert into sys_log_login (" + strings.Join(keys, ",") + ")values(" + placeholder + ")"
+
+ db := datasource.DefaultDB()
+ // 开启事务
+ tx := db.Begin()
+ // 执行插入
+ err := tx.Exec(sql, values...).Error
+ if err != nil {
+ logger.Errorf("insert row : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 获取生成的自增 ID
+ var insertedID string
+ err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID)
+ if err != nil {
+ logger.Errorf("insert last id : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 提交事务
+ tx.Commit()
+ return insertedID
+}
+
+// DeleteSysLogLoginByIds 批量删除系统登录日志
+func (r *SysLogLoginImpl) DeleteSysLogLoginByIds(loginIds []string) int64 {
+ placeholder := repo.KeyPlaceholderByQuery(len(loginIds))
+ sql := "delete from sys_log_login where login_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(loginIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
+
+// CleanSysLogLogin 清空系统登录日志
+func (r *SysLogLoginImpl) CleanSysLogLogin() error {
+ sql := "truncate table sys_log_login"
+ _, err := datasource.ExecDB("", sql, []any{})
+ return err
+}
diff --git a/src/modules/system/repository/sys_log_operate.go b/src/modules/system/repository/sys_log_operate.go
new file mode 100644
index 0000000..1283d31
--- /dev/null
+++ b/src/modules/system/repository/sys_log_operate.go
@@ -0,0 +1,24 @@
+package repository
+
+import "nms_nbi/src/modules/system/model"
+
+// ISysLogOperate 操作日志表 数据层接口
+type ISysLogOperate interface {
+ // SelectSysLogOperatePage 分页查询系统操作日志集合
+ SelectSysLogOperatePage(query map[string]any) map[string]any
+
+ // SelectSysLogOperateList 查询系统操作日志集合
+ SelectSysLogOperateList(sysLogOperate model.SysLogOperate) []model.SysLogOperate
+
+ // SelectSysLogOperateById 查询操作日志详细
+ SelectSysLogOperateById(operId string) model.SysLogOperate
+
+ // InsertSysLogOperate 新增操作日志
+ InsertSysLogOperate(sysLogOperate model.SysLogOperate) string
+
+ // DeleteSysLogOperateByIds 批量删除系统操作日志
+ DeleteSysLogOperateByIds(operIds []string) int64
+
+ // CleanSysLogOperate 清空操作日志
+ CleanSysLogOperate() error
+}
diff --git a/src/modules/system/repository/sys_log_operate.impl.go b/src/modules/system/repository/sys_log_operate.impl.go
new file mode 100644
index 0000000..c594054
--- /dev/null
+++ b/src/modules/system/repository/sys_log_operate.impl.go
@@ -0,0 +1,299 @@
+package repository
+
+import (
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/date"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/repo"
+ "nms_nbi/src/modules/system/model"
+)
+
+// 实例化数据层 SysLogOperateImpl 结构体
+var NewSysLogOperateImpl = &SysLogOperateImpl{
+ selectSql: `select
+ oper_id, title, business_type, method, request_method, operator_type, oper_name, dept_name,
+ oper_url, oper_ip, oper_location, oper_param, oper_msg, status, oper_time, cost_time
+ from sys_log_operate`,
+
+ resultMap: map[string]string{
+ "oper_id": "OperID",
+ "title": "Title",
+ "business_type": "BusinessType",
+ "method": "Method",
+ "request_method": "RequestMethod",
+ "operator_type": "OperatorType",
+ "oper_name": "OperName",
+ "dept_name": "DeptName",
+ "oper_url": "OperURL",
+ "oper_ip": "OperIP",
+ "oper_location": "OperLocation",
+ "oper_param": "OperParam",
+ "oper_msg": "OperMsg",
+ "status": "Status",
+ "oper_time": "OperTime",
+ "cost_time": "CostTime",
+ },
+}
+
+// SysLogOperateImpl 操作日志表 数据层处理
+type SysLogOperateImpl struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *SysLogOperateImpl) convertResultRows(rows []map[string]any) []model.SysLogOperate {
+ arr := make([]model.SysLogOperate, 0)
+ for _, row := range rows {
+ SysLogOperate := model.SysLogOperate{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ repo.SetFieldValue(&SysLogOperate, keyMapper, value)
+ }
+ }
+ arr = append(arr, SysLogOperate)
+ }
+ return arr
+}
+
+// SelectSysLogOperatePage 分页查询系统操作日志集合
+func (r *SysLogOperateImpl) SelectSysLogOperatePage(query map[string]any) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if v, ok := query["title"]; ok && v != "" {
+ conditions = append(conditions, "title like concat(?, '%')")
+ params = append(params, v)
+ }
+ if v, ok := query["businessType"]; ok && v != "" {
+ conditions = append(conditions, "business_type = ?")
+ params = append(params, v)
+ }
+ if v, ok := query["operName"]; ok && v != "" {
+ conditions = append(conditions, "oper_name like concat(?, '%')")
+ params = append(params, v)
+ }
+ if v, ok := query["status"]; ok && v != "" {
+ conditions = append(conditions, "status = ?")
+ params = append(params, v)
+ }
+ beginTime, ok := query["beginTime"]
+ if !ok {
+ beginTime, ok = query["params[beginTime]"]
+ }
+ if ok && beginTime != "" {
+ conditions = append(conditions, "oper_time >= ?")
+ beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD)
+ params = append(params, beginDate.UnixMilli())
+ }
+ endTime, ok := query["endTime"]
+ if !ok {
+ endTime, ok = query["params[endTime]"]
+ }
+ if ok && endTime != "" {
+ conditions = append(conditions, "oper_time <= ?")
+ endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD)
+ params = append(params, endDate.UnixMilli())
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询结果
+ result := map[string]any{
+ "total": 0,
+ "rows": []model.SysLogOperate{},
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := "select count(1) as 'total' from sys_log_operate"
+ totalRows, err := datasource.RawDB("", totalSql+whereSql, params)
+ if err != nil {
+ logger.Errorf("total err => %v", err)
+ return result
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return result
+ } else {
+ result["total"] = total
+ }
+
+ // 分页
+ pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " order by oper_id desc limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return result
+ }
+
+ // 转换实体
+ result["rows"] = r.convertResultRows(results)
+ return result
+}
+
+// SelectSysLogOperateList 查询系统操作日志集合
+func (r *SysLogOperateImpl) SelectSysLogOperateList(SysLogOperate model.SysLogOperate) []model.SysLogOperate {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if SysLogOperate.Title != "" {
+ conditions = append(conditions, "title like concat(?, '%')")
+ params = append(params, SysLogOperate.Title)
+ }
+ if SysLogOperate.BusinessType != "" {
+ conditions = append(conditions, "business_type = ?")
+ params = append(params, SysLogOperate.BusinessType)
+ }
+ if SysLogOperate.OperName != "" {
+ conditions = append(conditions, "oper_name like concat(?, '%')")
+ params = append(params, SysLogOperate.OperName)
+ }
+ if SysLogOperate.Status != "" {
+ conditions = append(conditions, "status = ?")
+ params = append(params, SysLogOperate.Status)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ querySql := r.selectSql + whereSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.SysLogOperate{}
+ }
+
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// SelectSysLogOperateById 查询操作日志详细
+func (r *SysLogOperateImpl) SelectSysLogOperateById(operId string) model.SysLogOperate {
+ querySql := r.selectSql + " where oper_id = ?"
+ results, err := datasource.RawDB("", querySql, []any{operId})
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return model.SysLogOperate{}
+ }
+ // 转换实体
+ rows := r.convertResultRows(results)
+ if len(rows) > 0 {
+ return rows[0]
+ }
+ return model.SysLogOperate{}
+}
+
+// InsertSysLogOperate 新增操作日志
+func (r *SysLogOperateImpl) InsertSysLogOperate(SysLogOperate model.SysLogOperate) string {
+ // 参数拼接
+ params := make(map[string]any)
+ params["oper_time"] = time.Now().UnixMilli()
+ if SysLogOperate.Title != "" {
+ params["title"] = SysLogOperate.Title
+ }
+ if SysLogOperate.BusinessType != "" {
+ params["business_type"] = SysLogOperate.BusinessType
+ }
+ if SysLogOperate.Method != "" {
+ params["method"] = SysLogOperate.Method
+ }
+ if SysLogOperate.RequestMethod != "" {
+ params["request_method"] = SysLogOperate.RequestMethod
+ }
+ if SysLogOperate.OperatorType != "" {
+ params["operator_type"] = SysLogOperate.OperatorType
+ }
+ if SysLogOperate.DeptName != "" {
+ params["dept_name"] = SysLogOperate.DeptName
+ }
+ if SysLogOperate.OperName != "" {
+ params["oper_name"] = SysLogOperate.OperName
+ }
+ if SysLogOperate.OperURL != "" {
+ params["oper_url"] = SysLogOperate.OperURL
+ }
+ if SysLogOperate.OperIP != "" {
+ params["oper_ip"] = SysLogOperate.OperIP
+ }
+ if SysLogOperate.OperLocation != "" {
+ params["oper_location"] = SysLogOperate.OperLocation
+ }
+ if SysLogOperate.OperParam != "" {
+ params["oper_param"] = SysLogOperate.OperParam
+ }
+ if SysLogOperate.OperMsg != "" {
+ params["oper_msg"] = SysLogOperate.OperMsg
+ }
+ if SysLogOperate.Status != "" {
+ params["status"] = SysLogOperate.Status
+ }
+ if SysLogOperate.CostTime > 0 {
+ params["cost_time"] = SysLogOperate.CostTime
+ }
+
+ // 构建执行语句
+ keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params)
+ sql := "insert into sys_log_operate (" + strings.Join(keys, ",") + ")values(" + placeholder + ")"
+
+ db := datasource.DefaultDB()
+ // 开启事务
+ tx := db.Begin()
+ // 执行插入
+ err := tx.Exec(sql, values...).Error
+ if err != nil {
+ logger.Errorf("insert row : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 获取生成的自增 ID
+ var insertedID string
+ err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID)
+ if err != nil {
+ logger.Errorf("insert last id : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 提交事务
+ tx.Commit()
+ return insertedID
+}
+
+// DeleteSysLogOperateByIds 批量删除系统操作日志
+func (r *SysLogOperateImpl) DeleteSysLogOperateByIds(operIds []string) int64 {
+ placeholder := repo.KeyPlaceholderByQuery(len(operIds))
+ sql := "delete from sys_log_operate where oper_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(operIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
+
+// CleanSysLogOperate 清空操作日志
+func (r *SysLogOperateImpl) CleanSysLogOperate() error {
+ sql := "truncate table sys_log_operate"
+ _, err := datasource.ExecDB("", sql, []any{})
+ return err
+}
diff --git a/src/modules/system/repository/sys_menu.go b/src/modules/system/repository/sys_menu.go
new file mode 100644
index 0000000..2b2c202
--- /dev/null
+++ b/src/modules/system/repository/sys_menu.go
@@ -0,0 +1,36 @@
+package repository
+
+import "nms_nbi/src/modules/system/model"
+
+// ISysMenu 菜单表 数据层接口
+type ISysMenu interface {
+ // SelectMenuList 查询系统菜单列表
+ SelectMenuList(sysMenu model.SysMenu, userId string) []model.SysMenu
+
+ // SelectMenuPermsByUserId 根据用户ID查询权限
+ SelectMenuPermsByUserId(userId string) []string
+
+ // SelectMenuTreeByUserId 根据用户ID查询菜单
+ SelectMenuTreeByUserId(userId string) []model.SysMenu
+
+ // SelectMenuListByRoleId 根据角色ID查询菜单树信息
+ SelectMenuListByRoleId(roleId string, menuCheckStrictly bool) []string
+
+ // SelectMenuByIds 根据菜单ID查询信息
+ SelectMenuByIds(menuIds []string) []model.SysMenu
+
+ // HasChildByMenuIdAndStatus 存在菜单子节点数量与状态
+ HasChildByMenuIdAndStatus(menuId, status string) int64
+
+ // InsertMenu 新增菜单信息
+ InsertMenu(sysMenu model.SysMenu) string
+
+ // UpdateMenu 修改菜单信息
+ UpdateMenu(sysMenu model.SysMenu) int64
+
+ // DeleteMenuById 删除菜单管理信息
+ DeleteMenuById(menuId string) int64
+
+ // CheckUniqueMenu 校验菜单是否唯一
+ CheckUniqueMenu(sysMenu model.SysMenu) string
+}
diff --git a/src/modules/system/repository/sys_menu.impl.go b/src/modules/system/repository/sys_menu.impl.go
new file mode 100644
index 0000000..a8acc22
--- /dev/null
+++ b/src/modules/system/repository/sys_menu.impl.go
@@ -0,0 +1,474 @@
+package repository
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/constants/menu"
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/repo"
+ "nms_nbi/src/modules/system/model"
+)
+
+// 实例化数据层 SysMenuImpl 结构体
+var NewSysMenuImpl = &SysMenuImpl{
+ selectSql: `select
+ m.menu_id, m.menu_name, m.parent_id, m.menu_sort, m.path, m.component, m.is_frame, m.is_cache, m.menu_type, m.visible, m.status, ifnull(m.perms,'') as perms, m.icon, m.create_time, m.remark
+ from sys_menu m`,
+
+ selectSqlByUser: `select distinct
+ m.menu_id, m.menu_name, m.parent_id, m.menu_sort, m.path, m.component, m.is_frame, m.is_cache, m.menu_type, m.visible, m.status, ifnull(m.perms,'') as perms, m.icon, m.create_time, m.remark
+ from sys_menu m
+ left join sys_role_menu rm on m.menu_id = rm.menu_id
+ left join sys_user_role ur on rm.role_id = ur.role_id
+ left join sys_role ro on ur.role_id = ro.role_id`,
+
+ resultMap: map[string]string{
+ "menu_id": "MenuID",
+ "menu_name": "MenuName",
+ "parent_name": "ParentName",
+ "parent_id": "ParentID",
+ "path": "Path",
+ "menu_sort": "MenuSort",
+ "component": "Component",
+ "is_frame": "IsFrame",
+ "is_cache": "IsCache",
+ "menu_type": "MenuType",
+ "visible": "Visible",
+ "status": "Status",
+ "perms": "Perms",
+ "icon": "Icon",
+ "create_by": "CreateBy",
+ "create_time": "CreateTime",
+ "update_by": "UpdateBy",
+ "update_time": "UpdateTime",
+ "remark": "Remark",
+ },
+}
+
+// SysMenuImpl 菜单表 数据层处理
+type SysMenuImpl struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 查询视图用户对象SQL
+ selectSqlByUser string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *SysMenuImpl) convertResultRows(rows []map[string]any) []model.SysMenu {
+ arr := make([]model.SysMenu, 0)
+ for _, row := range rows {
+ sysMenu := model.SysMenu{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ repo.SetFieldValue(&sysMenu, keyMapper, value)
+ }
+ }
+ arr = append(arr, sysMenu)
+ }
+ return arr
+}
+
+// SelectMenuList 查询系统菜单列表
+func (r *SysMenuImpl) SelectMenuList(sysMenu model.SysMenu, userId string) []model.SysMenu {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysMenu.MenuName != "" {
+ conditions = append(conditions, "m.menu_name like concat(?, '%')")
+ params = append(params, sysMenu.MenuName)
+ }
+ if sysMenu.Visible != "" {
+ conditions = append(conditions, "m.visible = ?")
+ params = append(params, sysMenu.Visible)
+ }
+ if sysMenu.Status != "" {
+ conditions = append(conditions, "m.status = ?")
+ params = append(params, sysMenu.Status)
+ }
+
+ fromSql := r.selectSql
+
+ // 个人菜单
+ if userId != "*" {
+ fromSql = r.selectSqlByUser
+ conditions = append(conditions, "ur.user_id = ?")
+ params = append(params, userId)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ orderSql := " order by m.parent_id, m.menu_sort"
+ querySql := fromSql + whereSql + orderSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.SysMenu{}
+ }
+
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// SelectMenuPermsByUserId 根据用户ID查询权限
+func (r *SysMenuImpl) SelectMenuPermsByUserId(userId string) []string {
+ querySql := `select distinct m.perms as 'str' from sys_menu m
+ left join sys_role_menu rm on m.menu_id = rm.menu_id
+ left join sys_user_role ur on rm.role_id = ur.role_id
+ left join sys_role r on r.role_id = ur.role_id
+ where m.status = '1' and m.perms != '' and r.status = '1' and ur.user_id = ? `
+
+ // 查询结果
+ results, err := datasource.RawDB("", querySql, []any{userId})
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []string{}
+ }
+
+ // 读取结果
+ rows := make([]string, 0)
+ for _, m := range results {
+ rows = append(rows, fmt.Sprintf("%v", m["str"]))
+ }
+ return rows
+}
+
+// SelectMenuTreeByUserId 根据用户ID查询菜单
+func (r *SysMenuImpl) SelectMenuTreeByUserId(userId string) []model.SysMenu {
+ var params []any
+ var querySql string
+
+ if userId == "*" {
+ // 管理员全部菜单
+ querySql = r.selectSql + ` where
+ m.menu_type in (?,?) and m.status = '1'
+ order by m.parent_id, m.menu_sort`
+ params = append(params, menu.TYPE_DIR)
+ params = append(params, menu.TYPE_MENU)
+ } else {
+ // 用户ID权限
+ querySql = r.selectSqlByUser + ` where
+ m.menu_type in (?, ?) and m.status = '1'
+ and ur.user_id = ? and ro.status = '1'
+ order by m.parent_id, m.menu_sort`
+ params = append(params, menu.TYPE_DIR)
+ params = append(params, menu.TYPE_MENU)
+ params = append(params, userId)
+ }
+
+ // 查询结果
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.SysMenu{}
+ }
+
+ return r.convertResultRows(results)
+}
+
+// SelectMenuListByRoleId 根据角色ID查询菜单树信息
+func (r *SysMenuImpl) SelectMenuListByRoleId(roleId string, menuCheckStrictly bool) []string {
+ querySql := `select m.menu_id as 'str' from sys_menu m
+ left join sys_role_menu rm on m.menu_id = rm.menu_id
+ where rm.role_id = ? `
+ var params []any
+ params = append(params, roleId)
+ // 展开
+ if menuCheckStrictly {
+ querySql += ` and m.menu_id not in
+ (select m.parent_id from sys_menu m
+ inner join sys_role_menu rm on m.menu_id = rm.menu_id
+ and rm.role_id = ?) `
+ params = append(params, roleId)
+ }
+
+ // 查询结果
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []string{}
+ }
+
+ if len(results) > 0 {
+ ids := make([]string, 0)
+ for _, v := range results {
+ ids = append(ids, fmt.Sprintf("%v", v["str"]))
+ }
+ return ids
+ }
+ return []string{}
+}
+
+// SelectMenuByIds 根据菜单ID查询信息
+func (r *SysMenuImpl) SelectMenuByIds(menuIds []string) []model.SysMenu {
+ placeholder := repo.KeyPlaceholderByQuery(len(menuIds))
+ querySql := r.selectSql + " where m.menu_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(menuIds)
+ results, err := datasource.RawDB("", querySql, parameters)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.SysMenu{}
+ }
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// HasChildByMenuIdAndStatus 存在菜单子节点数量与状态
+func (r *SysMenuImpl) HasChildByMenuIdAndStatus(menuId, status string) int64 {
+ querySql := "select count(1) as 'total' from sys_menu where parent_id = ?"
+ params := []any{menuId}
+
+ // 菜单状态
+ if status != "" {
+ querySql += " and status = ? and menu_type in (?, ?) "
+ params = append(params, status)
+ params = append(params, menu.TYPE_DIR)
+ params = append(params, menu.TYPE_MENU)
+ }
+
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return 0
+ }
+ if len(results) > 0 {
+ return parse.Number(results[0]["total"])
+ }
+ return 0
+}
+
+// InsertMenu 新增菜单信息
+func (r *SysMenuImpl) InsertMenu(sysMenu model.SysMenu) string {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysMenu.MenuID != "" {
+ params["menu_id"] = sysMenu.MenuID
+ }
+ if sysMenu.ParentID != "" {
+ params["parent_id"] = sysMenu.ParentID
+ }
+ if sysMenu.MenuName != "" {
+ params["menu_name"] = sysMenu.MenuName
+ }
+ if sysMenu.MenuSort > 0 {
+ params["menu_sort"] = sysMenu.MenuSort
+ }
+ if sysMenu.Path != "" {
+ params["path"] = sysMenu.Path
+ }
+ if sysMenu.Component != "" {
+ params["component"] = sysMenu.Component
+ }
+ if sysMenu.IsFrame != "" {
+ params["is_frame"] = sysMenu.IsFrame
+ }
+ if sysMenu.IsCache != "" {
+ params["is_cache"] = sysMenu.IsCache
+ }
+ if sysMenu.MenuType != "" {
+ params["menu_type"] = sysMenu.MenuType
+ }
+ if sysMenu.Visible != "" {
+ params["visible"] = sysMenu.Visible
+ }
+ if sysMenu.Status != "" {
+ params["status"] = sysMenu.Status
+ }
+ if sysMenu.Perms != "" {
+ params["perms"] = sysMenu.Perms
+ }
+ if sysMenu.Icon != "" {
+ params["icon"] = sysMenu.Icon
+ } else {
+ params["icon"] = "#"
+ }
+ if sysMenu.Remark != "" {
+ params["remark"] = sysMenu.Remark
+ }
+ if sysMenu.CreateBy != "" {
+ params["create_by"] = sysMenu.CreateBy
+ params["create_time"] = time.Now().UnixMilli()
+ }
+
+ // 根据菜单类型重置参数
+ if sysMenu.MenuType == menu.TYPE_BUTTON {
+ params["component"] = ""
+ params["path"] = ""
+ params["icon"] = "#"
+ params["is_cache"] = "1"
+ params["is_frame"] = "1"
+ params["visible"] = "1"
+ params["status"] = "1"
+ }
+ if sysMenu.MenuType == menu.TYPE_DIR {
+ params["component"] = ""
+ params["perms"] = ""
+ }
+
+ // 构建执行语句
+ keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params)
+ sql := "insert into sys_menu (" + strings.Join(keys, ",") + ")values(" + placeholder + ")"
+
+ db := datasource.DefaultDB()
+ // 开启事务
+ tx := db.Begin()
+ // 执行插入
+ err := tx.Exec(sql, values...).Error
+ if err != nil {
+ logger.Errorf("insert row : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 获取生成的自增 ID
+ var insertedID string
+ err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID)
+ if err != nil {
+ logger.Errorf("insert last id : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 提交事务
+ tx.Commit()
+ return insertedID
+}
+
+// UpdateMenu 修改菜单信息
+func (r *SysMenuImpl) UpdateMenu(sysMenu model.SysMenu) int64 {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysMenu.MenuID != "" {
+ params["menu_id"] = sysMenu.MenuID
+ }
+ if sysMenu.ParentID != "" {
+ params["parent_id"] = sysMenu.ParentID
+ }
+ if sysMenu.MenuName != "" {
+ params["menu_name"] = sysMenu.MenuName
+ }
+ if sysMenu.MenuSort > 0 {
+ params["menu_sort"] = sysMenu.MenuSort
+ }
+ if sysMenu.Path != "" {
+ params["path"] = sysMenu.Path
+ }
+ if sysMenu.Component != "" {
+ params["component"] = sysMenu.Component
+ }
+ if sysMenu.IsFrame != "" {
+ params["is_frame"] = sysMenu.IsFrame
+ }
+ if sysMenu.IsCache != "" {
+ params["is_cache"] = sysMenu.IsCache
+ }
+ if sysMenu.MenuType != "" {
+ params["menu_type"] = sysMenu.MenuType
+ }
+ if sysMenu.Visible != "" {
+ params["visible"] = sysMenu.Visible
+ }
+ if sysMenu.Status != "" {
+ params["status"] = sysMenu.Status
+ }
+ if sysMenu.Perms != "" {
+ params["perms"] = sysMenu.Perms
+ }
+ if sysMenu.Icon != "" {
+ params["icon"] = sysMenu.Icon
+ } else {
+ params["icon"] = "#"
+ }
+ params["remark"] = sysMenu.Remark
+ if sysMenu.UpdateBy != "" {
+ params["update_by"] = sysMenu.UpdateBy
+ params["update_time"] = time.Now().UnixMilli()
+ }
+
+ // 根据菜单类型重置参数
+ if sysMenu.MenuType == menu.TYPE_BUTTON {
+ params["component"] = ""
+ params["path"] = ""
+ params["icon"] = "#"
+ params["is_cache"] = "1"
+ params["is_frame"] = "1"
+ params["visible"] = "1"
+ params["status"] = "1"
+ }
+ if sysMenu.MenuType == menu.TYPE_DIR {
+ params["component"] = ""
+ params["perms"] = ""
+ }
+
+ // 构建执行语句
+ keys, values := repo.KeyValueByUpdate(params)
+ sql := "update sys_menu set " + strings.Join(keys, ",") + " where menu_id = ?"
+
+ // 执行更新
+ values = append(values, sysMenu.MenuID)
+ rows, err := datasource.ExecDB("", sql, values)
+ if err != nil {
+ logger.Errorf("update row : %v", err.Error())
+ return 0
+ }
+ return rows
+}
+
+// DeleteMenuById 删除菜单管理信息
+func (r *SysMenuImpl) DeleteMenuById(menuId string) int64 {
+ sql := "delete from sys_menu where menu_id = ?"
+ results, err := datasource.ExecDB("", sql, []any{menuId})
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
+
+// CheckUniqueMenu 校验菜单是否唯一
+func (r *SysMenuImpl) CheckUniqueMenu(sysMenu model.SysMenu) string {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysMenu.MenuName != "" {
+ conditions = append(conditions, "menu_name = ?")
+ params = append(params, sysMenu.MenuName)
+ }
+ if sysMenu.ParentID != "" {
+ conditions = append(conditions, "parent_id = ?")
+ params = append(params, sysMenu.ParentID)
+ }
+ if sysMenu.Path != "" {
+ conditions = append(conditions, "path = ?")
+ params = append(params, sysMenu.Path)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+ if whereSql == "" {
+ return ""
+ }
+
+ // 查询数据
+ querySql := "select menu_id as 'str' from sys_menu " + whereSql + " limit 1"
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err %v", err)
+ return ""
+ }
+ if len(results) > 0 {
+ return fmt.Sprint(results[0]["str"])
+ }
+ return ""
+}
diff --git a/src/modules/system/repository/sys_post.go b/src/modules/system/repository/sys_post.go
new file mode 100644
index 0000000..3683ea9
--- /dev/null
+++ b/src/modules/system/repository/sys_post.go
@@ -0,0 +1,30 @@
+package repository
+
+import "nms_nbi/src/modules/system/model"
+
+// ISysPost 岗位表 数据层接口
+type ISysPost interface {
+ // SelectPostPage 查询岗位分页数据集合
+ SelectPostPage(query map[string]any) map[string]any
+
+ // SelectPostList 查询岗位数据集合
+ SelectPostList(sysPost model.SysPost) []model.SysPost
+
+ // SelectPostByIds 通过岗位ID查询岗位信息
+ SelectPostByIds(postIds []string) []model.SysPost
+
+ // SelectPostListByUserId 根据用户ID获取岗位选择框列表
+ SelectPostListByUserId(userId string) []model.SysPost
+
+ // DeletePostByIds 批量删除岗位信息
+ DeletePostByIds(postIds []string) int64
+
+ // UpdatePost 修改岗位信息
+ UpdatePost(sysPost model.SysPost) int64
+
+ // InsertPost 新增岗位信息
+ InsertPost(sysPost model.SysPost) string
+
+ // CheckUniquePost 校验岗位唯一
+ CheckUniquePost(sysPost model.SysPost) string
+}
diff --git a/src/modules/system/repository/sys_post.impl.go b/src/modules/system/repository/sys_post.impl.go
new file mode 100644
index 0000000..fef22df
--- /dev/null
+++ b/src/modules/system/repository/sys_post.impl.go
@@ -0,0 +1,321 @@
+package repository
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/repo"
+ "nms_nbi/src/modules/system/model"
+)
+
+// 实例化数据层 SysPostImpl 结构体
+var NewSysPostImpl = &SysPostImpl{
+ selectSql: `select
+ post_id, post_code, post_name, post_sort, status, create_by, create_time, remark
+ from sys_post`,
+
+ resultMap: map[string]string{
+ "post_id": "PostID",
+ "post_code": "PostCode",
+ "post_name": "PostName",
+ "post_sort": "PostSort",
+ "status": "Status",
+ "create_by": "CreateBy",
+ "create_time": "CreateTime",
+ "update_by": "UpdateBy",
+ "update_time": "UpdateTime",
+ "remark": "Remark",
+ },
+}
+
+// SysPostImpl 岗位表 数据层处理
+type SysPostImpl struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *SysPostImpl) convertResultRows(rows []map[string]any) []model.SysPost {
+ arr := make([]model.SysPost, 0)
+ for _, row := range rows {
+ sysPost := model.SysPost{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ repo.SetFieldValue(&sysPost, keyMapper, value)
+ }
+ }
+ arr = append(arr, sysPost)
+ }
+ return arr
+}
+
+// SelectPostPage 查询岗位分页数据集合
+func (r *SysPostImpl) SelectPostPage(query map[string]any) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if v, ok := query["postCode"]; ok && v != "" {
+ conditions = append(conditions, "post_code like concat(?, '%')")
+ params = append(params, v)
+ }
+ if v, ok := query["postName"]; ok && v != "" {
+ conditions = append(conditions, "post_name like concat(?, '%')")
+ params = append(params, v)
+ }
+ if v, ok := query["status"]; ok && v != "" {
+ conditions = append(conditions, "status = ?")
+ params = append(params, v)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询结果
+ result := map[string]any{
+ "total": 0,
+ "rows": []model.SysPost{},
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := "select count(1) as 'total' from sys_post"
+ totalRows, err := datasource.RawDB("", totalSql+whereSql, params)
+ if err != nil {
+ logger.Errorf("total err => %v", err)
+ return result
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return result
+ } else {
+ result["total"] = total
+ }
+
+ // 分页
+ pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " order by post_sort limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ }
+
+ // 转换实体
+ result["rows"] = r.convertResultRows(results)
+ return result
+}
+
+// SelectPostList 查询岗位数据集合
+func (r *SysPostImpl) SelectPostList(sysPost model.SysPost) []model.SysPost {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysPost.PostCode != "" {
+ conditions = append(conditions, "post_code like concat(?, '%')")
+ params = append(params, sysPost.PostCode)
+ }
+ if sysPost.PostName != "" {
+ conditions = append(conditions, "post_name like concat(?, '%')")
+ params = append(params, sysPost.PostName)
+ }
+ if sysPost.Status != "" {
+ conditions = append(conditions, "status = ?")
+ params = append(params, sysPost.Status)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ orderSql := " order by post_sort"
+ querySql := r.selectSql + whereSql + orderSql
+ rows, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.SysPost{}
+ }
+ return r.convertResultRows(rows)
+}
+
+// SelectPostByIds 通过岗位ID查询岗位信息
+func (r *SysPostImpl) SelectPostByIds(postIds []string) []model.SysPost {
+ placeholder := repo.KeyPlaceholderByQuery(len(postIds))
+ querySql := r.selectSql + " where post_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(postIds)
+ results, err := datasource.RawDB("", querySql, parameters)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.SysPost{}
+ }
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// SelectPostListByUserId 根据用户ID获取岗位选择框列表
+func (r *SysPostImpl) SelectPostListByUserId(userId string) []model.SysPost {
+ // 查询数据
+ querySql := `select distinct
+ p.post_id, p.post_name, p.post_code
+ from sys_post p
+ left join sys_user_post up on up.post_id = p.post_id
+ left join sys_user u on u.user_id = up.user_id
+ where u.user_id = ? order by p.post_id`
+ rows, err := datasource.RawDB("", querySql, []any{userId})
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.SysPost{}
+ }
+ return r.convertResultRows(rows)
+}
+
+// DeletePostByIds 批量删除岗位信息
+func (r *SysPostImpl) DeletePostByIds(postIds []string) int64 {
+ placeholder := repo.KeyPlaceholderByQuery(len(postIds))
+ sql := "delete from sys_post where post_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(postIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
+
+// UpdatePost 修改岗位信息
+func (r *SysPostImpl) UpdatePost(sysPost model.SysPost) int64 {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysPost.PostCode != "" {
+ params["post_code"] = sysPost.PostCode
+ }
+ if sysPost.PostName != "" {
+ params["post_name"] = sysPost.PostName
+ }
+ if sysPost.PostSort > 0 {
+ params["post_sort"] = sysPost.PostSort
+ }
+ if sysPost.Status != "" {
+ params["status"] = sysPost.Status
+ }
+ params["remark"] = sysPost.Remark
+ if sysPost.UpdateBy != "" {
+ params["update_by"] = sysPost.UpdateBy
+ params["update_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, values := repo.KeyValueByUpdate(params)
+ sql := "update sys_post set " + strings.Join(keys, ",") + " where post_id = ?"
+
+ // 执行更新
+ values = append(values, sysPost.PostID)
+ rows, err := datasource.ExecDB("", sql, values)
+ if err != nil {
+ logger.Errorf("update row : %v", err.Error())
+ return 0
+ }
+ return rows
+}
+
+// InsertPost 新增岗位信息
+func (r *SysPostImpl) InsertPost(sysPost model.SysPost) string {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysPost.PostID != "" {
+ params["post_id"] = sysPost.PostID
+ }
+ if sysPost.PostCode != "" {
+ params["post_code"] = sysPost.PostCode
+ }
+ if sysPost.PostName != "" {
+ params["post_name"] = sysPost.PostName
+ }
+ if sysPost.PostSort > 0 {
+ params["post_sort"] = sysPost.PostSort
+ }
+ if sysPost.Status != "" {
+ params["status"] = sysPost.Status
+ }
+ if sysPost.Remark != "" {
+ params["remark"] = sysPost.Remark
+ }
+ if sysPost.CreateBy != "" {
+ params["create_by"] = sysPost.CreateBy
+ params["create_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params)
+ sql := "insert into sys_post (" + strings.Join(keys, ",") + ")values(" + placeholder + ")"
+
+ db := datasource.DefaultDB()
+ // 开启事务
+ tx := db.Begin()
+ // 执行插入
+ err := tx.Exec(sql, values...).Error
+ if err != nil {
+ logger.Errorf("insert row : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 获取生成的自增 ID
+ var insertedID string
+ err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID)
+ if err != nil {
+ logger.Errorf("insert last id : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 提交事务
+ tx.Commit()
+ return insertedID
+}
+
+// CheckUniquePost 校验岗位唯一
+func (r *SysPostImpl) CheckUniquePost(sysPost model.SysPost) string {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysPost.PostName != "" {
+ conditions = append(conditions, "post_name= ?")
+ params = append(params, sysPost.PostName)
+ }
+ if sysPost.PostCode != "" {
+ conditions = append(conditions, "post_code = ?")
+ params = append(params, sysPost.PostCode)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ } else {
+ return ""
+ }
+
+ // 查询数据
+ querySql := "select post_id as 'str' from sys_post " + whereSql + " limit 1"
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err %v", err)
+ }
+ if len(results) > 0 {
+ return fmt.Sprint(results[0]["str"])
+ }
+ return ""
+}
diff --git a/src/modules/system/repository/sys_role.go b/src/modules/system/repository/sys_role.go
new file mode 100644
index 0000000..f15dad0
--- /dev/null
+++ b/src/modules/system/repository/sys_role.go
@@ -0,0 +1,30 @@
+package repository
+
+import "nms_nbi/src/modules/system/model"
+
+// ISysRole 角色表 数据层接口
+type ISysRole interface {
+ // SelectRolePage 根据条件分页查询角色数据
+ SelectRolePage(query map[string]any, dataScopeSQL string) map[string]any
+
+ // SelectRoleList 根据条件查询角色数据
+ SelectRoleList(sysRole model.SysRole, dataScopeSQL string) []model.SysRole
+
+ // SelectRoleListByUserId 根据用户ID获取角色选择框列表
+ SelectRoleListByUserId(userId string) []model.SysRole
+
+ // SelectRoleByIds 通过角色ID查询角色
+ SelectRoleByIds(roleIds []string) []model.SysRole
+
+ // UpdateRole 修改角色信息
+ UpdateRole(sysRole model.SysRole) int64
+
+ // InsertRole 新增角色信息
+ InsertRole(sysRole model.SysRole) string
+
+ // DeleteRoleByIds 批量删除角色信息
+ DeleteRoleByIds(roleIds []string) int64
+
+ // CheckUniqueRole 校验角色是否唯一
+ CheckUniqueRole(sysRole model.SysRole) string
+}
diff --git a/src/modules/system/repository/sys_role.impl.go b/src/modules/system/repository/sys_role.impl.go
new file mode 100644
index 0000000..e3d5265
--- /dev/null
+++ b/src/modules/system/repository/sys_role.impl.go
@@ -0,0 +1,378 @@
+package repository
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/date"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/repo"
+ "nms_nbi/src/modules/system/model"
+)
+
+// 实例化数据层 SysRoleImpl 结构体
+var NewSysRoleImpl = &SysRoleImpl{
+ selectSql: `select distinct
+ r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.menu_check_strictly,
+ r.dept_check_strictly, r.status, r.del_flag, r.create_time, r.remark
+ from sys_role r
+ left join sys_user_role ur on ur.role_id = r.role_id
+ left join sys_user u on u.user_id = ur.user_id
+ left join sys_dept d on u.dept_id = d.dept_id`,
+
+ resultMap: map[string]string{
+ "role_id": "RoleID",
+ "role_name": "RoleName",
+ "role_key": "RoleKey",
+ "role_sort": "RoleSort",
+ "data_scope": "DataScope",
+ "menu_check_strictly": "MenuCheckStrictly",
+ "dept_check_strictly": "DeptCheckStrictly",
+ "status": "Status",
+ "del_flag": "DelFlag",
+ "create_by": "CreateBy",
+ "create_time": "CreateTime",
+ "update_by": "UpdateBy",
+ "update_time": "UpdateTime",
+ "remark": "Remark",
+ },
+}
+
+// SysRoleImpl 角色表 数据层处理
+type SysRoleImpl struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 结果字段与实体映射
+ resultMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *SysRoleImpl) convertResultRows(rows []map[string]any) []model.SysRole {
+ arr := make([]model.SysRole, 0)
+ for _, row := range rows {
+ sysRole := model.SysRole{}
+ for key, value := range row {
+ if keyMapper, ok := r.resultMap[key]; ok {
+ repo.SetFieldValue(&sysRole, keyMapper, value)
+ }
+ }
+ arr = append(arr, sysRole)
+ }
+ return arr
+}
+
+// SelectRolePage 根据条件分页查询角色数据
+func (r *SysRoleImpl) SelectRolePage(query map[string]any, dataScopeSQL string) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if v, ok := query["roleId"]; ok && v != "" {
+ conditions = append(conditions, "r.role_id = ?")
+ params = append(params, v)
+ }
+ if v, ok := query["roleName"]; ok && v != "" {
+ conditions = append(conditions, "r.role_name like concat(?, '%')")
+ params = append(params, v)
+ }
+ if v, ok := query["roleKey"]; ok && v != "" {
+ conditions = append(conditions, "r.role_key like concat(?, '%')")
+ params = append(params, v)
+ }
+ if v, ok := query["status"]; ok && v != "" {
+ conditions = append(conditions, "r.status = ?")
+ params = append(params, v)
+ }
+ beginTime, ok := query["beginTime"]
+ if !ok {
+ beginTime, ok = query["params[beginTime]"]
+ }
+ if ok && beginTime != "" {
+ conditions = append(conditions, "r.create_time >= ?")
+ beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD)
+ params = append(params, beginDate.UnixMilli())
+ }
+ endTime, ok := query["endTime"]
+ if !ok {
+ endTime, ok = query["params[endTime]"]
+ }
+ if ok && endTime != "" {
+ conditions = append(conditions, "r.create_time <= ?")
+ endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD)
+ params = append(params, endDate.UnixMilli())
+ }
+ if v, ok := query["deptId"]; ok && v != "" {
+ conditions = append(conditions, `(u.dept_id = ? or u.dept_id in (
+ select t.dept_id from sys_dept t where find_in_set(?, ancestors)
+ ))`)
+ params = append(params, v)
+ params = append(params, v)
+ }
+
+ // 构建查询条件语句
+ whereSql := " where r.del_flag = '0' "
+ if len(conditions) > 0 {
+ whereSql += " and " + strings.Join(conditions, " and ")
+ }
+
+ // 查询结果
+ result := map[string]any{
+ "total": 0,
+ "rows": []model.SysRole{},
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := `select count(distinct r.role_id) as 'total' from sys_role r
+ left join sys_user_role ur on ur.role_id = r.role_id
+ left join sys_user u on u.user_id = ur.user_id
+ left join sys_dept d on u.dept_id = d.dept_id`
+ totalRows, err := datasource.RawDB("", totalSql+whereSql+dataScopeSQL, params)
+ if err != nil {
+ logger.Errorf("total err => %v", err)
+ return result
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return result
+ } else {
+ result["total"] = total
+ }
+
+ // 分页
+ pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " order by r.role_sort asc limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + dataScopeSQL + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return result
+ }
+
+ // 转换实体
+ result["rows"] = r.convertResultRows(results)
+ return result
+}
+
+// SelectRoleList 根据条件查询角色数据
+func (r *SysRoleImpl) SelectRoleList(sysRole model.SysRole, dataScopeSQL string) []model.SysRole {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysRole.RoleID != "" {
+ conditions = append(conditions, "r.role_id = ?")
+ params = append(params, sysRole.RoleID)
+ }
+ if sysRole.RoleKey != "" {
+ conditions = append(conditions, "r.role_key like concat(?, '%')")
+ params = append(params, sysRole.RoleKey)
+ }
+ if sysRole.RoleName != "" {
+ conditions = append(conditions, "r.role_name like concat(?, '%')")
+ params = append(params, sysRole.RoleName)
+ }
+ if sysRole.Status != "" {
+ conditions = append(conditions, "r.status = ?")
+ params = append(params, sysRole.Status)
+ }
+
+ // 构建查询条件语句
+ whereSql := " where r.del_flag = '0' "
+ if len(conditions) > 0 {
+ whereSql += " and " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ orderSql := " order by r.role_sort"
+ querySql := r.selectSql + whereSql + dataScopeSQL + orderSql
+ rows, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.SysRole{}
+ }
+ return r.convertResultRows(rows)
+}
+
+// SelectRoleListByUserId 根据用户ID获取角色选择框列表
+func (r *SysRoleImpl) SelectRoleListByUserId(userId string) []model.SysRole {
+ querySql := r.selectSql + " where r.del_flag = '0' and ur.user_id = ?"
+ results, err := datasource.RawDB("", querySql, []any{userId})
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.SysRole{}
+ }
+ return r.convertResultRows(results)
+}
+
+// SelectRoleByIds 通过角色ID查询角色
+func (r *SysRoleImpl) SelectRoleByIds(roleIds []string) []model.SysRole {
+ placeholder := repo.KeyPlaceholderByQuery(len(roleIds))
+ querySql := r.selectSql + " where r.role_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(roleIds)
+ results, err := datasource.RawDB("", querySql, parameters)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.SysRole{}
+ }
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// UpdateRole 修改角色信息
+func (r *SysRoleImpl) UpdateRole(sysRole model.SysRole) int64 {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysRole.RoleName != "" {
+ params["role_name"] = sysRole.RoleName
+ }
+ if sysRole.RoleKey != "" {
+ params["role_key"] = sysRole.RoleKey
+ }
+ if sysRole.RoleSort > 0 {
+ params["role_sort"] = sysRole.RoleSort
+ }
+ if sysRole.DataScope != "" {
+ params["data_scope"] = sysRole.DataScope
+ }
+ if sysRole.MenuCheckStrictly != "" {
+ params["menu_check_strictly"] = sysRole.MenuCheckStrictly
+ }
+ if sysRole.DeptCheckStrictly != "" {
+ params["dept_check_strictly"] = sysRole.DeptCheckStrictly
+ }
+ if sysRole.Status != "" {
+ params["status"] = sysRole.Status
+ }
+ params["remark"] = sysRole.Remark
+ if sysRole.UpdateBy != "" {
+ params["update_by"] = sysRole.UpdateBy
+ params["update_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, values := repo.KeyValueByUpdate(params)
+ sql := "update sys_role set " + strings.Join(keys, ",") + " where role_id = ?"
+
+ // 执行更新
+ values = append(values, sysRole.RoleID)
+ rows, err := datasource.ExecDB("", sql, values)
+ if err != nil {
+ logger.Errorf("update row : %v", err.Error())
+ return 0
+ }
+ return rows
+}
+
+// InsertRole 新增角色信息
+func (r *SysRoleImpl) InsertRole(sysRole model.SysRole) string {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysRole.RoleID != "" {
+ params["role_id"] = sysRole.RoleID
+ }
+ if sysRole.RoleName != "" {
+ params["role_name"] = sysRole.RoleName
+ }
+ if sysRole.RoleKey != "" {
+ params["role_key"] = sysRole.RoleKey
+ }
+ if sysRole.RoleSort > 0 {
+ params["role_sort"] = sysRole.RoleSort
+ }
+ if sysRole.DataScope != "" {
+ params["data_scope"] = sysRole.DataScope
+ }
+ if sysRole.MenuCheckStrictly != "" {
+ params["menu_check_strictly"] = sysRole.MenuCheckStrictly
+ }
+ if sysRole.DeptCheckStrictly != "" {
+ params["dept_check_strictly"] = sysRole.DeptCheckStrictly
+ }
+ if sysRole.Status != "" {
+ params["status"] = sysRole.Status
+ }
+ params["remark"] = sysRole.Remark
+ if sysRole.CreateBy != "" {
+ params["create_by"] = sysRole.CreateBy
+ params["create_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params)
+ sql := "insert into sys_role (" + strings.Join(keys, ",") + ")values(" + placeholder + ")"
+
+ db := datasource.DefaultDB()
+ // 开启事务
+ tx := db.Begin()
+ // 执行插入
+ err := tx.Exec(sql, values...).Error
+ if err != nil {
+ logger.Errorf("insert row : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 获取生成的自增 ID
+ var insertedID string
+ err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID)
+ if err != nil {
+ logger.Errorf("insert last id : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 提交事务
+ tx.Commit()
+ return insertedID
+}
+
+// DeleteRoleByIds 批量删除角色信息
+func (r *SysRoleImpl) DeleteRoleByIds(roleIds []string) int64 {
+ placeholder := repo.KeyPlaceholderByQuery(len(roleIds))
+ sql := "update sys_role set del_flag = '1' where role_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(roleIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
+
+// CheckUniqueRole 校验角色是否唯一
+func (r *SysRoleImpl) CheckUniqueRole(sysRole model.SysRole) string {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysRole.RoleName != "" {
+ conditions = append(conditions, "r.role_name = ?")
+ params = append(params, sysRole.RoleName)
+ }
+ if sysRole.RoleKey != "" {
+ conditions = append(conditions, "r.role_key = ?")
+ params = append(params, sysRole.RoleKey)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ } else {
+ return ""
+ }
+
+ // 查询数据
+ querySql := "select role_id as 'str' from sys_role r " + whereSql + " and r.del_flag = '0' limit 1"
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err %v", err)
+ return ""
+ }
+ if len(results) > 0 {
+ return fmt.Sprint(results[0]["str"])
+ }
+ return ""
+}
diff --git a/src/modules/system/repository/sys_role_dept.go b/src/modules/system/repository/sys_role_dept.go
new file mode 100644
index 0000000..885f1e3
--- /dev/null
+++ b/src/modules/system/repository/sys_role_dept.go
@@ -0,0 +1,15 @@
+package repository
+
+import "nms_nbi/src/modules/system/model"
+
+// ISysRoleDept 角色与部门关联表 数据层接口
+type ISysRoleDept interface {
+ // DeleteRoleDept 批量删除角色部门关联信息
+ DeleteRoleDept(roleIds []string) int64
+
+ // DeleteDeptRole 批量删除部门角色关联信息
+ DeleteDeptRole(deptIds []string) int64
+
+ // BatchRoleDept 批量新增角色部门信息
+ BatchRoleDept(sysRoleDepts []model.SysRoleDept) int64
+}
diff --git a/src/modules/system/repository/sys_role_dept.impl.go b/src/modules/system/repository/sys_role_dept.impl.go
new file mode 100644
index 0000000..0ea52e6
--- /dev/null
+++ b/src/modules/system/repository/sys_role_dept.impl.go
@@ -0,0 +1,58 @@
+package repository
+
+import (
+ "fmt"
+ "strings"
+
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/repo"
+ "nms_nbi/src/modules/system/model"
+)
+
+// 实例化数据层 SysRoleDeptImpl 结构体
+var NewSysRoleDeptImpl = &SysRoleDeptImpl{}
+
+// SysRoleDeptImpl 角色与部门关联表 数据层处理
+type SysRoleDeptImpl struct{}
+
+// DeleteRoleDept 批量删除角色部门关联信息
+func (r *SysRoleDeptImpl) DeleteRoleDept(roleIds []string) int64 {
+ placeholder := repo.KeyPlaceholderByQuery(len(roleIds))
+ sql := "delete from sys_role_dept where role_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(roleIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
+
+// DeleteDeptRole 批量删除部门角色关联信息
+func (r *SysRoleDeptImpl) DeleteDeptRole(deptIds []string) int64 {
+ placeholder := repo.KeyPlaceholderByQuery(len(deptIds))
+ sql := "delete from sys_role_dept where dept_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(deptIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
+
+// BatchRoleDept 批量新增角色部门信息
+func (r *SysRoleDeptImpl) BatchRoleDept(sysRoleDepts []model.SysRoleDept) int64 {
+ keyValues := make([]string, 0)
+ for _, item := range sysRoleDepts {
+ keyValues = append(keyValues, fmt.Sprintf("(%s,%s)", item.RoleID, item.DeptID))
+ }
+ sql := "insert into sys_role_dept(role_id, dept_id) values " + strings.Join(keyValues, ",")
+ results, err := datasource.ExecDB("", sql, nil)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
diff --git a/src/modules/system/repository/sys_role_menu.go b/src/modules/system/repository/sys_role_menu.go
new file mode 100644
index 0000000..1fed97c
--- /dev/null
+++ b/src/modules/system/repository/sys_role_menu.go
@@ -0,0 +1,18 @@
+package repository
+
+import "nms_nbi/src/modules/system/model"
+
+// ISysRoleMenu 角色与菜单关联表 数据层接口
+type ISysRoleMenu interface {
+ // CheckMenuExistRole 查询菜单分配给角色使用数量
+ CheckMenuExistRole(menuId string) int64
+
+ // DeleteRoleMenu 批量删除角色和菜单关联
+ DeleteRoleMenu(roleIds []string) int64
+
+ // DeleteMenuRole 批量删除菜单和角色关联
+ DeleteMenuRole(menuIds []string) int64
+
+ // BatchRoleMenu 批量新增角色菜单信息
+ BatchRoleMenu(sysRoleMenus []model.SysRoleMenu) int64
+}
diff --git a/src/modules/system/repository/sys_role_menu.impl.go b/src/modules/system/repository/sys_role_menu.impl.go
new file mode 100644
index 0000000..9307f2f
--- /dev/null
+++ b/src/modules/system/repository/sys_role_menu.impl.go
@@ -0,0 +1,73 @@
+package repository
+
+import (
+ "fmt"
+ "strings"
+
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/repo"
+ "nms_nbi/src/modules/system/model"
+)
+
+// 实例化数据层 SysRoleMenuImpl 结构体
+var NewSysRoleMenuImpl = &SysRoleMenuImpl{}
+
+// SysRoleMenuImpl 角色与菜单关联表 数据层处理
+type SysRoleMenuImpl struct{}
+
+// CheckMenuExistRole 查询菜单分配给角色使用数量
+func (r *SysRoleMenuImpl) CheckMenuExistRole(menuId string) int64 {
+ querySql := "select count(1) as 'total' from sys_role_menu where menu_id = ?"
+ results, err := datasource.RawDB("", querySql, []any{menuId})
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return 0
+ }
+ if len(results) > 0 {
+ return parse.Number(results[0]["total"])
+ }
+ return 0
+}
+
+// DeleteRoleMenu 批量删除角色和菜单关联
+func (r *SysRoleMenuImpl) DeleteRoleMenu(roleIds []string) int64 {
+ placeholder := repo.KeyPlaceholderByQuery(len(roleIds))
+ sql := "delete from sys_role_menu where role_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(roleIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
+
+// DeleteMenuRole 批量删除菜单和角色关联
+func (r *SysRoleMenuImpl) DeleteMenuRole(menuIds []string) int64 {
+ placeholder := repo.KeyPlaceholderByQuery(len(menuIds))
+ sql := "delete from sys_role_menu where menu_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(menuIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
+
+// BatchRoleMenu 批量新增角色菜单信息
+func (r *SysRoleMenuImpl) BatchRoleMenu(sysRoleMenus []model.SysRoleMenu) int64 {
+ keyValues := make([]string, 0)
+ for _, item := range sysRoleMenus {
+ keyValues = append(keyValues, fmt.Sprintf("(%s,%s)", item.RoleID, item.MenuID))
+ }
+ sql := "insert into sys_role_menu(role_id, menu_id) values " + strings.Join(keyValues, ",")
+ results, err := datasource.ExecDB("", sql, nil)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
diff --git a/src/modules/system/repository/sys_user.go b/src/modules/system/repository/sys_user.go
new file mode 100644
index 0000000..b4971fd
--- /dev/null
+++ b/src/modules/system/repository/sys_user.go
@@ -0,0 +1,33 @@
+package repository
+
+import "nms_nbi/src/modules/system/model"
+
+// ISysUser 用户表 数据层接口
+type ISysUser interface {
+ // SelectUserPage 根据条件分页查询用户列表
+ SelectUserPage(query map[string]any, dataScopeSQL string) map[string]any
+
+ // SelectAllocatedPage 根据条件分页查询分配用户角色列表
+ SelectAllocatedPage(query map[string]any, dataScopeSQL string) map[string]any
+
+ // SelectUserList 根据条件查询用户列表
+ SelectUserList(sysUser model.SysUser, dataScopeSQL string) []model.SysUser
+
+ // SelectUserByIds 通过用户ID查询用户
+ SelectUserByIds(userIds []string) []model.SysUser
+
+ // SelectUserByUserName 通过用户登录账号查询用户
+ SelectUserByUserName(userName string) model.SysUser
+
+ // InsertUser 新增用户信息
+ InsertUser(sysUser model.SysUser) string
+
+ // UpdateUser 修改用户信息
+ UpdateUser(sysUser model.SysUser) int64
+
+ // DeleteUserByIds 批量删除用户信息
+ DeleteUserByIds(userIds []string) int64
+
+ // CheckUniqueUser 校验用户信息是否唯一
+ CheckUniqueUser(sysUser model.SysUser) string
+}
diff --git a/src/modules/system/repository/sys_user.impl.go b/src/modules/system/repository/sys_user.impl.go
new file mode 100644
index 0000000..4959002
--- /dev/null
+++ b/src/modules/system/repository/sys_user.impl.go
@@ -0,0 +1,564 @@
+package repository
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/crypto"
+ "nms_nbi/src/framework/utils/date"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/repo"
+ "nms_nbi/src/modules/system/model"
+)
+
+// 实例化数据层 SysUserImpl 结构体
+var NewSysUserImpl = &SysUserImpl{
+ selectSql: `select
+ u.user_id, u.dept_id, u.user_name, u.nick_name, u.user_type, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark,
+ d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status,
+ r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status
+ from sys_user u
+ left join sys_dept d on u.dept_id = d.dept_id
+ left join sys_user_role ur on u.user_id = ur.user_id
+ left join sys_role r on r.role_id = ur.role_id`,
+
+ sysUserMap: map[string]string{
+ "user_id": "UserID",
+ "dept_id": "DeptID",
+ "user_name": "UserName",
+ "nick_name": "NickName",
+ "user_type": "UserType",
+ "email": "Email",
+ "phonenumber": "PhoneNumber",
+ "sex": "Sex",
+ "avatar": "Avatar",
+ "password": "Password",
+ "status": "Status",
+ "del_flag": "DelFlag",
+ "login_ip": "LoginIP",
+ "login_date": "LoginDate",
+ "create_by": "CreateBy",
+ "create_time": "CreateTime",
+ "update_by": "UpdateBy",
+ "update_time": "UpdateTime",
+ "remark": "Remark",
+ },
+
+ sysDeptMap: map[string]string{
+ "dept_id": "DeptID",
+ "parent_id": "ParentID",
+ "dept_name": "DeptName",
+ "ancestors": "Ancestors",
+ "order_num": "OrderNum",
+ "leader": "Leader",
+ "dept_status": "Status",
+ },
+
+ sysRoleMap: map[string]string{
+ "role_id": "RoleID",
+ "role_name": "RoleName",
+ "role_key": "RoleKey",
+ "role_sort": "RoleSort",
+ "data_scope": "DataScope",
+ "role_status": "Status",
+ },
+}
+
+// SysUserImpl 用户表 数据层处理
+type SysUserImpl struct {
+ // 查询视图对象SQL
+ selectSql string
+ // 用户信息实体映射
+ sysUserMap map[string]string
+ // 用户部门实体映射 一对一
+ sysDeptMap map[string]string
+ // 用户角色实体映射 一对多
+ sysRoleMap map[string]string
+}
+
+// convertResultRows 将结果记录转实体结果组
+func (r *SysUserImpl) convertResultRows(rows []map[string]any) []model.SysUser {
+ arr := make([]model.SysUser, 0)
+
+ for _, row := range rows {
+ sysUser := model.SysUser{}
+ sysDept := model.SysDept{}
+ sysRole := model.SysRole{}
+ sysUser.Roles = []model.SysRole{}
+
+ for key, value := range row {
+ if keyMapper, ok := r.sysUserMap[key]; ok {
+ repo.SetFieldValue(&sysUser, keyMapper, value)
+ }
+ if keyMapper, ok := r.sysDeptMap[key]; ok {
+ repo.SetFieldValue(&sysDept, keyMapper, value)
+ }
+ if keyMapper, ok := r.sysRoleMap[key]; ok {
+ repo.SetFieldValue(&sysRole, keyMapper, value)
+ }
+ }
+
+ sysUser.Dept = sysDept
+ if sysRole.RoleKey != "" {
+ sysUser.Roles = append(sysUser.Roles, sysRole)
+ }
+
+ one := true
+ for i, a := range arr {
+ if a.UserID == sysUser.UserID {
+ arrUser := &arr[i]
+ arrUser.Roles = append(arrUser.Roles, sysUser.Roles...)
+ one = false
+ break
+ }
+ }
+ if one {
+ arr = append(arr, sysUser)
+ }
+ }
+
+ return arr
+}
+
+// SelectUserPage 根据条件分页查询用户列表
+func (r *SysUserImpl) SelectUserPage(query map[string]any, dataScopeSQL string) map[string]any {
+ selectUserTotalSql := `select count(distinct u.user_id) as 'total'
+ from sys_user u
+ left join sys_dept d on u.dept_id = d.dept_id
+ left join sys_user_role ur on u.user_id = ur.user_id
+ left join sys_role r on r.role_id = ur.role_id`
+
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if v, ok := query["userId"]; ok && v != "" {
+ conditions = append(conditions, "u.user_id = ?")
+ params = append(params, v)
+ }
+ if v, ok := query["userName"]; ok && v != "" {
+ conditions = append(conditions, "u.user_name like concat(?, '%')")
+ params = append(params, v)
+ }
+ if v, ok := query["status"]; ok && v != "" {
+ conditions = append(conditions, "u.status = ?")
+ params = append(params, v)
+ }
+ if v, ok := query["phonenumber"]; ok && v != "" {
+ conditions = append(conditions, "u.phonenumber like concat(?, '%')")
+ params = append(params, v)
+ }
+ beginTime, ok := query["beginTime"]
+ if !ok {
+ beginTime, ok = query["params[beginTime]"]
+ }
+ if ok && beginTime != "" {
+ conditions = append(conditions, "u.login_date >= ?")
+ beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD)
+ params = append(params, beginDate.UnixMilli())
+ }
+ endTime, ok := query["endTime"]
+ if !ok {
+ endTime, ok = query["params[endTime]"]
+ }
+ if ok && endTime != "" {
+ conditions = append(conditions, "u.login_date <= ?")
+ endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD)
+ params = append(params, endDate.UnixMilli())
+ }
+ if v, ok := query["deptId"]; ok && v != "" {
+ conditions = append(conditions, "(u.dept_id = ? or u.dept_id in ( select t.dept_id from sys_dept t where find_in_set(?, ancestors) ))")
+ params = append(params, v)
+ params = append(params, v)
+ }
+
+ // 构建查询条件语句
+ whereSql := " where u.del_flag = '0' "
+ if len(conditions) > 0 {
+ whereSql += " and " + strings.Join(conditions, " and ")
+ }
+
+ // 查询结果
+ result := map[string]any{
+ "total": 0,
+ "rows": []model.SysUser{},
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := selectUserTotalSql + whereSql + dataScopeSQL
+ totalRows, err := datasource.RawDB("", totalSql, params)
+ if err != nil {
+ logger.Errorf("total err => %v", err)
+ return result
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return result
+ } else {
+ result["total"] = total
+ }
+
+ // 分页
+ pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 查询数据
+ querySql := r.selectSql + whereSql + dataScopeSQL + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return result
+ }
+
+ // 转换实体
+ result["rows"] = r.convertResultRows(results)
+ return result
+}
+
+// SelectAllocatedPage 根据条件分页查询分配用户角色列表
+func (r *SysUserImpl) SelectAllocatedPage(query map[string]any, dataScopeSQL string) map[string]any {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if v, ok := query["userName"]; ok && v != "" {
+ conditions = append(conditions, "u.user_name like concat(?, '%')")
+ params = append(params, v)
+ }
+ if v, ok := query["phonenumber"]; ok && v != "" {
+ conditions = append(conditions, "u.phonenumber like concat(?, '%')")
+ params = append(params, v)
+ }
+ if v, ok := query["status"]; ok && v != "" {
+ conditions = append(conditions, "u.status = ?")
+ params = append(params, v)
+ }
+ // 分配角色用户
+ if allocated, ok := query["allocated"]; ok && allocated != "" {
+ if roleId, ok := query["roleId"]; ok && roleId != "" {
+ if parse.Boolean(allocated) {
+ conditions = append(conditions, "r.role_id = ?")
+ params = append(params, roleId)
+ } else {
+ conditions = append(conditions, `(r.role_id != ? or r.role_id IS NULL)
+ and u.user_id not in (
+ select u.user_id from sys_user u
+ inner join sys_user_role ur on u.user_id = ur.user_id
+ and ur.role_id = ?
+ )`)
+ params = append(params, roleId)
+ params = append(params, roleId)
+ }
+
+ }
+ }
+
+ // 构建查询条件语句
+ whereSql := " where u.del_flag = '0' "
+ if len(conditions) > 0 {
+ whereSql += " and " + strings.Join(conditions, " and ")
+ }
+
+ // 查询结果
+ result := map[string]any{
+ "total": 0,
+ "rows": []model.SysUser{},
+ }
+
+ // 查询数量 长度为0直接返回
+ totalSql := `select count(distinct u.user_id) as 'total' from sys_user u
+ left join sys_dept d on u.dept_id = d.dept_id
+ left join sys_user_role ur on u.user_id = ur.user_id
+ left join sys_role r on r.role_id = ur.role_id`
+ totalRows, err := datasource.RawDB("", totalSql+whereSql+dataScopeSQL, params)
+ if err != nil {
+ logger.Errorf("total err => %v", err)
+ return result
+ }
+ total := parse.Number(totalRows[0]["total"])
+ if total == 0 {
+ return result
+ } else {
+ result["total"] = total
+ }
+
+ // 分页
+ pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"])
+ pageSql := " limit ?,? "
+ params = append(params, pageNum*pageSize)
+ params = append(params, pageSize)
+
+ // 查询数据
+ querySql := `select distinct
+ u.user_id, u.dept_id, u.user_name, u.nick_name, u.email,
+ u.phonenumber, u.status, u.create_time, d.dept_name
+ from sys_user u
+ left join sys_dept d on u.dept_id = d.dept_id
+ left join sys_user_role ur on u.user_id = ur.user_id
+ left join sys_role r on r.role_id = ur.role_id`
+ querySql = querySql + whereSql + dataScopeSQL + pageSql
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ }
+
+ // 转换实体
+ result["rows"] = r.convertResultRows(results)
+ return result
+}
+
+// SelectUserList 根据条件查询用户列表
+func (r *SysUserImpl) SelectUserList(sysUser model.SysUser, dataScopeSQL string) []model.SysUser {
+ selectUserSql := `select
+ u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader
+ from sys_user u
+ left join sys_dept d on u.dept_id = d.dept_id`
+
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysUser.UserID != "" {
+ conditions = append(conditions, "u.user_id = ?")
+ params = append(params, sysUser.UserID)
+ }
+ if sysUser.UserName != "" {
+ conditions = append(conditions, "u.user_name like concat(?, '%')")
+ params = append(params, sysUser.UserName)
+ }
+ if sysUser.Status != "" {
+ conditions = append(conditions, "u.status = ?")
+ params = append(params, sysUser.Status)
+ }
+ if sysUser.PhoneNumber != "" {
+ conditions = append(conditions, "u.phonenumber like concat(?, '%')")
+ params = append(params, sysUser.PhoneNumber)
+ }
+
+ // 构建查询条件语句
+ whereSql := " where u.del_flag = '0' "
+ if len(conditions) > 0 {
+ whereSql += " and " + strings.Join(conditions, " and ")
+ }
+
+ // 查询数据
+ querySql := selectUserSql + whereSql + dataScopeSQL
+ rows, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.SysUser{}
+ }
+ return r.convertResultRows(rows)
+}
+
+// SelectUserByIds 通过用户ID查询用户
+func (r *SysUserImpl) SelectUserByIds(userIds []string) []model.SysUser {
+ placeholder := repo.KeyPlaceholderByQuery(len(userIds))
+ querySql := r.selectSql + " where u.del_flag = '0' and u.user_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(userIds)
+ results, err := datasource.RawDB("", querySql, parameters)
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return []model.SysUser{}
+ }
+ // 转换实体
+ return r.convertResultRows(results)
+}
+
+// SelectUserByUserName 通过用户登录账号查询用户
+func (r *SysUserImpl) SelectUserByUserName(userName string) model.SysUser {
+ querySql := r.selectSql + " where u.del_flag = '0' and u.user_name = ?"
+ results, err := datasource.RawDB("", querySql, []any{userName})
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return model.SysUser{}
+ }
+ // 转换实体
+ rows := r.convertResultRows(results)
+ if len(rows) > 0 {
+ return rows[0]
+ }
+ return model.SysUser{}
+}
+
+// InsertUser 新增用户信息
+func (r *SysUserImpl) InsertUser(sysUser model.SysUser) string {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysUser.UserID != "" {
+ params["user_id"] = sysUser.UserID
+ }
+ if sysUser.DeptID != "" {
+ params["dept_id"] = sysUser.DeptID
+ }
+ if sysUser.UserName != "" {
+ params["user_name"] = sysUser.UserName
+ }
+ if sysUser.NickName != "" {
+ params["nick_name"] = sysUser.NickName
+ }
+ if sysUser.UserType != "" {
+ params["user_type"] = sysUser.UserType
+ }
+ if sysUser.Avatar != "" {
+ params["avatar"] = sysUser.Avatar
+ }
+ if sysUser.Email != "" {
+ params["email"] = sysUser.Email
+ }
+ if sysUser.PhoneNumber != "" {
+ params["phonenumber"] = sysUser.PhoneNumber
+ }
+ if sysUser.Sex != "" {
+ params["sex"] = sysUser.Sex
+ }
+ if sysUser.Password != "" {
+ password := crypto.BcryptHash(sysUser.Password)
+ params["password"] = password
+ }
+ if sysUser.Status != "" {
+ params["status"] = sysUser.Status
+ }
+ if sysUser.Remark != "" {
+ params["remark"] = sysUser.Remark
+ }
+ if sysUser.CreateBy != "" {
+ params["create_by"] = sysUser.CreateBy
+ params["create_time"] = time.Now().UnixMilli()
+ }
+
+ // 构建执行语句
+ keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params)
+ sql := "insert into sys_user (" + strings.Join(keys, ",") + ")values(" + placeholder + ")"
+
+ db := datasource.DefaultDB()
+ // 开启事务
+ tx := db.Begin()
+ // 执行插入
+ err := tx.Exec(sql, values...).Error
+ if err != nil {
+ logger.Errorf("insert row : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 获取生成的自增 ID
+ var insertedID string
+ err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID)
+ if err != nil {
+ logger.Errorf("insert last id : %v", err.Error())
+ tx.Rollback()
+ return ""
+ }
+ // 提交事务
+ tx.Commit()
+ return insertedID
+}
+
+// UpdateUser 修改用户信息
+func (r *SysUserImpl) UpdateUser(sysUser model.SysUser) int64 {
+ // 参数拼接
+ params := make(map[string]any)
+ if sysUser.DeptID != "" {
+ params["dept_id"] = sysUser.DeptID
+ }
+ if sysUser.UserName != "" {
+ params["user_name"] = sysUser.UserName
+ }
+ if sysUser.NickName != "" {
+ params["nick_name"] = sysUser.NickName
+ }
+ if sysUser.UserType != "" {
+ params["user_type"] = sysUser.UserType
+ }
+ if sysUser.Avatar != "" {
+ params["avatar"] = sysUser.Avatar
+ }
+
+ params["email"] = sysUser.Email
+ params["phonenumber"] = sysUser.PhoneNumber
+ params["sex"] = sysUser.Sex
+ if sysUser.Password != "" {
+ password := crypto.BcryptHash(sysUser.Password)
+ params["password"] = password
+ }
+ if sysUser.Status != "" {
+ params["status"] = sysUser.Status
+ }
+ params["remark"] = sysUser.Remark
+ if sysUser.UpdateBy != "" {
+ params["update_by"] = sysUser.UpdateBy
+ params["update_time"] = time.Now().UnixMilli()
+ }
+ if sysUser.LoginIP != "" {
+ params["login_ip"] = sysUser.LoginIP
+ }
+ if sysUser.LoginDate > 0 {
+ params["login_date"] = sysUser.LoginDate
+ }
+
+ // 构建执行语句
+ keys, values := repo.KeyValueByUpdate(params)
+ sql := "update sys_user set " + strings.Join(keys, ",") + " where user_id = ?"
+
+ // 执行更新
+ values = append(values, sysUser.UserID)
+ rows, err := datasource.ExecDB("", sql, values)
+ if err != nil {
+ logger.Errorf("update row : %v", err.Error())
+ return 0
+ }
+ return rows
+}
+
+// DeleteUserByIds 批量删除用户信息
+func (r *SysUserImpl) DeleteUserByIds(userIds []string) int64 {
+ placeholder := repo.KeyPlaceholderByQuery(len(userIds))
+ sql := "update sys_user set del_flag = '1' where user_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(userIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ logger.Errorf("update err => %v", err)
+ return 0
+ }
+ return results
+}
+
+// CheckUniqueUser 校验用户信息是否唯一
+func (r *SysUserImpl) CheckUniqueUser(sysUser model.SysUser) string {
+ // 查询条件拼接
+ var conditions []string
+ var params []any
+ if sysUser.UserName != "" {
+ conditions = append(conditions, "user_name = ?")
+ params = append(params, sysUser.UserName)
+ }
+ if sysUser.PhoneNumber != "" {
+ conditions = append(conditions, "phonenumber = ?")
+ params = append(params, sysUser.PhoneNumber)
+ }
+ if sysUser.Email != "" {
+ conditions = append(conditions, "email = ?")
+ params = append(params, sysUser.Email)
+ }
+
+ // 构建查询条件语句
+ whereSql := ""
+ if len(conditions) > 0 {
+ whereSql += " where " + strings.Join(conditions, " and ")
+ } else {
+ return ""
+ }
+
+ // 查询数据
+ querySql := "select user_id as 'str' from sys_user " + whereSql + " limit 1"
+ results, err := datasource.RawDB("", querySql, params)
+ if err != nil {
+ logger.Errorf("query err %v", err)
+ }
+ if len(results) > 0 {
+ return fmt.Sprint(results[0]["str"])
+ }
+ return ""
+}
diff --git a/src/modules/system/repository/sys_user_post.go b/src/modules/system/repository/sys_user_post.go
new file mode 100644
index 0000000..ef87742
--- /dev/null
+++ b/src/modules/system/repository/sys_user_post.go
@@ -0,0 +1,15 @@
+package repository
+
+import "nms_nbi/src/modules/system/model"
+
+// ISysUserPost 用户与岗位关联表 数据层接口
+type ISysUserPost interface {
+ // CountUserPostByPostId 通过岗位ID查询岗位使用数量
+ CountUserPostByPostId(postId string) int64
+
+ // BatchUserPost 批量新增用户岗位信息
+ BatchUserPost(sysUserPosts []model.SysUserPost) int64
+
+ // DeleteUserPost 批量删除用户和岗位关联
+ DeleteUserPost(userIds []string) int64
+}
diff --git a/src/modules/system/repository/sys_user_post.impl.go b/src/modules/system/repository/sys_user_post.impl.go
new file mode 100644
index 0000000..69ebbb7
--- /dev/null
+++ b/src/modules/system/repository/sys_user_post.impl.go
@@ -0,0 +1,60 @@
+package repository
+
+import (
+ "fmt"
+ "strings"
+
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/repo"
+ "nms_nbi/src/modules/system/model"
+)
+
+// 实例化数据层 SysUserPostImpl 结构体
+var NewSysUserPostImpl = &SysUserPostImpl{}
+
+// SysUserPostImpl 用户与岗位关联表 数据层处理
+type SysUserPostImpl struct{}
+
+// CountUserPostByPostId 通过岗位ID查询岗位使用数量
+func (r *SysUserPostImpl) CountUserPostByPostId(postId string) int64 {
+ querySql := "select count(1) as total from sys_user_role where role_id = ?"
+ results, err := datasource.RawDB("", querySql, []any{postId})
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return 0
+ }
+ if len(results) > 0 {
+ return parse.Number(results[0]["total"])
+ }
+ return 0
+}
+
+// DeleteUserPost 批量删除用户和岗位关联
+func (r *SysUserPostImpl) DeleteUserPost(userIds []string) int64 {
+ placeholder := repo.KeyPlaceholderByQuery(len(userIds))
+ sql := "delete from sys_user_post where user_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(userIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
+
+// BatchUserPost 批量新增用户岗位信息
+func (r *SysUserPostImpl) BatchUserPost(sysUserPosts []model.SysUserPost) int64 {
+ keyValues := make([]string, 0)
+ for _, item := range sysUserPosts {
+ keyValues = append(keyValues, fmt.Sprintf("(%s,%s)", item.UserID, item.PostID))
+ }
+ sql := "insert into sys_user_post(user_id, post_id) values " + strings.Join(keyValues, ",")
+ results, err := datasource.ExecDB("", sql, nil)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
diff --git a/src/modules/system/repository/sys_user_role.go b/src/modules/system/repository/sys_user_role.go
new file mode 100644
index 0000000..9a5f77c
--- /dev/null
+++ b/src/modules/system/repository/sys_user_role.go
@@ -0,0 +1,18 @@
+package repository
+
+import "nms_nbi/src/modules/system/model"
+
+// ISysUserRole 用户与角色关联表 数据层接口
+type ISysUserRole interface {
+ // CountUserRoleByRoleId 通过角色ID查询角色使用数量
+ CountUserRoleByRoleId(roleId string) int64
+
+ // BatchUserRole 批量新增用户角色信息
+ BatchUserRole(sysUserRoles []model.SysUserRole) int64
+
+ // DeleteUserRole 批量删除用户和角色关联
+ DeleteUserRole(userIds []string) int64
+
+ // DeleteUserRoleByRoleId 批量取消授权用户角色
+ DeleteUserRoleByRoleId(roleId string, userIds []string) int64
+}
diff --git a/src/modules/system/repository/sys_user_role.impl.go b/src/modules/system/repository/sys_user_role.impl.go
new file mode 100644
index 0000000..5c4c095
--- /dev/null
+++ b/src/modules/system/repository/sys_user_role.impl.go
@@ -0,0 +1,74 @@
+package repository
+
+import (
+ "fmt"
+ "strings"
+
+ "nms_nbi/src/framework/datasource"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/repo"
+ "nms_nbi/src/modules/system/model"
+)
+
+// 实例化数据层 SysUserRoleImpl 结构体
+var NewSysUserRoleImpl = &SysUserRoleImpl{}
+
+// SysUserRoleImpl 用户与角色关联表 数据层处理
+type SysUserRoleImpl struct{}
+
+// CountUserRoleByRoleId 通过角色ID查询角色使用数量
+func (r *SysUserRoleImpl) CountUserRoleByRoleId(roleId string) int64 {
+ querySql := "select count(1) as total from sys_user_role where role_id = ?"
+ results, err := datasource.RawDB("", querySql, []any{roleId})
+ if err != nil {
+ logger.Errorf("query err => %v", err)
+ return 0
+ }
+ if len(results) > 0 {
+ return parse.Number(results[0]["total"])
+ }
+ return 0
+}
+
+// BatchUserRole 批量新增用户角色信息
+func (r *SysUserRoleImpl) BatchUserRole(sysUserRoles []model.SysUserRole) int64 {
+ keyValues := make([]string, 0)
+ for _, item := range sysUserRoles {
+ keyValues = append(keyValues, fmt.Sprintf("(%s,%s)", item.UserID, item.RoleID))
+ }
+ sql := "insert into sys_user_role(user_id, role_id) values " + strings.Join(keyValues, ",")
+ results, err := datasource.ExecDB("", sql, nil)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
+
+// DeleteUserRole 批量删除用户和角色关联
+func (r *SysUserRoleImpl) DeleteUserRole(userIds []string) int64 {
+ placeholder := repo.KeyPlaceholderByQuery(len(userIds))
+ sql := "delete from sys_user_role where user_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(userIds)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
+
+// DeleteUserRoleByRoleId 批量取消授权用户角色
+func (r *SysUserRoleImpl) DeleteUserRoleByRoleId(roleId string, userIds []string) int64 {
+ placeholder := repo.KeyPlaceholderByQuery(len(userIds))
+ sql := "delete from sys_user_role where role_id= ? and user_id in (" + placeholder + ")"
+ parameters := repo.ConvertIdsSlice(userIds)
+ parameters = append([]any{roleId}, parameters...)
+ results, err := datasource.ExecDB("", sql, parameters)
+ if err != nil {
+ logger.Errorf("delete err => %v", err)
+ return 0
+ }
+ return results
+}
diff --git a/src/modules/system/service/sys_config.go b/src/modules/system/service/sys_config.go
new file mode 100644
index 0000000..67c9e47
--- /dev/null
+++ b/src/modules/system/service/sys_config.go
@@ -0,0 +1,33 @@
+package service
+
+import "nms_nbi/src/modules/system/model"
+
+// ISysConfig 参数配置 服务层接口
+type ISysConfig interface {
+ // SelectDictDataPage 分页查询参数配置列表数据
+ SelectConfigPage(query map[string]any) map[string]any
+
+ // SelectConfigValueByKey 通过参数键名查询参数键值
+ SelectConfigValueByKey(configKey string) string
+
+ // SelectConfigById 通过配置ID查询参数配置信息
+ SelectConfigById(configId string) model.SysConfig
+
+ // CheckUniqueConfigKey 校验参数键名是否唯一
+ CheckUniqueConfigKey(configKey, configId string) bool
+
+ // InsertConfig 新增参数配置
+ InsertConfig(sysConfig model.SysConfig) string
+
+ // UpdateConfig 修改参数配置
+ UpdateConfig(sysConfig model.SysConfig) int64
+
+ // DeleteConfigByIds 批量删除参数配置信息
+ DeleteConfigByIds(configIds []string) (int64, error)
+
+ // ResetConfigCache 重置参数缓存数据
+ ResetConfigCache()
+
+ // SelectConfigByKey 查询配置信息BY键
+ SelectConfigByKey(configKey string) model.SysConfig
+}
diff --git a/src/modules/system/service/sys_config.impl.go b/src/modules/system/service/sys_config.impl.go
new file mode 100644
index 0000000..6907e5e
--- /dev/null
+++ b/src/modules/system/service/sys_config.impl.go
@@ -0,0 +1,170 @@
+package service
+
+import (
+ "fmt"
+
+ "nms_nbi/src/framework/constants/cachekey"
+ "nms_nbi/src/framework/redis"
+ "nms_nbi/src/modules/system/model"
+ "nms_nbi/src/modules/system/repository"
+)
+
+// 实例化服务层 SysConfigImpl 结构体
+var NewSysConfigImpl = &SysConfigImpl{
+ sysConfigRepository: repository.NewSysConfigImpl,
+}
+
+// SysConfigImpl 参数配置 服务层处理
+type SysConfigImpl struct {
+ // 参数配置表
+ sysConfigRepository repository.ISysConfig
+}
+
+// SelectDictDataPage 分页查询参数配置列表数据
+func (r *SysConfigImpl) SelectConfigPage(query map[string]any) map[string]any {
+ return r.sysConfigRepository.SelectConfigPage(query)
+}
+
+// SelectConfigList 查询参数配置列表
+func (r *SysConfigImpl) SelectConfigList(sysConfig model.SysConfig) []model.SysConfig {
+ return r.sysConfigRepository.SelectConfigList(sysConfig)
+}
+
+// SelectConfigValueByKey 通过参数键名查询参数键值
+func (r *SysConfigImpl) SelectConfigValueByKey(configKey string) string {
+ cacheKey := r.getCacheKey(configKey)
+ // 从缓存中读取
+ cacheValue, _ := redis.Get("", cacheKey)
+ if cacheValue != "" {
+ return cacheValue
+ }
+ // 无缓存时读取数据放入缓存中
+ configValue := r.sysConfigRepository.SelectConfigValueByKey(configKey)
+ if configValue != "" {
+ redis.Set("", cacheKey, configValue)
+ return configValue
+ }
+ return ""
+}
+
+// SelectConfigById 通过配置ID查询参数配置信息
+func (r *SysConfigImpl) SelectConfigById(configId string) model.SysConfig {
+ if configId == "" {
+ return model.SysConfig{}
+ }
+ configs := r.sysConfigRepository.SelectConfigByIds([]string{configId})
+ if len(configs) > 0 {
+ return configs[0]
+ }
+ return model.SysConfig{}
+}
+
+// CheckUniqueConfigKey 校验参数键名是否唯一
+func (r *SysConfigImpl) CheckUniqueConfigKey(configKey, configId string) bool {
+ uniqueId := r.sysConfigRepository.CheckUniqueConfig(model.SysConfig{
+ ConfigKey: configKey,
+ })
+ if uniqueId == configId {
+ return true
+ }
+ return uniqueId == ""
+}
+
+// InsertConfig 新增参数配置
+func (r *SysConfigImpl) InsertConfig(sysConfig model.SysConfig) string {
+ configId := r.sysConfigRepository.InsertConfig(sysConfig)
+ if configId != "" {
+ r.loadingConfigCache(sysConfig.ConfigKey)
+ }
+ return configId
+}
+
+// UpdateConfig 修改参数配置
+func (r *SysConfigImpl) UpdateConfig(sysConfig model.SysConfig) int64 {
+ rows := r.sysConfigRepository.UpdateConfig(sysConfig)
+ if rows > 0 {
+ r.loadingConfigCache(sysConfig.ConfigKey)
+ }
+ return rows
+}
+
+// DeleteConfigByIds 批量删除参数配置信息
+func (r *SysConfigImpl) DeleteConfigByIds(configIds []string) (int64, error) {
+ // 检查是否存在
+ configs := r.sysConfigRepository.SelectConfigByIds(configIds)
+ if len(configs) <= 0 {
+ return 0, fmt.Errorf("config.noData")
+ }
+ for _, config := range configs {
+ // 检查是否为内置参数
+ if config.ConfigType == "Y" {
+ // 操作含有内置参数,禁止删除!
+ return 0, fmt.Errorf("config.errType")
+ }
+ // 清除缓存
+ r.clearConfigCache(config.ConfigKey)
+ }
+ if len(configs) == len(configIds) {
+ rows := r.sysConfigRepository.DeleteConfigByIds(configIds)
+ return rows, nil
+ }
+ // 删除参数配置信息失败!
+ return 0, fmt.Errorf("config.errDelete")
+}
+
+// ResetConfigCache 重置参数缓存数据
+func (r *SysConfigImpl) ResetConfigCache() {
+ r.clearConfigCache("*")
+ r.loadingConfigCache("*")
+}
+
+// getCacheKey 组装缓存key
+func (r *SysConfigImpl) getCacheKey(configKey string) string {
+ return cachekey.SYS_CONFIG_KEY + configKey
+}
+
+// loadingConfigCache 加载参数缓存数据
+func (r *SysConfigImpl) loadingConfigCache(configKey string) {
+ // 查询全部参数
+ if configKey == "*" {
+ sysConfigs := r.SelectConfigList(model.SysConfig{})
+ for _, v := range sysConfigs {
+ key := r.getCacheKey(v.ConfigKey)
+ redis.Del("", key)
+ redis.Set("", key, v.ConfigValue)
+ }
+ return
+ }
+ // 指定参数
+ if configKey != "" {
+ cacheValue := r.sysConfigRepository.SelectConfigValueByKey(configKey)
+ if cacheValue != "" {
+ key := r.getCacheKey(configKey)
+ redis.Del("", key)
+ redis.Set("", key, cacheValue)
+ }
+ return
+ }
+}
+
+// clearConfigCache 清空参数缓存数据
+func (r *SysConfigImpl) clearConfigCache(configKey string) bool {
+ key := r.getCacheKey(configKey)
+ keys, err := redis.GetKeys("", key)
+ if err != nil {
+ return false
+ }
+ delOk, _ := redis.DelKeys("", keys)
+ return delOk
+}
+
+// SelectConfigByKey 查询配置信息BY键
+func (r *SysConfigImpl) SelectConfigByKey(configKey string) model.SysConfig {
+ sysConf := r.sysConfigRepository.SelectConfigList(model.SysConfig{
+ ConfigKey: configKey,
+ })
+ if len(sysConf) > 0 {
+ return sysConf[0]
+ }
+ return model.SysConfig{}
+}
diff --git a/src/modules/system/service/sys_dept.go b/src/modules/system/service/sys_dept.go
new file mode 100644
index 0000000..4c21a8d
--- /dev/null
+++ b/src/modules/system/service/sys_dept.go
@@ -0,0 +1,39 @@
+package service
+
+import (
+ "nms_nbi/src/framework/vo"
+ "nms_nbi/src/modules/system/model"
+)
+
+// ISysDept 部门管理 服务层接口
+type ISysDept interface {
+ // SelectDeptList 查询部门管理数据
+ SelectDeptList(sysDept model.SysDept, dataScopeSQL string) []model.SysDept
+
+ // SelectDeptListByRoleId 根据角色ID查询部门树信息
+ SelectDeptListByRoleId(roleId string) []string
+
+ // SelectDeptById 根据部门ID查询信息
+ SelectDeptById(deptId string) model.SysDept
+
+ // HasChildByDeptId 是否存在子节点
+ HasChildByDeptId(deptId string) int64
+
+ // CheckDeptExistUser 查询部门是否存在用户
+ CheckDeptExistUser(deptId string) int64
+
+ // CheckUniqueDeptName 校验同级部门名称是否唯一
+ CheckUniqueDeptName(deptName, parentId, deptId string) bool
+
+ // InsertDept 新增部门信息
+ InsertDept(sysDept model.SysDept) string
+
+ // UpdateDept 修改部门信息
+ UpdateDept(sysDept model.SysDept) int64
+
+ // DeleteDeptById 删除部门管理信息
+ DeleteDeptById(deptId string) int64
+
+ // SelectDeptTreeSelect 查询部门树结构信息
+ SelectDeptTreeSelect(sysDept model.SysDept, dataScopeSQL string) []vo.TreeSelect
+}
diff --git a/src/modules/system/service/sys_dept.impl.go b/src/modules/system/service/sys_dept.impl.go
new file mode 100644
index 0000000..993764a
--- /dev/null
+++ b/src/modules/system/service/sys_dept.impl.go
@@ -0,0 +1,202 @@
+package service
+
+import (
+ "strings"
+
+ "nms_nbi/src/framework/constants/common"
+ "nms_nbi/src/framework/vo"
+ "nms_nbi/src/modules/system/model"
+ "nms_nbi/src/modules/system/repository"
+)
+
+// 实例化服务层 SysDeptImpl 结构体
+var NewSysDeptImpl = &SysDeptImpl{
+ sysDeptRepository: repository.NewSysDeptImpl,
+ sysRoleRepository: repository.NewSysRoleImpl,
+ sysRoleDeptRepository: repository.NewSysRoleDeptImpl,
+}
+
+// SysDeptImpl 部门表 服务层处理
+type SysDeptImpl struct {
+ // 部门服务
+ sysDeptRepository repository.ISysDept
+ // 角色服务
+ sysRoleRepository repository.ISysRole
+ // 角色与部门关联服务
+ sysRoleDeptRepository repository.ISysRoleDept
+}
+
+// SelectDeptList 查询部门管理数据
+func (r *SysDeptImpl) SelectDeptList(sysDept model.SysDept, dataScopeSQL string) []model.SysDept {
+ return r.sysDeptRepository.SelectDeptList(sysDept, dataScopeSQL)
+}
+
+// SelectDeptListByRoleId 根据角色ID查询部门树信息 TODO
+func (r *SysDeptImpl) SelectDeptListByRoleId(roleId string) []string {
+ roles := r.sysRoleRepository.SelectRoleByIds([]string{roleId})
+ if len(roles) > 0 {
+ role := roles[0]
+ if role.RoleID == roleId {
+ return r.sysDeptRepository.SelectDeptListByRoleId(
+ role.RoleID,
+ role.DeptCheckStrictly == "1",
+ )
+ }
+ }
+ return []string{}
+}
+
+// SelectDeptById 根据部门ID查询信息
+func (r *SysDeptImpl) SelectDeptById(deptId string) model.SysDept {
+ return r.sysDeptRepository.SelectDeptById(deptId)
+}
+
+// HasChildByDeptId 是否存在子节点
+func (r *SysDeptImpl) HasChildByDeptId(deptId string) int64 {
+ return r.sysDeptRepository.HasChildByDeptId(deptId)
+}
+
+// CheckDeptExistUser 查询部门是否存在用户
+func (r *SysDeptImpl) CheckDeptExistUser(deptId string) int64 {
+ return r.sysDeptRepository.CheckDeptExistUser(deptId)
+}
+
+// CheckUniqueDeptName 校验同级部门名称是否唯一
+func (r *SysDeptImpl) CheckUniqueDeptName(deptName, parentId, deptId string) bool {
+ uniqueId := r.sysDeptRepository.CheckUniqueDept(model.SysDept{
+ DeptName: deptName,
+ ParentID: parentId,
+ })
+ if uniqueId == deptId {
+ return true
+ }
+ return uniqueId == ""
+}
+
+// InsertDept 新增部门信息
+func (r *SysDeptImpl) InsertDept(sysDept model.SysDept) string {
+ return r.sysDeptRepository.InsertDept(sysDept)
+}
+
+// UpdateDept 修改部门信息
+func (r *SysDeptImpl) UpdateDept(sysDept model.SysDept) int64 {
+ parentDept := r.sysDeptRepository.SelectDeptById(sysDept.ParentID)
+ dept := r.sysDeptRepository.SelectDeptById(sysDept.DeptID)
+ // 上级与当前部门祖级列表更新
+ if parentDept.DeptID == sysDept.ParentID && dept.DeptID == sysDept.DeptID {
+ newAncestors := parentDept.Ancestors + "," + parentDept.DeptID
+ oldAncestors := dept.Ancestors
+ // 祖级列表不一致时更新
+ if newAncestors != oldAncestors {
+ sysDept.Ancestors = newAncestors
+ r.updateDeptChildren(sysDept.DeptID, newAncestors, oldAncestors)
+ }
+ }
+ // 如果该部门是启用状态,则启用该部门的所有上级部门
+ if sysDept.Status == common.STATUS_YES && parentDept.Status == common.STATUS_NO {
+ r.updateDeptStatusNormal(sysDept.Ancestors)
+ }
+ return r.sysDeptRepository.UpdateDept(sysDept)
+}
+
+// updateDeptStatusNormal 修改所在部门正常状态
+func (r *SysDeptImpl) updateDeptStatusNormal(ancestors string) int64 {
+ if ancestors == "" || ancestors == "0" {
+ return 0
+ }
+ deptIds := strings.Split(ancestors, ",")
+ return r.sysDeptRepository.UpdateDeptStatusNormal(deptIds)
+}
+
+// updateDeptChildren 修改子元素关系
+func (r *SysDeptImpl) updateDeptChildren(deptId, newAncestors, oldAncestors string) int64 {
+ childrens := r.sysDeptRepository.SelectChildrenDeptById(deptId)
+ if len(childrens) == 0 {
+ return 0
+ }
+ // 替换父ID
+ for i := range childrens {
+ child := &childrens[i]
+ ancestors := strings.Replace(child.Ancestors, oldAncestors, newAncestors, -1)
+ child.Ancestors = ancestors
+ }
+ return r.sysDeptRepository.UpdateDeptChildren(childrens)
+}
+
+// DeleteDeptById 删除部门管理信息
+func (r *SysDeptImpl) DeleteDeptById(deptId string) int64 {
+ // 删除角色与部门关联
+ r.sysRoleDeptRepository.DeleteDeptRole([]string{deptId})
+ return r.sysDeptRepository.DeleteDeptById(deptId)
+}
+
+// SelectDeptTreeSelect 查询部门树结构信息
+func (r *SysDeptImpl) SelectDeptTreeSelect(sysDept model.SysDept, dataScopeSQL string) []vo.TreeSelect {
+ sysDepts := r.sysDeptRepository.SelectDeptList(sysDept, dataScopeSQL)
+ depts := r.parseDataToTree(sysDepts)
+ tree := make([]vo.TreeSelect, 0)
+ for _, dept := range depts {
+ tree = append(tree, vo.SysDeptTreeSelect(dept))
+ }
+ return tree
+}
+
+// parseDataToTree 将数据解析为树结构,构建前端所需要下拉树结构
+func (r *SysDeptImpl) parseDataToTree(sysDepts []model.SysDept) []model.SysDept {
+ // 节点分组
+ nodesMap := make(map[string][]model.SysDept)
+ // 节点id
+ treeIds := []string{}
+ // 树节点
+ tree := []model.SysDept{}
+
+ for _, item := range sysDepts {
+ parentID := item.ParentID
+ // 分组
+ mapItem, ok := nodesMap[parentID]
+ if !ok {
+ mapItem = []model.SysDept{}
+ }
+ mapItem = append(mapItem, item)
+ nodesMap[parentID] = mapItem
+ // 记录节点ID
+ treeIds = append(treeIds, item.DeptID)
+ }
+
+ for key, value := range nodesMap {
+ // 选择不是节点ID的作为树节点
+ found := false
+ for _, id := range treeIds {
+ if id == key {
+ found = true
+ break
+ }
+ }
+ if !found {
+ tree = append(tree, value...)
+ }
+ }
+
+ for i, node := range tree {
+ iN := r.parseDataToTreeComponet(node, &nodesMap)
+ tree[i] = iN
+ }
+
+ return tree
+}
+
+// parseDataToTreeComponet 递归函数处理子节点
+func (r *SysDeptImpl) parseDataToTreeComponet(node model.SysDept, nodesMap *map[string][]model.SysDept) model.SysDept {
+ id := node.DeptID
+ children, ok := (*nodesMap)[id]
+ if ok {
+ node.Children = children
+ }
+ if len(node.Children) > 0 {
+ for i, child := range node.Children {
+ icN := r.parseDataToTreeComponet(child, nodesMap)
+ node.Children[i] = icN
+ }
+ }
+ return node
+}
diff --git a/src/modules/system/service/sys_dict_data.go b/src/modules/system/service/sys_dict_data.go
new file mode 100644
index 0000000..c45cc81
--- /dev/null
+++ b/src/modules/system/service/sys_dict_data.go
@@ -0,0 +1,33 @@
+package service
+
+import "nms_nbi/src/modules/system/model"
+
+// ISysDictData 字典类型数据 服务层接口
+type ISysDictData interface {
+ // SelectDictDataPage 根据条件分页查询字典数据
+ SelectDictDataPage(query map[string]any) map[string]any
+
+ // SelectDictDataList 根据条件查询字典数据
+ SelectDictDataList(sysDictData model.SysDictData) []model.SysDictData
+
+ // SelectDictDataByCode 根据字典数据编码查询信息
+ SelectDictDataByCode(dictCode string) model.SysDictData
+
+ // SelectDictDataByType 根据字典类型查询信息
+ SelectDictDataByType(dictType string) []model.SysDictData
+
+ // CheckUniqueDictLabel 校验字典标签是否唯一
+ CheckUniqueDictLabel(dictType, dictLabel, dictCode string) bool
+
+ // CheckUniqueDictValue 校验字典键值是否唯一
+ CheckUniqueDictValue(dictType, dictValue, dictCode string) bool
+
+ // DeleteDictDataByCodes 批量删除字典数据信息
+ DeleteDictDataByCodes(dictCodes []string) (int64, error)
+
+ // InsertDictData 新增字典数据信息
+ InsertDictData(sysDictData model.SysDictData) string
+
+ // UpdateDictData 修改字典数据信息
+ UpdateDictData(sysDictData model.SysDictData) int64
+}
diff --git a/src/modules/system/service/sys_dict_data.impl.go b/src/modules/system/service/sys_dict_data.impl.go
new file mode 100644
index 0000000..243ed39
--- /dev/null
+++ b/src/modules/system/service/sys_dict_data.impl.go
@@ -0,0 +1,116 @@
+package service
+
+import (
+ "fmt"
+
+ "nms_nbi/src/modules/system/model"
+ "nms_nbi/src/modules/system/repository"
+)
+
+// 实例化服务层 SysDictDataImpl 结构体
+var NewSysDictDataImpl = &SysDictDataImpl{
+ sysDictDataRepository: repository.NewSysDictDataImpl,
+ sysDictTypeService: NewSysDictTypeImpl,
+}
+
+// SysDictDataImpl 字典类型数据 服务层处理
+type SysDictDataImpl struct {
+ // 字典数据服务
+ sysDictDataRepository repository.ISysDictData
+ // 字典类型服务
+ sysDictTypeService ISysDictType
+}
+
+// SelectDictDataPage 根据条件分页查询字典数据
+func (r *SysDictDataImpl) SelectDictDataPage(query map[string]any) map[string]any {
+ return r.sysDictDataRepository.SelectDictDataPage(query)
+}
+
+// SelectDictDataList 根据条件查询字典数据
+func (r *SysDictDataImpl) SelectDictDataList(sysDictData model.SysDictData) []model.SysDictData {
+ return r.sysDictDataRepository.SelectDictDataList(sysDictData)
+}
+
+// SelectDictDataByCode 根据字典数据编码查询信息
+func (r *SysDictDataImpl) SelectDictDataByCode(dictCode string) model.SysDictData {
+ if dictCode == "" {
+ return model.SysDictData{}
+ }
+ dictCodes := r.sysDictDataRepository.SelectDictDataByCodes([]string{dictCode})
+ if len(dictCodes) > 0 {
+ return dictCodes[0]
+ }
+ return model.SysDictData{}
+}
+
+// SelectDictDataByType 根据字典类型查询信息
+func (r *SysDictDataImpl) SelectDictDataByType(dictType string) []model.SysDictData {
+ return r.sysDictTypeService.DictDataCache(dictType)
+}
+
+// CheckUniqueDictLabel 校验字典标签是否唯一
+func (r *SysDictDataImpl) CheckUniqueDictLabel(dictType, dictLabel, dictCode string) bool {
+ uniqueId := r.sysDictDataRepository.CheckUniqueDictData(model.SysDictData{
+ DictType: dictType,
+ DictLabel: dictLabel,
+ })
+ if uniqueId == dictCode {
+ return true
+ }
+ return uniqueId == ""
+}
+
+// CheckUniqueDictValue 校验字典键值是否唯一
+func (r *SysDictDataImpl) CheckUniqueDictValue(dictType, dictValue, dictCode string) bool {
+ uniqueId := r.sysDictDataRepository.CheckUniqueDictData(model.SysDictData{
+ DictType: dictType,
+ DictValue: dictValue,
+ })
+ if uniqueId == dictCode {
+ return true
+ }
+ return uniqueId == ""
+}
+
+// DeleteDictDataByCodes 批量删除字典数据信息
+func (r *SysDictDataImpl) DeleteDictDataByCodes(dictCodes []string) (int64, error) {
+ // 检查是否存在
+ dictDatas := r.sysDictDataRepository.SelectDictDataByCodes(dictCodes)
+ if len(dictDatas) <= 0 {
+ // 没有可访问字典编码数据!
+ return 0, fmt.Errorf("there is no accessible dictionary-encoded data")
+ }
+ if len(dictDatas) == len(dictCodes) {
+ for _, v := range dictDatas {
+ // 刷新缓存
+ r.sysDictTypeService.ClearDictCache(v.DictType)
+ r.sysDictTypeService.LoadingDictCache(v.DictType)
+ }
+ rows := r.sysDictDataRepository.DeleteDictDataByCodes(dictCodes)
+ return rows, nil
+ }
+ // 删除字典数据信息失败!
+ return 0, fmt.Errorf("failed to delete dictionary data information")
+}
+
+// InsertDictData 新增字典数据信息
+func (r *SysDictDataImpl) InsertDictData(sysDictData model.SysDictData) string {
+ insertId := r.sysDictDataRepository.InsertDictData(sysDictData)
+ if insertId != "" {
+ // 刷新缓存
+ r.sysDictTypeService.ClearDictCache(sysDictData.DictType)
+ r.sysDictTypeService.LoadingDictCache(sysDictData.DictType)
+ }
+ return insertId
+}
+
+// UpdateDictData 修改字典数据信息
+func (r *SysDictDataImpl) UpdateDictData(sysDictData model.SysDictData) int64 {
+ rows := r.sysDictDataRepository.UpdateDictData(sysDictData)
+ if rows > 0 {
+ // 刷新缓存
+ r.sysDictTypeService.ClearDictCache(sysDictData.DictType)
+ r.sysDictTypeService.LoadingDictCache(sysDictData.DictType)
+ }
+ return rows
+}
diff --git a/src/modules/system/service/sys_dict_type.go b/src/modules/system/service/sys_dict_type.go
new file mode 100644
index 0000000..b2a0a86
--- /dev/null
+++ b/src/modules/system/service/sys_dict_type.go
@@ -0,0 +1,45 @@
+package service
+
+import "nms_nbi/src/modules/system/model"
+
+// ISysDictType 字典类型 服务层接口
+type ISysDictType interface {
+ // SelectDictTypePage 根据条件分页查询字典类型
+ SelectDictTypePage(query map[string]any) map[string]any
+
+ // SelectDictTypeList 根据条件查询字典类型
+ SelectDictTypeList(sysDictType model.SysDictType) []model.SysDictType
+
+ // SelectDictTypeByID 根据字典类型ID查询信息
+ SelectDictTypeByID(dictID string) model.SysDictType
+
+ // SelectDictTypeByType 根据字典类型查询信息
+ SelectDictTypeByType(dictType string) model.SysDictType
+
+ // CheckUniqueDictName 校验字典名称是否唯一
+ CheckUniqueDictName(dictName, dictID string) bool
+
+ // CheckUniqueDictType 校验字典类型是否唯一
+ CheckUniqueDictType(dictType, dictID string) bool
+
+ // InsertDictType 新增字典类型信息
+ InsertDictType(sysDictType model.SysDictType) string
+
+ // UpdateDictType 修改字典类型信息
+ UpdateDictType(sysDictType model.SysDictType) int64
+
+ // DeleteDictTypeByIDs 批量删除字典类型信息
+ DeleteDictTypeByIDs(dictIDs []string) (int64, error)
+
+ // ResetDictCache 重置字典缓存数据
+ ResetDictCache()
+
+ // 加载字典缓存数据
+ LoadingDictCache(dictType string)
+
+ // 清空字典缓存数据
+ ClearDictCache(dictType string) bool
+
+ // DictDataCache 获取字典数据缓存数据
+ DictDataCache(dictType string) []model.SysDictData
+}
diff --git a/src/modules/system/service/sys_dict_type.impl.go b/src/modules/system/service/sys_dict_type.impl.go
new file mode 100644
index 0000000..74949c5
--- /dev/null
+++ b/src/modules/system/service/sys_dict_type.impl.go
@@ -0,0 +1,212 @@
+package service
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "nms_nbi/src/framework/constants/cachekey"
+ "nms_nbi/src/framework/constants/common"
+ "nms_nbi/src/framework/redis"
+ "nms_nbi/src/modules/system/model"
+ "nms_nbi/src/modules/system/repository"
+)
+
+// 实例化服务层 SysDictTypeImpl 结构体
+var NewSysDictTypeImpl = &SysDictTypeImpl{
+ sysDictTypeRepository: repository.NewSysDictTypeImpl,
+ sysDictDataRepository: repository.NewSysDictDataImpl,
+}
+
+// SysDictTypeImpl 字典类型 服务层处理
+type SysDictTypeImpl struct {
+ // 字典类型服务
+ sysDictTypeRepository repository.ISysDictType
+ // 字典数据服务
+ sysDictDataRepository repository.ISysDictData
+}
+
+// SelectDictTypePage 根据条件分页查询字典类型
+func (r *SysDictTypeImpl) SelectDictTypePage(query map[string]any) map[string]any {
+ return r.sysDictTypeRepository.SelectDictTypePage(query)
+}
+
+// SelectDictTypeList 根据条件查询字典类型
+func (r *SysDictTypeImpl) SelectDictTypeList(sysDictType model.SysDictType) []model.SysDictType {
+ return r.sysDictTypeRepository.SelectDictTypeList(sysDictType)
+}
+
+// SelectDictTypeByID 根据字典类型ID查询信息
+func (r *SysDictTypeImpl) SelectDictTypeByID(dictID string) model.SysDictType {
+ if dictID == "" {
+ return model.SysDictType{}
+ }
+ dictTypes := r.sysDictTypeRepository.SelectDictTypeByIDs([]string{dictID})
+ if len(dictTypes) > 0 {
+ return dictTypes[0]
+ }
+ return model.SysDictType{}
+}
+
+// SelectDictTypeByType 根据字典类型查询信息
+func (r *SysDictTypeImpl) SelectDictTypeByType(dictType string) model.SysDictType {
+ return r.sysDictTypeRepository.SelectDictTypeByType(dictType)
+}
+
+// CheckUniqueDictName 校验字典名称是否唯一
+func (r *SysDictTypeImpl) CheckUniqueDictName(dictName, dictID string) bool {
+ uniqueId := r.sysDictTypeRepository.CheckUniqueDictType(model.SysDictType{
+ DictName: dictName,
+ })
+ if uniqueId == dictID {
+ return true
+ }
+ return uniqueId == ""
+}
+
+// CheckUniqueDictType 校验字典类型是否唯一
+func (r *SysDictTypeImpl) CheckUniqueDictType(dictType, dictID string) bool {
+ uniqueId := r.sysDictTypeRepository.CheckUniqueDictType(model.SysDictType{
+ DictType: dictType,
+ })
+ if uniqueId == dictID {
+ return true
+ }
+ return uniqueId == ""
+}
+
+// InsertDictType 新增字典类型信息
+func (r *SysDictTypeImpl) InsertDictType(sysDictType model.SysDictType) string {
+ insertId := r.sysDictTypeRepository.InsertDictType(sysDictType)
+ if insertId != "" {
+ r.LoadingDictCache(sysDictType.DictType)
+ }
+ return insertId
+}
+
+// UpdateDictType 修改字典类型信息
+func (r *SysDictTypeImpl) UpdateDictType(sysDictType model.SysDictType) int64 {
+ data := r.sysDictTypeRepository.SelectDictTypeByIDs([]string{sysDictType.DictID})
+ if len(data) == 0 {
+ return 0
+ }
+ // 修改字典类型key时同步更新其字典数据的类型key
+ oldDictType := data[0].DictType
+ rows := r.sysDictTypeRepository.UpdateDictType(sysDictType)
+ if rows > 0 && oldDictType != "" && oldDictType != sysDictType.DictType {
+ r.sysDictDataRepository.UpdateDictDataType(oldDictType, sysDictType.DictType)
+ }
+ // 刷新缓存
+ r.ClearDictCache(oldDictType)
+ r.LoadingDictCache(sysDictType.DictType)
+ return rows
+}
+
+// DeleteDictTypeByIDs 批量删除字典类型信息
+func (r *SysDictTypeImpl) DeleteDictTypeByIDs(dictIDs []string) (int64, error) {
+ // 检查是否存在
+ dictTypes := r.sysDictTypeRepository.SelectDictTypeByIDs(dictIDs)
+ if len(dictTypes) <= 0 {
+ // 没有可访问字典类型数据!
+ return 0, fmt.Errorf("there is no accessible dictionary type data")
+ }
+ for _, v := range dictTypes {
+ // 字典类型下级含有数据
+ useCount := r.sysDictDataRepository.CountDictDataByType(v.DictType)
+ if useCount > 0 {
+ // 【%s】存在字典数据,不能删除
+ return 0, fmt.Errorf("[%s] dictionary data exists and cannot be deleted", v.DictName)
+ }
+ // 清除缓存
+ r.ClearDictCache(v.DictType)
+ }
+ if len(dictTypes) == len(dictIDs) {
+ rows := r.sysDictTypeRepository.DeleteDictTypeByIDs(dictIDs)
+ return rows, nil
+ }
+ // 删除字典数据信息失败!
+ return 0, fmt.Errorf("failed to delete dictionary data information")
+}
+
+// ResetDictCache 重置字典缓存数据
+func (r *SysDictTypeImpl) ResetDictCache() {
+ r.ClearDictCache("*")
+ r.LoadingDictCache("")
+}
+
+// getCacheKey 组装缓存key
+func (r *SysDictTypeImpl) getDictCache(dictType string) string {
+ return cachekey.SYS_DICT_KEY + dictType
+}
+
+// LoadingDictCache 加载字典缓存数据
+func (r *SysDictTypeImpl) LoadingDictCache(dictType string) {
+ sysDictData := model.SysDictData{
+ Status: common.STATUS_YES,
+ }
+
+ // 指定字典类型
+ if dictType != "" {
+ sysDictData.DictType = dictType
+ // 删除缓存
+ key := r.getDictCache(dictType)
+ redis.Del("", key)
+ }
+
+ sysDictDataList := r.sysDictDataRepository.SelectDictDataList(sysDictData)
+ if len(sysDictDataList) == 0 {
+ return
+ }
+
+ // 将字典数据按类型分组
+ m := make(map[string][]model.SysDictData, 0)
+ for _, v := range sysDictDataList {
+ key := v.DictType
+ if item, ok := m[key]; ok {
+ m[key] = append(item, v)
+ } else {
+ m[key] = []model.SysDictData{v}
+ }
+ }
+
+ // 放入缓存
+ for k, v := range m {
+ key := r.getDictCache(k)
+ values, _ := json.Marshal(v)
+ redis.Set("", key, string(values))
+ }
+}
+
+// ClearDictCache 清空字典缓存数据
+func (r *SysDictTypeImpl) ClearDictCache(dictType string) bool {
+ key := r.getDictCache(dictType)
+ keys, err := redis.GetKeys("", key)
+ if err != nil {
+ return false
+ }
+ delOk, _ := redis.DelKeys("", keys)
+ return delOk
+}
+
+// DictDataCache 获取字典数据缓存数据
+func (r *SysDictTypeImpl) DictDataCache(dictType string) []model.SysDictData {
+ data := []model.SysDictData{}
+ key := r.getDictCache(dictType)
+ jsonStr, _ := redis.Get("", key)
+ if len(jsonStr) > 7 {
+ err := json.Unmarshal([]byte(jsonStr), &data)
+ if err != nil {
+ data = []model.SysDictData{}
+ }
+ } else {
+ data = r.sysDictDataRepository.SelectDictDataList(model.SysDictData{
+ Status: common.STATUS_YES,
+ DictType: dictType,
+ })
+ if len(data) > 0 {
+ redis.Del("", key)
+ values, _ := json.Marshal(data)
+ redis.Set("", key, string(values))
+ }
+ }
+ return data
+}
diff --git a/src/modules/system/service/sys_log_login.go b/src/modules/system/service/sys_log_login.go
new file mode 100644
index 0000000..a6908c6
--- /dev/null
+++ b/src/modules/system/service/sys_log_login.go
@@ -0,0 +1,24 @@
+package service
+
+import "nms_nbi/src/modules/system/model"
+
+// ISysLogLogin 系统登录日志 服务层接口
+type ISysLogLogin interface {
+ // SelectSysLogLoginPage 分页查询系统登录日志集合
+ SelectSysLogLoginPage(query map[string]any) map[string]any
+
+ // SelectSysLogLoginList 查询系统登录日志集合
+ SelectSysLogLoginList(sysLogLogin model.SysLogLogin) []model.SysLogLogin
+
+ // InsertSysLogLogin 新增系统登录日志
+ InsertSysLogLogin(sysLogLogin model.SysLogLogin) string
+
+ // DeleteSysLogLoginByIds 批量删除系统登录日志
+ DeleteSysLogLoginByIds(loginIds []string) int64
+
+ // CleanSysLogLogin 清空系统登录日志
+ CleanSysLogLogin() error
+
+ // CreateSysLogLogin 创建系统登录日志
+ CreateSysLogLogin(userName, status, msg string, ilobArgs ...string) string
+}
diff --git a/src/modules/system/service/sys_log_login.impl.go b/src/modules/system/service/sys_log_login.impl.go
new file mode 100644
index 0000000..b2fd0f4
--- /dev/null
+++ b/src/modules/system/service/sys_log_login.impl.go
@@ -0,0 +1,56 @@
+package service
+
+import (
+ "nms_nbi/src/modules/system/model"
+ "nms_nbi/src/modules/system/repository"
+)
+
+// 实例化服务层 SysLogLoginImpl 结构体
+var NewSysLogLoginImpl = &SysLogLoginImpl{
+ sysLogLoginService: repository.NewSysLogLoginImpl,
+}
+
+// SysLogLoginImpl 系统登录访问 服务层处理
+type SysLogLoginImpl struct {
+ // 系统登录访问信息
+ sysLogLoginService repository.ISysLogLogin
+}
+
+// SelectSysLogLoginPage 分页查询系统登录日志集合
+func (s *SysLogLoginImpl) SelectSysLogLoginPage(query map[string]any) map[string]any {
+ return s.sysLogLoginService.SelectSysLogLoginPage(query)
+}
+
+// SelectSysLogLoginList 查询系统登录日志集合
+func (s *SysLogLoginImpl) SelectSysLogLoginList(sysSysLogLogin model.SysLogLogin) []model.SysLogLogin {
+ return s.sysLogLoginService.SelectSysLogLoginList(sysSysLogLogin)
+}
+
+// InsertSysLogLogin 新增系统登录日志
+func (s *SysLogLoginImpl) InsertSysLogLogin(sysSysLogLogin model.SysLogLogin) string {
+ return s.sysLogLoginService.InsertSysLogLogin(sysSysLogLogin)
+}
+
+// DeleteSysLogLoginByIds 批量删除系统登录日志
+func (s *SysLogLoginImpl) DeleteSysLogLoginByIds(loginIds []string) int64 {
+ return s.sysLogLoginService.DeleteSysLogLoginByIds(loginIds)
+}
+
+// CleanSysLogLogin 清空系统登录日志
+func (s *SysLogLoginImpl) CleanSysLogLogin() error {
+ return s.sysLogLoginService.CleanSysLogLogin()
+}
+
+// CreateSysLogLogin 创建系统登录日志
+func (s *SysLogLoginImpl) CreateSysLogLogin(userName, status, msg string, ilobArgs ...string) string {
+ sysSysLogLogin := model.SysLogLogin{
+ IPAddr: ilobArgs[0],
+ LoginLocation: ilobArgs[1],
+ OS: ilobArgs[2],
+ Browser: ilobArgs[3],
+ UserName: userName,
+ Status: status,
+ Msg: msg,
+ }
+ return s.InsertSysLogLogin(sysSysLogLogin)
+}
diff --git a/src/modules/system/service/sys_log_operate.go b/src/modules/system/service/sys_log_operate.go
new file mode 100644
index 0000000..dfeec1d
--- /dev/null
+++ b/src/modules/system/service/sys_log_operate.go
@@ -0,0 +1,24 @@
+package service
+
+import "nms_nbi/src/modules/system/model"
+
+// ISysLogOperate 操作日志表 服务层接口
+type ISysLogOperate interface {
+ // SelectSysLogOperatePage 分页查询系统操作日志集合
+ SelectSysLogOperatePage(query map[string]any) map[string]any
+
+ // SelectSysLogOperateList 查询系统操作日志集合
+ SelectSysLogOperateList(sysLogOperate model.SysLogOperate) []model.SysLogOperate
+
+ // SelectSysLogOperateById 查询操作日志详细
+ SelectSysLogOperateById(operId string) model.SysLogOperate
+
+ // InsertSysLogOperate 新增操作日志
+ InsertSysLogOperate(sysLogOperate model.SysLogOperate) string
+
+ // DeleteSysLogOperateByIds 批量删除系统操作日志
+ DeleteSysLogOperateByIds(operIds []string) int64
+
+ // CleanSysLogOperate 清空操作日志
+ CleanSysLogOperate() error
+}
diff --git a/src/modules/system/service/sys_log_operate.impl.go b/src/modules/system/service/sys_log_operate.impl.go
new file mode 100644
index 0000000..0250043
--- /dev/null
+++ b/src/modules/system/service/sys_log_operate.impl.go
@@ -0,0 +1,47 @@
+package service
+
+import (
+ "nms_nbi/src/modules/system/model"
+ "nms_nbi/src/modules/system/repository"
+)
+
+// 实例化服务层 SysLogOperateImpl 结构体
+var NewSysLogOperateImpl = &SysLogOperateImpl{
+ SysLogOperateService: repository.NewSysLogOperateImpl,
+}
+
+// SysLogOperateImpl 操作日志表 服务层处理
+type SysLogOperateImpl struct {
+ // 操作日志信息
+ SysLogOperateService repository.ISysLogOperate
+}
+
+// SelectSysLogOperatePage 分页查询系统操作日志集合
+func (r *SysLogOperateImpl) SelectSysLogOperatePage(query map[string]any) map[string]any {
+ return r.SysLogOperateService.SelectSysLogOperatePage(query)
+}
+
+// SelectSysLogOperateList 查询系统操作日志集合
+func (r *SysLogOperateImpl) SelectSysLogOperateList(SysLogOperate model.SysLogOperate) []model.SysLogOperate {
+ return r.SysLogOperateService.SelectSysLogOperateList(SysLogOperate)
+}
+
+// SelectSysLogOperateById 查询操作日志详细
+func (r *SysLogOperateImpl) SelectSysLogOperateById(operId string) model.SysLogOperate {
+ return r.SysLogOperateService.SelectSysLogOperateById(operId)
+}
+
+// InsertSysLogOperate 新增操作日志
+func (r *SysLogOperateImpl) InsertSysLogOperate(SysLogOperate model.SysLogOperate) string {
+ return r.SysLogOperateService.InsertSysLogOperate(SysLogOperate)
+}
+
+// DeleteSysLogOperateByIds 批量删除系统操作日志
+func (r *SysLogOperateImpl) DeleteSysLogOperateByIds(operIds []string) int64 {
+ return r.SysLogOperateService.DeleteSysLogOperateByIds(operIds)
+}
+
+// CleanSysLogOperate 清空操作日志
+func (r *SysLogOperateImpl) CleanSysLogOperate() error {
+ return r.SysLogOperateService.CleanSysLogOperate()
+}
diff --git a/src/modules/system/service/sys_menu.go b/src/modules/system/service/sys_menu.go
new file mode 100644
index 0000000..8322cf8
--- /dev/null
+++ b/src/modules/system/service/sys_menu.go
@@ -0,0 +1,51 @@
+package service
+
+import (
+ "nms_nbi/src/framework/vo"
+ "nms_nbi/src/modules/system/model"
+)
+
+// ISysMenu 菜单 服务层接口
+type ISysMenu interface {
+ // SelectMenuList 查询系统菜单列表
+ SelectMenuList(sysMenu model.SysMenu, userId string) []model.SysMenu
+
+ // SelectMenuPermsByUserId 根据用户ID查询权限
+ SelectMenuPermsByUserId(userId string) []string
+
+ // SelectMenuTreeByUserId 根据用户ID查询权限
+ SelectMenuTreeByUserId(userId string) []model.SysMenu
+
+ // SelectMenuTreeSelectByUserId 查询菜单树结构信息
+ SelectMenuTreeSelectByUserId(sysMenu model.SysMenu, userId string) []vo.TreeSelect
+
+ // SelectMenuListByRoleId 根据角色ID查询菜单树信息
+ SelectMenuListByRoleId(roleId string) []string
+
+ // SelectMenuById 根据菜单ID查询信息
+ SelectMenuById(menuId string) model.SysMenu
+
+ // HasChildByMenuIdAndStatus 存在菜单子节点数量与状态
+ HasChildByMenuIdAndStatus(menuId, status string) int64
+
+ // CheckMenuExistRole 查询菜单分配角色数量
+ CheckMenuExistRole(menuId string) int64
+
+ // InsertMenu 新增菜单信息
+ InsertMenu(sysMenu model.SysMenu) string
+
+ // UpdateMenu 修改菜单信息
+ UpdateMenu(sysMenu model.SysMenu) int64
+
+ // DeleteMenuById 删除菜单管理信息
+ DeleteMenuById(menuId string) int64
+
+ // CheckUniqueMenuName 校验菜单名称是否唯一
+ CheckUniqueMenuName(menuName, parentId, menuId string) bool
+
+ // CheckUniqueMenuPath 校验路由地址是否唯一(针对目录和菜单)
+ CheckUniqueMenuPath(path, parentId, menuId string) bool
+
+ // BuildRouteMenus 构建前端路由所需要的菜单
+ BuildRouteMenus(sysMenus []model.SysMenu, prefix string) []vo.Router
+}
diff --git a/src/modules/system/service/sys_menu.impl.go b/src/modules/system/service/sys_menu.impl.go
new file mode 100644
index 0000000..10e9078
--- /dev/null
+++ b/src/modules/system/service/sys_menu.impl.go
@@ -0,0 +1,391 @@
+package service
+
+import (
+ "encoding/base64"
+ "strings"
+
+ "nms_nbi/src/framework/constants/common"
+ "nms_nbi/src/framework/constants/menu"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/regular"
+ "nms_nbi/src/framework/vo"
+ "nms_nbi/src/modules/system/model"
+ "nms_nbi/src/modules/system/repository"
+)
+
+// 实例化服务层 SysMenuImpl 结构体
+var NewSysMenuImpl = &SysMenuImpl{
+ sysMenuRepository: repository.NewSysMenuImpl,
+ sysRoleMenuRepository: repository.NewSysRoleMenuImpl,
+ sysRoleRepository: repository.NewSysRoleImpl,
+}
+
+// SysMenuImpl 菜单 服务层处理
+type SysMenuImpl struct {
+ // 菜单服务
+ sysMenuRepository repository.ISysMenu
+ // 角色与菜单关联服务
+ sysRoleMenuRepository repository.ISysRoleMenu
+ // 角色服务
+ sysRoleRepository repository.ISysRole
+}
+
+// SelectMenuList 查询系统菜单列表
+func (r *SysMenuImpl) SelectMenuList(sysMenu model.SysMenu, userId string) []model.SysMenu {
+ return r.sysMenuRepository.SelectMenuList(sysMenu, userId)
+}
+
+// SelectMenuPermsByUserId 根据用户ID查询权限
+func (r *SysMenuImpl) SelectMenuPermsByUserId(userId string) []string {
+ return r.sysMenuRepository.SelectMenuPermsByUserId(userId)
+}
+
+// SelectMenuTreeByUserId 根据用户ID查询菜单
+func (r *SysMenuImpl) SelectMenuTreeByUserId(userId string) []model.SysMenu {
+ sysMenus := r.sysMenuRepository.SelectMenuTreeByUserId(userId)
+ return r.parseDataToTree(sysMenus)
+}
+
+// SelectMenuTreeSelectByUserId 根据用户ID查询菜单树结构信息
+func (r *SysMenuImpl) SelectMenuTreeSelectByUserId(sysMenu model.SysMenu, userId string) []vo.TreeSelect {
+ sysMenus := r.sysMenuRepository.SelectMenuList(sysMenu, userId)
+
+ // 过滤旧前端菜单
+ sysMenusF := []model.SysMenu{}
+ for _, v := range sysMenus {
+ if v.Perms == "page" {
+ continue
+ }
+ sysMenusF = append(sysMenusF, v)
+ }
+
+ menus := r.parseDataToTree(sysMenusF)
+ tree := make([]vo.TreeSelect, 0)
+ for _, menu := range menus {
+ tree = append(tree, vo.SysMenuTreeSelect(menu))
+ }
+ return tree
+}
+
+// SelectMenuListByRoleId 根据角色ID查询菜单树信息 TODO
+func (r *SysMenuImpl) SelectMenuListByRoleId(roleId string) []string {
+ roles := r.sysRoleRepository.SelectRoleByIds([]string{roleId})
+ if len(roles) > 0 {
+ role := roles[0]
+ if role.RoleID == roleId {
+ return r.sysMenuRepository.SelectMenuListByRoleId(
+ role.RoleID,
+ role.MenuCheckStrictly == "1",
+ )
+ }
+ }
+ return []string{}
+}
+
+// SelectMenuById 根据菜单ID查询信息
+func (r *SysMenuImpl) SelectMenuById(menuId string) model.SysMenu {
+ if menuId == "" {
+ return model.SysMenu{}
+ }
+ menus := r.sysMenuRepository.SelectMenuByIds([]string{menuId})
+ if len(menus) > 0 {
+ return menus[0]
+ }
+ return model.SysMenu{}
+}
+
+// HasChildByMenuIdAndStatus 存在菜单子节点数量与状态
+func (r *SysMenuImpl) HasChildByMenuIdAndStatus(menuId, status string) int64 {
+ return r.sysMenuRepository.HasChildByMenuIdAndStatus(menuId, status)
+}
+
+// CheckMenuExistRole 查询菜单是否存在角色
+func (r *SysMenuImpl) CheckMenuExistRole(menuId string) int64 {
+ return r.sysRoleMenuRepository.CheckMenuExistRole(menuId)
+}
+
+// InsertMenu 新增菜单信息
+func (r *SysMenuImpl) InsertMenu(sysMenu model.SysMenu) string {
+ return r.sysMenuRepository.InsertMenu(sysMenu)
+}
+
+// UpdateMenu 修改菜单信息
+func (r *SysMenuImpl) UpdateMenu(sysMenu model.SysMenu) int64 {
+ return r.sysMenuRepository.UpdateMenu(sysMenu)
+}
+
+// DeleteMenuById 删除菜单管理信息
+func (r *SysMenuImpl) DeleteMenuById(menuId string) int64 {
+ // 删除菜单与角色关联
+ r.sysRoleMenuRepository.DeleteMenuRole([]string{menuId})
+ return r.sysMenuRepository.DeleteMenuById(menuId)
+}
+
+// CheckUniqueMenuName 校验菜单名称是否唯一
+func (r *SysMenuImpl) CheckUniqueMenuName(menuName, parentId, menuId string) bool {
+ uniqueId := r.sysMenuRepository.CheckUniqueMenu(model.SysMenu{
+ MenuName: menuName,
+ ParentID: parentId,
+ })
+ if uniqueId == menuId {
+ return true
+ }
+ return uniqueId == ""
+}
+
+// CheckUniqueMenuPath 校验路由地址是否唯一(针对目录和菜单)
+func (r *SysMenuImpl) CheckUniqueMenuPath(path, parentId, menuId string) bool {
+ uniqueId := r.sysMenuRepository.CheckUniqueMenu(model.SysMenu{
+ Path: path,
+ ParentID: parentId,
+ })
+ if uniqueId == menuId {
+ return true
+ }
+ return uniqueId == ""
+}
+
+// BuildRouteMenus 构建前端路由所需要的菜单
+func (r *SysMenuImpl) BuildRouteMenus(sysMenus []model.SysMenu, prefix string) []vo.Router {
+ routers := []vo.Router{}
+ for _, item := range sysMenus {
+ router := vo.Router{}
+ router.Name = r.getRouteName(item)
+ router.Path = r.getRouterPath(item)
+ router.Component = r.getComponent(item)
+ router.Meta = r.getRouteMeta(item)
+
+ // 子项菜单 目录类型 非路径链接
+ cMenus := item.Children
+ if len(cMenus) > 0 && item.MenuType == menu.TYPE_DIR && !regular.ValidHttp(item.Path) {
+ // 获取重定向地址
+ redirectPrefix, redirectPath := r.getRouteRedirect(
+ cMenus,
+ router.Path,
+ prefix,
+ )
+ router.Redirect = redirectPath
+ // 子菜单进入递归
+ router.Children = r.BuildRouteMenus(cMenus, redirectPrefix)
+ } else if item.ParentID == "0" && item.IsFrame == common.STATUS_YES && item.MenuType == menu.TYPE_MENU {
+ // 父菜单 内部跳转 菜单类型
+ menuPath := "/" + item.MenuID
+ childPath := menuPath + r.getRouterPath(item)
+ children := vo.Router{
+ Name: r.getRouteName(item),
+ Path: childPath,
+ Component: item.Component,
+ Meta: r.getRouteMeta(item),
+ }
+ router.Meta.HideChildInMenu = true
+ router.Children = append(router.Children, children)
+ router.Name = item.MenuID
+ router.Path = menuPath
+ router.Redirect = childPath
+ router.Component = menu.COMPONENT_LAYOUT_BASIC
+ } else if item.ParentID == "0" && item.IsFrame == common.STATUS_YES && regular.ValidHttp(item.Path) {
+ // 父菜单 内部跳转 路径链接
+ menuPath := "/" + item.MenuID
+ childPath := menuPath + r.getRouterPath(item)
+ children := vo.Router{
+ Name: r.getRouteName(item),
+ Path: childPath,
+ Component: menu.COMPONENT_LAYOUT_LINK,
+ Meta: r.getRouteMeta(item),
+ }
+ router.Meta.HideChildInMenu = true
+ router.Children = append(router.Children, children)
+ router.Name = item.MenuID
+ router.Path = menuPath
+ router.Redirect = childPath
+ router.Component = menu.COMPONENT_LAYOUT_BASIC
+ }
+
+ routers = append(routers, router)
+ }
+ return routers
+}
+
+// getRouteName 获取路由名称 路径英文首字母大写
+func (r *SysMenuImpl) getRouteName(sysMenu model.SysMenu) string {
+ routerName := parse.ConvertToCamelCase(sysMenu.Path)
+ // 路径链接
+ if regular.ValidHttp(sysMenu.Path) {
+ routerName = routerName[:5] + "Link"
+ }
+ // 拼上菜单ID防止name重名
+ return routerName + "_" + sysMenu.MenuID
+}
+
+// getRouterPath 获取路由地址
+func (r *SysMenuImpl) getRouterPath(sysMenu model.SysMenu) string {
+ routerPath := sysMenu.Path
+
+ // 显式路径
+ if routerPath == "" || strings.HasPrefix(routerPath, "/") {
+ return routerPath
+ }
+
+ // 路径链接 内部跳转
+ if regular.ValidHttp(routerPath) && sysMenu.IsFrame == common.STATUS_YES {
+ routerPath = regular.Replace(routerPath, `/^http(s)?:\/\/+/`, "")
+ routerPath = base64.StdEncoding.EncodeToString([]byte(routerPath))
+ }
+
+ // 父菜单 内部跳转
+ if sysMenu.ParentID == "0" && sysMenu.IsFrame == common.STATUS_YES {
+ routerPath = "/" + routerPath
+ }
+
+ return routerPath
+}
+
+// getComponent 获取组件信息
+func (r *SysMenuImpl) getComponent(sysMenu model.SysMenu) string {
+ // 内部跳转 路径链接
+ if sysMenu.IsFrame == common.STATUS_YES && regular.ValidHttp(sysMenu.Path) {
+ return menu.COMPONENT_LAYOUT_LINK
+ }
+
+ // 非父菜单 目录类型
+ if sysMenu.ParentID != "0" && sysMenu.MenuType == menu.TYPE_DIR {
+ return menu.COMPONENT_LAYOUT_BLANK
+ }
+
+ // 组件路径 内部跳转 菜单类型
+ if sysMenu.Component != "" && sysMenu.IsFrame == common.STATUS_YES && sysMenu.MenuType == menu.TYPE_MENU {
+ // 父菜单套外层布局
+ if sysMenu.ParentID == "0" {
+ return menu.COMPONENT_LAYOUT_BASIC
+ }
+ return sysMenu.Component
+ }
+
+ return menu.COMPONENT_LAYOUT_BASIC
+}
+
+// getRouteMeta 获取路由元信息
+func (r *SysMenuImpl) getRouteMeta(sysMenu model.SysMenu) vo.RouterMeta {
+ meta := vo.RouterMeta{}
+ if sysMenu.Icon == "#" {
+ meta.Icon = ""
+ } else {
+ meta.Icon = sysMenu.Icon
+ }
+ meta.Title = sysMenu.MenuName
+ meta.HideChildInMenu = false
+ meta.HideInMenu = sysMenu.Visible == common.STATUS_NO
+ meta.Cache = sysMenu.IsCache == common.STATUS_YES
+ meta.Target = ""
+
+ // 路径链接 非内部跳转
+ if regular.ValidHttp(sysMenu.Path) && sysMenu.IsFrame == common.STATUS_NO {
+ meta.Target = "_blank"
+ }
+
+ return meta
+}
+
+// getRouteRedirect 获取路由重定向地址(针对目录)
+//
+// cMenus 子菜单数组
+// routerPath 当前菜单路径
+// prefix 菜单重定向路径前缀
+func (r *SysMenuImpl) getRouteRedirect(cMenus []model.SysMenu, routerPath string, prefix string) (string, string) {
+ redirectPath := ""
+
+ // 重定向为首个显示并启用的子菜单
+ var firstChild *model.SysMenu
+ for _, item := range cMenus {
+ if item.IsFrame == common.STATUS_YES && item.Visible == common.STATUS_YES {
+ firstChild = &item
+ break
+ }
+ }
+
+ // 检查内嵌隐藏菜单是否可做重定向
+ if firstChild == nil {
+ for _, item := range cMenus {
+ if item.IsFrame == common.STATUS_YES && item.Visible == common.STATUS_NO && strings.Contains(item.Path, menu.PATH_INLINE) {
+ firstChild = &item
+ break
+ }
+ }
+ }
+
+ if firstChild != nil {
+ firstChildPath := r.getRouterPath(*firstChild)
+ if strings.HasPrefix(firstChildPath, "/") {
+ redirectPath = firstChildPath
+ } else {
+ // 拼接追加路径
+ if !strings.HasPrefix(routerPath, "/") {
+ prefix += "/"
+ }
+ prefix = prefix + routerPath
+ redirectPath = prefix + "/" + firstChildPath
+ }
+ }
+
+ return prefix, redirectPath
+}
+
+// parseDataToTree 将数据解析为树结构,构建前端所需要下拉树结构
+func (r *SysMenuImpl) parseDataToTree(sysMenus []model.SysMenu) []model.SysMenu {
+ // 节点分组
+ nodesMap := make(map[string][]model.SysMenu)
+ // 节点id
+ treeIds := []string{}
+ // 树节点
+ tree := []model.SysMenu{}
+
+ for _, item := range sysMenus {
+ parentID := item.ParentID
+ // 分组
+ mapItem, ok := nodesMap[parentID]
+ if !ok {
+ mapItem = []model.SysMenu{}
+ }
+ mapItem = append(mapItem, item)
+ nodesMap[parentID] = mapItem
+ // 记录节点ID
+ treeIds = append(treeIds, item.MenuID)
+ }
+
+ for key, value := range nodesMap {
+ // 选择不是节点ID的作为树节点
+ found := false
+ for _, id := range treeIds {
+ if id == key {
+ found = true
+ break
+ }
+ }
+ if !found {
+ tree = append(tree, value...)
+ }
+ }
+
+ for i, node := range tree {
+ iN := r.parseDataToTreeComponet(node, &nodesMap)
+ tree[i] = iN
+ }
+
+ return tree
+}
+
+// parseDataToTreeComponet 递归函数处理子节点
+func (r *SysMenuImpl) parseDataToTreeComponet(node model.SysMenu, nodesMap *map[string][]model.SysMenu) model.SysMenu {
+ id := node.MenuID
+ children, ok := (*nodesMap)[id]
+ if ok {
+ node.Children = children
+ }
+ if len(node.Children) > 0 {
+ for i, child := range node.Children {
+ icN := r.parseDataToTreeComponet(child, nodesMap)
+ node.Children[i] = icN
+ }
+ }
+ return node
+}
diff --git a/src/modules/system/service/sys_post.go b/src/modules/system/service/sys_post.go
new file mode 100644
index 0000000..7326636
--- /dev/null
+++ b/src/modules/system/service/sys_post.go
@@ -0,0 +1,33 @@
+package service
+
+import "nms_nbi/src/modules/system/model"
+
+// ISysPost 岗位信息 服务层接口
+type ISysPost interface {
+ // SelectPostPage 查询岗位分页数据集合
+ SelectPostPage(query map[string]any) map[string]any
+
+ // SelectPostList 查询岗位数据集合
+ SelectPostList(sysPost model.SysPost) []model.SysPost
+
+ // SelectPostById 通过岗位ID查询岗位信息
+ SelectPostById(postId string) model.SysPost
+
+ // SelectPostListByUserId 根据用户ID获取岗位选择框列表
+ SelectPostListByUserId(userId string) []model.SysPost
+
+ // DeletePostByIds 批量删除岗位信息
+ DeletePostByIds(postIds []string) (int64, error)
+
+ // UpdatePost 修改岗位信息
+ UpdatePost(sysPost model.SysPost) int64
+
+ // InsertPost 新增岗位信息
+ InsertPost(sysPost model.SysPost) string
+
+ // CheckUniquePostName 校验岗位名称
+ CheckUniquePostName(postName, postId string) bool
+
+ // CheckUniquePostCode 校验岗位编码
+ CheckUniquePostCode(postCode, postId string) bool
+}
diff --git a/src/modules/system/service/sys_post.impl.go b/src/modules/system/service/sys_post.impl.go
new file mode 100644
index 0000000..1885253
--- /dev/null
+++ b/src/modules/system/service/sys_post.impl.go
@@ -0,0 +1,104 @@
+package service
+
+import (
+ "fmt"
+
+ "nms_nbi/src/modules/system/model"
+ "nms_nbi/src/modules/system/repository"
+)
+
+// 实例化服务层 SysPostImpl 结构体
+var NewSysPostImpl = &SysPostImpl{
+ sysPostRepository: repository.NewSysPostImpl,
+ sysUserPostRepository: repository.NewSysUserPostImpl,
+}
+
+// SysPostImpl 岗位表 服务层处理
+type SysPostImpl struct {
+ // 岗位服务
+ sysPostRepository repository.ISysPost
+ // 用户与岗位关联服务
+ sysUserPostRepository repository.ISysUserPost
+}
+
+// SelectPostPage 查询岗位分页数据集合
+func (r *SysPostImpl) SelectPostPage(query map[string]any) map[string]any {
+ return r.sysPostRepository.SelectPostPage(query)
+}
+
+// SelectPostList 查询岗位数据集合
+func (r *SysPostImpl) SelectPostList(sysPost model.SysPost) []model.SysPost {
+ return r.sysPostRepository.SelectPostList(sysPost)
+}
+
+// SelectPostById 通过岗位ID查询岗位信息
+func (r *SysPostImpl) SelectPostById(postId string) model.SysPost {
+ if postId == "" {
+ return model.SysPost{}
+ }
+ posts := r.sysPostRepository.SelectPostByIds([]string{postId})
+ if len(posts) > 0 {
+ return posts[0]
+ }
+ return model.SysPost{}
+}
+
+// SelectPostListByUserId 根据用户ID获取岗位选择框列表
+func (r *SysPostImpl) SelectPostListByUserId(userId string) []model.SysPost {
+ return r.sysPostRepository.SelectPostListByUserId(userId)
+}
+
+// DeletePostByIds 批量删除岗位信息
+func (r *SysPostImpl) DeletePostByIds(postIds []string) (int64, error) {
+ // 检查是否存在
+ posts := r.sysPostRepository.SelectPostByIds(postIds)
+ if len(posts) <= 0 {
+ // 没有可访问岗位数据!
+ return 0, fmt.Errorf("there is no accessible post data")
+ }
+ for _, post := range posts {
+ useCount := r.sysUserPostRepository.CountUserPostByPostId(post.PostID)
+ if useCount > 0 {
+ // 【%s】已分配给用户,不能删除
+ return 0, fmt.Errorf("[%s] has been assigned to a user and cannot be deleted", post.PostName)
+ }
+ }
+ if len(posts) == len(postIds) {
+ rows := r.sysPostRepository.DeletePostByIds(postIds)
+ return rows, nil
+ }
+ // 删除岗位信息失败!
+ return 0, fmt.Errorf("failed to delete post information")
+}
+
+// UpdatePost 修改岗位信息
+func (r *SysPostImpl) UpdatePost(sysPost model.SysPost) int64 {
+ return r.sysPostRepository.UpdatePost(sysPost)
+}
+
+// InsertPost 新增岗位信息
+func (r *SysPostImpl) InsertPost(sysPost model.SysPost) string {
+ return r.sysPostRepository.InsertPost(sysPost)
+}
+
+// CheckUniquePostName 校验岗位名称
+func (r *SysPostImpl) CheckUniquePostName(postName, postId string) bool {
+ uniqueId := r.sysPostRepository.CheckUniquePost(model.SysPost{
+ PostName: postName,
+ })
+ if uniqueId == postId {
+ return true
+ }
+ return uniqueId == ""
+}
+
+// CheckUniquePostCode 校验岗位编码
+func (r *SysPostImpl) CheckUniquePostCode(postCode, postId string) bool {
+ uniqueId := r.sysPostRepository.CheckUniquePost(model.SysPost{
+ PostCode: postCode,
+ })
+ if uniqueId == postId {
+ return true
+ }
+ return uniqueId == ""
+}
diff --git a/src/modules/system/service/sys_role.go b/src/modules/system/service/sys_role.go
new file mode 100644
index 0000000..cd22cc1
--- /dev/null
+++ b/src/modules/system/service/sys_role.go
@@ -0,0 +1,42 @@
+package service
+
+import "nms_nbi/src/modules/system/model"
+
+// ISysRole 角色 服务层接口
+type ISysRole interface {
+ // SelectRolePage 根据条件分页查询角色数据
+ SelectRolePage(query map[string]any, dataScopeSQL string) map[string]any
+
+ // SelectRoleList 根据条件查询角色数据
+ SelectRoleList(sysRole model.SysRole, dataScopeSQL string) []model.SysRole
+
+ // SelectRoleListByUserId 根据用户ID获取角色选择框列表
+ SelectRoleListByUserId(userId string) []model.SysRole
+
+ // SelectRoleById 通过角色ID查询角色
+ SelectRoleById(roleId string) model.SysRole
+
+ // UpdateRole 修改角色信息
+ UpdateRole(sysRole model.SysRole) int64
+
+ // InsertRole 新增角色信息
+ InsertRole(sysRole model.SysRole) string
+
+ // DeleteRoleByIds 批量删除角色信息
+ DeleteRoleByIds(roleIds []string) (int64, error)
+
+ // CheckUniqueRoleName 校验角色名称是否唯一
+ CheckUniqueRoleName(roleName, roleId string) bool
+
+ // CheckUniqueRoleKey 校验角色权限是否唯一
+ CheckUniqueRoleKey(roleKey, roleId string) bool
+
+ // AuthDataScope 修改数据权限信息
+ AuthDataScope(sysRole model.SysRole) int64
+
+ // DeleteAuthUsers 批量取消授权用户角色
+ DeleteAuthUsers(roleId string, userIds []string) int64
+
+ // InsertAuthUsers 批量新增授权用户角色
+ InsertAuthUsers(roleId string, userIds []string) int64
+}
diff --git a/src/modules/system/service/sys_role.impl.go b/src/modules/system/service/sys_role.impl.go
new file mode 100644
index 0000000..934a63d
--- /dev/null
+++ b/src/modules/system/service/sys_role.impl.go
@@ -0,0 +1,189 @@
+package service
+
+import (
+ "fmt"
+
+ "nms_nbi/src/modules/system/model"
+ "nms_nbi/src/modules/system/repository"
+)
+
+// 实例化服务层 SysRoleImpl 结构体
+var NewSysRoleImpl = &SysRoleImpl{
+ sysRoleRepository: repository.NewSysRoleImpl,
+ sysUserRoleRepository: repository.NewSysUserRoleImpl,
+ sysRoleDeptRepository: repository.NewSysRoleDeptImpl,
+ sysRoleMenuRepository: repository.NewSysRoleMenuImpl,
+}
+
+// SysRoleImpl 角色 服务层处理
+type SysRoleImpl struct {
+ // 角色服务
+ sysRoleRepository repository.ISysRole
+ // 用户与角色关联服务
+ sysUserRoleRepository repository.ISysUserRole
+ // 角色与部门关联服务
+ sysRoleDeptRepository repository.ISysRoleDept
+ // 角色与菜单关联服务
+ sysRoleMenuRepository repository.ISysRoleMenu
+}
+
+// SelectRolePage 根据条件分页查询角色数据
+func (r *SysRoleImpl) SelectRolePage(query map[string]any, dataScopeSQL string) map[string]any {
+ return r.sysRoleRepository.SelectRolePage(query, dataScopeSQL)
+}
+
+// SelectRoleList 根据条件查询角色数据
+func (r *SysRoleImpl) SelectRoleList(sysRole model.SysRole, dataScopeSQL string) []model.SysRole {
+ return r.sysRoleRepository.SelectRoleList(sysRole, dataScopeSQL)
+}
+
+// SelectRoleListByUserId 根据用户ID获取角色选择框列表
+func (r *SysRoleImpl) SelectRoleListByUserId(userId string) []model.SysRole {
+ return r.sysRoleRepository.SelectRoleListByUserId(userId)
+}
+
+// SelectRoleById 通过角色ID查询角色
+func (r *SysRoleImpl) SelectRoleById(roleId string) model.SysRole {
+ if roleId == "" {
+ return model.SysRole{}
+ }
+ posts := r.sysRoleRepository.SelectRoleByIds([]string{roleId})
+ if len(posts) > 0 {
+ return posts[0]
+ }
+ return model.SysRole{}
+}
+
+// UpdateRole 修改角色信息
+func (r *SysRoleImpl) UpdateRole(sysRole model.SysRole) int64 {
+ rows := r.sysRoleRepository.UpdateRole(sysRole)
+ if rows > 0 && len(sysRole.MenuIds) > 0 {
+ // 删除角色与菜单关联
+ r.sysRoleMenuRepository.DeleteRoleMenu([]string{sysRole.RoleID})
+ r.insertRoleMenu(sysRole.RoleID, sysRole.MenuIds)
+ }
+ return rows
+}
+
+// InsertRole 新增角色信息
+func (r *SysRoleImpl) InsertRole(sysRole model.SysRole) string {
+ insertId := r.sysRoleRepository.InsertRole(sysRole)
+ if insertId != "" && len(sysRole.MenuIds) > 0 {
+ r.insertRoleMenu(insertId, sysRole.MenuIds)
+ }
+ return insertId
+}
+
+// insertRoleMenu 新增角色菜单信息
+func (r *SysRoleImpl) insertRoleMenu(roleId string, menuIds []string) int64 {
+ if roleId == "" || len(menuIds) <= 0 {
+ return 0
+ }
+
+ sysRoleMenus := []model.SysRoleMenu{}
+ for _, menuId := range menuIds {
+ if menuId == "" {
+ continue
+ }
+ sysRoleMenus = append(sysRoleMenus, model.NewSysRoleMenu(roleId, menuId))
+ }
+
+ return r.sysRoleMenuRepository.BatchRoleMenu(sysRoleMenus)
+}
+
+// DeleteRoleByIds 批量删除角色信息
+func (r *SysRoleImpl) DeleteRoleByIds(roleIds []string) (int64, error) {
+ // 检查是否存在
+ roles := r.sysRoleRepository.SelectRoleByIds(roleIds)
+ if len(roles) <= 0 {
+ // 没有可访问角色数据!
+ return 0, fmt.Errorf("there is no accessible role data")
+ }
+ for _, role := range roles {
+ // 检查是否为已删除
+ if role.DelFlag == "1" {
+ // 【%s】角色信息已经删除!
+ return 0, fmt.Errorf("[%s] Role information has been deleted", role.RoleID)
+ }
+ // 检查分配用户
+ userCount := r.sysUserRoleRepository.CountUserRoleByRoleId(role.RoleID)
+ if userCount > 0 {
+ // 【%s】已分配给用户,不能删除
+ return 0, fmt.Errorf("[%s] has been assigned to a user and cannot be deleted", role.RoleName)
+ }
+ }
+ if len(roles) == len(roleIds) {
+ // 删除角色与菜单关联
+ r.sysRoleMenuRepository.DeleteRoleMenu(roleIds)
+ // 删除角色与部门关联
+ r.sysRoleDeptRepository.DeleteRoleDept(roleIds)
+ rows := r.sysRoleRepository.DeleteRoleByIds(roleIds)
+ return rows, nil
+ }
+ // 删除角色信息失败!
+ return 0, fmt.Errorf("failed to delete role information")
+}
+
+// CheckUniqueRoleName 校验角色名称是否唯一
+func (r *SysRoleImpl) CheckUniqueRoleName(roleName, roleId string) bool {
+ uniqueId := r.sysRoleRepository.CheckUniqueRole(model.SysRole{
+ RoleName: roleName,
+ })
+ if uniqueId == roleId {
+ return true
+ }
+ return uniqueId == ""
+}
+
+// CheckUniqueRoleKey 校验角色权限是否唯一
+func (r *SysRoleImpl) CheckUniqueRoleKey(roleKey, roleId string) bool {
+ uniqueId := r.sysRoleRepository.CheckUniqueRole(model.SysRole{
+ RoleKey: roleKey,
+ })
+ if uniqueId == roleId {
+ return true
+ }
+ return uniqueId == ""
+}
+
+// AuthDataScope 修改数据权限信息
+func (r *SysRoleImpl) AuthDataScope(sysRole model.SysRole) int64 {
+ // 修改角色信息
+ rows := r.sysRoleRepository.UpdateRole(sysRole)
+ // 删除角色与部门关联
+ r.sysRoleDeptRepository.DeleteRoleDept([]string{sysRole.RoleID})
+ // 新增角色和部门信息
+ if sysRole.DataScope == "2" && len(sysRole.DeptIds) > 0 {
+ sysRoleDepts := []model.SysRoleDept{}
+ for _, deptId := range sysRole.DeptIds {
+ if deptId == "" {
+ continue
+ }
+ sysRoleDepts = append(sysRoleDepts, model.NewSysRoleDept(sysRole.RoleID, deptId))
+ }
+ rows += r.sysRoleDeptRepository.BatchRoleDept(sysRoleDepts)
+ }
+ return rows
+}
+
+// DeleteAuthUsers 批量取消授权用户角色
+func (r *SysRoleImpl) DeleteAuthUsers(roleId string, userIds []string) int64 {
+ return r.sysUserRoleRepository.DeleteUserRoleByRoleId(roleId, userIds)
+}
+
+// InsertAuthUsers 批量新增授权用户角色
+func (r *SysRoleImpl) InsertAuthUsers(roleId string, userIds []string) int64 {
+ if roleId == "" || len(userIds) <= 0 {
+ return 0
+ }
+
+ sysUserRoles := []model.SysUserRole{}
+ for _, userId := range userIds {
+ if userId == "" {
+ continue
+ }
+ sysUserRoles = append(sysUserRoles, model.NewSysUserRole(userId, roleId))
+ }
+
+ return r.sysUserRoleRepository.BatchUserRole(sysUserRoles)
+}
diff --git a/src/modules/system/service/sys_user.go b/src/modules/system/service/sys_user.go
new file mode 100644
index 0000000..53fbd14
--- /dev/null
+++ b/src/modules/system/service/sys_user.go
@@ -0,0 +1,42 @@
+package service
+
+import "nms_nbi/src/modules/system/model"
+
+// ISysUser 用户 服务层接口
+type ISysUser interface {
+ // SelectUserPage 根据条件分页查询用户列表
+ SelectUserPage(query map[string]any, dataScopeSQL string) map[string]any
+
+ // SelectUserList 根据条件查询用户列表
+ SelectUserList(sysUser model.SysUser, dataScopeSQL string) []model.SysUser
+
+ // SelectAllocatedPage 根据条件分页查询分配用户角色列表
+ SelectAllocatedPage(query map[string]any, dataScopeSQL string) map[string]any
+
+ // SelectUserByUserName 通过用户名查询用户
+ SelectUserByUserName(userName string) model.SysUser
+
+ // SelectUserById 通过用户ID查询用户
+ SelectUserById(userId string) model.SysUser
+
+ // InsertUser 新增用户信息
+ InsertUser(sysUser model.SysUser) string
+
+ // UpdateUser 修改用户信息
+ UpdateUser(sysUser model.SysUser) int64
+
+ // UpdateUserAndRolePost 修改用户信息同时更新角色和岗位
+ UpdateUserAndRolePost(sysUser model.SysUser) int64
+
+ // DeleteUserByIds 批量删除用户信息
+ DeleteUserByIds(userIds []string) (int64, error)
+
+ // CheckUniqueUserName 校验用户名称是否唯一
+ CheckUniqueUserName(userName, userId string) bool
+
+ // CheckUniquePhone 校验手机号码是否唯一
+ CheckUniquePhone(phonenumber, userId string) bool
+
+ // CheckUniqueEmail 校验email是否唯一
+ CheckUniqueEmail(email, userId string) bool
+}
diff --git a/src/modules/system/service/sys_user.impl.go b/src/modules/system/service/sys_user.impl.go
new file mode 100644
index 0000000..3759c54
--- /dev/null
+++ b/src/modules/system/service/sys_user.impl.go
@@ -0,0 +1,178 @@
+package service
+
+import (
+ "fmt"
+
+ "nms_nbi/src/framework/constants/admin"
+ "nms_nbi/src/modules/system/model"
+ "nms_nbi/src/modules/system/repository"
+)
+
+// 实例化服务层 SysUserImpl 结构体
+var NewSysUserImpl = &SysUserImpl{
+ sysUserRepository: repository.NewSysUserImpl,
+ sysUserRoleRepository: repository.NewSysUserRoleImpl,
+ sysUserPostRepository: repository.NewSysUserPostImpl,
+}
+
+// SysUserImpl 用户 服务层处理
+type SysUserImpl struct {
+ // 用户服务
+ sysUserRepository repository.ISysUser
+ // 用户与角色服务
+ sysUserRoleRepository repository.ISysUserRole
+ // 用户与岗位服务
+ sysUserPostRepository repository.ISysUserPost
+}
+
+// SelectUserPage 根据条件分页查询用户列表
+func (r *SysUserImpl) SelectUserPage(query map[string]any, dataScopeSQL string) map[string]any {
+ return r.sysUserRepository.SelectUserPage(query, dataScopeSQL)
+}
+
+// SelectUserList 根据条件查询用户列表
+func (r *SysUserImpl) SelectUserList(sysUser model.SysUser, dataScopeSQL string) []model.SysUser {
+ return []model.SysUser{}
+}
+
+// SelectAllocatedPage 根据条件分页查询分配用户角色列表
+func (r *SysUserImpl) SelectAllocatedPage(query map[string]any, dataScopeSQL string) map[string]any {
+ return r.sysUserRepository.SelectAllocatedPage(query, dataScopeSQL)
+}
+
+// SelectUserByUserName 通过用户名查询用户
+func (r *SysUserImpl) SelectUserByUserName(userName string) model.SysUser {
+ return r.sysUserRepository.SelectUserByUserName(userName)
+}
+
+// SelectUserById 通过用户ID查询用户
+func (r *SysUserImpl) SelectUserById(userId string) model.SysUser {
+ if userId == "" {
+ return model.SysUser{}
+ }
+ users := r.sysUserRepository.SelectUserByIds([]string{userId})
+ if len(users) > 0 {
+ return users[0]
+ }
+ return model.SysUser{}
+}
+
+// InsertUser 新增用户信息
+func (r *SysUserImpl) InsertUser(sysUser model.SysUser) string {
+ // 新增用户信息
+ insertId := r.sysUserRepository.InsertUser(sysUser)
+ if insertId != "" {
+ // 新增用户角色信息
+ r.insertUserRole(insertId, sysUser.RoleIDs)
+ // 新增用户岗位信息
+ r.insertUserPost(insertId, sysUser.PostIDs)
+ }
+ return insertId
+}
+
+// insertUserRole 新增用户角色信息
+func (r *SysUserImpl) insertUserRole(userId string, roleIds []string) int64 {
+ if userId == "" || len(roleIds) <= 0 {
+ return 0
+ }
+
+ sysUserRoles := []model.SysUserRole{}
+ for _, roleId := range roleIds {
+ // 管理员角色禁止操作,只能通过配置指定用户ID分配
+ if roleId == "" || roleId == admin.ROLE_ID {
+ continue
+ }
+ sysUserRoles = append(sysUserRoles, model.NewSysUserRole(userId, roleId))
+ }
+
+ return r.sysUserRoleRepository.BatchUserRole(sysUserRoles)
+}
+
+// insertUserPost 新增用户岗位信息
+func (r *SysUserImpl) insertUserPost(userId string, postIds []string) int64 {
+ if userId == "" || len(postIds) <= 0 {
+ return 0
+ }
+
+ sysUserPosts := []model.SysUserPost{}
+ for _, postId := range postIds {
+ if postId == "" {
+ continue
+ }
+ sysUserPosts = append(sysUserPosts, model.NewSysUserPost(userId, postId))
+ }
+
+ return r.sysUserPostRepository.BatchUserPost(sysUserPosts)
+}
+
+// UpdateUser 修改用户信息
+func (r *SysUserImpl) UpdateUser(sysUser model.SysUser) int64 {
+ return r.sysUserRepository.UpdateUser(sysUser)
+}
+
+// UpdateUserAndRolePost 修改用户信息同时更新角色和岗位
+func (r *SysUserImpl) UpdateUserAndRolePost(sysUser model.SysUser) int64 {
+ // 删除用户与角色关联
+ r.sysUserRoleRepository.DeleteUserRole([]string{sysUser.UserID})
+ // 新增用户角色信息
+ r.insertUserRole(sysUser.UserID, sysUser.RoleIDs)
+ // 删除用户与岗位关联
+ r.sysUserPostRepository.DeleteUserPost([]string{sysUser.UserID})
+ // 新增用户岗位信息
+ r.insertUserPost(sysUser.UserID, sysUser.PostIDs)
+ return r.sysUserRepository.UpdateUser(sysUser)
+}
+
+// DeleteUserByIds 批量删除用户信息
+func (r *SysUserImpl) DeleteUserByIds(userIds []string) (int64, error) {
+ // 检查是否存在
+ users := r.sysUserRepository.SelectUserByIds(userIds)
+ if len(users) <= 0 {
+ // 没有可访问用户数据!
+ return 0, fmt.Errorf("there is no accessible user data")
+ }
+ if len(users) == len(userIds) {
+ // 删除用户与角色关联
+ r.sysUserRoleRepository.DeleteUserRole(userIds)
+ // 删除用户与岗位关联
+ r.sysUserPostRepository.DeleteUserPost(userIds)
+ // ... 注意其他userId进行关联的表
+ // 删除用户
+ rows := r.sysUserRepository.DeleteUserByIds(userIds)
+ return rows, nil
+ }
+ return 0, fmt.Errorf("failed to delete user information")
+}
+
+// CheckUniqueUserName 校验用户名称是否唯一
+func (r *SysUserImpl) CheckUniqueUserName(userName, userId string) bool {
+ uniqueId := r.sysUserRepository.CheckUniqueUser(model.SysUser{
+ UserName: userName,
+ })
+ if uniqueId == userId {
+ return true
+ }
+ return uniqueId == ""
+}
+
+// CheckUniquePhone 校验手机号码是否唯一
+func (r *SysUserImpl) CheckUniquePhone(phonenumber, userId string) bool {
+ uniqueId := r.sysUserRepository.CheckUniqueUser(model.SysUser{
+ PhoneNumber: phonenumber,
+ })
+ if uniqueId == userId {
+ return true
+ }
+ return uniqueId == ""
+}
+
+// CheckUniqueEmail 校验email是否唯一
+func (r *SysUserImpl) CheckUniqueEmail(email, userId string) bool {
+ uniqueId := r.sysUserRepository.CheckUniqueUser(model.SysUser{
+ Email: email,
+ })
+ if uniqueId == userId {
+ return true
+ }
+ return uniqueId == ""
+}
diff --git a/src/modules/system/system.go b/src/modules/system/system.go
new file mode 100644
index 0000000..0cc0567
--- /dev/null
+++ b/src/modules/system/system.go
@@ -0,0 +1,443 @@
+package system
+
+import (
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/middleware"
+ "nms_nbi/src/framework/middleware/collectlogs"
+ "nms_nbi/src/framework/middleware/repeat"
+ "nms_nbi/src/modules/system/controller"
+ "nms_nbi/src/modules/system/service"
+
+ "github.com/gin-gonic/gin"
+)
+
+// Setup 模块路由注册
+func Setup(router *gin.Engine) {
+ logger.Infof("开始加载 ====> system 模块路由")
+
+ // 启动时需要的初始参数
+ InitLoad()
+
+ // 参数配置信息
+ sysConfigGroup := router.Group("/system/config")
+ {
+ sysConfigGroup.GET("/list",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:config:list"}}),
+ controller.NewSysConfig.List,
+ )
+ sysConfigGroup.GET("/:configId",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:config:query"}}),
+ controller.NewSysConfig.Info,
+ )
+ sysConfigGroup.POST("",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:config:add"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysConfig", collectlogs.BUSINESS_TYPE_INSERT)),
+ controller.NewSysConfig.Add,
+ )
+ sysConfigGroup.PUT("",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:config:edit"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysConfig", collectlogs.BUSINESS_TYPE_UPDATE)),
+ controller.NewSysConfig.Edit,
+ )
+ sysConfigGroup.DELETE("/:configIds",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:config:remove"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysConfig", collectlogs.BUSINESS_TYPE_DELETE)),
+ controller.NewSysConfig.Remove,
+ )
+ sysConfigGroup.PUT("/refreshCache",
+ repeat.RepeatSubmit(5),
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:config:remove"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysConfig", collectlogs.BUSINESS_TYPE_CLEAN)),
+ controller.NewSysConfig.RefreshCache,
+ )
+ sysConfigGroup.GET("/configKey/:configKey", controller.NewSysConfig.ConfigKey)
+ sysConfigGroup.POST("/export",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:config:export"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysConfig", collectlogs.BUSINESS_TYPE_EXPORT)),
+ controller.NewSysConfig.Export,
+ )
+ sysConfigGroup.PUT("/changeValue",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:config:edit"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysConfig", collectlogs.BUSINESS_TYPE_UPDATE)),
+ controller.NewSysConfig.ConfigValue,
+ )
+ }
+
+ // 部门信息
+ sysDeptGroup := router.Group("/system/dept")
+ {
+ sysDeptGroup.GET("/list",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dept:list"}}),
+ controller.NewSysDept.List,
+ )
+ sysDeptGroup.GET("/:deptId",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dept:query"}}),
+ controller.NewSysDept.Info,
+ )
+ sysDeptGroup.POST("",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dept:add"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysDept", collectlogs.BUSINESS_TYPE_INSERT)),
+ controller.NewSysDept.Add,
+ )
+ sysDeptGroup.PUT("",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dept:edit"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysDept", collectlogs.BUSINESS_TYPE_UPDATE)),
+ controller.NewSysDept.Edit,
+ )
+ sysDeptGroup.DELETE("/:deptId",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dept:remove"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysDept", collectlogs.BUSINESS_TYPE_DELETE)),
+ controller.NewSysDept.Remove,
+ )
+ sysDeptGroup.GET("/list/exclude/:deptId",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dept:list"}}),
+ controller.NewSysDept.ExcludeChild,
+ )
+ sysDeptGroup.GET("/treeSelect",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dept:list", "system:user:list"}}),
+ controller.NewSysDept.TreeSelect,
+ )
+ sysDeptGroup.GET("/roleDeptTreeSelect/:roleId",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dept:query", "system:user:edit"}}),
+ controller.NewSysDept.RoleDeptTreeSelect,
+ )
+ }
+
+ // 字典数据信息
+ sysDictDataGroup := router.Group("/system/dict/data")
+ {
+ sysDictDataGroup.GET("/list",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:list"}}),
+ controller.NewSysDictData.List,
+ )
+ sysDictDataGroup.GET("/:dictCode",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:query"}}),
+ controller.NewSysDictData.Info,
+ )
+ sysDictDataGroup.POST("",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:add"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysDictData", collectlogs.BUSINESS_TYPE_INSERT)),
+ controller.NewSysDictData.Add,
+ )
+ sysDictDataGroup.PUT("",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:edit"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysDictData", collectlogs.BUSINESS_TYPE_UPDATE)),
+ controller.NewSysDictData.Edit,
+ )
+ sysDictDataGroup.DELETE("/:dictCodes",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:remove"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysDictData", collectlogs.BUSINESS_TYPE_DELETE)),
+ controller.NewSysDictData.Remove,
+ )
+ sysDictDataGroup.GET("/type/:dictType",
+ middleware.PreAuthorize(nil),
+ controller.NewSysDictData.DictType,
+ )
+ sysDictDataGroup.POST("/export",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:export"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysDictData", collectlogs.BUSINESS_TYPE_EXPORT)),
+ controller.NewSysDictData.Export,
+ )
+ }
+
+ // 字典类型信息
+ sysDictTypeGroup := router.Group("/system/dict/type")
+ {
+ sysDictTypeGroup.GET("/list",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:list"}}),
+ controller.NewSysDictType.List,
+ )
+ sysDictTypeGroup.GET("/:dictId",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:query"}}),
+ controller.NewSysDictType.Info,
+ )
+ sysDictTypeGroup.POST("",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:add"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysDictType", collectlogs.BUSINESS_TYPE_INSERT)),
+ controller.NewSysDictType.Add,
+ )
+ sysDictTypeGroup.PUT("",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:edit"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysDictType", collectlogs.BUSINESS_TYPE_UPDATE)),
+ controller.NewSysDictType.Edit,
+ )
+ sysDictTypeGroup.DELETE("/:dictIds",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:remove"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysDictType", collectlogs.BUSINESS_TYPE_DELETE)),
+ controller.NewSysDictType.Remove,
+ )
+ sysDictTypeGroup.PUT("/refreshCache",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:remove"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysDictType", collectlogs.BUSINESS_TYPE_CLEAN)),
+ controller.NewSysDictType.RefreshCache,
+ )
+ sysDictTypeGroup.GET("/getDictOptionselect",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:query"}}),
+ controller.NewSysDictType.DictOptionselect,
+ )
+ sysDictTypeGroup.POST("/export",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:export"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysDictType", collectlogs.BUSINESS_TYPE_EXPORT)),
+ controller.NewSysDictType.Export,
+ )
+ }
+
+ // 菜单信息
+ sysMenuGroup := router.Group("/system/menu")
+ {
+ sysMenuGroup.GET("/list",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:menu:list"}}),
+ controller.NewSysMenu.List,
+ )
+ sysMenuGroup.GET("/:menuId",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:menu:query"}}),
+ controller.NewSysMenu.Info,
+ )
+ sysMenuGroup.POST("",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:menu:add"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysMenu", collectlogs.BUSINESS_TYPE_INSERT)),
+ controller.NewSysMenu.Add,
+ )
+ sysMenuGroup.PUT("",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:menu:edit"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysMenu", collectlogs.BUSINESS_TYPE_UPDATE)),
+ controller.NewSysMenu.Edit,
+ )
+ sysMenuGroup.DELETE("/:menuId",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:menu:remove"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysMenu", collectlogs.BUSINESS_TYPE_DELETE)),
+ controller.NewSysMenu.Remove,
+ )
+ sysMenuGroup.GET("/treeSelect",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:menu:list"}}),
+ controller.NewSysMenu.TreeSelect,
+ )
+ sysMenuGroup.GET("/roleMenuTreeSelect/:roleId",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:menu:list", "system:role:query"}}),
+ controller.NewSysMenu.RoleMenuTreeSelect,
+ )
+ }
+
+ // 岗位信息
+ sysPostGroup := router.Group("/system/post")
+ {
+ sysPostGroup.GET("/list",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:post:list"}}),
+ controller.NewSysPost.List,
+ )
+ sysPostGroup.GET("/:postId",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:post:query"}}),
+ controller.NewSysPost.Info,
+ )
+ sysPostGroup.POST("",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:post:add"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysPost", collectlogs.BUSINESS_TYPE_INSERT)),
+ controller.NewSysPost.Add,
+ )
+ sysPostGroup.PUT("",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:post:edit"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysPost", collectlogs.BUSINESS_TYPE_UPDATE)),
+ controller.NewSysPost.Edit,
+ )
+ sysPostGroup.DELETE("/:postIds",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:post:remove"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysPost", collectlogs.BUSINESS_TYPE_DELETE)),
+ controller.NewSysPost.Remove,
+ )
+ sysPostGroup.POST("/export",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:post:export"}}),
+ controller.NewSysPost.Export,
+ )
+ }
+
+ // 个人信息
+ sysProfileGroup := router.Group("/system/user/profile")
+ {
+ sysProfileGroup.GET("",
+ middleware.PreAuthorize(nil),
+ controller.NewSysProfile.Info,
+ )
+ sysProfileGroup.PUT("",
+ middleware.PreAuthorize(nil),
+ controller.NewSysProfile.UpdateProfile,
+ )
+ sysProfileGroup.PUT("/updatePwd",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysProfile", collectlogs.BUSINESS_TYPE_UPDATE)),
+ controller.NewSysProfile.UpdatePwd,
+ )
+ sysProfileGroup.POST("/avatar",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysProfileAvatar", collectlogs.BUSINESS_TYPE_UPDATE)),
+ controller.NewSysProfile.Avatar,
+ )
+ }
+
+ // 角色信息
+ sysRoleGroup := router.Group("/system/role")
+ {
+ sysRoleGroup.GET("/list",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:role:list"}}),
+ controller.NewSysRole.List,
+ )
+ sysRoleGroup.GET("/:roleId",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:role:query"}}),
+ controller.NewSysRole.Info,
+ )
+ sysRoleGroup.POST("",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:role:add"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysRole", collectlogs.BUSINESS_TYPE_INSERT)),
+ controller.NewSysRole.Add,
+ )
+ sysRoleGroup.PUT("",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:role:edit"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysRole", collectlogs.BUSINESS_TYPE_UPDATE)),
+ controller.NewSysRole.Edit,
+ )
+ sysRoleGroup.DELETE("/:roleIds",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:role:remove"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysRole", collectlogs.BUSINESS_TYPE_DELETE)),
+ controller.NewSysRole.Remove,
+ )
+ sysRoleGroup.PUT("/changeStatus",
+ repeat.RepeatSubmit(5),
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:role:edit"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysRole", collectlogs.BUSINESS_TYPE_UPDATE)),
+ controller.NewSysRole.Status,
+ )
+ sysRoleGroup.PUT("/dataScope",
+ repeat.RepeatSubmit(5),
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:edit"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysRole", collectlogs.BUSINESS_TYPE_UPDATE)),
+ controller.NewSysRole.DataScope,
+ )
+ sysRoleGroup.GET("/authUser/allocatedList",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:list"}}),
+ controller.NewSysRole.AuthUserAllocatedList,
+ )
+ sysRoleGroup.PUT("/authUser/checked",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:edit"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysRole", collectlogs.BUSINESS_TYPE_GRANT)),
+ controller.NewSysRole.AuthUserChecked,
+ )
+ sysRoleGroup.POST("/export",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:export"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysRole", collectlogs.BUSINESS_TYPE_EXPORT)),
+ controller.NewSysRole.Export,
+ )
+ }
+
+ // 用户信息
+ sysUserGroup := router.Group("/system/user")
+ {
+ sysUserGroup.GET("/list",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:list"}}),
+ controller.NewSysUser.List,
+ )
+ sysUserGroup.GET("/:userId",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:query"}}),
+ controller.NewSysUser.Info,
+ )
+ sysUserGroup.POST("",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:add"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysUser", collectlogs.BUSINESS_TYPE_INSERT)),
+ controller.NewSysUser.Add,
+ )
+ sysUserGroup.PUT("",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:edit"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysUser", collectlogs.BUSINESS_TYPE_UPDATE)),
+ controller.NewSysUser.Edit,
+ )
+ sysUserGroup.DELETE("/:userIds",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:remove"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysUser", collectlogs.BUSINESS_TYPE_DELETE)),
+ controller.NewSysUser.Remove,
+ )
+ sysUserGroup.PUT("/resetPwd",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:resetPwd"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysUser", collectlogs.BUSINESS_TYPE_UPDATE)),
+ controller.NewSysUser.ResetPwd,
+ )
+ sysUserGroup.PUT("/changeStatus",
+ repeat.RepeatSubmit(5),
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:edit"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysUser", collectlogs.BUSINESS_TYPE_UPDATE)),
+ controller.NewSysUser.Status,
+ )
+ sysUserGroup.POST("/export",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:export"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysUser", collectlogs.BUSINESS_TYPE_EXPORT)),
+ controller.NewSysUser.Export,
+ )
+ sysUserGroup.GET("/importTemplate",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:import"}}),
+ controller.NewSysUser.Template,
+ )
+ sysUserGroup.POST("/importData",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:import"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysUser", collectlogs.BUSINESS_TYPE_INSERT)),
+ controller.NewSysUser.ImportData,
+ )
+ }
+
+ // 操作日志记录信息
+ sysLogOperGroup := router.Group("/system/log/operate")
+ {
+ sysLogOperGroup.GET("/list",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:operate:list"}}),
+ controller.NewSysLogOperate.List,
+ )
+ sysLogOperGroup.DELETE("/:operIds",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:operate:remove"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysLogOper", collectlogs.BUSINESS_TYPE_DELETE)),
+ controller.NewSysLogOperate.Remove,
+ )
+ sysLogOperGroup.DELETE("/clean",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:operate:remove"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysLogOper", collectlogs.BUSINESS_TYPE_CLEAN)),
+ controller.NewSysLogOperate.Clean,
+ )
+ sysLogOperGroup.POST("/export",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:operate:export"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysLogOper", collectlogs.BUSINESS_TYPE_EXPORT)),
+ controller.NewSysLogOperate.Export,
+ )
+ }
+
+ // 系统登录日志信息
+ sysLogLoginGroup := router.Group("/system/log/login")
+ {
+ sysLogLoginGroup.GET("/list",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:login:list"}}),
+ controller.NewSysLogLogin.List,
+ )
+ sysLogLoginGroup.DELETE("/:loginIds",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:login:remove"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysLogLogin", collectlogs.BUSINESS_TYPE_DELETE)),
+ controller.NewSysLogLogin.Remove,
+ )
+ sysLogLoginGroup.DELETE("/clean",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:login:remove"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysLogLogin", collectlogs.BUSINESS_TYPE_CLEAN)),
+ controller.NewSysLogLogin.Clean,
+ )
+ sysLogLoginGroup.PUT("/unlock/:userName",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:login:unlock"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysLogLogin", collectlogs.BUSINESS_TYPE_CLEAN)),
+ controller.NewSysLogLogin.Unlock,
+ )
+ sysLogLoginGroup.POST("/export",
+ middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:login:export"}}),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysLogLogin", collectlogs.BUSINESS_TYPE_EXPORT)),
+ controller.NewSysLogLogin.Export,
+ )
+ }
+}
+
+// InitLoad 初始参数
+func InitLoad() {
+ // 启动时,刷新缓存-参数配置
+ service.NewSysConfigImpl.ResetConfigCache()
+ // 启动时,刷新缓存-字典类型数据
+ service.NewSysDictTypeImpl.ResetDictCache()
+}
diff --git a/src/modules/trace/controller/tcpdump.go b/src/modules/trace/controller/tcpdump.go
new file mode 100644
index 0000000..7465590
--- /dev/null
+++ b/src/modules/trace/controller/tcpdump.go
@@ -0,0 +1,125 @@
+package controller
+
+import (
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/vo/result"
+ traceService "nms_nbi/src/modules/trace/service"
+
+ "github.com/gin-gonic/gin"
+ "github.com/gin-gonic/gin/binding"
+)
+
+// 实例化控制层 TcpdumpController 结构体
+var NewTcpdump = &TcpdumpController{
+ TcpdumpService: traceService.NewTcpdumpImpl,
+}
+
+// 信令抓包请求
+//
+// PATH /tcpdump
+type TcpdumpController struct {
+ // 信令抓包服务
+ TcpdumpService traceService.ITcpdump
+}
+
+// 网元抓包PACP 开始
+//
+// POST /start
+func (s *TcpdumpController) DumpStart(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body struct {
+ NeType string `json:"neType" binding:"required"` // 网元类型
+ NeId string `json:"neId" binding:"required"` // 网元ID
+ Cmd string `json:"cmd" binding:"required"` // 命令 "-n -s 0 -v -w"
+ }
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ fileName, err := s.TcpdumpService.DumpStart(body.NeType, body.NeId, body.Cmd)
+ if err != nil {
+ msg := err.Error()
+ if msg == "noData" {
+ // 找不到 %s %s 对应网元信息
+ msg = i18n.TTemplate(language, "trace.tcpdump.noData", map[string]any{"type": body.NeType, "id": body.NeId})
+ }
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+ c.JSON(200, result.OkData(map[string]any{
+ "msg": "tcpdump started",
+ "out": fileName,
+ "log": "",
+ }))
+}
+
+// 网元抓包PACP 结束
+//
+// POST /stop
+func (s *TcpdumpController) DumpStop(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body struct {
+ NeType string `json:"neType" binding:"required"` // 网元类型
+ NeId string `json:"neId" binding:"required"` // 网元ID
+ FileName string `json:"fileName"` // 文件名 查看日志信息
+ }
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ logMsg, err := s.TcpdumpService.DumpStop(body.NeType, body.NeId, body.FileName)
+ if err != nil {
+ msg := err.Error()
+ if msg == "noData" {
+ // 找不到 %s %s 对应网元信息
+ msg := i18n.TTemplate(language, "trace.tcpdump.noData", map[string]any{"type": body.NeType, "id": body.NeId})
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+ c.JSON(200, result.OkData(map[string]any{
+ "msg": "tcpdump stopped",
+ "out": body.FileName,
+ "log": logMsg,
+ }))
+}
+
+// UPF标准版内部抓包
+//
+// POST /traceUPF
+func (s *TcpdumpController) TraceUPF(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+ var body struct {
+ NeType string `json:"neType" binding:"required"` // 网元类型
+ NeId string `json:"neId" binding:"required"` // 网元ID
+ Cmd string `json:"cmd" binding:"required"` // 命令
+ }
+ err := c.ShouldBindBodyWith(&body, binding.JSON)
+ if err != nil {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+
+ fileName, logMsg, err := s.TcpdumpService.DumpUPF(body.NeType, body.NeId, body.Cmd)
+ if err != nil {
+ msg := err.Error()
+ if msg == "noData" {
+ // 找不到 %s %s 对应网元信息
+ msg = i18n.TTemplate(language, "trace.tcpdump.noData", map[string]any{"type": body.NeType, "id": body.NeId})
+ }
+ c.JSON(200, result.ErrMsg(msg))
+ return
+ }
+ c.JSON(200, result.OkData(map[string]any{
+ "msg": "trace UPF dump pacp",
+ "out": fileName,
+ "log": logMsg,
+ }))
+}
diff --git a/src/modules/trace/service/tcpdump.go b/src/modules/trace/service/tcpdump.go
new file mode 100644
index 0000000..cc74606
--- /dev/null
+++ b/src/modules/trace/service/tcpdump.go
@@ -0,0 +1,13 @@
+package service
+
+// 信令抓包 服务层接口
+type ITcpdump interface {
+ // DumpStart 触发tcpdump开始抓包 filePcapName, err
+ DumpStart(neType, neId, cmdStr string) (string, error)
+
+ // DumpStop 停止已存在抓包句柄
+ DumpStop(neType, neId, fileName string) (string, error)
+
+ // DumpUPF UPF标准版抓包
+ DumpUPF(neType, neId, cmdStr string) (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..c9a3d8e
--- /dev/null
+++ b/src/modules/trace/service/tcpdump.impl.go
@@ -0,0 +1,185 @@
+package service
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "nms_nbi/src/framework/config"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/cmd"
+ "nms_nbi/src/framework/utils/date"
+ neService "nms_nbi/src/modules/network_element/service"
+)
+
+// 实例化服务层 TcpdumpImpl 结构体
+var NewTcpdumpImpl = &TcpdumpImpl{
+ neInfoService: neService.NewNeInfoImpl,
+ tcpdumpPIDMap: map[string]string{},
+}
+
+// 信令抓包 服务层处理
+type TcpdumpImpl struct {
+ // 网元信息服务
+ neInfoService neService.INeInfo
+ // 抓包进程PID
+ tcpdumpPIDMap map[string]string
+}
+
+// DumpStart 触发tcpdump开始抓包 filePcapName, err
+func (s *TcpdumpImpl) DumpStart(neType, neId, cmdStr string) (string, error) {
+ // 检查网元信息
+ neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID(neType, neId)
+ if neInfo.NeId != neId {
+ return "", fmt.Errorf("noData")
+ }
+
+ // SSH命令
+ usernameNe := config.Get("ne.user").(string) // 网元统一用户
+ sshHost := fmt.Sprintf("%s@%s", usernameNe, neInfo.IP)
+
+ // 是否拥有sudo权限并拼接
+ withSudo := ""
+ if _, err := cmd.ExecWithCheck("ssh", sshHost, "sudo -n uname"); err == nil {
+ withSudo = "sudo "
+ }
+
+ if msg, err := cmd.ExecWithCheck("ssh", sshHost, fmt.Sprintf("%s tcpdump --version", withSudo)); err != nil {
+ // stderr: bash: tcpdump:未找到命令 => exit status 127
+ msg = strings.TrimSpace(msg)
+ logger.Warnf("DumpStart err: %s => %s", msg, err.Error())
+ return "", fmt.Errorf(msg)
+ }
+
+ // 拼装命令
+ neTypeID := fmt.Sprintf("%s_%s", neInfo.NeType, neInfo.NeId)
+ timeStr := date.ParseDateToStr(time.Now(), date.YYYYMMDDHHMMSS)
+ fileName := fmt.Sprintf("%s_%s", timeStr, neTypeID)
+ sendCmd := fmt.Sprintf("cd /tmp \n %s nohup timeout 30m tcpdump -i any %s -s0 -w %s.pcap > %s.log 2>&1 & \necho $!", withSudo, cmdStr, fileName, fileName)
+ // cd /tmp
+ // sudo nohup timeout 60m tcpdump -i any -n -s 0 -v -w -s0 -w 20240115140822_UDM_001.pcap > 20240115140822_UDM_001.log 2>&1 & echo $!
+ msg, err := cmd.ExecWithCheck("ssh", sshHost, sendCmd)
+ msg = strings.TrimSpace(msg)
+ if err != nil || strings.HasPrefix(msg, "stderr:") {
+ logger.Warnf("DumpStart err: %s => %s", msg, err.Error())
+ return "", err
+ }
+
+ // 检查进程 ps aux | grep tcpdump
+ // 强杀 sudo pkill tcpdump
+ s.tcpdumpPIDMap[neTypeID] = msg
+ return fileName, err
+}
+
+// DumpStop 停止已存在抓包句柄
+func (s *TcpdumpImpl) DumpStop(neType, neId, fileName string) (string, error) {
+ // 检查网元信息
+ neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID(neType, neId)
+ if neInfo.NeId != neId {
+ return "", fmt.Errorf("noData")
+ }
+
+ // SSH命令
+ usernameNe := config.Get("ne.user").(string) // 网元统一用户
+ sshHost := fmt.Sprintf("%s@%s", usernameNe, neInfo.IP)
+
+ // 是否拥有sudo权限并拼接
+ withSudo := ""
+ if _, err := cmd.ExecWithCheck("ssh", sshHost, "sudo -n uname"); err == nil {
+ withSudo = "sudo "
+ }
+
+ // 是否存在进程
+ neTypeID := fmt.Sprintf("%s_%s", neInfo.NeType, neInfo.NeId)
+ pid, ok := s.tcpdumpPIDMap[neTypeID]
+ if !ok || pid == "" {
+ return "", fmt.Errorf("tcpdump is not running")
+ }
+
+ // 查看日志
+ viewLogFile := ""
+ if fileName != "" && strings.Contains(fileName, neTypeID) {
+ viewLogFile = fmt.Sprintf("\n cat %s.log", fileName)
+ }
+
+ // 拼装命令
+ sendCmd := fmt.Sprintf("cd /tmp \n %s kill %s %s", withSudo, pid, viewLogFile)
+ msg, err := cmd.ExecWithCheck("ssh", sshHost, sendCmd)
+ delete(s.tcpdumpPIDMap, neTypeID)
+ if err != nil || strings.HasPrefix(msg, "stderr:") {
+ logger.Warnf("DumpStop err: %s => %s", strings.TrimSpace(msg), err.Error())
+ return "", err
+ }
+ return msg, nil
+}
+
+// DumpUPF UPF标准版抓包
+func (s *TcpdumpImpl) DumpUPF(neType, neId, cmdStr string) (string, string, error) {
+ // 检查网元信息
+ neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID(neType, neId)
+ if neInfo.NeId != neId {
+ return "", "", fmt.Errorf("noData")
+ }
+
+ // SSH命令
+ usernameNe := config.Get("ne.user").(string) // 网元统一用户
+ sshHost := fmt.Sprintf("%s@%s", usernameNe, neInfo.IP)
+
+ // 是否拥有sudo权限并拼接
+ withSudo := ""
+ if _, err := cmd.ExecWithCheck("ssh", sshHost, "sudo -n uname"); err == nil {
+ withSudo = "sudo "
+ }
+
+ if msg, err := cmd.ExecWithCheck("ssh", sshHost, fmt.Sprintf("%s expect -version", withSudo)); err != nil {
+ // stderr: bash: expect:未找到命令 => exit status 127
+ msg = strings.TrimSpace(msg)
+ logger.Warnf("DumpUPF err: %s => %s", msg, err.Error())
+ return "", "", fmt.Errorf(msg)
+ }
+
+ // 拼装命令
+ neTypeID := fmt.Sprintf("%s_%s", neInfo.NeType, neInfo.NeId)
+ timeStr := date.ParseDateToStr(time.Now(), date.YYYYMMDDHHMMSS)
+ fileName := fmt.Sprintf("%s_%s", timeStr, neTypeID)
+ // UPF标准版本telnet脚本
+ scriptStr := "set pcapCmd [lindex $argv 0]\nspawn telnet localhost 5002\nexpect \"upfd1# \"\nsend \"$pcapCmd\\n\"\nexpect \"upfd1# \"\nsend \"quit\\n\"\nexpect \"eof\""
+ writePcapFile := fmt.Sprintf("echo '%s' > pcapUPF.sh\n %s chmod +x pcapUPF.sh", scriptStr, withSudo)
+ writeLogFile := fmt.Sprintf("> %s.log 2>&1 \ncat %s.log", fileName, fileName)
+
+ // 以off结尾是停止抓包,不需要写文件
+ pcapCmd := cmdStr
+ if !strings.HasSuffix(pcapCmd, "off") {
+ pcapCmd = fmt.Sprintf("%s file %s.pcap", cmdStr, fileName)
+ }
+ sendCmd := fmt.Sprintf("cd /tmp \n%s\n expect ./pcapUPF.sh '%s' %s", writePcapFile, pcapCmd, writeLogFile)
+ // cd /tmp
+ // echo '' >
+ // expect ./cap.sh > pcapUPF.sh
+ // sudo chmod +x pcapUPF.sh
+ // expect ./cap.sh 'pcap dispatch trace off' > 20240115165701_UDM_001.log 2>&1
+ // cat 20240115165701_UDM_001.log
+ msg, err := cmd.ExecWithCheck("ssh", sshHost, sendCmd)
+ msg = strings.TrimSpace(msg)
+ if err != nil || strings.HasPrefix(msg, "stderr:") {
+ logger.Warnf("DumpUPF err: %s => %s", msg, err.Error())
+ return "", "", err
+ }
+ if strings.Contains(msg, "Unable to connect to remote host") {
+ return "", "", fmt.Errorf("connection refused")
+ }
+ // 以off结尾是停止抓包,不需要写文件
+ if strings.HasSuffix(pcapCmd, "off") {
+ if strings.Contains(msg, "Write ") {
+ lastTmpIndex := strings.LastIndex(msg, "/tmp/")
+ text := msg[lastTmpIndex+5:]
+ extensionIndex := strings.LastIndex(text, ".pcap")
+ if extensionIndex != -1 {
+ fileName = text[:extensionIndex]
+ }
+ } else {
+ fileName = ""
+ }
+ }
+ return fileName, msg, err
+}
diff --git a/src/modules/trace/trace.go b/src/modules/trace/trace.go
new file mode 100644
index 0000000..39a27bb
--- /dev/null
+++ b/src/modules/trace/trace.go
@@ -0,0 +1,35 @@
+package trace
+
+import (
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/middleware"
+ "nms_nbi/src/framework/middleware/collectlogs"
+ "nms_nbi/src/modules/trace/controller"
+
+ "github.com/gin-gonic/gin"
+)
+
+// 模块路由注册
+func Setup(router *gin.Engine) {
+ logger.Infof("开始加载 ====> trace 模块路由")
+
+ // 信令抓包
+ tcpdumpGroup := router.Group("/tcpdump")
+ {
+ tcpdumpGroup.POST("/start",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.tcpdump", collectlogs.BUSINESS_TYPE_OTHER)),
+ controller.NewTcpdump.DumpStart,
+ )
+ tcpdumpGroup.POST("/stop",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.tcpdump", collectlogs.BUSINESS_TYPE_OTHER)),
+ controller.NewTcpdump.DumpStop,
+ )
+ tcpdumpGroup.POST("/traceUPF",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.tcpdump", collectlogs.BUSINESS_TYPE_OTHER)),
+ controller.NewTcpdump.TraceUPF,
+ )
+ }
+}
diff --git a/src/modules/ws/controller/ws.go b/src/modules/ws/controller/ws.go
new file mode 100644
index 0000000..d2745c6
--- /dev/null
+++ b/src/modules/ws/controller/ws.go
@@ -0,0 +1,302 @@
+package controller
+
+import (
+ "encoding/json"
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+
+ neService "nms_nbi/src/modules/network_element/service"
+
+ "nms_nbi/src/framework/i18n"
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/ctx"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/utils/ssh"
+ "nms_nbi/src/framework/utils/telnet"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/ws/service"
+
+ "github.com/gin-gonic/gin"
+)
+
+// 实例化控制层 WSController 结构体
+var NewWSController = &WSController{
+ wsService: service.NewWSImpl,
+ wsSendService: service.NewWSSendImpl,
+ neHostService: neService.NewNeHostImpl,
+}
+
+// WebSocket通信
+//
+// PATH /ws
+type WSController struct {
+ // WebSocket 服务
+ wsService service.IWS
+ // WebSocket消息发送 服务
+ wsSendService service.IWSSend
+ // 网元主机连接服务
+ neHostService neService.INeHost
+}
+
+// 通用
+//
+// GET /?subGroupIDs=0
+func (s *WSController) WS(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+
+ // 登录用户信息
+ loginUser, err := ctx.LoginUser(c)
+ if err != nil {
+ c.JSON(401, result.CodeMsg(401, i18n.TKey(language, err.Error())))
+ return
+ }
+
+ // 订阅消息组
+ var subGroupIDs []string
+ subGroupIDStr := c.Query("subGroupID")
+ if subGroupIDStr != "" {
+ // 处理字符转id数组后去重
+ ids := strings.Split(subGroupIDStr, ",")
+ uniqueIDs := parse.RemoveDuplicates(ids)
+ if len(uniqueIDs) > 0 {
+ subGroupIDs = uniqueIDs
+ }
+ }
+
+ // 将 HTTP 连接升级为 WebSocket 连接
+ conn := s.wsService.UpgraderWs(c.Writer, c.Request)
+ if conn == nil {
+ return
+ }
+ defer conn.Close()
+
+ wsClient := s.wsService.NewClient(loginUser.UserID, subGroupIDs, conn, nil)
+
+ // 等待停止信号
+ for value := range wsClient.StopChan {
+ s.wsService.CloseClient(wsClient.ID)
+ logger.Infof("ws Stop Client UID %s %s", wsClient.BindUid, value)
+ return
+ }
+}
+
+// 测试
+//
+// GET /test?clientId=&groupID=
+func (s *WSController) Test(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+
+ // 登录用户信息
+ loginUser, err := ctx.LoginUser(c)
+ if err != nil {
+ c.JSON(401, result.CodeMsg(401, i18n.TKey(language, err.Error())))
+ return
+ }
+
+ errMsgArr := []string{}
+
+ clientId := c.Query("clientId")
+ if clientId != "" {
+ err := s.wsSendService.ByClientID(c.Query("clientId"), loginUser)
+ if err != nil {
+ errMsgArr = append(errMsgArr, "clientId: "+err.Error())
+ }
+ }
+
+ groupID := c.Query("groupID")
+ if groupID != "" {
+ err := s.wsSendService.ByGroupID(c.Query("groupID"), loginUser)
+ if err != nil {
+ errMsgArr = append(errMsgArr, "groupID: "+err.Error())
+ }
+ }
+
+ c.JSON(200, result.OkData(errMsgArr))
+}
+
+// SSH终端
+//
+// GET /ssh?hostId=1&cols=80&rows=40
+func (s *WSController) SSH(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+
+ // 登录用户信息
+ loginUser, err := ctx.LoginUser(c)
+ if err != nil {
+ c.JSON(401, result.CodeMsg(401, i18n.TKey(language, err.Error())))
+ return
+ }
+
+ // 连接主机ID
+ hostId := c.Query("hostId")
+ if hostId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ neHost := s.neHostService.SelectById(hostId)
+ if neHost.HostID != hostId || neHost.HostType != "ssh" {
+ // 没有可访问主机信息数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "neHost.noData")))
+ return
+ }
+
+ // 创建链接SSH客户端
+ var connSSH ssh.ConnSSH
+ neHost.CopyTo(&connSSH)
+ client, err := connSSH.NewClient()
+ if err != nil {
+ // 连接主机失败,请检查连接参数后重试
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "neHost.errByHostInfo")))
+ return
+ }
+ defer client.Close()
+
+ // 终端单行字符数
+ cols, err := strconv.Atoi(c.Query("cols"))
+ if err != nil {
+ cols = 80
+ }
+ // 终端显示行数
+ rows, err := strconv.Atoi(c.Query("rows"))
+ if err != nil {
+ rows = 40
+ }
+
+ // 创建SSH客户端会话
+ clientSession, err := client.NewClientSession(cols, rows)
+ if err != nil {
+ // 连接主机失败,请检查连接参数后重试
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "neHost.errByHostInfo")))
+ return
+ }
+ defer clientSession.Close()
+
+ // 将 HTTP 连接升级为 WebSocket 连接
+ wsConn := s.wsService.UpgraderWs(c.Writer, c.Request)
+ if wsConn == nil {
+ return
+ }
+ defer wsConn.Close()
+
+ wsClient := s.wsService.NewClient(loginUser.UserID, nil, wsConn, clientSession)
+
+ // 实时读取SSH消息直接输出
+ msTicker := time.NewTicker(100 * time.Millisecond)
+ defer msTicker.Stop()
+ go func() {
+ for ms := range msTicker.C {
+ outputByte := clientSession.Read()
+ if len(outputByte) > 0 {
+ outputStr := string(outputByte)
+ msgByte, _ := json.Marshal(result.Ok(map[string]any{
+ "requestId": fmt.Sprintf("ssh_%s_%d", hostId, ms.UnixMilli()),
+ "data": outputStr,
+ }))
+ wsClient.MsgChan <- msgByte
+
+ // 退出ssh登录
+ if strings.LastIndex(outputStr, "logout\r\n") != -1 {
+ time.Sleep(1 * time.Second)
+ s.wsService.CloseClient(wsClient.ID)
+ return
+ }
+ }
+ }
+ }()
+
+ // 等待停止信号
+ for value := range wsClient.StopChan {
+ s.wsService.CloseClient(wsClient.ID)
+ logger.Infof("ws Stop Client UID %s %s", wsClient.BindUid, value)
+ return
+ }
+}
+
+// Telnet终端
+//
+// GET /telnet?hostId=1
+func (s *WSController) Telnet(c *gin.Context) {
+ language := ctx.AcceptLanguage(c)
+
+ // 登录用户信息
+ loginUser, err := ctx.LoginUser(c)
+ if err != nil {
+ c.JSON(401, result.CodeMsg(401, i18n.TKey(language, err.Error())))
+ return
+ }
+
+ // 连接主机ID
+ hostId := c.Query("hostId")
+ if hostId == "" {
+ c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
+ return
+ }
+ neHost := s.neHostService.SelectById(hostId)
+ if neHost.HostID != hostId || neHost.HostType != "telnet" {
+ // 没有可访问主机信息数据!
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "neHost.noData")))
+ return
+ }
+
+ // 创建链接Telnet客户端
+ var connTelnet telnet.ConnTelnet
+ neHost.CopyTo(&connTelnet)
+ client, err := connTelnet.NewClient()
+ if err != nil {
+ // 连接主机失败,请检查连接参数后重试
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "neHost.errByHostInfo")))
+ return
+ }
+ defer client.Close()
+
+ // 创建Telnet客户端会话
+ clientSession, err := client.NewClientSession()
+ if err != nil {
+ // 连接主机失败,请检查连接参数后重试
+ c.JSON(200, result.ErrMsg(i18n.TKey(language, "neHost.errByHostInfo")))
+ return
+ }
+ defer clientSession.Close()
+
+ // 将 HTTP 连接升级为 WebSocket 连接
+ wsConn := s.wsService.UpgraderWs(c.Writer, c.Request)
+ if wsConn == nil {
+ return
+ }
+ defer wsConn.Close()
+
+ wsClient := s.wsService.NewClient(loginUser.UserID, nil, wsConn, clientSession)
+
+ // 实时读取Telnet消息直接输出
+ msTicker := time.NewTicker(100 * time.Millisecond)
+ defer msTicker.Stop()
+ go func() {
+ for ms := range msTicker.C {
+ outputByte := clientSession.Read()
+ if len(outputByte) > 0 {
+ outputStr := strings.TrimRight(string(outputByte), "\x00")
+ msgByte, _ := json.Marshal(result.Ok(map[string]any{
+ "requestId": fmt.Sprintf("telnet_%s_%d", hostId, ms.UnixMilli()),
+ "data": outputStr,
+ }))
+ wsClient.MsgChan <- msgByte
+
+ // 退出telnet登录
+ if strings.LastIndex(outputStr, "logout\r\n") != -1 {
+ time.Sleep(1 * time.Second)
+ s.wsService.CloseClient(wsClient.ID)
+ return
+ }
+ }
+ }
+ }()
+
+ // 等待停止信号
+ for value := range wsClient.StopChan {
+ s.wsService.CloseClient(wsClient.ID)
+ logger.Infof("ws Stop Client UID %s %s", wsClient.BindUid, value)
+ return
+ }
+}
diff --git a/src/modules/ws/model/net_connect.go b/src/modules/ws/model/net_connect.go
new file mode 100644
index 0000000..8116c28
--- /dev/null
+++ b/src/modules/ws/model/net_connect.go
@@ -0,0 +1,20 @@
+package model
+
+import "github.com/shirou/gopsutil/v3/net"
+
+// NetConnectData 网络连接进程数据
+type NetConnectData struct {
+ Type string `json:"type"`
+ Status string `json:"status"`
+ Laddr net.Addr `json:"localaddr"`
+ Raddr net.Addr `json:"remoteaddr"`
+ PID int32 `json:"PID"`
+ Name string `json:"name"`
+}
+
+// NetConnectQuery 网络连接进程查询
+type NetConnectQuery struct {
+ Port int32 `json:"port"`
+ ProcessName string `json:"processName"`
+ ProcessID int32 `json:"processID"`
+}
diff --git a/src/modules/ws/model/ps_process.go b/src/modules/ws/model/ps_process.go
new file mode 100644
index 0000000..e93247a
--- /dev/null
+++ b/src/modules/ws/model/ps_process.go
@@ -0,0 +1,38 @@
+package model
+
+// PsProcessData 进程数据
+type PsProcessData struct {
+ PID int32 `json:"PID"`
+ Name string `json:"name"`
+ PPID int32 `json:"PPID"`
+ Username string `json:"username"`
+ Status string `json:"status"`
+ StartTime string `json:"startTime"`
+ NumThreads int32 `json:"numThreads"`
+ NumConnections int `json:"numConnections"`
+ CpuPercent string `json:"cpuPercent"`
+
+ DiskRead string `json:"diskRead"`
+ DiskWrite string `json:"diskWrite"`
+ CmdLine string `json:"cmdLine"`
+
+ Rss string `json:"rss"`
+ VMS string `json:"vms"`
+ HWM string `json:"hwm"`
+ Data string `json:"data"`
+ Stack string `json:"stack"`
+ Locked string `json:"locked"`
+ Swap string `json:"swap"`
+
+ CpuValue float64 `json:"cpuValue"`
+ RssValue uint64 `json:"rssValue"`
+
+ Envs []string `json:"envs"`
+}
+
+// PsProcessQuery 进程查询
+type PsProcessQuery struct {
+ Pid int32 `json:"pid"`
+ Name string `json:"name"`
+ Username string `json:"username"`
+}
diff --git a/src/modules/ws/model/ws.go b/src/modules/ws/model/ws.go
new file mode 100644
index 0000000..759195f
--- /dev/null
+++ b/src/modules/ws/model/ws.go
@@ -0,0 +1,22 @@
+package model
+
+import "github.com/gorilla/websocket"
+
+// WSClient ws客户端
+type WSClient struct {
+ ID string // 客户端连接ID-随机字符串16位
+ Conn *websocket.Conn // 连接实例
+ LastHeartbeat int64 // 最近一次心跳消息(毫秒)
+ BindUid string // 绑定登录用户ID
+ SubGroup []string // 订阅组ID
+ MsgChan chan []byte // 消息通道
+ StopChan chan struct{} // 停止信号-退出协程
+ ChildConn any // 子连接实例-携带某种连接会话
+}
+
+// WSRequest ws消息接收
+type WSRequest struct {
+ RequestID string `json:"requestId"` // 请求ID
+ Type string `json:"type"` // 业务类型
+ Data any `json:"data"` // 查询结构
+}
diff --git a/src/modules/ws/processor/cdr_connect.go b/src/modules/ws/processor/cdr_connect.go
new file mode 100644
index 0000000..7b419fb
--- /dev/null
+++ b/src/modules/ws/processor/cdr_connect.go
@@ -0,0 +1,29 @@
+package processor
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/vo/result"
+ neDataModel "nms_nbi/src/modules/network_data/model"
+ neDataService "nms_nbi/src/modules/network_data/service"
+)
+
+// GetCDRConnect 获取CDR会话事件-IMS
+func GetCDRConnect(requestID string, data any) ([]byte, error) {
+ msgByte, _ := json.Marshal(data)
+ var query neDataModel.CDREventQuery
+ err := json.Unmarshal(msgByte, &query)
+ if err != nil {
+ logger.Warnf("ws processor GetCDRConnect err: %s", err.Error())
+ return nil, fmt.Errorf("query data structure error")
+ }
+
+ dataMap := neDataService.NewCDREventImpl.SelectPage(query)
+ resultByte, err := json.Marshal(result.Ok(map[string]any{
+ "requestId": requestID,
+ "data": dataMap,
+ }))
+ return resultByte, err
+}
diff --git a/src/modules/ws/processor/ne_state.go b/src/modules/ws/processor/ne_state.go
new file mode 100644
index 0000000..ef13c99
--- /dev/null
+++ b/src/modules/ws/processor/ne_state.go
@@ -0,0 +1,57 @@
+package processor
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/vo/result"
+ neService "nms_nbi/src/modules/network_element/service"
+)
+
+// GetNeState 获取网元服务状态
+func GetNeState(requestID string, data any) ([]byte, error) {
+ msgByte, _ := json.Marshal(data)
+ var querys struct {
+ NeType string `json:"neType" form:"neType" binding:"required"`
+ NeID string `json:"neId" form:"neId" binding:"required"`
+ }
+ err := json.Unmarshal(msgByte, &querys)
+ if err != nil {
+ logger.Warnf("ws processor GetUPFTotalFlow err: %s", err.Error())
+ return nil, fmt.Errorf("query data structure error")
+ }
+
+ if querys.NeType == "" || querys.NeID == "" {
+ return nil, fmt.Errorf("query neType any neId empty")
+ }
+
+ // 查询网元获取IP
+ neInfo := neService.NewNeInfoImpl.SelectNeInfoByNeTypeAndNeID(querys.NeType, querys.NeID)
+ if neInfo.NeId != querys.NeID || neInfo.IP == "" {
+ return nil, fmt.Errorf("no matching network element information found")
+ }
+
+ // 网元直连
+ resData, err := neService.NeState(neInfo)
+ if err != nil {
+ resultByte, err := json.Marshal(result.Ok(map[string]any{
+ "requestId": requestID,
+ "data": map[string]any{
+ "online": false,
+ "neId": neInfo.NeId,
+ "neName": neInfo.NeName,
+ "neType": neInfo.NeType,
+ "neIP": neInfo.IP,
+ },
+ }))
+ return resultByte, err
+ }
+
+ resData["online"] = true
+ resultByte, err := json.Marshal(result.Ok(map[string]any{
+ "requestId": requestID,
+ "data": resData,
+ }))
+ return resultByte, err
+}
diff --git a/src/modules/ws/processor/net_connect.go b/src/modules/ws/processor/net_connect.go
new file mode 100644
index 0000000..3042c36
--- /dev/null
+++ b/src/modules/ws/processor/net_connect.go
@@ -0,0 +1,62 @@
+package processor
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/ws/model"
+
+ "github.com/shirou/gopsutil/v3/net"
+ "github.com/shirou/gopsutil/v3/process"
+)
+
+// GetNetConnections 获取网络连接进程
+func GetNetConnections(requestID string, data any) ([]byte, error) {
+ msgByte, _ := json.Marshal(data)
+ var query model.NetConnectQuery
+ err := json.Unmarshal(msgByte, &query)
+ if err != nil {
+ logger.Warnf("ws processor GetNetConnections err: %s", err.Error())
+ return nil, fmt.Errorf("query data structure error")
+ }
+
+ dataArr := []model.NetConnectData{}
+ for _, netType := range [...]string{"tcp", "udp"} {
+ connections, err := net.Connections(netType)
+ if err != nil {
+ continue
+ }
+ for _, conn := range connections {
+ if query.ProcessID > 0 && query.ProcessID != conn.Pid {
+ continue
+ }
+ proc, err := process.NewProcess(conn.Pid)
+ if err == nil {
+ name, _ := proc.Name()
+ if name != "" && query.ProcessName != "" && !strings.Contains(name, query.ProcessName) {
+ continue
+ }
+ if query.Port > 0 && query.Port != int32(conn.Laddr.Port) && query.Port != int32(conn.Raddr.Port) {
+ continue
+ }
+ dataArr = append(dataArr, model.NetConnectData{
+ Type: netType,
+ Status: conn.Status,
+ Laddr: conn.Laddr,
+ Raddr: conn.Raddr,
+ PID: conn.Pid,
+ Name: name,
+ })
+ }
+ }
+ }
+
+ resultByte, err := json.Marshal(result.Ok(map[string]any{
+ "requestId": requestID,
+ "data": dataArr,
+ }))
+ return resultByte, err
+}
diff --git a/src/modules/ws/processor/ps_process.go b/src/modules/ws/processor/ps_process.go
new file mode 100644
index 0000000..f736bde
--- /dev/null
+++ b/src/modules/ws/processor/ps_process.go
@@ -0,0 +1,143 @@
+package processor
+
+import (
+ "encoding/json"
+ "fmt"
+ "sort"
+ "strings"
+ "sync"
+
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/date"
+ "nms_nbi/src/framework/utils/parse"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/ws/model"
+
+ "github.com/shirou/gopsutil/v3/process"
+)
+
+// GetProcessData 获取进程数据
+func GetProcessData(requestID string, data any) ([]byte, error) {
+ msgByte, _ := json.Marshal(data)
+ var query model.PsProcessQuery
+ err := json.Unmarshal(msgByte, &query)
+ if err != nil {
+ logger.Warnf("ws processor GetNetConnections err: %s", err.Error())
+ return nil, fmt.Errorf("query data structure error")
+ }
+
+ var processes []*process.Process
+ processes, err = process.Processes()
+ if err != nil {
+ return nil, err
+ }
+
+ var (
+ dataArr = []model.PsProcessData{}
+ resultMutex sync.Mutex
+ wg sync.WaitGroup
+ numWorkers = 4
+ )
+
+ handleData := func(proc *process.Process) {
+ procData := model.PsProcessData{
+ PID: proc.Pid,
+ }
+ if query.Pid > 0 && query.Pid != proc.Pid {
+ return
+ }
+ procName, err := proc.Name()
+ if procName == "" || err != nil {
+ return
+ } else {
+ procData.Name = procName
+ }
+ if query.Name != "" && !strings.Contains(procData.Name, query.Name) {
+ return
+ }
+ if username, err := proc.Username(); err == nil {
+ procData.Username = username
+ }
+ if query.Username != "" && !strings.Contains(procData.Username, query.Username) {
+ return
+ }
+
+ procData.PPID, _ = proc.Ppid()
+ statusArray, _ := proc.Status()
+ if len(statusArray) > 0 {
+ procData.Status = strings.Join(statusArray, ",")
+ }
+ createTime, procErr := proc.CreateTime()
+ if procErr == nil {
+ procData.StartTime = date.ParseDateToStr(createTime, date.YYYY_MM_DD_HH_MM_SS)
+ }
+ procData.NumThreads, _ = proc.NumThreads()
+ procData.CpuValue, _ = proc.CPUPercent()
+ procData.CpuPercent = fmt.Sprintf("%.2f", procData.CpuValue) + "%"
+ menInfo, procErr := proc.MemoryInfo()
+ if procErr == nil {
+ procData.Rss = parse.Bit(float64(menInfo.RSS))
+ procData.Data = parse.Bit(float64(menInfo.Data))
+ procData.VMS = parse.Bit(float64(menInfo.VMS))
+ procData.HWM = parse.Bit(float64(menInfo.HWM))
+ procData.Stack = parse.Bit(float64(menInfo.Stack))
+ procData.Locked = parse.Bit(float64(menInfo.Locked))
+ procData.Swap = parse.Bit(float64(menInfo.Swap))
+
+ procData.RssValue = menInfo.RSS
+ } else {
+ procData.Rss = "--"
+ procData.Data = "--"
+ procData.VMS = "--"
+ procData.HWM = "--"
+ procData.Stack = "--"
+ procData.Locked = "--"
+ procData.Swap = "--"
+
+ procData.RssValue = 0
+ }
+ ioStat, procErr := proc.IOCounters()
+ if procErr == nil {
+ procData.DiskWrite = parse.Bit(float64(ioStat.WriteBytes))
+ procData.DiskRead = parse.Bit(float64(ioStat.ReadBytes))
+ } else {
+ procData.DiskWrite = "--"
+ procData.DiskRead = "--"
+ }
+ procData.CmdLine, _ = proc.Cmdline()
+ procData.Envs, _ = proc.Environ()
+
+ resultMutex.Lock()
+ dataArr = append(dataArr, procData)
+ resultMutex.Unlock()
+ }
+
+ chunkSize := (len(processes) + numWorkers - 1) / numWorkers
+ for i := 0; i < numWorkers; i++ {
+ wg.Add(1)
+ start := i * chunkSize
+ end := (i + 1) * chunkSize
+ if end > len(processes) {
+ end = len(processes)
+ }
+
+ go func(start, end int) {
+ defer wg.Done()
+ for j := start; j < end; j++ {
+ handleData(processes[j])
+ }
+ }(start, end)
+ }
+
+ wg.Wait()
+
+ sort.Slice(dataArr, func(i, j int) bool {
+ return dataArr[i].PID < dataArr[j].PID
+ })
+
+ resultByte, err := json.Marshal(result.Ok(map[string]any{
+ "requestId": requestID,
+ "data": dataArr,
+ }))
+ return resultByte, err
+}
diff --git a/src/modules/ws/processor/ue_connect.go b/src/modules/ws/processor/ue_connect.go
new file mode 100644
index 0000000..9058e72
--- /dev/null
+++ b/src/modules/ws/processor/ue_connect.go
@@ -0,0 +1,29 @@
+package processor
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/vo/result"
+ neDataModel "nms_nbi/src/modules/network_data/model"
+ neDataService "nms_nbi/src/modules/network_data/service"
+)
+
+// GetUEConnect 获取UE会话事件-AMF
+func GetUEConnect(requestID string, data any) ([]byte, error) {
+ msgByte, _ := json.Marshal(data)
+ var query neDataModel.UEEventQuery
+ err := json.Unmarshal(msgByte, &query)
+ if err != nil {
+ logger.Warnf("ws processor GetUEConnect err: %s", err.Error())
+ return nil, fmt.Errorf("query data structure error")
+ }
+
+ dataMap := neDataService.NewUEEventImpl.SelectPage(query)
+ resultByte, err := json.Marshal(result.Ok(map[string]any{
+ "requestId": requestID,
+ "data": dataMap,
+ }))
+ return resultByte, err
+}
diff --git a/src/modules/ws/processor/upf_total_flow.go b/src/modules/ws/processor/upf_total_flow.go
new file mode 100644
index 0000000..ced15d3
--- /dev/null
+++ b/src/modules/ws/processor/upf_total_flow.go
@@ -0,0 +1,44 @@
+package processor
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/vo/result"
+ neDataService "nms_nbi/src/modules/network_data/service"
+ neService "nms_nbi/src/modules/network_element/service"
+)
+
+// GetUPFTotalFlow 获取UPF-总流量数 N3上行 N6下行 单位比特(bit)
+func GetUPFTotalFlow(requestID string, data any) ([]byte, error) {
+ msgByte, _ := json.Marshal(data)
+ var querys struct {
+ NeType string `json:"neType" form:"neType" binding:"required"`
+ NeID string `json:"neId" form:"neId" binding:"required"`
+ Day int `json:"day" binding:"required"`
+ }
+ err := json.Unmarshal(msgByte, &querys)
+ if err != nil {
+ logger.Warnf("ws processor GetUPFTotalFlow err: %s", err.Error())
+ return nil, fmt.Errorf("query data structure error")
+ }
+
+ if querys.NeType == "" || querys.NeID == "" || querys.Day < 0 {
+ return nil, fmt.Errorf("query neType any neId empty or day less 0 ")
+ }
+
+ // 查询网元获取IP
+ neInfo := neService.NewNeInfoImpl.SelectNeInfoByNeTypeAndNeID(querys.NeType, querys.NeID)
+ if neInfo.NeId != querys.NeID || neInfo.IP == "" {
+ return nil, fmt.Errorf("no matching network element information found")
+ }
+
+ dataMap := neDataService.NewPerfKPIImpl.SelectUPFTotalFlow(neInfo.NeType, neInfo.RmUID, querys.Day)
+
+ resultByte, err := json.Marshal(result.Ok(map[string]any{
+ "requestId": requestID,
+ "data": dataMap,
+ }))
+ return resultByte, err
+}
diff --git a/src/modules/ws/service/ws.go b/src/modules/ws/service/ws.go
new file mode 100644
index 0000000..34697e4
--- /dev/null
+++ b/src/modules/ws/service/ws.go
@@ -0,0 +1,26 @@
+package service
+
+import (
+ "net/http"
+
+ "nms_nbi/src/modules/ws/model"
+
+ "github.com/gorilla/websocket"
+)
+
+// IWS WebSocket通信 服务层接口
+type IWS interface {
+ // UpgraderWs http升级ws请求
+ UpgraderWs(w http.ResponseWriter, r *http.Request) *websocket.Conn
+
+ // NewClient 新建客户端
+ //
+ // uid 登录用户ID
+ // groupIDs 用户订阅组
+ // conn ws连接实例
+ // childConn 子连接实例
+ NewClient(uid string, groupIDs []string, conn *websocket.Conn, childConn any) *model.WSClient
+
+ // CloseClient 关闭客户端
+ CloseClient(clientID string)
+}
diff --git a/src/modules/ws/service/ws.impl.go b/src/modules/ws/service/ws.impl.go
new file mode 100644
index 0000000..60b2fe9
--- /dev/null
+++ b/src/modules/ws/service/ws.impl.go
@@ -0,0 +1,224 @@
+package service
+
+import (
+ "encoding/json"
+ "net/http"
+ "sync"
+ "time"
+
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/generate"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/ws/model"
+
+ "github.com/gorilla/websocket"
+)
+
+var (
+ // ws客户端 [clientId: client]
+ WsClients sync.Map
+ // ws用户对应的多个客户端id [uid:clientIds]
+ WsUsers sync.Map
+ // ws组对应的多个用户id [groupID:uids]
+ WsGroup sync.Map
+)
+
+// 实例化服务层 WSImpl 结构体
+var NewWSImpl = &WSImpl{}
+
+// WSImpl WebSocket通信 服务层处理
+type WSImpl struct{}
+
+// UpgraderWs http升级ws请求
+func (s *WSImpl) UpgraderWs(w http.ResponseWriter, r *http.Request) *websocket.Conn {
+ wsUpgrader := websocket.Upgrader{
+ Subprotocols: []string{"omc-ws"},
+ // 设置消息发送缓冲区大小(byte),如果这个值设置得太小,可能会导致服务端在发送大型消息时遇到问题
+ WriteBufferSize: 1024,
+ // 消息包启用压缩
+ EnableCompression: true,
+ // ws握手超时时间
+ HandshakeTimeout: 5 * time.Second,
+ // ws握手过程中允许跨域
+ CheckOrigin: func(r *http.Request) bool {
+ return true
+ },
+ }
+ conn, err := wsUpgrader.Upgrade(w, r, nil)
+ if err != nil {
+ logger.Errorf("ws Upgrade err: %s", err.Error())
+ }
+ return conn
+}
+
+// NewClient 新建客户端
+//
+// uid 登录用户ID
+// groupIDs 用户订阅组
+// conn ws连接实例
+// childConn 子连接实例
+func (s *WSImpl) NewClient(uid string, groupIDs []string, conn *websocket.Conn, childConn any) *model.WSClient {
+ // clientID也可以用其他方式生成,只要能保证在所有服务端中都能保证唯一即可
+ clientID := generate.Code(16)
+
+ wsClient := &model.WSClient{
+ ID: clientID,
+ Conn: conn,
+ LastHeartbeat: time.Now().UnixMilli(),
+ BindUid: uid,
+ SubGroup: groupIDs,
+ MsgChan: make(chan []byte, 100),
+ StopChan: make(chan struct{}, 1), // 卡死循环标记
+ ChildConn: childConn,
+ }
+
+ // 存入客户端
+ WsClients.Store(clientID, wsClient)
+
+ // 存入用户持有客户端
+ if uid != "" {
+ if v, ok := WsUsers.Load(uid); ok {
+ uidClientIds := v.(*[]string)
+ *uidClientIds = append(*uidClientIds, clientID)
+ } else {
+ WsUsers.Store(uid, &[]string{clientID})
+ }
+ }
+
+ // 存入用户订阅组
+ if uid != "" && len(groupIDs) > 0 {
+ for _, groupID := range groupIDs {
+ if v, ok := WsGroup.Load(groupID); ok {
+ groupUIDs := v.(*[]string)
+ // 避免同组内相同用户
+ hasUid := false
+ for _, uidv := range *groupUIDs {
+ if uidv == uid {
+ hasUid = true
+ break
+ }
+ }
+ if !hasUid {
+ *groupUIDs = append(*groupUIDs, uid)
+ }
+ } else {
+ WsGroup.Store(groupID, &[]string{uid})
+ }
+ }
+ }
+
+ go s.clientRead(wsClient)
+ go s.clientWrite(wsClient)
+
+ // 发客户端id确认是否连接
+ msgByte, _ := json.Marshal(result.OkData(map[string]string{
+ "clientId": clientID,
+ }))
+ wsClient.MsgChan <- msgByte
+
+ return wsClient
+}
+
+// clientRead 客户端读取消息
+func (s *WSImpl) clientRead(wsClient *model.WSClient) {
+ defer func() {
+ if err := recover(); err != nil {
+ logger.Errorf("ws ReadMessage Panic Error: %v", err)
+ }
+ }()
+ for {
+ // 读取消息
+ messageType, msg, err := wsClient.Conn.ReadMessage()
+ if err != nil {
+ logger.Warnf("ws ReadMessage UID %s err: %s", wsClient.BindUid, err.Error())
+ s.CloseClient(wsClient.ID)
+ return
+ }
+ // fmt.Println(messageType, string(msg))
+
+ // 文本和二进制类型,只处理文本json
+ if messageType == websocket.TextMessage {
+ var reqMsg model.WSRequest
+ err := json.Unmarshal(msg, &reqMsg)
+ if err != nil {
+ msgByte, _ := json.Marshal(result.ErrMsg("message format not supported"))
+ wsClient.MsgChan <- msgByte
+ } else {
+ // 协程异步处理
+ go NewWSReceiveImpl.AsyncReceive(wsClient, reqMsg)
+ }
+ }
+ }
+}
+
+// clientWrite 客户端写入消息
+func (s *WSImpl) clientWrite(wsClient *model.WSClient) {
+ defer func() {
+ if err := recover(); err != nil {
+ logger.Errorf("ws WriteMessage Panic Error: %v", err)
+ }
+ }()
+ for msg := range wsClient.MsgChan {
+ // 发送消息
+ err := wsClient.Conn.WriteMessage(websocket.TextMessage, msg)
+ if err != nil {
+ logger.Warnf("ws WriteMessage UID %s err: %s", wsClient.BindUid, err.Error())
+ s.CloseClient(wsClient.ID)
+ return
+ }
+ wsClient.LastHeartbeat = time.Now().UnixMilli()
+ }
+}
+
+// CloseClient 客户端关闭
+func (s *WSImpl) CloseClient(clientID string) {
+ v, ok := WsClients.Load(clientID)
+ if !ok {
+ return
+ }
+
+ client := v.(*model.WSClient)
+ defer func() {
+ client.Conn.WriteMessage(websocket.CloseMessage, []byte{})
+ client.Conn.Close()
+ WsClients.Delete(clientID)
+ client.StopChan <- struct{}{}
+ }()
+
+ // 客户端断线时自动踢出Uid绑定列表
+ if client.BindUid != "" {
+ if clientIds, ok := WsUsers.Load(client.BindUid); ok {
+ uidClientIds := clientIds.(*[]string)
+ if len(*uidClientIds) > 0 {
+ tempClientIds := make([]string, 0, len(*uidClientIds))
+ for _, v := range *uidClientIds {
+ if v != client.ID {
+ tempClientIds = append(tempClientIds, v)
+ }
+ }
+ *uidClientIds = tempClientIds
+ }
+ }
+ }
+
+ // 客户端断线时自动踢出已加入的组
+ if client.BindUid != "" && len(client.SubGroup) > 0 {
+ for _, groupID := range client.SubGroup {
+ uids, ok := WsGroup.Load(groupID)
+ if !ok {
+ continue
+ }
+
+ groupUIDs := uids.(*[]string)
+ if len(*groupUIDs) > 0 {
+ tempUIDs := make([]string, 0, len(*groupUIDs))
+ for _, v := range *groupUIDs {
+ if v != client.BindUid {
+ tempUIDs = append(tempUIDs, v)
+ }
+ }
+ *groupUIDs = tempUIDs
+ }
+ }
+ }
+}
diff --git a/src/modules/ws/service/ws_receive.go b/src/modules/ws/service/ws_receive.go
new file mode 100644
index 0000000..ee7a0ae
--- /dev/null
+++ b/src/modules/ws/service/ws_receive.go
@@ -0,0 +1,9 @@
+package service
+
+import "nms_nbi/src/modules/ws/model"
+
+// IWSReceive WebSocket消息接收处理 服务层接口
+type IWSReceive interface {
+ // AsyncReceive 接收业务异步处理
+ AsyncReceive(client *model.WSClient, reqMsg model.WSRequest)
+}
diff --git a/src/modules/ws/service/ws_receive.impl.go b/src/modules/ws/service/ws_receive.impl.go
new file mode 100644
index 0000000..373e43f
--- /dev/null
+++ b/src/modules/ws/service/ws_receive.impl.go
@@ -0,0 +1,91 @@
+package service
+
+import (
+ "encoding/json"
+ "fmt"
+ "time"
+
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/utils/ssh"
+ "nms_nbi/src/framework/utils/telnet"
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/ws/model"
+ "nms_nbi/src/modules/ws/processor"
+)
+
+// 实例化服务层 WSReceiveImpl 结构体
+var NewWSReceiveImpl = &WSReceiveImpl{}
+
+// WSReceiveImpl WebSocket消息接收处理 服务层处理
+type WSReceiveImpl struct{}
+
+// AsyncReceive 接收业务异步处理
+func (s *WSReceiveImpl) AsyncReceive(client *model.WSClient, reqMsg model.WSRequest) {
+ // 必传requestId确认消息
+ if reqMsg.RequestID == "" {
+ msg := "message requestId is required"
+ logger.Infof("ws AsyncReceive 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":
+ // 主动关闭
+ resultByte, _ := json.Marshal(result.OkMsg("user initiated closure"))
+ client.MsgChan <- resultByte
+ // 等待1s后关闭连接
+ time.Sleep(1 * time.Second)
+ client.StopChan <- struct{}{}
+ case "ssh":
+ // SSH会话消息接收直接写入会话
+ command := reqMsg.Data.(string)
+ sshClientSession := client.ChildConn.(*ssh.SSHClientSession)
+ _, err = sshClientSession.Write(command)
+ case "ssh_resize":
+ // SSH会话窗口重置
+ msgByte, _ := json.Marshal(reqMsg.Data)
+ var data struct {
+ Cols int `json:"cols"`
+ Rows int `json:"rows"`
+ }
+ err = json.Unmarshal(msgByte, &data)
+ if err == nil {
+ sshClientSession := client.ChildConn.(*ssh.SSHClientSession)
+ err = sshClientSession.Session.WindowChange(data.Rows, data.Cols)
+ }
+ case "telnet":
+ // Telnet会话消息接收直接写入会话
+ command := reqMsg.Data.(string)
+ telnetClientSession := client.ChildConn.(*telnet.TelnetClientSession)
+ _, err = telnetClientSession.Write(command)
+ case "ps":
+ resByte, err = processor.GetProcessData(reqMsg.RequestID, reqMsg.Data)
+ case "net":
+ resByte, err = processor.GetNetConnections(reqMsg.RequestID, reqMsg.Data)
+ case "cdr":
+ resByte, err = processor.GetCDRConnect(reqMsg.RequestID, reqMsg.Data)
+ case "ue":
+ resByte, err = processor.GetUEConnect(reqMsg.RequestID, reqMsg.Data)
+ case "upf_tf":
+ resByte, err = processor.GetUPFTotalFlow(reqMsg.RequestID, reqMsg.Data)
+ case "ne_state":
+ resByte, err = processor.GetNeState(reqMsg.RequestID, reqMsg.Data)
+ default:
+ err = fmt.Errorf("message type not supported")
+ }
+
+ if err != nil {
+ logger.Warnf("ws AsyncReceive UID %s err: %s", client.BindUid, err.Error())
+ msgByte, _ := json.Marshal(result.ErrMsg(err.Error()))
+ client.MsgChan <- msgByte
+ return
+ }
+ if len(resByte) > 0 {
+ client.MsgChan <- resByte
+ }
+}
diff --git a/src/modules/ws/service/ws_send.go b/src/modules/ws/service/ws_send.go
new file mode 100644
index 0000000..020d022
--- /dev/null
+++ b/src/modules/ws/service/ws_send.go
@@ -0,0 +1,10 @@
+package service
+
+// IWSSend WebSocket消息发送处理 服务层接口
+type IWSSend interface {
+ // ByClientID 给已知客户端发消息
+ ByClientID(clientID string, data any) error
+
+ // ByGroupID 给订阅组的用户发送消息
+ ByGroupID(gid string, data any) error
+}
diff --git a/src/modules/ws/service/ws_send.impl.go b/src/modules/ws/service/ws_send.impl.go
new file mode 100644
index 0000000..ad97c0e
--- /dev/null
+++ b/src/modules/ws/service/ws_send.impl.go
@@ -0,0 +1,85 @@
+package service
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "nms_nbi/src/framework/vo/result"
+ "nms_nbi/src/modules/ws/model"
+)
+
+// 订阅组指定编号为支持服务器向客户端主动推送数据
+const (
+ // 组号-其他
+ GROUP_OTHER = "0"
+ // 组号-指标
+ GROUP_KPI = "10"
+ // 组号-指标UPF
+ GROUP_KPI_UPF = "12"
+ // 组号-IMS_CDR会话事件
+ GROUP_IMS_CDR = "1005"
+ // 组号-AMF_UE会话事件
+ GROUP_AMF_UE = "1010"
+)
+
+// 实例化服务层 WSSendImpl 结构体
+var NewWSSendImpl = &WSSendImpl{}
+
+// IWSSend WebSocket消息发送处理 服务层处理
+type WSSendImpl struct{}
+
+// ByClientID 给已知客户端发消息
+func (s *WSSendImpl) ByClientID(clientID string, data any) error {
+ v, ok := WsClients.Load(clientID)
+ if !ok {
+ return fmt.Errorf("no fount client ID: %s", clientID)
+ }
+
+ dataByte, err := json.Marshal(result.OkData(data))
+ if err != nil {
+ return err
+ }
+
+ client := v.(*model.WSClient)
+ if len(client.MsgChan) > 90 {
+ NewWSImpl.CloseClient(client.ID)
+ return fmt.Errorf("msg chan over 90 will close client ID: %s", clientID)
+ }
+ client.MsgChan <- dataByte
+ return nil
+}
+
+// ByGroupID 给订阅组的用户发送消息
+func (s *WSSendImpl) ByGroupID(groupID string, data any) error {
+ uids, ok := WsGroup.Load(groupID)
+ if !ok {
+ return fmt.Errorf("no fount Group ID: %s", groupID)
+ }
+
+ groupUids := uids.(*[]string)
+ // 群组中没有成员
+ if len(*groupUids) == 0 {
+ return fmt.Errorf("no members in the group")
+ }
+
+ // 在群组中找到对应的 uid
+ for _, uid := range *groupUids {
+ clientIds, ok := WsUsers.Load(uid)
+ if !ok {
+ continue
+ }
+ // 在用户中找到客户端并发送
+ uidClientIds := clientIds.(*[]string)
+ for _, clientId := range *uidClientIds {
+ err := s.ByClientID(clientId, map[string]any{
+ "groupId": groupID,
+ "data": data,
+ })
+ if err != nil {
+ continue
+ }
+ }
+ }
+
+ return nil
+}
diff --git a/src/modules/ws/ws.go b/src/modules/ws/ws.go
new file mode 100644
index 0000000..1efdac5
--- /dev/null
+++ b/src/modules/ws/ws.go
@@ -0,0 +1,39 @@
+package ws
+
+import (
+ "nms_nbi/src/framework/logger"
+ "nms_nbi/src/framework/middleware"
+ "nms_nbi/src/framework/middleware/collectlogs"
+ "nms_nbi/src/modules/ws/controller"
+
+ "github.com/gin-gonic/gin"
+)
+
+// 模块路由注册
+func Setup(router *gin.Engine) {
+ logger.Infof("开始加载 ====> ws 模块路由")
+
+ // WebSocket 协议
+ wsGroup := router.Group("/ws")
+ {
+ wsGroup.GET("",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.ws", collectlogs.BUSINESS_TYPE_OTHER)),
+ controller.NewWSController.WS,
+ )
+ wsGroup.GET("/ssh",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.ws", collectlogs.BUSINESS_TYPE_OTHER)),
+ controller.NewWSController.SSH,
+ )
+ wsGroup.GET("/telnet",
+ middleware.PreAuthorize(nil),
+ collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.ws", collectlogs.BUSINESS_TYPE_OTHER)),
+ controller.NewWSController.Telnet,
+ )
+ wsGroup.GET("/test",
+ middleware.PreAuthorize(nil),
+ controller.NewWSController.Test,
+ )
+ }
+}