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, + ) + } +}