diff --git a/.gitignore b/.gitignore index 8c2b884..c3c377d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,38 @@ # Local History for Visual Studio Code .history/ +# Run temp file and dir +crontask/log/ +crontask/ftp/ +crontask/database/ +crontask/export/ +crontask/temp +crontask/crontask + +restagent/backup/ +restagent/log/ +restagent/upload/ +restagent/software/ +restagent/database/ +restagent/restagent + +sshsvc/sshsvc +sshsvc/mmllog/ +sshsvc/mmlhome/ +sshsvc/log/ + +captrace/captrace +captrace/log/ + +tools/loadmconf/loadmconf +tools/loadpconf/loadpconf + +vendor + # Built Visual Studio Code Extensions *.vsix +*.log +*.log-* +*.bak +__debug_bin*.exe 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..8f7c445 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,41 @@ +{ + // 使用 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" + }, + { + "name": "DEBUG restagent", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "d:/local.git/ems.agt/restagent/restagent.go", + "console": "integratedTerminal" + }, + { + "name": "debug sshsvc", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "d:/local.git/ems.agt/sshsvc/sshsvc.go", + "console": "integratedTerminal" + }, + { + "name": "debug loadpconf", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "d:/local.git/ems.agt/tools/loadpconf", + "args": ["-p","../../config/param/upf_param_config.yaml"], + "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 index ad4b314..8c3dfa9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # ems_backend -临时仓库 \ No newline at end of file +后端 \ No newline at end of file diff --git a/features/aaaa/aaaa.go b/features/aaaa/aaaa.go new file mode 100644 index 0000000..8b10720 --- /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" + + "ems.agt/lib/dborm" + "ems.agt/lib/log" + "ems.agt/lib/oauth" + "ems.agt/lib/services" + "ems.agt/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/cm/exec_linux.go b/features/cm/exec_linux.go new file mode 100644 index 0000000..3044649 --- /dev/null +++ b/features/cm/exec_linux.go @@ -0,0 +1,66 @@ +//go:build linux +// +build linux + +package cm + +import ( + "bytes" + "os/exec" + + "ems.agt/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 +} diff --git a/features/cm/exec_windows.go b/features/cm/exec_windows.go new file mode 100644 index 0000000..fa15045 --- /dev/null +++ b/features/cm/exec_windows.go @@ -0,0 +1,53 @@ +//go:build windows +// +build windows + +package cm + +import ( + "os/exec" + + "ems.agt/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 +} diff --git a/features/cm/license.go b/features/cm/license.go new file mode 100644 index 0000000..e691a90 --- /dev/null +++ b/features/cm/license.go @@ -0,0 +1,151 @@ +package cm + +import ( + "net/http" + + "ems.agt/lib/log" + "ems.agt/lib/services" + "ems.agt/restagent/config" + + "github.com/gorilla/mux" +) + +var ( + // License + LicenseUri = config.DefaultUriPrefix + "/systemManagement/{apiVersion}/{neType}/license" + NeLicenseUri = config.DefaultUriPrefix + "/systemManagement/{apiVersion}/{neType}/license/{neId}" + + CustomLicenseUri = config.UriPrefix + "/systemManagement/{apiVersion}/{neType}/license" + CustomNeLicenseUri = config.UriPrefix + "/systemManagement/{apiVersion}/{neType}/license/{neId}" +) + +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) + return +} + +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) + return +} + +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) + return +} diff --git a/features/cm/ne.go b/features/cm/ne.go new file mode 100644 index 0000000..3ecf8eb --- /dev/null +++ b/features/cm/ne.go @@ -0,0 +1,906 @@ +package cm + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "os/exec" + "strings" + "time" + + "ems.agt/lib/dborm" + "ems.agt/lib/global" + "ems.agt/lib/log" + "ems.agt/lib/services" + "ems.agt/restagent/config" + + "github.com/go-resty/resty/v2" + "github.com/gorilla/mux" +) + +var ( + UriParamOmcNeConfig = config.DefaultUriPrefix + "/systemManagement/v1/elementType/%s/objectType/config/omcNeConfig" + // 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}" + + CustomUriParamOmcNeConfig = config.UriPrefix + "/systemManagement/v1/elementType/%s/objectType/config/omcNeConfig" + 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}" +) + +func init() { + +} + +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.ResponseInternalServerError500DatabaseOperationFailed(w) + 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 + } + 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)) + + 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) + + hostUri := global.CombineHostUri(neInfo.Ip, neInfo.Port) + //hostUri := fmt.Sprintf("http://%s:%v", neInfo.Ip, neInfo.Port) + apiUri := fmt.Sprintf(UriParamOmcNeConfig, 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) + 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:", err) + services.ResponseInternalServerError500NFConnectRefused(w) + return + } + log.Info("StatusCode: ", response.StatusCode()) + + if !config.GetYamlConfig().OMC.Chk2Ne { + affected, err := dborm.XormInsertNeInfo(neInfo) + if err != nil { + log.Error("Failed to insert Ne info:", err) + services.ResponseInternalServerError500DatabaseOperationFailed(w) + return + } + + mapRow := make(map[string]interface{}) + row := map[string]interface{}{"affectedRows": affected} + mapRow["data"] = row + services.ResponseWithJson(w, http.StatusOK, mapRow) + return + } else { + respMsg := make(map[string]interface{}) + switch response.StatusCode() { + case http.StatusOK, http.StatusCreated, http.StatusNoContent, http.StatusAccepted: + affected, err := dborm.XormInsertNeInfo(neInfo) + if err != nil { + log.Error("Failed to dborm.XormInsertNeInfo:", err) + services.ResponseInternalServerError500DatabaseOperationFailed(w) + return + } else if affected <= 0 { + log.Infof("Not record affected to insert ne_info") + } + 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 + } + 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.UpdateTime = time.Now().Format(time.DateTime) + log.Debug("NE info:", neInfo) + + hostUri := global.CombineHostUri(neInfo.Ip, neInfo.Port) + //hostUri := fmt.Sprintf("http://%s:%v", neInfo.Ip, neInfo.Port) + apiUri := fmt.Sprintf(UriParamOmcNeConfig, strings.ToLower(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) + 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:", err) + services.ResponseInternalServerError500NFConnectRefused(w) + return + } + log.Info("StatusCode: ", response.StatusCode()) + + if !config.GetYamlConfig().OMC.Chk2Ne { + affected, err := dborm.XormUpdateNeInfo(neInfo) + if err != nil { + log.Error("Failed to update Ne info:", err) + services.ResponseInternalServerError500DatabaseOperationFailed(w) + return + } + + mapRow := make(map[string]interface{}) + row := map[string]interface{}{"affectedRows": affected} + mapRow["data"] = row + services.ResponseWithJson(w, http.StatusOK, mapRow) + return + } else { + respMsg := make(map[string]interface{}) + switch response.StatusCode() { + case http.StatusOK, http.StatusCreated, http.StatusNoContent, http.StatusAccepted: + affected, err := dborm.XormUpdateNeInfo(neInfo) + if err != nil { + log.Error("Failed to dborm.XormUpdateNeInfo:", err) + services.ResponseInternalServerError500DatabaseOperationFailed(w) + return + } else if affected <= 0 { + log.Infof("Not record affected to insert ne_info") + } + 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.ResponseInternalServerError500DatabaseOperationFailed(w) + 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.ResponseInternalServerError500DatabaseOperationFailed(w) + return + } + + 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)) + + 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(hostUri + requestURI) + if err != nil { + log.Error("Failed to Get:", err) + } + + 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.ResponseInternalServerError500DatabaseOperationFailed(w) + 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) + if neTypeLower != "omc" { + 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) + } + } else { + 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) + } + } + + 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.ResponseInternalServerError500DatabaseOperationFailed(w) + 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 rpm 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, "ne_id", ",", false, false) + + // neInfo := new(dborm.NeInfo) + neInfo, err := dborm.XormGetNeInfo(neType, neId) + if err != nil { + log.Error("Failed to get ne_info:", err) + services.ResponseInternalServerError500DatabaseOperationFailed(w) + 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, "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.ResponseInternalServerError500DatabaseOperationFailed(w) + 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..d273661 --- /dev/null +++ b/features/cm/param.go @@ -0,0 +1,242 @@ +package cm + +import ( + "ems.agt/lib/dborm" + "ems.agt/lib/global" + "ems.agt/lib/log" + "ems.agt/lib/services" + "ems.agt/restagent/config" + + "encoding/json" + "fmt" + "io" + "net/http" + + "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.UriPrefix+"/databaseManagement/v1/%s/ne_info", config.GetYamlConfig().Database.Name) + getNeInfoURI := restHostPort + getNeInfoPattern + neId := services.GetUriParamString(r, "ne_id", ",", true, false) + if neId == "" { + getNeInfoURI = getNeInfoURI + fmt.Sprintf("?WHERE=status='0'+and+ne_type='%s'", neType) + } else { + getNeInfoURI = getNeInfoURI + fmt.Sprintf("?WHERE=status='0'+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{"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 from database is failure!") + services.ResponseInternalServerError500NFConnectRefused(w) + return + } + log.Debug("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 + } + 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) +} + +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..d65ff53 --- /dev/null +++ b/features/cm/software.go @@ -0,0 +1,911 @@ +package cm + +import ( + "bytes" + "fmt" + "io" + "net/http" + "os" + "os/exec" + "strconv" + "strings" + + "ems.agt/lib/dborm" + "ems.agt/lib/global" + "ems.agt/lib/log" + "ems.agt/lib/services" + "ems.agt/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(200 << 20) // 200MB + 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.Debug("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.Debug("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.Debugf("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.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 + } + neFilePath := config.GetYamlConfig().NE.ScpDir + "/" + fileName + cpCmd := fmt.Sprintf("sudo cp -f %s %s/software/%s", neFilePath, + config.GetYamlConfig().NE.OmcDir, neTypeLower) + 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 + } + + 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.Debug("neVersion:", 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.Debug("neVersion:", neVersion) + + if !config.GetYamlConfig().OMC.TestMode { + filePath := (*neVersion)[0]["file_path"] + sshHost := fmt.Sprintf("%s@%s", config.GetYamlConfig().NE.User, neInfo.Ip) + fileType := global.IsRpmOrDebPackage(filePath) + if fileType == 1 { + rpmCmd := fmt.Sprintf("sudo rpm -Uvh '%s'", filePath) + cmd := exec.Command("ssh", sshHost, rpmCmd) + out, err := cmd.CombinedOutput() + log.Debugf("Exec output: %v", string(out)) + if err != nil { + log.Error("Faile to execute rpm command:", err) + services.ResponseInternalServerError500ProcessError(w, err) + return + } + } else if fileType == 2 { + dpkgCmd := fmt.Sprintf("sudo dpkg -i '%s'", filePath) + cmd := exec.Command("ssh", sshHost, dpkgCmd) + out, err := cmd.CombinedOutput() + log.Debugf("Exec output: %v", string(out)) + if err != nil { + log.Error("Faile to execute dpkg command:", 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.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 + } + + } + } + + 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) + fileType := global.IsRpmOrDebPackage(filePath) + if fileType == 1 { + rpmCmd := fmt.Sprintf("sudo rpm -Uvh --oldpackage '%s'", filePath) + cmd := exec.Command("ssh", sshHost, rpmCmd) + out, err := cmd.CombinedOutput() + log.Debugf("Exec output: %v", string(out)) + if err != nil { + log.Error("Faile to execute rpm command:", err) + services.ResponseInternalServerError500ProcessError(w, err) + return + } + } else if fileType == 2 { + dpkgCmd := fmt.Sprintf("sudo dpkg -i '%s'", filePath) + cmd := exec.Command("ssh", sshHost, dpkgCmd) + out, err := cmd.CombinedOutput() + log.Debugf("Exec output: %v", string(out)) + if err != nil { + log.Error("Faile to execute dpkg command:", 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.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 + } + + } + } + + 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..f8cfb05 --- /dev/null +++ b/features/dbrest/backup.go @@ -0,0 +1,134 @@ +package dbrest + +import ( + "archive/tar" + "compress/gzip" + "io" + "net/http" + "os" + "os/exec" + "path/filepath" + "time" + + "ems.agt/lib/core/conf" + "ems.agt/lib/dborm" + "ems.agt/lib/log" + "ems.agt/lib/services" + "ems.agt/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..a529794 --- /dev/null +++ b/features/dbrest/dbrest.go @@ -0,0 +1,916 @@ +package dbrest + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "regexp" + "strings" + "time" + + "ems.agt/lib/core/utils/ctx" + "ems.agt/lib/dborm" + "ems.agt/lib/global" + "ems.agt/lib/log" + "ems.agt/lib/services" + "ems.agt/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 + 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 xormResponse XormResponse + +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=utf8&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=utf8&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 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/file/file.go b/features/file/file.go new file mode 100644 index 0000000..815daca --- /dev/null +++ b/features/file/file.go @@ -0,0 +1,194 @@ +package file + +import ( + "fmt" + "net/http" + "path/filepath" + + "ems.agt/lib/core/utils/ctx" + "ems.agt/lib/dborm" + "ems.agt/lib/file" + "ems.agt/lib/log" + "ems.agt/lib/services" + "ems.agt/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..9fc3ec6 --- /dev/null +++ b/features/file/service.go @@ -0,0 +1,206 @@ +package file + +import ( + "errors" + "fmt" + "os" + "path" + "path/filepath" + "strings" + + "ems.agt/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/firewall/api_firewall.go b/features/firewall/api_firewall.go new file mode 100644 index 0000000..136f3b6 --- /dev/null +++ b/features/firewall/api_firewall.go @@ -0,0 +1,88 @@ +package firewall + +import ( + "net/http" + + "ems.agt/features/firewall/model" + "ems.agt/features/firewall/service" + "ems.agt/lib/core/utils/ctx" + "ems.agt/lib/core/vo/result" + "ems.agt/lib/services" + "ems.agt/restagent/config" +) + +// 防火墙管理接口添加到路由 +func Routers() []services.RouterItem { + // 实例化控制层 FirewallApi 结构体 + var apis = &FirewallApi{ + firewallService: *service.NewServiceFirewall, + } + + rs := [...]services.RouterItem{ + { + Method: "GET", + Pattern: "/base", + Handler: apis.BaseInfo, + Middleware: nil, //midware.Authorize(nil), + }, + { + Method: "POST", + Pattern: "/rule", + Handler: apis.Rule, + Middleware: nil, //midware.Authorize(nil), + }, + // 添加更多的 Router 对象... + } + + // 生成两组前缀路由 + rsPrefix := []services.RouterItem{} + for _, v := range rs { + path := "/firewallManage/{apiVersion}" + v.Pattern + // 固定前缀 + v.Pattern = config.DefaultUriPrefix + path + rsPrefix = append(rsPrefix, v) + // 可配置 + v.Pattern = config.UriPrefix + path + rsPrefix = append(rsPrefix, v) + } + return rsPrefix +} + +// 防火墙管理 +// +// PATH /firewallManage +type FirewallApi struct { + firewallService service.ServiceFirewall +} + +// 获取防火墙基础信息 +// +// GET /base +func (s *FirewallApi) BaseInfo(w http.ResponseWriter, r *http.Request) { + data, err := s.firewallService.LoadBaseInfo() + if err != nil { + ctx.JSON(w, 200, result.ErrMsg(err.Error())) + return + } + + ctx.JSON(w, 200, result.OkData(data)) +} + +// 获取防火墙规则列表分页 +// +// GET /rule +func (s *FirewallApi) Rule(w http.ResponseWriter, r *http.Request) { + var body model.RuleQuerys + err := ctx.ShouldBindJSON(r, &body) + if err != nil || body.Type == "" { + ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + return + } + data, err := s.firewallService.RulePage(body) + if err != nil { + ctx.JSON(w, 400, result.ErrMsg(err.Error())) + return + } + + ctx.JSON(w, 200, result.OkData(data)) +} diff --git a/features/firewall/model/firewall.go b/features/firewall/model/firewall.go new file mode 100644 index 0000000..782aa89 --- /dev/null +++ b/features/firewall/model/firewall.go @@ -0,0 +1,13 @@ +package model + +type Firewall struct { + ID int64 `json:"id" xorm:"id"` + CreatedAt int64 `json:"createdAt" xorm:"created_at"` + UpdatedAt int64 `json:"updatedAt" xorm:"updated_at"` + Type string `json:"type" xorm:"type"` + Port string `json:"port" xorm:"port"` + Protocol string `json:"protocol" xorm:"protocol"` + Address string `json:"address" xorm:"address"` + Strategy string `json:"strategy" xorm:"strategy"` + Description string `json:"description" xorm:"description"` +} diff --git a/features/firewall/model/firewall_vo.go b/features/firewall/model/firewall_vo.go new file mode 100644 index 0000000..d4e3553 --- /dev/null +++ b/features/firewall/model/firewall_vo.go @@ -0,0 +1,64 @@ +package model + +type FirewallBaseInfo struct { + Name string `json:"name"` + Status string `json:"status"` + Version string `json:"version"` + PingStatus string `json:"pingStatus"` +} + +type RuleQuerys struct { + PageNum int `json:"pageNum" validate:"required,number"` + PageSize int `json:"pageSize" validate:"required,number"` + Info string `json:"info"` + Status string `json:"status"` + Strategy string `json:"strategy"` + Type string `json:"type" validate:"required"` +} + +type FirewallOperation struct { + Operation string `json:"operation" validate:"required,oneof=start stop disablePing enablePing"` +} + +type PortRuleOperate struct { + Operation string `json:"operation" validate:"required,oneof=add remove"` + Address string `json:"address"` + Port string `json:"port" validate:"required"` + Protocol string `json:"protocol" validate:"required,oneof=tcp udp tcp/udp"` + Strategy string `json:"strategy" validate:"required,oneof=accept drop"` + + Description string `json:"description"` +} + +type UpdateFirewallDescription struct { + Type string `json:"type"` + Address string `json:"address"` + Port string `json:"port"` + Protocol string `json:"protocol"` + Strategy string `json:"strategy" validate:"required,oneof=accept drop"` + + Description string `json:"description"` +} + +type AddrRuleOperate struct { + Operation string `json:"operation" validate:"required,oneof=add remove"` + Address string `json:"address" validate:"required"` + Strategy string `json:"strategy" validate:"required,oneof=accept drop"` + + Description string `json:"description"` +} + +type PortRuleUpdate struct { + OldRule PortRuleOperate `json:"oldRule"` + NewRule PortRuleOperate `json:"newRule"` +} + +type AddrRuleUpdate struct { + OldRule AddrRuleOperate `json:"oldRule"` + NewRule AddrRuleOperate `json:"newRule"` +} + +type BatchRuleOperate struct { + Type string `json:"type" validate:"required"` + Rules []PortRuleOperate `json:"rules"` +} diff --git a/features/firewall/repo/repo_firewall.go b/features/firewall/repo/repo_firewall.go new file mode 100644 index 0000000..e95a056 --- /dev/null +++ b/features/firewall/repo/repo_firewall.go @@ -0,0 +1,133 @@ +package repo + +import ( + "strings" + + "ems.agt/features/firewall/model" + "ems.agt/lib/core/datasource" + "ems.agt/lib/log" +) + +// 实例化数据层 RepoFirewall 结构体 +var NewRepoFirewall = &RepoFirewall{ + selectSql: `select + id, created_at, updated_at, type, port, protocol, address, strategy, description + from monitor_firewall`, + + resultMap: map[string]string{ + "id": "ID", + "created_at": "CreatedAt", + "updated_at": "UpdatedAt", + "type": "Type", + "port": "Port", + "protocol": "Protocol", + "address": "Address", + "strategy": "Strategy", + "description": "Description", + }, +} + +// RepoFirewall 防火墙 数据层处理 +type RepoFirewall struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *RepoFirewall) convertResultRows(rows []map[string]any) []model.Firewall { + arr := make([]model.Firewall, 0) + for _, row := range rows { + UdmUser := model.Firewall{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + datasource.SetFieldValue(&UdmUser, keyMapper, value) + } + } + arr = append(arr, UdmUser) + } + return arr +} + +// List 根据实体查询 +func (r *RepoFirewall) List(f model.Firewall) []model.Firewall { + // 查询条件拼接 + var conditions []string + var params []any + if f.Type != "" { + conditions = append(conditions, "type = ?") + params = append(params, f.Type) + } + if f.Protocol != "" { + conditions = append(conditions, "protocol = ?") + params = append(params, f.Protocol) + } + + // 构建查询条件语句 + 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 r.convertResultRows(results) +} + +// Insert 新增实体 +func (r *RepoFirewall) Insert(f model.Firewall) int64 { + results, err := datasource.DefaultDB().Table("monitor_firewall").Insert(f) + if err != nil { + return results + } + return results +} + +// Update 修改更新 +func (r *RepoFirewall) Update(f model.Firewall) int64 { + // 查询先 + var fd model.Firewall + if f.Type == "port" { + has, err := datasource.DefaultDB().Table("monitor_firewall").Where("type = ? AND port = ? AND protocol = ? AND address = ? AND strategy = ?", "port", f.Port, f.Protocol, f.Address, f.Strategy).Get(&fd) + if !has || err != nil { + return 0 + } + } else { + has, err := datasource.DefaultDB().Table("monitor_firewall").Where("type = ? AND address = ? AND strategy = ?", "address", f.Address, f.Strategy).Get(&fd) + if !has || err != nil { + return 0 + } + } + f.ID = fd.ID + + results, err := datasource.DefaultDB().Table("monitor_firewall").Where("id = ?", f.ID).Update(f) + if err != nil { + return 0 + } + return results +} + +// Delete 删除实体 +func (r *RepoFirewall) Delete(id int64) int64 { + results, err := datasource.DefaultDB().Table("u_sub_user").Where("id = ?", id).Delete() + if err != nil { + return results + } + return results +} + +// DeleteFirewallRecord 删除实体 +func (r *RepoFirewall) DeleteFirewallRecord(fType, port, protocol, address, strategy string) int64 { + results, err := datasource.DefaultDB().Table("u_sub_user").Where("type = ? AND port = ? AND protocol = ? AND address = ? AND strategy = ?", fType, port, protocol, address, strategy).Delete() + if err != nil { + return results + } + return results +} diff --git a/features/firewall/service/service_firewall.go b/features/firewall/service/service_firewall.go new file mode 100644 index 0000000..62579a2 --- /dev/null +++ b/features/firewall/service/service_firewall.go @@ -0,0 +1,207 @@ +package service + +import ( + "fmt" + "os" + "strconv" + "strings" + + "ems.agt/features/firewall/model" + "ems.agt/features/firewall/repo" + "ems.agt/lib/core/cmd" + "ems.agt/lib/core/utils/firewall" + fireClient "ems.agt/lib/core/utils/firewall/client" + "ems.agt/lib/core/utils/scan" +) + +// 实例化服务层 ServiceFirewall 结构体 +var NewServiceFirewall = &ServiceFirewall{ + repoFirewall: *repo.NewRepoFirewall, +} + +// ServiceFirewall 防火墙 服务层处理 +type ServiceFirewall struct { + repoFirewall repo.RepoFirewall +} + +// LoadBaseInfo 获取防火墙基础信息 +func (s *ServiceFirewall) LoadBaseInfo() (model.FirewallBaseInfo, error) { + var baseInfo model.FirewallBaseInfo + baseInfo.PingStatus = s.pingStatus() + baseInfo.Status = "not running" + baseInfo.Version = "-" + baseInfo.Name = "-" + client, err := firewall.NewFirewallClient() + if err != nil { + if err.Error() == "no such type" { + return baseInfo, nil + } + return baseInfo, err + } + baseInfo.Name = client.Name() + baseInfo.Status, err = client.Status() + if err != nil { + return baseInfo, err + } + if baseInfo.Status == "not running" { + return baseInfo, err + } + baseInfo.Version, err = client.Version() + if err != nil { + return baseInfo, err + } + return baseInfo, nil +} + +// LoadBaseInfo 获取防火墙基础信息 +func (s *ServiceFirewall) RulePage(querys model.RuleQuerys) (map[string]any, error) { + var ( + datas []fireClient.FireInfo + backDatas []fireClient.FireInfo + ) + + data := map[string]any{ + "total": 0, + "rows": backDatas, + } + + client, err := firewall.NewFirewallClient() + if err != nil { + return data, err + } + if querys.Type == "port" { + ports, err := client.ListPort() + if err != nil { + return data, err + } + if len(querys.Info) != 0 { + for _, port := range ports { + if strings.Contains(port.Port, querys.Info) { + datas = append(datas, port) + } + } + } else { + datas = ports + } + } else { + addrs, err := client.ListAddress() + if err != nil { + return data, err + } + if len(querys.Info) != 0 { + for _, addr := range addrs { + if strings.Contains(addr.Address, querys.Info) { + datas = append(datas, addr) + } + } + } else { + datas = addrs + } + } + + var datasFilterStatus []fireClient.FireInfo + if len(querys.Status) != 0 { + for _, data := range datas { + portItem, _ := strconv.Atoi(data.Port) + if querys.Status == "free" && !scan.ScanPortWithProto(portItem, data.Protocol) { + datasFilterStatus = append(datasFilterStatus, data) + } + if querys.Status == "used" && scan.ScanPortWithProto(portItem, data.Protocol) { + datasFilterStatus = append(datasFilterStatus, data) + } + } + } else { + datasFilterStatus = datas + } + var datasFilterStrategy []fireClient.FireInfo + if len(querys.Strategy) != 0 { + for _, data := range datasFilterStatus { + if querys.Strategy == data.Strategy { + datasFilterStrategy = append(datasFilterStrategy, data) + } + } + } else { + datasFilterStrategy = datasFilterStatus + } + + total, start, end := len(datasFilterStrategy), (querys.PageNum-1)*querys.PageSize, querys.PageNum*querys.PageSize + if start > total { + backDatas = make([]fireClient.FireInfo, 0) + } else { + if end >= total { + end = total + } + backDatas = datasFilterStrategy[start:end] + } + + datasFromDB := s.repoFirewall.List(model.Firewall{}) + for i := 0; i < len(backDatas); i++ { + for _, des := range datasFromDB { + if querys.Type != des.Type { + continue + } + if backDatas[i].Port == des.Port && querys.Type == "port" && + backDatas[i].Protocol == des.Protocol && + backDatas[i].Strategy == des.Strategy && + backDatas[i].Address == des.Address { + backDatas[i].Description = des.Description + break + } + if querys.Type == "address" && backDatas[i].Strategy == des.Strategy && backDatas[i].Address == des.Address { + backDatas[i].Description = des.Description + break + } + } + } + + if querys.Type == "port" { + for i := 0; i < len(backDatas); i++ { + port, _ := strconv.Atoi(backDatas[i].Port) + backDatas[i].IsUsed = scan.ScanPort(port) + if backDatas[i].Protocol == "udp" { + backDatas[i].IsUsed = scan.ScanUDPPort(port) + continue + } + } + } + go s.cleanUnUsedData(client) + + return data, nil +} + +func (s *ServiceFirewall) pingStatus() string { + if _, err := os.Stat("/etc/sysctl.conf"); err != nil { + return "None" + } + sudo := cmd.SudoHandleCmd() + command := fmt.Sprintf("%s cat /etc/sysctl.conf | grep net/ipv4/icmp_echo_ignore_all= ", sudo) + stdout, _ := cmd.Exec(command) + if stdout == "net/ipv4/icmp_echo_ignore_all=1\n" { + return "Enable" + } + return "Disable" +} + +func (s *ServiceFirewall) cleanUnUsedData(client firewall.FirewallClient) { + list, _ := client.ListPort() + addressList, _ := client.ListAddress() + list = append(list, addressList...) + if len(list) == 0 { + return + } + records := s.repoFirewall.List(model.Firewall{}) + if len(records) == 0 { + return + } + for _, item := range list { + for i := 0; i < len(records); i++ { + if records[i].Port == item.Port && records[i].Protocol == item.Protocol && records[i].Strategy == item.Strategy && records[i].Address == item.Address { + records = append(records[:i], records[i+1:]...) + } + } + } + + for _, record := range records { + _ = s.repoFirewall.Delete(record.ID) + } +} diff --git a/features/fm/alarm.go b/features/fm/alarm.go new file mode 100644 index 0000000..6a317fa --- /dev/null +++ b/features/fm/alarm.go @@ -0,0 +1,700 @@ +package fm + +import ( + "database/sql" + "encoding/json" + "fmt" + "io" + "net/http" + "strconv" + "strings" + "time" + + "ems.agt/lib/dborm" + "ems.agt/lib/global" + "ems.agt/lib/log" + "ems.agt/lib/services" + "ems.agt/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 +) + +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 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=utf8&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=utf8&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=utf8&parseTime=true&loc=Local", + dbUser, dbPassword, dbHost, dbPort, dbName) + log.Debugf("dbType:%s Connect to:%s:******@tcp(%s:%s)/%s?charset=utf8&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) == true { + 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 == true { + 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 == true && 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 == true { + 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 == true { + 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 == false || 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.LocationInfo = fmt.Sprintf("subNeInfo:%s", alarmData.NeType) + } + if IsNeedToAckAlarm(valueJson, &alarmData) == true { + 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 = AlarmForwardBySMS(&alarmData); err != nil { + log.Error("Failed to AlarmForwardBySMS:", 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 + } + + //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.ResponseInternalServerError500DatabaseOperationFailed(w) + 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 + } + body := response.Body() + log.Debug("Request body:", string(body)) + alarmArray := new([]Alarm) + err = json.Unmarshal(body, &alarmArray) + if err != nil { + log.Error("Failed to Unmarshal:", err) + //services.ResponseInternalServerError500ProcessError(w, err) + continue + } + valueJson, err := dborm.XormGetAAConfig() + if err != nil { + log.Error("Failed to XormGetAAConfig:", err) + //services.ResponseInternalServerError500ProcessError(w, err) + continue + } + if alarmArray == nil { + log.Info("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 == false { + log.Info("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) == true { + 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 == true && 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 == true { + 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 == true { + 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 == false || 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) == true { + 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 = AlarmForwardBySMS(&alarmData); err != nil { + log.Error("Failed to AlarmForwardBySMS:", err) + } + } + } + log.Warn("Failed to insert alarm data:", err) + } + } + } + services.ResponseStatusOK200Null(w) +} diff --git a/features/fm/email.go b/features/fm/email.go new file mode 100644 index 0000000..79665a2 --- /dev/null +++ b/features/fm/email.go @@ -0,0 +1,111 @@ +package fm + +import ( + "crypto/tls" + "errors" + "fmt" + "strings" + + "ems.agt/lib/dborm" + "ems.agt/lib/log" + "ems.agt/restagent/config" + + "gopkg.in/gomail.v2" +) + +func AlarmEmailForward(alarmData *Alarm) error { + log.Info("AlarmEmailForward processing... ") + + message := ` +

Hello information,

+ test, test +

Best Wishes!

+ ` + + // 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, ","), + } + for _, email := range *emails { + m.SetHeader("To", email) // 收件人,可以多个收件人,但必须使用相同的 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", "纯文本") + // 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 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 + } + + operResult := fmt.Sprintf("Email sent successfully!:", 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 + } + return nil +} diff --git a/features/fm/smsforward.go b/features/fm/smsforward.go new file mode 100644 index 0000000..c58e6e5 --- /dev/null +++ b/features/fm/smsforward.go @@ -0,0 +1,103 @@ +package fm + +import ( + "errors" + "fmt" + "net/http" + "net/url" + + "ems.agt/lib/dborm" + "ems.agt/lib/log" + "ems.agt/restagent/config" +) + +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 { + log.Info("SMS sent successfully!") + operResult := fmt.Sprintf("SMS sent successfully!") + 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 +} diff --git a/features/handle/handle.go b/features/handle/handle.go new file mode 100644 index 0000000..5c71b00 --- /dev/null +++ b/features/handle/handle.go @@ -0,0 +1,586 @@ +package handle + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "strconv" + "strings" + + "ems.agt/lib/dborm" + + "github.com/gorilla/mux" + g "github.com/gosnmp/gosnmp" + + "ems.agt/lib/global" + "ems.agt/lib/log" + "ems.agt/lib/oauth" + "ems.agt/lib/services" + "ems.agt/lib/session" + "ems.agt/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..0cca290 --- /dev/null +++ b/features/lm/logbak.go @@ -0,0 +1,149 @@ +package lm + +import ( + "fmt" + "net/http" + "os/exec" + "time" + + "ems.agt/lib/global" + "ems.agt/lib/log" + "ems.agt/lib/services" + "ems.agt/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=utf8&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=utf8&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..e297671 --- /dev/null +++ b/features/maintenance/maintenance.go @@ -0,0 +1,313 @@ +package maintenance + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + "os/exec" + "path" + "runtime" + "time" + + "ems.agt/lib/core/utils/ctx" + "ems.agt/lib/dborm" + "ems.agt/lib/log" + "ems.agt/lib/services" + "ems.agt/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..045d055 --- /dev/null +++ b/features/mml/mml.go @@ -0,0 +1,246 @@ +package mml + +import ( + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "strings" + "time" + + "ems.agt/lib/dborm" + "ems.agt/lib/global" + "ems.agt/lib/log" + "ems.agt/lib/mmlp" + "ems.agt/lib/services" + "ems.agt/restagent/config" + + "github.com/gorilla/mux" + + _ "github.com/go-sql-driver/mysql" +) + +const ( + //经过测试,linux下,延时需要大于100ms + TIME_DELAY_AFTER_WRITE = 200 +) + +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" + 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" + 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}" +) + +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 + } + 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 [8192]byte + 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 { + 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 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.Debug("Body:", string(body)) + + hostUri := fmt.Sprintf("http://%s:%s", neInfo.Ip, neInfo.Port) + + omcMmlVar := &mmlp.MmlVar{ + Version: "16.1.1", + Output: mmlp.DefaultFormatType, + MmlHome: config.GetYamlConfig().MML.MmlHome, + Limit: 50, + User: "", + SessionToken: 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..7ce950d --- /dev/null +++ b/features/monitor/monitor/monitor.go @@ -0,0 +1,162 @@ +package monitor + +import ( + "net/http" + "sort" + "strings" + "time" + + "ems.agt/lib/core/utils/ctx" + "ems.agt/lib/dborm" + "ems.agt/lib/services" + "ems.agt/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..a8185c3 --- /dev/null +++ b/features/monitor/monitor/task.go @@ -0,0 +1,233 @@ +package monitor + +import ( + "encoding/json" + "fmt" + "strconv" + "time" + + "ems.agt/lib/dborm" + "ems.agt/lib/log" + "ems.agt/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..78877bc --- /dev/null +++ b/features/monitor/psnet/psnet.go @@ -0,0 +1,93 @@ +package psnet + +import ( + "fmt" + "net" + "net/http" + "time" + + "ems.agt/lib/core/utils/ctx" + "ems.agt/lib/log" + "ems.agt/lib/services" + "ems.agt/lib/wsinfo" + "ems.agt/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..a6a8aab --- /dev/null +++ b/features/nbi/nbi.go @@ -0,0 +1,259 @@ +package nbi + +import ( + "bytes" + "fmt" + "net/http" + "strings" + + "ems.agt/lib/dborm" + "github.com/go-resty/resty/v2" + + "github.com/gorilla/mux" + + "ems.agt/lib/global" + "ems.agt/lib/log" + "ems.agt/lib/oauth" + "ems.agt/lib/services" + "ems.agt/lib/session" + "ems.agt/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..38c9c5c --- /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" + + "ems.agt/lib/log" + "ems.agt/lib/services" + "ems.agt/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..d7fe84a --- /dev/null +++ b/features/pm/performance.go @@ -0,0 +1,977 @@ +package pm + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strconv" + "time" + + "ems.agt/lib/dborm" + "ems.agt/lib/global" + "ems.agt/lib/log" + "ems.agt/lib/services" + "ems.agt/restagent/config" + "xorm.io/xorm" + + "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"` + 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=utf8&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=utf8&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=utf8&parseTime=true&loc=Local", + dbUser, dbPassword, dbHost, dbPort, dbName) + log.Debugf("dbType:%s Connect to:%s:******@tcp(%s:%s)/%s?charset=utf8&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.Debug("Request body:", string(body)) + kpiReport := new(KpiReport) + _ = json.Unmarshal(body, &kpiReport) + log.Debug("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) + 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) + for _, k := range kpiReport.Task.NE.KPIs { + 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 + } + } + + 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..36a36ea --- /dev/null +++ b/features/security/account.go @@ -0,0 +1,409 @@ +package security + +import ( + "encoding/json" + "fmt" + "image/color" + "io" + "net/http" + "strconv" + "strings" + "time" + + "ems.agt/features/security/service" + sysConfigService "ems.agt/features/sys_config/service" + "ems.agt/lib/core/account" + "ems.agt/lib/core/cache" + "ems.agt/lib/core/conf" + "ems.agt/lib/core/constants/cachekey" + "ems.agt/lib/core/utils/ctx" + "ems.agt/lib/core/vo/result" + "ems.agt/lib/dborm" + "ems.agt/lib/global" + "ems.agt/lib/log" + "ems.agt/lib/oauth" + "ems.agt/lib/services" + "ems.agt/restagent/config" + "github.com/go-admin-team/go-admin-core/logger" + "github.com/mojocn/base64Captcha" +) + +var ( + UriOauthToken = config.DefaultUriPrefix + "/securityManagement/{apiVersion}/{elementTypeValue}/token" + 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) + // 角色权限集合,管理员拥有所有权限 + userId := fmt.Sprint(user.Id) + isAdmin := conf.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, "参数错误")) + return + } + // response 400-5 + if body.Username == "" || body.Password == "" { + log.Error("Wrong parameter value") + ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + 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, "验证码信息错误")) + 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, "验证码已失效")) + return + } + cache.DeleteLocalTTL(verifyKey) + if captcha.(string) != body.Code { + log.Error("Authentication failed, not match captcha") + ctx.JSON(w, 400, result.CodeMsg(400, "验证码错误")) + 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) + 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 { + logger.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 := conf.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 := conf.IsAdmin(userID) + buildMenus := service.NewServiceAccount.RouteMenus(userID, isAdmin) + ctx.JSON(w, 200, result.OkData(buildMenus)) +} diff --git a/features/security/service/service_account.go b/features/security/service/service_account.go new file mode 100644 index 0000000..ace6d82 --- /dev/null +++ b/features/security/service/service_account.go @@ -0,0 +1,56 @@ +package service + +import ( + menuService "ems.agt/features/sys_menu/service" + roleService "ems.agt/features/sys_role/service" + userService "ems.agt/features/sys_user/service" + "ems.agt/lib/core/utils/parse" + "ems.agt/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..ffb7051 --- /dev/null +++ b/features/sm/backup.go @@ -0,0 +1,109 @@ +package sm + +import ( + "database/sql" + "fmt" + "os" + "os/exec" + "time" + + "ems.agt/lib/log" + "ems.agt/restagent/config" + _ "github.com/go-sql-driver/mysql" +) + +var dbConfig = config.GetYamlConfig().Database + +func DatabaseWhoreBackup() { + // MySQL数据库连接信息 + sqlStr := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&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=utf8&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..bcffc13 --- /dev/null +++ b/features/state/getstate.go @@ -0,0 +1,852 @@ +package state + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + "strconv" + "strings" + "time" + + "github.com/go-resty/resty/v2" + "github.com/gorilla/mux" + + "ems.agt/lib/dborm" + "ems.agt/lib/global" + "ems.agt/lib/log" + "ems.agt/lib/services" + "ems.agt/restagent/config" +) + +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(3 * 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 == true && 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{"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) + 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") + } + } 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") + // 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) + 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 == true && 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.R(). + EnableTrace(). + 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") + // 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") + // 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 == true && 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{"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") + } + } 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") + // 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) + 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 == true && 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.R(). + EnableTrace(). + 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") + } + } 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") + } + + 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... ") + + 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.UriPrefix+"/databaseManagement/v1/elementType/%s/objectType/ne_info", + config.GetYamlConfig().Database.Name) + getNeInfoURI := restHostPort + getNeInfoPattern + "?WHERE=status='0'" + log.Debug("getNeInfoPattern:", getNeInfoPattern) + + resp, err := client.R(). + EnableTrace(). + 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.UriPrefix+"/databaseManagement/v1/elementType/%s/objectType/ne_info", + config.GetYamlConfig().Database.Name) + getNeInfoURI := restHostPort + getNeInfoPattern + neId := services.GetUriParamString(r, "ne_id", ",", true, false) + if neId == "" { + getNeInfoURI = getNeInfoURI + fmt.Sprintf("?WHERE=status='0'+and+ne_type='%s'", neType) + } else { + getNeInfoURI = getNeInfoURI + fmt.Sprintf("?WHERE=status='0'+and+ne_type='%v'+and+ne_id+in+%v", neType, neId) + } + log.Debug("getNeInfoURI:", getNeInfoURI) + + resp, err := client.R(). + EnableTrace(). + 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.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, ne.NeType) + log.Debug("requestURI2NF:", requestURI2NF) + + result["ipAddress"] = ne.Ip + resp, err := client.R(). + EnableTrace(). + 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 + } 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, + } + + version := "16.1.1" + if global.Version != "" { + version = global.Version + } + hostName, _ := os.Hostname() + emsState := &SysState{ + HostName: hostName, + OsInfo: getUnameStr(), + DbInfo: "mysql Ver 15.1 Distrib 10.3.35-MariaDB, for Linux (aarch64) using readline 5.1", + IpAddr: []string{ip}, + Port: 3030, + 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..2c9a330 --- /dev/null +++ b/features/state/state_linux.go @@ -0,0 +1,243 @@ +//go:build linux +// +build linux + +package state + +import ( + "encoding/binary" + "fmt" + "os" + "runtime" + "syscall" + "time" + + "ems.agt/lib/log" + "github.com/shirou/gopsutil/cpu" + "github.com/shirou/gopsutil/disk" + "github.com/shirou/gopsutil/mem" + "github.com/shirou/gopsutil/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..548dbe3 --- /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/cpu" + "github.com/shirou/gopsutil/disk" + "github.com/shirou/gopsutil/mem" + "github.com/shirou/gopsutil/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..9aa9080 --- /dev/null +++ b/features/state/sysinfo.go @@ -0,0 +1,74 @@ +package state + +import ( + "ems.agt/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..37019f9 --- /dev/null +++ b/features/sys_config/api_sys_config.go @@ -0,0 +1,230 @@ +package sysconfig + +import ( + "fmt" + "net/http" + "strings" + + "ems.agt/features/sys_config/model" + "ems.agt/features/sys_config/service" + "ems.agt/lib/core/utils/ctx" + "ems.agt/lib/core/utils/parse" + "ems.agt/lib/core/vo/result" + "ems.agt/lib/midware" + "ems.agt/lib/services" + "ems.agt/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, "参数错误")) + 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, "参数错误")) + return + } + + // 检查属性值唯一 + uniqueConfigKey := s.sysConfigService.CheckUniqueConfigKey(body.ConfigKey, "") + if !uniqueConfigKey { + msg := fmt.Sprintf("参数配置新增【%s】失败,参数键名已存在", 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, "参数错误")) + return + } + + // 检查属性值唯一 + uniqueConfigKey := s.sysConfigService.CheckUniqueConfigKey(body.ConfigKey, body.ConfigID) + if !uniqueConfigKey { + msg := fmt.Sprintf("参数配置修改【%s】失败,参数键名已存在", 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("没有权限访问参数配置数据!")) + 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, "参数错误")) + 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, "参数错误")) + 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..488d7c9 --- /dev/null +++ b/features/sys_config/service/repo_sys_config.go @@ -0,0 +1,336 @@ +package service + +import ( + "fmt" + "strings" + "time" + + "ems.agt/features/sys_config/model" + "ems.agt/lib/core/datasource" + "ems.agt/lib/core/utils/date" + "ems.agt/lib/core/utils/parse" + "ems.agt/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.Sprintf("%v", 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.Sprintf("%v", 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..ed0bf3c --- /dev/null +++ b/features/sys_config/service/service_sys_config.go @@ -0,0 +1,155 @@ +package service + +import ( + "errors" + + "ems.agt/features/sys_config/model" + "ems.agt/lib/core/cache" + "ems.agt/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("没有权限访问参数配置数据!") + } + for _, config := range configs { + // 检查是否为内置参数 + if config.ConfigType == "Y" { + return 0, errors.New(config.ConfigID + " 配置参数属于内置参数,禁止删除!") + } + // 清除缓存 + r.clearConfigCache(config.ConfigKey) + } + if len(configs) == len(configIds) { + rows := r.sysConfigRepository.DeleteConfigByIds(configIds) + return rows, nil + } + return 0, errors.New("删除参数配置信息失败!") +} + +// 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..05dbb08 --- /dev/null +++ b/features/sys_dict_data/api_sys_dict_data.go @@ -0,0 +1,247 @@ +package sysdictdata + +import ( + "fmt" + "net/http" + "strings" + + "ems.agt/features/sys_dict_data/model" + sysDictDataService "ems.agt/features/sys_dict_data/service" + sysDictTypeService "ems.agt/features/sys_dict_type/service" + "ems.agt/lib/core/utils/ctx" + "ems.agt/lib/core/utils/parse" + "ems.agt/lib/core/vo/result" + "ems.agt/lib/midware" + "ems.agt/lib/services" + "ems.agt/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, "参数错误")) + 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, "参数错误")) + return + } + + // 检查字典类型是否存在 + sysDictType := s.sysDictTypeService.SelectDictTypeByType(body.DictType) + if sysDictType.DictType != body.DictType { + ctx.JSON(w, 200, result.ErrMsg("没有权限访问字典类型数据!")) + return + } + + // 检查字典标签唯一 + uniqueDictLabel := s.sysDictDataService.CheckUniqueDictLabel(body.DictType, body.DictLabel, "") + if !uniqueDictLabel { + msg := fmt.Sprintf("数据新增【%s】失败,该字典类型下标签名已存在", body.DictLabel) + ctx.JSON(w, 200, result.ErrMsg(msg)) + return + } + + // 检查字典键值唯一 + uniqueDictValue := s.sysDictDataService.CheckUniqueDictValue(body.DictType, body.DictValue, "") + if !uniqueDictValue { + msg := fmt.Sprintf("数据新增【%s】失败,该字典类型下标签值已存在", 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, "参数错误")) + return + } + + // 检查字典类型是否存在 + sysDictType := s.sysDictTypeService.SelectDictTypeByType(body.DictType) + if sysDictType.DictType != body.DictType { + ctx.JSON(w, 200, result.ErrMsg("没有权限访问字典类型数据!")) + return + } + + // 检查字典编码是否存在 + SysDictDataApi := s.sysDictDataService.SelectDictDataByCode(body.DictCode) + if SysDictDataApi.DictCode != body.DictCode { + ctx.JSON(w, 200, result.ErrMsg("没有权限访问字典编码数据!")) + return + } + + // 检查字典标签唯一 + uniqueDictLabel := s.sysDictDataService.CheckUniqueDictLabel(body.DictType, body.DictLabel, body.DictCode) + if !uniqueDictLabel { + msg := fmt.Sprintf("数据修改【%s】失败,该字典类型下标签名已存在", 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("数据修改【%s】失败,该字典类型下标签值已存在", 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, "参数错误")) + 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("删除成功:%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, "参数错误")) + 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..cd6268a --- /dev/null +++ b/features/sys_dict_data/repo/repo_sys_dict_data.go @@ -0,0 +1,369 @@ +package repo + +import ( + "fmt" + "strings" + "time" + + "ems.agt/features/sys_dict_data/model" + "ems.agt/lib/core/datasource" + "ems.agt/lib/core/utils/parse" + "ems.agt/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.Sprintf("%v", 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..624105e --- /dev/null +++ b/features/sys_dict_data/service/service_sys_dict_data.go @@ -0,0 +1,111 @@ +package service + +import ( + "errors" + + "ems.agt/features/sys_dict_data/model" + "ems.agt/features/sys_dict_data/repo" + sysDictTypeService "ems.agt/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("没有权限访问字典编码数据!") + } + 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("删除字典数据信息失败!") +} + +// 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..480ff01 --- /dev/null +++ b/features/sys_dict_type/api_sys_dict_type.go @@ -0,0 +1,253 @@ +package sysdicttype + +import ( + "fmt" + "net/http" + "strings" + + "ems.agt/features/sys_dict_type/model" + sysDictTypeService "ems.agt/features/sys_dict_type/service" + "ems.agt/lib/core/utils/ctx" + "ems.agt/lib/core/utils/parse" + "ems.agt/lib/core/vo/result" + "ems.agt/lib/midware" + "ems.agt/lib/services" + "ems.agt/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, "参数错误")) + 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, "参数错误")) + return + } + + // 检查字典名称唯一 + uniqueDictName := s.sysDictTypeService.CheckUniqueDictName(body.DictName, "") + if !uniqueDictName { + msg := fmt.Sprintf("字典新增【%s】失败,字典名称已存在", body.DictName) + ctx.JSON(w, 200, result.ErrMsg(msg)) + return + } + + // 检查字典类型唯一 + uniqueDictType := s.sysDictTypeService.CheckUniqueDictType(body.DictType, "") + if !uniqueDictType { + msg := fmt.Sprintf("字典新增【%s】失败,字典类型已存在", 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, "参数错误")) + return + } + + // 检查数据是否存在 + dictInfo := s.sysDictTypeService.SelectDictTypeByID(body.DictID) + if dictInfo.DictID != body.DictID { + ctx.JSON(w, 200, result.ErrMsg("没有权限访问字典类型数据!")) + return + } + + // 检查字典名称唯一 + uniqueDictName := s.sysDictTypeService.CheckUniqueDictName(body.DictName, body.DictID) + if !uniqueDictName { + msg := fmt.Sprintf("字典修改【%s】失败,字典名称已存在", body.DictName) + ctx.JSON(w, 200, result.ErrMsg(msg)) + return + } + + // 检查字典类型唯一 + uniqueDictType := s.sysDictTypeService.CheckUniqueDictType(body.DictType, body.DictID) + if !uniqueDictType { + msg := fmt.Sprintf("字典修改【%s】失败,字典类型已存在", 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, "参数错误")) + 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("删除成功:%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..874dac1 --- /dev/null +++ b/features/sys_dict_type/repo/repo_sys_dict_type.go @@ -0,0 +1,330 @@ +package repo + +import ( + "fmt" + "strings" + "time" + + "ems.agt/features/sys_dict_type/model" + "ems.agt/lib/core/datasource" + "ems.agt/lib/core/utils/date" + "ems.agt/lib/core/utils/parse" + "ems.agt/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.Sprintf("%v", 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..8cbb16b --- /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 "ems.agt/features/sys_dict_data/model" + sysDictDataRepo "ems.agt/features/sys_dict_data/repo" + sysDictTypeModel "ems.agt/features/sys_dict_type/model" + "ems.agt/features/sys_dict_type/repo" + "ems.agt/lib/core/cache" + "ems.agt/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("没有权限访问字典类型数据!") + } + for _, v := range dictTypes { + // 字典类型下级含有数据 + useCount := r.sysDictDataRepository.CountDictDataByType(v.DictType) + if useCount > 0 { + msg := fmt.Sprintf("【%s】存在字典数据,不能删除", 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("删除字典数据信息失败!") +} + +// 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..a78e0d8 --- /dev/null +++ b/features/sys_menu/api_sys_menu.go @@ -0,0 +1,354 @@ +package sysmenu + +import ( + "fmt" + "net/http" + + "ems.agt/features/sys_menu/consts" + "ems.agt/features/sys_menu/model" + "ems.agt/features/sys_menu/service" + "ems.agt/lib/core/conf" + "ems.agt/lib/core/utils/ctx" + "ems.agt/lib/core/utils/regular" + "ems.agt/lib/core/vo/result" + "ems.agt/lib/midware" + "ems.agt/lib/services" + "ems.agt/restagent/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 conf.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, "参数错误")) + 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, "参数错误")) + 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, "参数错误")) + return + } + + // 上级菜单不能选自己 + if body.MenuID == body.ParentID { + msg := fmt.Sprintf("菜单修改【%s】失败,上级菜单不能选择自己", 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("没有权限访问菜单数据")) + return + } + // 父级ID不为0是要检查 + if body.ParentID != "0" { + menuParent := s.sysMenuService.SelectMenuById(body.ParentID) + if menuParent.MenuID != body.ParentID { + ctx.JSON(w, 200, result.ErrMsg("没有权限访问菜单数据")) + return + } + // 禁用菜单时检查父菜单是否使用 + if body.Status == "1" && menuParent.Status == "0" { + ctx.JSON(w, 200, result.ErrMsg("父菜单未启用!")) + 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, "参数错误")) + return + } + + // 检查数据是否存在 + menu := s.sysMenuService.SelectMenuById(menuId) + if menu.MenuID != menuId { + ctx.JSON(w, 200, result.ErrMsg("没有权限访问菜单数据!")) + return + } + + // 检查是否存在子菜单 + hasChild := s.sysMenuService.HasChildByMenuIdAndStatus(menuId, "") + if hasChild > 0 { + msg := fmt.Sprintf("不允许删除,存在子菜单数:%d", hasChild) + ctx.JSON(w, 200, result.ErrMsg(msg)) + return + } + + // 检查是否分配给角色 + existRole := s.sysMenuService.CheckMenuExistRole(menuId) + if existRole > 0 { + msg := fmt.Sprintf("不允许删除,菜单已分配给角色数:%d", existRole) + ctx.JSON(w, 200, result.ErrMsg(msg)) + return + } + + rows := s.sysMenuService.DeleteMenuById(menuId) + if rows > 0 { + msg := fmt.Sprintf("删除成功:%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 conf.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, "参数错误")) + 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 conf.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..af809a1 --- /dev/null +++ b/features/sys_menu/service/repo_sys_menu.go @@ -0,0 +1,475 @@ +package service + +import ( + "fmt" + "strings" + "time" + + "ems.agt/features/sys_menu/consts" + "ems.agt/features/sys_menu/model" + "ems.agt/lib/core/datasource" + "ems.agt/lib/core/utils/parse" + "ems.agt/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.Sprintf("%v", 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..3c71d17 --- /dev/null +++ b/features/sys_menu/service/service_sys_menu.go @@ -0,0 +1,398 @@ +package service + +import ( + "encoding/base64" + "strings" + + "ems.agt/features/sys_menu/consts" + "ems.agt/features/sys_menu/model" + sysRoleService "ems.agt/features/sys_role/service" + sysrolemenu "ems.agt/features/sys_role_menu" + "ems.agt/lib/core/utils/parse" + "ems.agt/lib/core/utils/regular" + "ems.agt/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) + menus := r.parseDataToTree(sysMenus) + 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..5ba0582 --- /dev/null +++ b/features/sys_role/api_sys_role.go @@ -0,0 +1,368 @@ +package sysrole + +import ( + "fmt" + "net/http" + "strings" + + "ems.agt/features/sys_role/model" + "ems.agt/features/sys_role/service" + userService "ems.agt/features/sys_user/service" + "ems.agt/lib/core/utils/ctx" + "ems.agt/lib/core/utils/parse" + "ems.agt/lib/core/vo/result" + "ems.agt/lib/midware" + "ems.agt/lib/services" + "ems.agt/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, "参数错误")) + 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, "参数错误")) + return + } + + // 判断角色名称是否唯一 + uniqueRoleName := s.sysRoleService.CheckUniqueRoleName(body.RoleName, "") + if !uniqueRoleName { + msg := fmt.Sprintf("角色新增【%s】失败,角色名称已存在", body.RoleName) + ctx.JSON(w, 200, result.ErrMsg(msg)) + return + } + + // 判断角色键值是否唯一 + uniqueRoleKey := s.sysRoleService.CheckUniqueRoleKey(body.RoleKey, "") + if !uniqueRoleKey { + msg := fmt.Sprintf("角色新增【%s】失败,角色键值已存在", 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, "参数错误")) + return + } + + // 检查是否管理员角色 + if body.RoleID == "1" { + ctx.JSON(w, 200, result.ErrMsg("不允许操作管理员角色")) + return + } + + // 检查是否存在 + role := s.sysRoleService.SelectRoleById(body.RoleID) + if role.RoleID != body.RoleID { + ctx.JSON(w, 200, result.ErrMsg("没有权限访问角色数据!")) + return + } + + // 判断角色名称是否唯一 + uniqueRoleName := s.sysRoleService.CheckUniqueRoleName(body.RoleName, body.RoleID) + if !uniqueRoleName { + msg := fmt.Sprintf("角色修改【%s】失败,角色名称已存在", body.RoleName) + ctx.JSON(w, 200, result.ErrMsg(msg)) + return + } + + // 判断角色键值是否唯一 + uniqueRoleKey := s.sysRoleService.CheckUniqueRoleKey(body.RoleKey, body.RoleID) + if !uniqueRoleKey { + msg := fmt.Sprintf("角色修改【%s】失败,角色键值已存在", 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, "参数错误")) + 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("不允许操作管理员角色")) + return + } + } + rows, err := s.sysRoleService.DeleteRoleByIds(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 /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, "参数错误")) + return + } + + // 检查是否管理员角色 + if body.RoleID == "1" { + ctx.JSON(w, 200, result.ErrMsg("不允许操作管理员角色")) + return + } + + // 检查是否存在 + role := s.sysRoleService.SelectRoleById(body.RoleID) + if role.RoleID != body.RoleID { + ctx.JSON(w, 200, result.ErrMsg("没有权限访问角色数据!")) + return + } + + // 与旧值相等不变更 + if role.Status == body.Status { + ctx.JSON(w, 200, result.ErrMsg("变更状态与旧值相等!")) + 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, "参数错误")) + return + } + + // 检查是否存在 + role := s.sysRoleService.SelectRoleById(roleId.(string)) + if role.RoleID != roleId { + ctx.JSON(w, 200, result.ErrMsg("没有权限访问角色数据!")) + 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, "参数错误")) + 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("没有权限访问角色数据!")) + 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..7f61e8a --- /dev/null +++ b/features/sys_role/service/repo_sys_role.go @@ -0,0 +1,362 @@ +package service + +import ( + "fmt" + "strings" + "time" + + "ems.agt/features/sys_role/model" + "ems.agt/lib/core/datasource" + "ems.agt/lib/core/utils/date" + "ems.agt/lib/core/utils/parse" + "ems.agt/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.Sprintf("%v", 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..187d0c0 --- /dev/null +++ b/features/sys_role/service/service_sys_role.go @@ -0,0 +1,165 @@ +package service + +import ( + "errors" + "fmt" + + "ems.agt/features/sys_role/model" + sysrolemenu "ems.agt/features/sys_role_menu" + sysuserrole "ems.agt/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("没有权限访问角色数据!") + } + for _, role := range roles { + // 检查是否为已删除 + if role.DelFlag == "1" { + return 0, errors.New(role.RoleID + " 角色信息已经删除!") + } + // 检查分配用户 + userCount := r.sysUserRoleRepository.CountUserRoleByRoleId(role.RoleID) + if userCount > 0 { + msg := fmt.Sprintf("【%s】已分配给用户,不能删除", 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("删除角色信息失败!") +} + +// 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..02dc826 --- /dev/null +++ b/features/sys_role_menu/repo_sys_role_menu.go @@ -0,0 +1,86 @@ +package sysrolemenu + +import ( + "fmt" + "strings" + + "ems.agt/lib/core/datasource" + "ems.agt/lib/core/utils/parse" + "ems.agt/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..b16a5b5 --- /dev/null +++ b/features/sys_user/api_sys_user.go @@ -0,0 +1,323 @@ +package sysuser + +import ( + "fmt" + "net/http" + "strings" + + sysRoleModel "ems.agt/features/sys_role/model" + sysRoleService "ems.agt/features/sys_role/service" + sysUserModel "ems.agt/features/sys_user/model" + "ems.agt/features/sys_user/service" + "ems.agt/lib/core/conf" + "ems.agt/lib/core/utils/ctx" + "ems.agt/lib/core/utils/parse" + "ems.agt/lib/core/vo/result" + "ems.agt/lib/midware" + "ems.agt/lib/services" + "ems.agt/restagent/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, "参数错误")) + return + } + // 查询系统角色列表 + roles := s.sysRoleService.SelectRoleList(sysRoleModel.SysRole{}) + + // 不是系统指定管理员需要排除其角色 + if !conf.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("没有权限访问用户数据!")) + 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, "参数错误")) + return + } + + // 检查用户登录账号是否唯一 + uniqueUserName := s.sysUserService.CheckUniqueUserName(body.AccountId, "") + if !uniqueUserName { + msg := fmt.Sprintf("新增用户【%s】失败,登录账号已存在", 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, "参数错误")) + return + } + + // 检查是否管理员用户 + // if conf.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("没有权限访问用户数据!")) + return + } + + // 检查用户登录账号是否唯一 + uniqueUserName := s.sysUserService.CheckUniqueUserName(body.AccountId, body.Id) + if !uniqueUserName { + msg := fmt.Sprintf("修改用户【%s】失败,登录账号已存在", 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, "参数错误")) + 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("删除成功:%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, "参数错误")) + return + } + + // 检查是否管理员用户 + if conf.IsAdmin(body.UserID) { + ctx.JSON(w, 200, result.ErrMsg("不允许操作管理员用户")) + return + } + + user := s.sysUserService.SelectUserById(body.UserID) + if user.Id != body.UserID { + ctx.JSON(w, 200, result.ErrMsg("没有权限访问用户数据!")) + 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, "参数错误")) + return + } + + // 检查是否存在 + user := s.sysUserService.SelectUserById(body.UserID) + if user.Id != body.UserID { + ctx.JSON(w, 200, result.ErrMsg("没有权限访问用户数据!")) + return + } + + // 与旧值相等不变更 + if user.Status == body.Status { + ctx.JSON(w, 200, result.ErrMsg("变更状态与旧值相等!")) + 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..b7a0259 --- /dev/null +++ b/features/sys_user/model/sys_user.go @@ -0,0 +1,42 @@ +package model + +import "ems.agt/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..622b7e6 --- /dev/null +++ b/features/sys_user/service/repo_sys_user.go @@ -0,0 +1,578 @@ +package service + +import ( + "fmt" + "strings" + "time" + + sysRoleModel "ems.agt/features/sys_role/model" + sysUserModel "ems.agt/features/sys_user/model" + "ems.agt/lib/core/datasource" + "ems.agt/lib/core/utils/crypto" + "ems.agt/lib/core/utils/date" + "ems.agt/lib/core/utils/parse" + "ems.agt/lib/log" +) + +// 实例化数据层 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.Sprintf("%v", 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..0401d30 --- /dev/null +++ b/features/sys_user/service/service_sys_user.go @@ -0,0 +1,150 @@ +package service + +import ( + "errors" + "fmt" + + sysUserModel "ems.agt/features/sys_user/model" + sysuserrole "ems.agt/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("没有权限访问用户数据!") + } + if len(users) == len(userIds) { + // 删除用户与角色关联 + r.sysUserRoleRepository.DeleteUserRole(userIds) + // ... 注意其他userId进行关联的表 + // 删除用户 + rows := r.sysUserRepository.DeleteUserByIds(userIds) + return rows, nil + } + return 0, errors.New("删除用户信息失败!") +} + +// 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..97585a1 --- /dev/null +++ b/features/sys_user_role/repo_sys_user_role.go @@ -0,0 +1,87 @@ +package sysuserrole + +import ( + "fmt" + "strings" + + "ems.agt/lib/core/datasource" + "ems.agt/lib/core/utils/parse" + "ems.agt/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..e3d4c4f --- /dev/null +++ b/features/trace/tcpdump.go @@ -0,0 +1,272 @@ +package trace + +import ( + "fmt" + "net" + "net/http" + "strings" + "time" + + "ems.agt/lib/core/cmd" + "ems.agt/lib/core/conf" + "ems.agt/lib/core/file" + "ems.agt/lib/core/utils/ctx" + "ems.agt/lib/core/vo/result" + "ems.agt/lib/dborm" + "ems.agt/lib/log" + "ems.agt/restagent/config" +) + +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, "参数错误")) + 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 \ntimeout %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, "参数错误")) + 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 = file.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, "参数错误")) + return + } + + neInfo, err := NeInfo(body.NeType, body.NeId) + if err != nil { + ctx.JSON(w, 200, result.ErrMsg(err.Error())) + return + } + + // 开始 + if body.RunType == "start" { + // 创建TCP连接 + conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", neInfo.Ip, 5002)) + if err != nil { + conn.Close() + ctx.JSON(w, 200, result.ErrMsg(err.Error())) + return + } + + 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 + } + // 停止 + if body.RunType == "stop" { + // 创建TCP连接 + conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", neInfo.Ip, 5002)) + if err != nil { + conn.Close() + ctx.JSON(w, 200, result.ErrMsg(err.Error())) + return + } + + 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 := file.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 := file.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 + } + + 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..b22382c --- /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" + + "ems.agt/lib/dborm" + "ems.agt/lib/global" + "ems.agt/lib/log" + "ems.agt/lib/run" + "ems.agt/lib/services" + "ems.agt/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..e034acb --- /dev/null +++ b/features/udm_user/api_udm_user.go @@ -0,0 +1,1209 @@ +package udmuser + +import ( + "fmt" + "net/http" + "strconv" + "strings" + "time" + + "ems.agt/features/udm_user/model" + "ems.agt/features/udm_user/service" + "ems.agt/lib/core/conf" + "ems.agt/lib/core/file" + mmlclient "ems.agt/lib/core/mml_client" + "ems.agt/lib/core/utils/ctx" + "ems.agt/lib/core/utils/parse" + "ems.agt/lib/core/vo/result" + "ems.agt/lib/dborm" + "ems.agt/lib/log" + "ems.agt/lib/midware" + "ems.agt/lib/services" + "ems.agt/restagent/config" +) + +// 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.Authorize(nil), + }, + { + Method: "GET", + Pattern: "/auth/{neId}/{imsi}", + Handler: apis.UdmAuthUserInfo, + Middleware: midware.Authorize(nil), + }, + { + Method: "POST", + Pattern: "/auth/{neId}", + Handler: apis.UdmAuthUserAdd, + Middleware: midware.Authorize(nil), + }, + { + Method: "POST", + Pattern: "/auth/{neId}/{num}", + Handler: apis.UdmAuthUserAdds, + Middleware: midware.Authorize(nil), + }, + { + Method: "PUT", + Pattern: "/auth/{neId}", + Handler: apis.UdmAuthUserEdit, + Middleware: midware.Authorize(nil), + }, + { + Method: "DELETE", + Pattern: "/auth/{neId}/{imsi}", + Handler: apis.UdmAuthUserRemove, + Middleware: midware.Authorize(nil), + }, + { + Method: "DELETE", + Pattern: "/auth/{neId}/{imsi}/{num}", + Handler: apis.UdmAuthUserRemoves, + Middleware: midware.Authorize(nil), + }, + { + Method: "POST", + Pattern: "/authExport", + Handler: apis.UdmAuthUserExport, + Middleware: midware.Authorize(nil), + }, + { + Method: "POST", + Pattern: "/authImport/{neId}", + Handler: apis.UdmAuthUserImport, + Middleware: midware.Authorize(nil), + }, + // UDM签约用户 + { + Method: "GET", + Pattern: "/subs", + Handler: apis.UdmSubUserList, + Middleware: midware.Authorize(nil), + }, + { + Method: "POST", + Pattern: "/subSave/{neId}", + Handler: apis.UdmSubUserSave, + Middleware: midware.Authorize(nil), + }, + { + Method: "GET", + Pattern: "/subInfo/{neId}/{imsi}", + Handler: apis.UdmSubUserInfo, + Middleware: midware.Authorize(nil), + }, + { + Method: "POST", + Pattern: "/sub/{neId}", + Handler: apis.UdmSubUserAdd, + Middleware: midware.Authorize(nil), + }, + { + Method: "POST", + Pattern: "/sub/{neId}/{num}", + Handler: apis.UdmSubUserAdds, + Middleware: midware.Authorize(nil), + }, + { + Method: "PUT", + Pattern: "/sub/{neId}", + Handler: apis.UdmSubUserEdit, + Middleware: midware.Authorize(nil), + }, + { + Method: "PUT", + Pattern: "/sub4gIP/{neId}", + Handler: apis.UdmSubUser4GIP, + Middleware: midware.Authorize(nil), + }, + { + Method: "PUT", + Pattern: "/subSmData/{neId}", + Handler: apis.UdmSubUserSmData, + Middleware: midware.Authorize(nil), + }, + { + Method: "DELETE", + Pattern: "/sub/{neId}/{imsi}", + Handler: apis.UdmSubUserRemove, + Middleware: midware.Authorize(nil), + }, + { + Method: "DELETE", + Pattern: "/sub/{neId}/{imsi}/{num}", + Handler: apis.UdmSubUserRemoves, + Middleware: midware.Authorize(nil), + }, + { + Method: "POST", + Pattern: "/subExport", + Handler: apis.UdmSubUserExport, + Middleware: midware.Authorize(nil), + }, + { + Method: "POST", + Pattern: "/subImport/{neId}", + Handler: apis.UdmSubUserImport, + Middleware: midware.Authorize(nil), + }, + // 添加更多的 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) + // 遍历安全掩码 + rows := data["rows"].([]model.UdmAuthUser) + maskRows := []model.UdmAuthUser{} + for _, v := range rows { + v.Ki = parse.SafeContent(v.Ki) + v.Opc = parse.SafeContent(v.Opc) + maskRows = append(maskRows, v) + } + data["rows"] = maskRows + 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, "参数错误")) + 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, "参数错误")) + 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] + } else { + userInfo = model.UdmAuthUser{ + Imsi: imsi, + Amf: data["amf"], + AlgoIndex: data["algo"], + Opc: data["opc"], + Ki: data["ki"], + } + + s.authUser.Insert(neId, userInfo) + } + userInfo.Ki = parse.SafeContent(userInfo.Ki) + userInfo.Opc = parse.SafeContent(userInfo.Opc) + 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, "参数错误")) + return + } + + var body model.UdmAuthUser + err := ctx.ShouldBindJSON(r, &body) + if err != nil || body.Imsi == "" { + ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + 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, "参数错误")) + return + } + + var body model.UdmAuthUser + err := ctx.ShouldBindJSON(r, &body) + if err != nil || body.Imsi == "" { + ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + 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", body.Imsi, num, body.Ki, body.Amf, body.AlgoIndex) + + // 发送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, "参数错误")) + return + } + + var body model.UdmAuthUser + err := ctx.ShouldBindJSON(r, &body) + if err != nil || body.Imsi == "" { + ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + 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, "参数错误")) + 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, "参数错误")) + 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, "参数错误")) + return + } + + if !(body.Type == "csv" || body.Type == "txt") { + ctx.JSON(w, 200, result.ErrMsg("导出文件类型支持csv、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", "amf", "algo", "opc"}) + for _, v := range list { + maskKi := parse.SafeContent(v.Ki) + maskOpc := parse.SafeContent(v.Opc) + data = append(data, []string{v.Imsi, maskKi, v.Amf, v.AlgoIndex, maskOpc}) + } + // 输出到文件 + 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 { + maskKi := parse.SafeContent(v.Ki) + maskOpc := parse.SafeContent(v.Opc) + data = append(data, []string{v.Imsi, maskKi, v.Amf, v.AlgoIndex, maskOpc}) + } + // 输出到文件 + 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, "参数错误")) + 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("请上传.csv、.txt格式文件,内容字段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 = file.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.OkData(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, "参数错误")) + 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, "参数错误")) + 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 + } + + neId = "" + var userInfo model.UdmSubUser + list := s.subUser.List(model.UdmSubUser{NeID: neId, Imsi: imsi}) + if len(list) > 0 { + userInfo = list[0] + } else { + cnType, _ := strconv.ParseInt(data["CNType"][:4], 0, 64) + rat, _ := strconv.ParseInt(data["RAT"][:4], 0, 64) + userInfo = model.UdmSubUser{ + Imsi: imsi, + Msisdn: data["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] + } + + 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, "参数错误")) + return + } + + var body model.UdmSubUser + err := ctx.ShouldBindJSON(r, &body) + if err != nil || body.Imsi == "" { + ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + 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, "参数错误")) + return + } + + var body model.UdmSubUser + err := ctx.ShouldBindJSON(r, &body) + if err != nil || body.Imsi == "" { + ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + 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, "参数错误")) + return + } + + var body model.UdmSubUser + err := ctx.ShouldBindJSON(r, &body) + if err != nil || body.Imsi == "" || body.SubNum == "" { + ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + 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, "参数错误")) + return + } + + var body model.UdmSubUser + err := ctx.ShouldBindJSON(r, &body) + if err != nil || body.Imsi == "" { + ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + 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, "参数错误")) + 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, "参数错误")) + 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, "参数错误")) + 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, "参数错误")) + 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, "参数错误")) + 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, "参数错误")) + 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, "参数错误")) + return + } + + if !(body.Type == "csv" || body.Type == "txt") { + ctx.JSON(w, 200, result.ErrMsg("导出文件类型支持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", "arfb", "sar", "rat", "cn", "smf_sel", "sm_dat", "eps_dat"}) + for _, v := range list { + data = append(data, []string{v.Imsi, v.Msisdn, v.Ambr, v.Arfb, v.Sar, v.Rat, v.Cn, v.SmfSel, v.SmData, v.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 { + data = append(data, []string{v.Imsi, v.Msisdn, v.Ambr, v.Arfb, v.Sar, v.Rat, v.Cn, v.SmfSel, v.SmData, v.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, "参数错误")) + 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("请上传.csv、.txt格式文件,内容字段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 = file.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.OkData(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..1f3bf67 --- /dev/null +++ b/features/udm_user/repo/repo_udm_auth_user.go @@ -0,0 +1,274 @@ +package repo + +import ( + "strconv" + "strings" + + "ems.agt/features/udm_user/model" + "ems.agt/lib/core/datasource" + "ems.agt/lib/core/utils/parse" + "ems.agt/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 v, ok := query["sortOrder"]; ok && v != nil { + if v == "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 + 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 { + 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 { + 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..202a53a --- /dev/null +++ b/features/udm_user/repo/repo_udm_sub_user.go @@ -0,0 +1,438 @@ +package repo + +import ( + "fmt" + "strconv" + "strings" + + "ems.agt/features/udm_user/model" + "ems.agt/lib/core/datasource" + "ems.agt/lib/core/utils/parse" + "ems.agt/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, ok := query["sortOrder"]; ok && v != nil { + if v == "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 + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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..1f208d2 --- /dev/null +++ b/features/udm_user/service/service_redis_data.go @@ -0,0 +1,108 @@ +package service + +import ( + "strings" + + "ems.agt/features/udm_user/model" + "ems.agt/lib/core/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 + } + + 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: key[5:], + 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: 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 { + a.EpsDat = v + arr := strings.Split(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] + 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..4fa4b56 --- /dev/null +++ b/features/udm_user/service/service_udm_auth_user.go @@ -0,0 +1,143 @@ +package service + +import ( + "fmt" + "strconv" + + "ems.agt/features/udm_user/model" + "ems.agt/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 = "1" + 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 = "1" + 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 = "1" + if s, ok := v["imsi"]; ok { + authUser.Imsi = s + } + if s, ok := v["ki"]; ok { + authUser.Ki = s + } + if s, ok := v["amf"]; ok { + authUser.Amf = s + } + if s, ok := v["algo"]; ok { + authUser.AlgoIndex = s + } + if s, ok := v["opc"]; ok { + authUser.Opc = s + } + 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 = "1" + authUser.Imsi = v[0] + authUser.Ki = v[1] + authUser.AlgoIndex = v[2] + authUser.Amf = v[3] + if len(v) == 5 { + authUser.Opc = v[4] + } + 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..6f02244 --- /dev/null +++ b/features/udm_user/service/service_udm_sub_user.go @@ -0,0 +1,201 @@ +package service + +import ( + "fmt" + "strconv" + "strings" + + "ems.agt/features/udm_user/model" + "ems.agt/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 subUser.StaticIp != "" { + 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 + } + 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_data"]; ok { + subUser.SmData = s + } + if s, ok := v["eps_dat"]; ok { + subUser.EpsDat = s + } + 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) == 11 { + subUser.EpsDat = v[10] + } + 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..15013f8 --- /dev/null +++ b/features/ue/ue.go @@ -0,0 +1,272 @@ +package ue + +import ( + "encoding/json" + "fmt" + "net/http" + "time" + + "ems.agt/lib/dborm" + "ems.agt/lib/global" + "ems.agt/lib/log" + "ems.agt/lib/services" + "ems.agt/restagent/config" + "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"` +} + +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" + + 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" +) + +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) +} + +// 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{"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.DataResponse + _ = 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{"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.DataResponse + _ = json.Unmarshal(resp.Body(), &response) + services.ResponseWithJson(w, resp.StatusCode(), response) + return + } +} + +// Get UEInfo 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{"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.DataResponse + _ = 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..130fb00 --- /dev/null +++ b/go.mod @@ -0,0 +1,75 @@ +module ems.agt + +go 1.20 + +require ( + github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/go-admin-team/go-admin-core v1.3.12-0.20221121065133-27b7dbe27a8f + github.com/go-resty/resty/v2 v2.7.0 + github.com/go-sql-driver/mysql v1.7.1 + github.com/gorilla/mux v1.8.0 + github.com/gorilla/websocket v1.5.0 + github.com/gosnmp/gosnmp v1.35.0 + github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f + github.com/patrickmn/go-cache v2.1.0+incompatible + github.com/redis/go-redis/v9 v9.1.0 + 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 + golang.org/x/crypto v0.12.0 + gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df + gopkg.in/yaml.v3 v3.0.1 + xorm.io/xorm v1.3.2 +) + +require ( + github.com/mattn/go-sqlite3 v1.14.15 // indirect + github.com/onsi/ginkgo v1.16.5 // indirect + github.com/onsi/gomega v1.21.1 // indirect +) + +require ( + github.com/cespare/xxhash/v2 v2.2.0 // 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/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 // indirect + github.com/json-iterator/go v1.1.12 // 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-isatty v0.0.19 // 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/mojocn/base64Captcha v1.3.5 + 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/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 // indirect + 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/yusufpapurcu/wmi v1.2.3 // indirect + golang.org/x/image v0.5.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/text v0.12.0 // indirect + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect + gopkg.in/ini.v1 v1.67.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..a962ae4 --- /dev/null +++ b/go.sum @@ -0,0 +1,1101 @@ +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/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +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/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +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.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +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/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +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/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/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/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/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +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-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-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-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.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/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/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/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.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/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/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/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/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/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/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/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/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +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.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +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/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/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/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-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.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/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +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/mojocn/base64Captcha v1.3.5 h1:Qeilr7Ta6eDtG4S+tQuZ5+hO+QHbiGAJdi4PfoagaA0= +github.com/mojocn/base64Captcha v1.3.5/go.mod h1:/tTTXn4WTpX9CfrmipqRytCpJ27Uw3G6I7NcP2WwcmY= +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/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/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.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +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/gomega v1.4.3/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.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/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/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +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/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/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/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +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.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.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.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/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/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +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/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= +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/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-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.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +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 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +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-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-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.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +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/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-20210902050250-f475640dd07b/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-20220520151302-bc2c85ada10a/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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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/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.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +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/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-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 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +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= +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-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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/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.v3 v3.0.0-20200313102051-9f266ea9e77c/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= +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/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..2279141 --- /dev/null +++ b/lib/core/account/account.go @@ -0,0 +1,54 @@ +package account + +import ( + "fmt" + "strconv" + "time" + + sysMenuService "ems.agt/features/sys_menu/service" + sysRoleService "ems.agt/features/sys_role/service" + "ems.agt/lib/core/cache" + "ems.agt/lib/core/conf" + "ems.agt/lib/core/vo" + "ems.agt/lib/dborm" +) + +// 登录缓存用户信息 +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 conf.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/cmd/cmd.go b/lib/core/cmd/cmd.go new file mode 100644 index 0000000..085494d --- /dev/null +++ b/lib/core/cmd/cmd.go @@ -0,0 +1,201 @@ +package cmd + +import ( + "bytes" + "context" + "fmt" + "os/exec" + "strings" + "time" +) + +func Exec(cmdStr string) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + defer cancel() + cmd := exec.Command("bash", "-c", cmdStr) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + if ctx.Err() == context.DeadlineExceeded { + return "", fmt.Errorf("errCmdTimeout %v", err) + } + if err != nil { + errMsg := "" + if len(stderr.String()) != 0 { + errMsg = fmt.Sprintf("stderr: %s", stderr.String()) + } + if len(stdout.String()) != 0 { + if len(errMsg) != 0 { + errMsg = fmt.Sprintf("%s; stdout: %s", errMsg, stdout.String()) + } else { + errMsg = fmt.Sprintf("stdout: %s", stdout.String()) + } + } + return errMsg, err + } + return stdout.String(), nil +} + +func ExecWithTimeOut(cmdStr string, timeout time.Duration) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + cmd := exec.Command("bash", "-c", cmdStr) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + if ctx.Err() == context.DeadlineExceeded { + return "", fmt.Errorf("errCmdTimeout %v", err) + } + if err != nil { + errMsg := "" + if len(stderr.String()) != 0 { + errMsg = fmt.Sprintf("stderr: %s", stderr.String()) + } + if len(stdout.String()) != 0 { + if len(errMsg) != 0 { + errMsg = fmt.Sprintf("%s; stdout: %s", errMsg, stdout.String()) + } else { + errMsg = fmt.Sprintf("stdout: %s", stdout.String()) + } + } + return errMsg, err + } + return stdout.String(), nil +} + +func ExecCronjobWithTimeOut(cmdStr string, workdir string, timeout time.Duration) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + cmd := exec.Command("bash", "-c", cmdStr) + cmd.Dir = workdir + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + if ctx.Err() == context.DeadlineExceeded { + return "", fmt.Errorf("errCmdTimeout %v", err) + } + + errMsg := "" + if len(stderr.String()) != 0 { + errMsg = fmt.Sprintf("stderr:\n %s", stderr.String()) + } + if len(stdout.String()) != 0 { + if len(errMsg) != 0 { + errMsg = fmt.Sprintf("%s \n\n; stdout:\n %s", errMsg, stdout.String()) + } else { + errMsg = fmt.Sprintf("stdout:\n %s", stdout.String()) + } + } + return errMsg, err +} + +func Execf(cmdStr string, a ...interface{}) (string, error) { + cmd := exec.Command("bash", "-c", fmt.Sprintf(cmdStr, a...)) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + if err != nil { + errMsg := "" + if len(stderr.String()) != 0 { + errMsg = fmt.Sprintf("stderr: %s", stderr.String()) + } + if len(stdout.String()) != 0 { + if len(errMsg) != 0 { + errMsg = fmt.Sprintf("%s; stdout: %s", errMsg, stdout.String()) + } else { + errMsg = fmt.Sprintf("stdout: %s", stdout.String()) + } + } + return errMsg, err + } + return stdout.String(), nil +} + +func ExecWithCheck(name string, a ...string) (string, error) { + cmd := exec.Command(name, a...) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + if err != nil { + errMsg := "" + if len(stderr.String()) != 0 { + errMsg = fmt.Sprintf("stderr: %s", stderr.String()) + } + if len(stdout.String()) != 0 { + if len(errMsg) != 0 { + errMsg = fmt.Sprintf("%s; stdout: %s", errMsg, stdout.String()) + } else { + errMsg = fmt.Sprintf("stdout: %s", stdout.String()) + } + } + return errMsg, err + } + return stdout.String(), nil +} + +func ExecScript(scriptPath, workDir string) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) + defer cancel() + cmd := exec.Command("bash", scriptPath) + cmd.Dir = workDir + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + if ctx.Err() == context.DeadlineExceeded { + return "", fmt.Errorf("errCmdTimeout %v", err) + } + if err != nil { + errMsg := "" + if len(stderr.String()) != 0 { + errMsg = fmt.Sprintf("stderr: %s", stderr.String()) + } + if len(stdout.String()) != 0 { + if len(errMsg) != 0 { + errMsg = fmt.Sprintf("%s; stdout: %s", errMsg, stdout.String()) + } else { + errMsg = fmt.Sprintf("stdout: %s", stdout.String()) + } + } + return errMsg, err + } + return stdout.String(), nil +} + +func CheckIllegal(args ...string) bool { + if args == nil { + return false + } + for _, arg := range args { + if strings.Contains(arg, "&") || strings.Contains(arg, "|") || strings.Contains(arg, ";") || + strings.Contains(arg, "$") || strings.Contains(arg, "'") || strings.Contains(arg, "`") || + strings.Contains(arg, "(") || strings.Contains(arg, ")") || strings.Contains(arg, "\"") { + return true + } + } + return false +} + +func HasNoPasswordSudo() bool { + cmd2 := exec.Command("sudo", "-n", "ls") + err2 := cmd2.Run() + return err2 == nil +} + +func SudoHandleCmd() string { + cmd := exec.Command("sudo", "-n", "ls") + if err := cmd.Run(); err == nil { + return "sudo " + } + return "" +} + +func Which(name string) bool { + _, err := exec.LookPath(name) + return err == nil +} diff --git a/lib/core/conf/conf.go b/lib/core/conf/conf.go new file mode 100644 index 0000000..506c66a --- /dev/null +++ b/lib/core/conf/conf.go @@ -0,0 +1,52 @@ +package conf + +import ( + "fmt" + "time" + + "github.com/spf13/viper" +) + +// 配置文件读取 +func InitConfig(configFile string) { + // 设置配置文件路径 + viper.SetConfigFile(configFile) + + // 读取配置文件 + err := viper.ReadInConfig() + if err != nil { + fmt.Printf("读取配置文件失败: %v \n", err) + return + } + + // 记录程序开始运行的时间点 + viper.Set("runTime", time.Now()) +} + +// RunTime 程序开始运行的时间 +func RunTime() time.Time { + return viper.GetTime("runTime") +} + +// Get 获取配置信息 +// +// Get("framework.name") +func Get(key string) any { + return viper.Get(key) +} + +// IsAdmin 用户是否为管理员 +func IsAdmin(userID string) bool { + if userID == "" { + return false + } + // 从本地配置获取user信息 + // admins := Get("user.adminList").([]any) + admins := []string{"1", "2", "3"} + for _, s := range admins { + if s == userID { + return true + } + } + return false +} 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..cde0a71 --- /dev/null +++ b/lib/core/datasource/datasource.go @@ -0,0 +1,49 @@ +package datasource + +import ( + "database/sql" + "regexp" + + "ems.agt/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..71bc422 --- /dev/null +++ b/lib/core/datasource/repo.go @@ -0,0 +1,135 @@ +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 { + if num > 5000 { + num = 5000 + } + num = v + } + + // 显示记录数 + pageSizeStr := fmt.Sprintf("%v", pageSize) + size := 10 + if v, err := strconv.Atoi(pageSizeStr); err == nil && v > 0 { + if size < 0 { + size = 10 + } else if size > 1000 { + size = 1000 + } else { + 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/file/csv.go b/lib/core/file/csv.go new file mode 100644 index 0000000..1473a79 --- /dev/null +++ b/lib/core/file/csv.go @@ -0,0 +1,88 @@ +package file + +import ( + "encoding/csv" + "os" + "path/filepath" + "strings" + + "ems.agt/lib/log" +) + +// 写入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 { + log.Errorf("创建文件夹失败 CreateFile %v", err) + } + + // 创建或打开文件 + file, err := os.Create(filePath) + if err != nil { + return err + } + defer file.Close() + + // 创建CSV编写器 + writer := csv.NewWriter(file) + defer writer.Flush() + + // 写入数据 + for _, row := range data { + writer.Write(row) + } + return nil +} + +// 读取CSV文件,转换map数据 +func ReadCSVFile(filePath string) []map[string]string { + // 创建 map 存储 CSV 数据 + arr := make([]map[string]string, 0) + + // 打开 CSV 文件 + file, err := os.Open(filePath) + if err != nil { + log.Fatal("无法打开 CSV 文件:", err) + return arr + } + defer file.Close() + + // 创建 CSV Reader + reader := csv.NewReader(file) + + // 读取 CSV 头部行 + header, err := reader.Read() + if err != nil { + log.Fatal("无法读取 CSV 头部行:", 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/lib/core/file/ssh.go b/lib/core/file/ssh.go new file mode 100644 index 0000000..c33a717 --- /dev/null +++ b/lib/core/file/ssh.go @@ -0,0 +1,49 @@ +package file + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + + "ems.agt/lib/core/conf" + "ems.agt/lib/log" +) + +// 网元NE 文件复制到远程文件 +func FileSCPLocalToNe(neIp, localPath, nePath string) error { + usernameNe := conf.Get("ne.user").(string) + // scp /path/to/local/file.txt user@remote-server:/path/to/remote/directory/ + neDir := fmt.Sprintf("%s@%s:%s", usernameNe, neIp, nePath) + cmd := exec.Command("scp", "-r", localPath, neDir) + out, err := cmd.CombinedOutput() + if err != nil { + return err + } + log.Infof("FileSCPLocalToNe %s", string(out)) + return nil +} + +// 网元NE 远程文件复制到本地文件 +func FileSCPNeToLocal(neIp, nePath, localPath string) error { + // 获取文件所在的目录路径 + dirPath := filepath.Dir(localPath) + + // 确保文件夹路径存在 + err := os.MkdirAll(dirPath, os.ModePerm) + if err != nil { + log.Errorf("创建文件夹失败 CreateFile %v", err) + return err + } + + usernameNe := conf.Get("ne.user").(string) + // scp user@remote-server:/path/to/remote/directory/ /path/to/local/file.txt + neDir := fmt.Sprintf("%s@%s:%s", usernameNe, neIp, nePath) + cmd := exec.Command("scp", "-r", neDir, localPath) + out, err := cmd.CombinedOutput() + if err != nil { + return err + } + log.Infof("FileSCPNeToLocal %s", string(out)) + return nil +} diff --git a/lib/core/file/txt.go b/lib/core/file/txt.go new file mode 100644 index 0000000..1690e3e --- /dev/null +++ b/lib/core/file/txt.go @@ -0,0 +1,79 @@ +package file + +import ( + "bufio" + "fmt" + "os" + "path/filepath" + "strings" + + "ems.agt/lib/log" +) + +// 写入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 { + log.Errorf("创建文件夹失败 CreateFile %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 { + log.Errorf("刷新缓冲区时发生错误:", 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 { + log.Fatal("无法打开文件:", err) + return arr + } + defer file.Close() + + // 创建一个 Scanner 对象,用于逐行读取文件内容 + scanner := bufio.NewScanner(file) + if scanner.Err() != nil { + log.Fatal("读取文件时出错:", scanner.Err()) + return arr + } + + for scanner.Scan() { + line := scanner.Text() + fields := strings.Split(line, ",") + arr = append(arr, fields) + } + + return arr +} diff --git a/lib/core/mml_client/mml_client.go b/lib/core/mml_client/mml_client.go new file mode 100644 index 0000000..5903123 --- /dev/null +++ b/lib/core/mml_client/mml_client.go @@ -0,0 +1,88 @@ +package mmlclient + +import ( + "bufio" + "fmt" + "io" + "net" + "time" + + "ems.agt/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) + + // 发送后等待 + sleepTime := conf.Get("mml.sleep").(int) + awaitTime := time.Duration(sleepTime) * time.Millisecond + time.Sleep(awaitTime) + + // 读取内容 + buf := make([]byte, 1024*8) + 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*8) + n, err := c.reader.Read(buf) + if err != nil { + if err == io.EOF { + return "", fmt.Errorf("server closed the connection") + } + return "", err + } + + 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..7e22579 --- /dev/null +++ b/lib/core/mml_client/send.go @@ -0,0 +1,104 @@ +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("创建MMLClient实例失败:%v", err) + } + defer client.Close() + + // 发送数据 + err = client.Send(msg) + if err != nil { + return "", fmt.Errorf("发送数据失败:%v", err) + } + + // 接收数据 + data, err := client.Receive() + if err != nil { + return "", fmt.Errorf("接收数据失败:%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 + } + + // 截断 + index := strings.Index(str, "\n") + if index != -1 { + str = str[:index] + str = strings.ToLower(str) + } + + // 命令成功 + if strings.Contains(str, "ok") || strings.Contains(str, "OK") { + 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.Split(item, "=") + } else if strings.Contains(item, ":") { + // 按照分隔符":"进行拆分键值对 + pair = strings.Split(item, ":") + } + + if len(pair) == 2 { + // 将键值对存入map中 + m[pair[0]] = pair[1] + } + } + return m, err +} diff --git a/lib/core/redis/redis.go b/lib/core/redis/redis.go new file mode 100644 index 0000000..d459778 --- /dev/null +++ b/lib/core/redis/redis.go @@ -0,0 +1,358 @@ +package redis + +import ( + "context" + "fmt" + "strings" + "time" + + "ems.agt/lib/core/conf" + "ems.agt/lib/log" + "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 := conf.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 { + log.Fatalf("failed error ping redis %s %d is %v", client["host"], client["db"], err) + continue + } + log.Infof("redis %s %d %s connection is successful.", client["host"], client["db"], pong) + rdbMap[k] = rdb + } +} + +// 关闭Redis实例 +func Close() { + for _, rdb := range rdbMap { + if err := rdb.Close(); err != nil { + log.Errorf("fatal error db close: %s", err) + } + } +} + +// 获取默认实例 +func DefaultRDB() *redis.Client { + source := conf.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 { + log.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 { + log.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 { + log.Errorf("redis lua script 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 { + log.Errorf("redis lua script 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 { + log.Errorf("redis lua script 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 { + log.Errorf("redis lua script 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 { + log.Errorf("redis lua script err %v", err) + return 0, err + } + return result.(int64), err +} diff --git a/lib/core/utils/crypto/crypto.go b/lib/core/utils/crypto/crypto.go new file mode 100644 index 0000000..c242f7f --- /dev/null +++ b/lib/core/utils/crypto/crypto.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/lib/core/utils/ctx/ctx.go b/lib/core/utils/ctx/ctx.go new file mode 100644 index 0000000..f800d7a --- /dev/null +++ b/lib/core/utils/ctx/ctx.go @@ -0,0 +1,133 @@ +package ctx + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "os" + "path/filepath" + + "ems.agt/lib/core/vo" + "github.com/gorilla/mux" +) + +// 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) +} + +// 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 +} + +// 读取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 +} + +/// ==== 登录用户信息, 通过中间件后预置入 + +// 定义自定义类型作为键 +type ContextKey string + +// LoginUser 登录用户信息需要Authorize中间件 +func LoginUser(r *http.Request) (vo.LoginUser, error) { + // 上下文 + v := r.Context().Value(ContextKey("LoginUser")) + if v != nil { + return v.(vo.LoginUser), nil + } + return vo.LoginUser{}, fmt.Errorf("无用户信息") +} + +// 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..e0893fb --- /dev/null +++ b/lib/core/utils/date/date.go @@ -0,0 +1,70 @@ +package date + +import ( + "fmt" + "time" + + "ems.agt/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/firewall/client.go b/lib/core/utils/firewall/client.go new file mode 100644 index 0000000..5e88704 --- /dev/null +++ b/lib/core/utils/firewall/client.go @@ -0,0 +1,34 @@ +package firewall + +import ( + "errors" + "os" + + "ems.agt/lib/core/utils/firewall/client" +) + +type FirewallClient interface { + Name() string // ufw firewalld + Start() error + Stop() error + Reload() error + Status() (string, error) // running not running + Version() (string, error) + + ListPort() ([]client.FireInfo, error) + ListAddress() ([]client.FireInfo, error) + + Port(port client.FireInfo, operation string) error + RichRules(rule client.FireInfo, operation string) error + PortForward(info client.Forward, operation string) error +} + +func NewFirewallClient() (FirewallClient, error) { + if _, err := os.Stat("/usr/sbin/firewalld"); err == nil { + return client.NewFirewalld() + } + if _, err := os.Stat("/usr/sbin/ufw"); err == nil { + return client.NewUfw() + } + return nil, errors.New("no such type") +} diff --git a/lib/core/utils/firewall/client/firewalld.go b/lib/core/utils/firewall/client/firewalld.go new file mode 100644 index 0000000..d026e4e --- /dev/null +++ b/lib/core/utils/firewall/client/firewalld.go @@ -0,0 +1,209 @@ +package client + +import ( + "fmt" + "strings" + "sync" + + "ems.agt/lib/core/cmd" +) + +type Firewall struct{} + +func NewFirewalld() (*Firewall, error) { + return &Firewall{}, nil +} + +func (f *Firewall) Name() string { + return "firewalld" +} + +func (f *Firewall) Status() (string, error) { + stdout, _ := cmd.Exec("firewall-cmd --state") + if stdout == "running\n" { + return "running", nil + } + return "not running", nil +} + +func (f *Firewall) Version() (string, error) { + stdout, err := cmd.Exec("firewall-cmd --version") + if err != nil { + return "", fmt.Errorf("load the firewall version failed, err: %s", stdout) + } + return strings.ReplaceAll(stdout, "\n ", ""), nil +} + +func (f *Firewall) Start() error { + stdout, err := cmd.Exec("systemctl start firewalld") + if err != nil { + return fmt.Errorf("enable the firewall failed, err: %s", stdout) + } + return nil +} + +func (f *Firewall) Stop() error { + stdout, err := cmd.Exec("systemctl stop firewalld") + if err != nil { + return fmt.Errorf("stop the firewall failed, err: %s", stdout) + } + return nil +} + +func (f *Firewall) Reload() error { + stdout, err := cmd.Exec("firewall-cmd --reload") + if err != nil { + return fmt.Errorf("reload firewall failed, err: %s", stdout) + } + return nil +} + +func (f *Firewall) ListPort() ([]FireInfo, error) { + var wg sync.WaitGroup + var datas []FireInfo + wg.Add(2) + go func() { + defer wg.Done() + stdout, err := cmd.Exec("firewall-cmd --zone=public --list-ports") + if err != nil { + return + } + ports := strings.Split(strings.ReplaceAll(stdout, "\n", ""), " ") + for _, port := range ports { + if len(port) == 0 { + continue + } + var itemPort FireInfo + if strings.Contains(port, "/") { + itemPort.Port = strings.Split(port, "/")[0] + itemPort.Protocol = strings.Split(port, "/")[1] + } + itemPort.Strategy = "accept" + datas = append(datas, itemPort) + } + }() + + go func() { + defer wg.Done() + stdout1, err := cmd.Exec("firewall-cmd --zone=public --list-rich-rules") + if err != nil { + return + } + rules := strings.Split(stdout1, "\n") + for _, rule := range rules { + if len(rule) == 0 { + continue + } + itemRule := f.loadInfo(rule) + if len(itemRule.Port) != 0 && itemRule.Family == "ipv4" { + datas = append(datas, itemRule) + } + } + }() + wg.Wait() + return datas, nil +} + +func (f *Firewall) ListAddress() ([]FireInfo, error) { + stdout, err := cmd.Exec("firewall-cmd --zone=public --list-rich-rules") + if err != nil { + return nil, err + } + var datas []FireInfo + rules := strings.Split(stdout, "\n") + for _, rule := range rules { + if len(rule) == 0 { + continue + } + itemRule := f.loadInfo(rule) + if len(itemRule.Port) == 0 && len(itemRule.Address) != 0 { + datas = append(datas, itemRule) + } + } + return datas, nil +} + +func (f *Firewall) Port(port FireInfo, operation string) error { + if cmd.CheckIllegal(operation, port.Protocol, port.Port) { + return fmt.Errorf("errCmdIllegal %v", port) + } + + stdout, err := cmd.Execf("firewall-cmd --zone=public --%s-port=%s/%s --permanent", operation, port.Port, port.Protocol) + if err != nil { + return fmt.Errorf("%s port failed, err: %s", operation, stdout) + } + return nil +} + +func (f *Firewall) RichRules(rule FireInfo, operation string) error { + if cmd.CheckIllegal(operation, rule.Address, rule.Protocol, rule.Port, rule.Strategy) { + return fmt.Errorf("errCmdIllegal %v", rule) + } + ruleStr := "" + if strings.Contains(rule.Address, "-") { + std, err := cmd.Execf("firewall-cmd --permanent --new-ipset=%s --type=hash:ip", rule.Address) + if err != nil { + return fmt.Errorf("add new ipset failed, err: %s", std) + } + std2, err := cmd.Execf("firewall-cmd --permanent --ipset=%s --add-entry=%s", rule.Address, rule.Address) + if err != nil { + return fmt.Errorf("add entry to ipset failed, err: %s", std2) + } + if err := f.Reload(); err != nil { + return err + } + ruleStr = fmt.Sprintf("rule source ipset=%s %s", rule.Address, rule.Strategy) + } else { + ruleStr = "rule family=ipv4 " + if len(rule.Address) != 0 { + ruleStr += fmt.Sprintf("source address=%s ", rule.Address) + } + if len(rule.Port) != 0 { + ruleStr += fmt.Sprintf("port port=%s ", rule.Port) + } + if len(rule.Protocol) != 0 { + ruleStr += fmt.Sprintf("protocol=%s ", rule.Protocol) + } + ruleStr += rule.Strategy + } + stdout, err := cmd.Execf("firewall-cmd --zone=public --%s-rich-rule '%s' --permanent", operation, ruleStr) + if err != nil { + return fmt.Errorf("%s rich rules failed, err: %s", operation, stdout) + } + return nil +} + +func (f *Firewall) PortForward(info Forward, operation string) error { + ruleStr := fmt.Sprintf("firewall-cmd --%s-forward-port=port=%s:proto=%s:toport=%s --permanent", operation, info.Port, info.Protocol, info.Target) + if len(info.Address) != 0 { + ruleStr = fmt.Sprintf("firewall-cmd --%s-forward-port=port=%s:proto=%s:toaddr=%s:toport=%s --permanent", operation, info.Port, info.Protocol, info.Address, info.Target) + } + + stdout, err := cmd.Exec(ruleStr) + if err != nil { + return fmt.Errorf("%s port forward failed, err: %s", operation, stdout) + } + return nil +} + +func (f *Firewall) loadInfo(line string) FireInfo { + var itemRule FireInfo + ruleInfo := strings.Split(strings.ReplaceAll(line, "\"", ""), " ") + for _, item := range ruleInfo { + switch { + case strings.Contains(item, "family="): + itemRule.Family = strings.ReplaceAll(item, "family=", "") + case strings.Contains(item, "ipset="): + itemRule.Address = strings.ReplaceAll(item, "ipset=", "") + case strings.Contains(item, "address="): + itemRule.Address = strings.ReplaceAll(item, "address=", "") + case strings.Contains(item, "port="): + itemRule.Port = strings.ReplaceAll(item, "port=", "") + case strings.Contains(item, "protocol="): + itemRule.Protocol = strings.ReplaceAll(item, "protocol=", "") + case item == "accept" || item == "drop" || item == "reject": + itemRule.Strategy = item + } + } + return itemRule +} diff --git a/lib/core/utils/firewall/client/info.go b/lib/core/utils/firewall/client/info.go new file mode 100644 index 0000000..86cc86e --- /dev/null +++ b/lib/core/utils/firewall/client/info.go @@ -0,0 +1,20 @@ +package client + +type FireInfo struct { + Family string `json:"family"` // ipv4 ipv6 + Address string `json:"address"` // Anywhere + Port string `json:"port"` + Protocol string `json:"protocol"` // tcp udp tcp/udp + Strategy string `json:"strategy"` // accept drop + + APPName string `json:"appName"` + IsUsed bool `json:"isUsed"` + Description string `json:"description"` +} + +type Forward struct { + Protocol string `json:"protocol"` + Address string `json:"address"` + Port string `json:"port"` + Target string `json:"target"` +} diff --git a/lib/core/utils/firewall/client/ufw.go b/lib/core/utils/firewall/client/ufw.go new file mode 100644 index 0000000..4729488 --- /dev/null +++ b/lib/core/utils/firewall/client/ufw.go @@ -0,0 +1,238 @@ +package client + +import ( + "fmt" + "strings" + + "ems.agt/lib/core/cmd" +) + +type Ufw struct { + CmdStr string +} + +func NewUfw() (*Ufw, error) { + var ufw Ufw + if cmd.HasNoPasswordSudo() { + ufw.CmdStr = "sudo ufw" + } else { + ufw.CmdStr = "ufw" + } + return &ufw, nil +} + +func (f *Ufw) Name() string { + return "ufw" +} + +func (f *Ufw) Status() (string, error) { + stdout, _ := cmd.Execf("%s status | grep Status", f.CmdStr) + if stdout == "Status: active\n" { + return "running", nil + } + stdout1, _ := cmd.Execf("%s status | grep 状态", f.CmdStr) + if stdout1 == "状态: 激活\n" { + return "running", nil + } + return "not running", nil +} + +func (f *Ufw) Version() (string, error) { + stdout, err := cmd.Execf("%s version | grep ufw", f.CmdStr) + if err != nil { + return "", fmt.Errorf("load the firewall status failed, err: %s", stdout) + } + info := strings.ReplaceAll(stdout, "\n", "") + return strings.ReplaceAll(info, "ufw ", ""), nil +} + +func (f *Ufw) Start() error { + stdout, err := cmd.Execf("echo y | %s enable", f.CmdStr) + if err != nil { + return fmt.Errorf("enable the firewall failed, err: %s", stdout) + } + return nil +} + +func (f *Ufw) Stop() error { + stdout, err := cmd.Execf("%s disable", f.CmdStr) + if err != nil { + return fmt.Errorf("stop the firewall failed, err: %s", stdout) + } + return nil +} + +func (f *Ufw) Reload() error { + return nil +} + +func (f *Ufw) ListPort() ([]FireInfo, error) { + stdout, err := cmd.Execf("%s status verbose", f.CmdStr) + if err != nil { + return nil, err + } + portInfos := strings.Split(stdout, "\n") + var datas []FireInfo + isStart := false + for _, line := range portInfos { + if strings.HasPrefix(line, "-") { + isStart = true + continue + } + if !isStart { + continue + } + itemFire := f.loadInfo(line, "port") + if len(itemFire.Port) != 0 && itemFire.Port != "Anywhere" && !strings.Contains(itemFire.Port, ".") { + itemFire.Port = strings.ReplaceAll(itemFire.Port, ":", "-") + datas = append(datas, itemFire) + } + } + return datas, nil +} + +func (f *Ufw) ListAddress() ([]FireInfo, error) { + stdout, err := cmd.Execf("%s status verbose", f.CmdStr) + if err != nil { + return nil, err + } + portInfos := strings.Split(stdout, "\n") + var datas []FireInfo + isStart := false + for _, line := range portInfos { + if strings.HasPrefix(line, "-") { + isStart = true + continue + } + if !isStart { + continue + } + if !strings.Contains(line, " IN") { + continue + } + itemFire := f.loadInfo(line, "address") + if strings.Contains(itemFire.Port, ".") { + itemFire.Address += ("-" + itemFire.Port) + itemFire.Port = "" + } + if len(itemFire.Port) == 0 && len(itemFire.Address) != 0 { + datas = append(datas, itemFire) + } + } + return datas, nil +} + +func (f *Ufw) Port(port FireInfo, operation string) error { + switch port.Strategy { + case "accept": + port.Strategy = "allow" + case "drop": + port.Strategy = "deny" + default: + return fmt.Errorf("unsupport strategy %s", port.Strategy) + } + if cmd.CheckIllegal(port.Protocol, port.Port) { + return fmt.Errorf("errCmdIllegal %v", port) + } + + command := fmt.Sprintf("%s %s %s", f.CmdStr, port.Strategy, port.Port) + if operation == "remove" { + command = fmt.Sprintf("%s delete %s %s", f.CmdStr, port.Strategy, port.Port) + } + if len(port.Protocol) != 0 { + command += fmt.Sprintf("/%s", port.Protocol) + } + stdout, err := cmd.Exec(command) + if err != nil { + return fmt.Errorf("%s port failed, err: %s", operation, stdout) + } + return nil +} + +func (f *Ufw) RichRules(rule FireInfo, operation string) error { + switch rule.Strategy { + case "accept": + rule.Strategy = "allow" + case "drop": + rule.Strategy = "deny" + default: + return fmt.Errorf("unsupport strategy %s", rule.Strategy) + } + + if cmd.CheckIllegal(operation, rule.Protocol, rule.Address, rule.Port) { + return fmt.Errorf("errCmdIllegal %v", rule) + } + + ruleStr := fmt.Sprintf("%s %s ", f.CmdStr, rule.Strategy) + if operation == "remove" { + ruleStr = fmt.Sprintf("%s delete %s ", f.CmdStr, rule.Strategy) + } + if len(rule.Protocol) != 0 { + ruleStr += fmt.Sprintf("proto %s ", rule.Protocol) + } + if strings.Contains(rule.Address, "-") { + ruleStr += fmt.Sprintf("from %s to %s ", strings.Split(rule.Address, "-")[0], strings.Split(rule.Address, "-")[1]) + } else { + ruleStr += fmt.Sprintf("from %s ", rule.Address) + } + if len(rule.Port) != 0 { + ruleStr += fmt.Sprintf("to any port %s ", rule.Port) + } + + stdout, err := cmd.Exec(ruleStr) + if err != nil { + return fmt.Errorf("%s rich rules failed, err: %s", operation, stdout) + } + return nil +} + +func (f *Ufw) PortForward(info Forward, operation string) error { + ruleStr := fmt.Sprintf("firewall-cmd --%s-forward-port=port=%s:proto=%s:toport=%s --permanent", operation, info.Port, info.Protocol, info.Target) + if len(info.Address) != 0 { + ruleStr = fmt.Sprintf("firewall-cmd --%s-forward-port=port=%s:proto=%s:toaddr=%s:toport=%s --permanent", operation, info.Port, info.Protocol, info.Address, info.Target) + } + + stdout, err := cmd.Exec(ruleStr) + if err != nil { + return fmt.Errorf("%s port forward failed, err: %s", operation, stdout) + } + if err := f.Reload(); err != nil { + return err + } + return nil +} + +func (f *Ufw) loadInfo(line string, fireType string) FireInfo { + fields := strings.Fields(line) + var itemInfo FireInfo + if len(fields) < 4 { + return itemInfo + } + if fields[1] == "(v6)" { + return itemInfo + } + if fields[0] == "Anywhere" && fireType != "port" { + itemInfo.Strategy = "drop" + if fields[1] == "ALLOW" { + itemInfo.Strategy = "accept" + } + itemInfo.Address = fields[3] + return itemInfo + } + if strings.Contains(fields[0], "/") { + itemInfo.Port = strings.Split(fields[0], "/")[0] + itemInfo.Protocol = strings.Split(fields[0], "/")[1] + } else { + itemInfo.Port = fields[0] + itemInfo.Protocol = "tcp/udp" + } + itemInfo.Family = "ipv4" + if fields[1] == "ALLOW" { + itemInfo.Strategy = "accept" + } else { + itemInfo.Strategy = "drop" + } + itemInfo.Address = fields[3] + + return itemInfo +} 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..4a36331 --- /dev/null +++ b/lib/core/vo/login_user.go @@ -0,0 +1,28 @@ +package vo + +import ( + "ems.agt/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..4af3187 --- /dev/null +++ b/lib/core/vo/treeselect.go @@ -0,0 +1,36 @@ +package vo + +// import sysmenu "ems.agt/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..58f4356 --- /dev/null +++ b/lib/dborm/dborm.go @@ -0,0 +1,1809 @@ +package dborm + +import ( + "database/sql" + "encoding/json" + "errors" + "fmt" + "strconv" + "time" + + "strings" + + "ems.agt/features/sys_role/model" + "ems.agt/lib/log" + "ems.agt/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=utf8&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=utf8&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=utf8&parseTime=true&loc=Local", + dbUser, dbPassword, dbHost, dbPort, dbName) + log.Debugf("dbType:%s Connect to:%s:******@tcp(%s:%s)/%s?charset=utf8&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 XormGetNeInfo(neType string, neId string) (*NeInfo, error) { + log.Debug("XormGetNeInfo processing... ") + + neInfo := new(NeInfo) + has, err := xEngine.Where("status='0' and 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 == false { + log.Infof("Not found ne_info from database, status='0', 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("status='0' and 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 == false { + log.Infof("Not found ne_info from database, status='0', 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").Where("status='0'").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.Debug("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("status='0' 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) + } + log.Debug("nes:", nes) + 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 == true { + 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).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("用户名或密码错误") + log.Error(err) + // 记录错误 + errCoutn := pwdErrCountAdd(user.AccountId, user.Profile, false) + if errCoutn > 3 { + return false, nil, errors.New("登录失败次数过多,请30分钟后重试") + } + 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 = "账户已禁用" + case "Locked": + errMsg = "账户已锁定" + 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 := "密码到期时间" + // 读取配置信息 + 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 := "用户账户到期" + 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"` + ObjectName string `json:"objectName" xorm:"object_name"` + 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.Debug("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.Errorf("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.Errorf("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"` + Capability int `json:"capability"` +} + +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"` + 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 +} 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/global.go b/lib/global/global.go new file mode 100644 index 0000000..29270bc --- /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..b36da02 --- /dev/null +++ b/lib/global/kits.go @@ -0,0 +1,677 @@ +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 +} + +// 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..7a6bbe8 --- /dev/null +++ b/lib/log/logger.go @@ -0,0 +1,337 @@ +// 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) { + + /* + 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()) +} + +// 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/midware/arrow_ip_addr.go b/lib/midware/arrow_ip_addr.go new file mode 100644 index 0000000..57f47c7 --- /dev/null +++ b/lib/midware/arrow_ip_addr.go @@ -0,0 +1,77 @@ +package midware + +import ( + "encoding/json" + "net/http" + "strings" + "time" + + "ems.agt/lib/dborm" + "ems.agt/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..12d9ca9 --- /dev/null +++ b/lib/midware/authorize.go @@ -0,0 +1,182 @@ +package midware + +import ( + "context" + "fmt" + "net/http" + + "ems.agt/lib/core/cache" + "ems.agt/lib/core/utils/ctx" + "ems.agt/lib/core/vo" + "ems.agt/lib/core/vo/result" + "ems.agt/lib/dborm" +) + +// 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) { + // 获取请求头标识信息 + accessToken := r.Header.Get("AccessToken") + if accessToken == "" { + ctx.JSON(w, 401, result.CodeMsg(401, "token error 无效身份授权")) + return + } + + // 验证令牌 == 这里直接查数据库session + if !dborm.XormExistValidToken(accessToken, 0) { + ctx.JSON(w, 401, result.CodeMsg(401, "valid error 无效身份授权")) + return + } + se, err := dborm.XormUpdateSessionShakeTime(accessToken) + if err != nil { + ctx.JSON(w, 401, result.CodeMsg(401, "shake error 无效身份授权")) + return + } + + // 获取缓存的用户信息 + data, ok := cache.GetLocalTTL(se.AccountId) + if data == nil || !ok { + ctx.JSON(w, 401, result.CodeMsg(401, "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("无权访问 %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("LoginUser"), 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..14cb958 --- /dev/null +++ b/lib/midware/midhandle.go @@ -0,0 +1,77 @@ +package midware + +import ( + "net/http" + "strings" + + "ems.agt/lib/log" + "ems.agt/lib/services" + "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("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/mmlp/parse.go b/lib/mmlp/parse.go new file mode 100644 index 0000000..d9a173a --- /dev/null +++ b/lib/mmlp/parse.go @@ -0,0 +1,1043 @@ +package mmlp + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "math" + "net/http" + "regexp" + "strconv" + "strings" + + "ems.agt/lib/dborm" + "ems.agt/lib/global" + "ems.agt/lib/log" + "ems.agt/lib/run" + "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"` + 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{"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{"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{"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{"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{"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) + errResult := mapResults["error"] + log.Trace("errResult:", errResult) + if len(errResult.(map[string]interface{})) > 0 { + errCode, _ := strconv.Atoi(fmt.Sprintf("%v", errResult.(map[string]interface{})["errorCode"])) + errorInfo := errResult.(map[string]interface{})["errorInfo"] + output = []byte(fmt.Sprintf(outputJson.ErrMsg, errCode, errorInfo)) + } + } + } + } + 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..4cabf08 --- /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" + + "ems.agt/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..d371e5b --- /dev/null +++ b/lib/routes/routes.go @@ -0,0 +1,395 @@ +package routes + +import ( + "net/http" + + // "log" + + "ems.agt/features/aaaa" + "ems.agt/features/cm" + "ems.agt/features/dbrest" + "ems.agt/features/file" + "ems.agt/features/fm" + "ems.agt/features/lm" + "ems.agt/features/mml" + "ems.agt/features/monitor/monitor" + "ems.agt/features/monitor/psnet" + "ems.agt/features/nbi" + "ems.agt/features/pm" + "ems.agt/features/security" + "ems.agt/features/state" + sysconfig "ems.agt/features/sys_config" + sysdictdata "ems.agt/features/sys_dict_data" + sysdicttype "ems.agt/features/sys_dict_type" + sysmenu "ems.agt/features/sys_menu" + sysrole "ems.agt/features/sys_role" + sysuser "ems.agt/features/sys_user" + "ems.agt/features/trace" + udmuser "ems.agt/features/udm_user" + "ems.agt/features/ue" + "ems.agt/lib/midware" + "ems.agt/lib/services" + + "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) + + // 数据库直连操作权限 + selectPermission := midware.Authorize(map[string][]string{ + "hasRoles": {"dba"}, + "hasPerms": {"db:select"}, + }) + updatePermission := midware.Authorize(map[string][]string{ + "hasRoles": {"dba"}, + "hasPerms": {"db:update"}, + }) + insertPermission := midware.Authorize(map[string][]string{ + "hasRoles": {"dba"}, + "hasPerms": {"db:insert"}, + }) + deletePermission := midware.Authorize(map[string][]string{ + "hasRoles": {"dba"}, + "hasPerms": {"db:delete"}, + }) + + // database management + Register("GET", dbrest.XormGetDataUri, dbrest.DatabaseGetData, selectPermission) + Register("GET", dbrest.XormSelectDataUri, dbrest.DatabaseGetData, selectPermission) + Register("POST", dbrest.XormInsertDataUri, dbrest.DatabaseInsertData, insertPermission) + Register("PUT", dbrest.XormUpdateDataUri, dbrest.DatabaseUpdateData, updatePermission) + Register("DELETE", dbrest.XormDeleteDataUri, dbrest.DatabaseDeleteData, deletePermission) + + Register("GET", dbrest.CustomXormGetDataUri, dbrest.DatabaseGetData, selectPermission) + Register("GET", dbrest.CustomXormSelectDataUri, dbrest.DatabaseGetData, selectPermission) + Register("POST", dbrest.CustomXormInsertDataUri, dbrest.DatabaseInsertData, insertPermission) + Register("PUT", dbrest.CustomXormUpdateDataUri, dbrest.DatabaseUpdateData, updatePermission) + Register("DELETE", dbrest.CustomXormDeleteDataUri, dbrest.DatabaseDeleteData, deletePermission) + + Register("GET", dbrest.XormCommonUri, dbrest.DatabaseGetData, selectPermission) + Register("POST", dbrest.XormCommonUri, dbrest.DatabaseInsertData, insertPermission) + Register("PUT", dbrest.XormCommonUri, dbrest.DatabaseUpdateData, updatePermission) + Register("DELETE", dbrest.XormCommonUri, dbrest.DatabaseDeleteData, deletePermission) + + Register("GET", dbrest.XormDatabaseUri, dbrest.TaskDatabaseGetData, selectPermission) + Register("POST", dbrest.XormDatabaseUri, dbrest.TaskDatabaseInsertData, insertPermission) + Register("PUT", dbrest.XormDatabaseUri, dbrest.TaskDatabaseUpdateData, updatePermission) + Register("DELETE", dbrest.XormDatabaseUri, dbrest.TaskDatabaseDeleteData, deletePermission) + + Register("GET", dbrest.CustomXormCommonUri, dbrest.DatabaseGetData, selectPermission) + Register("POST", dbrest.CustomXormCommonUri, dbrest.DatabaseInsertData, insertPermission) + Register("PUT", dbrest.CustomXormCommonUri, dbrest.DatabaseUpdateData, updatePermission) + Register("DELETE", dbrest.CustomXormCommonUri, dbrest.DatabaseDeleteData, deletePermission) + + Register("GET", dbrest.XormExtDataUri, dbrest.ExtDatabaseGetData, selectPermission) + Register("POST", dbrest.XormExtDataUri, dbrest.ExtDatabaseInsertData, insertPermission) + Register("PUT", dbrest.XormExtDataUri, dbrest.ExtDatabaseUpdateData, updatePermission) + Register("DELETE", dbrest.XormExtDataUri, dbrest.ExtDatabaseDeleteData, deletePermission) + + Register("GET", dbrest.CustomXormExtDataUri, dbrest.ExtDatabaseGetData, selectPermission) + Register("POST", dbrest.CustomXormExtDataUri, dbrest.ExtDatabaseInsertData, insertPermission) + Register("PUT", dbrest.CustomXormExtDataUri, dbrest.ExtDatabaseUpdateData, updatePermission) + Register("DELETE", dbrest.CustomXormExtDataUri, dbrest.ExtDatabaseDeleteData, deletePermission) + + // 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, nil) + Register("PUT", pm.MeasureTaskUri, pm.PutMeasureTaskToNF, nil) + Register("DELETE", pm.MeasureTaskUri, pm.DeleteMeasureTaskToNF, nil) + Register("PATCH", pm.MeasureTaskUri, pm.PatchMeasureTaskToNF, nil) + Register("POST", pm.MeasureReportUri, pm.PostMeasureReportFromNF, nil) + + 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, nil) + Register("PUT", cm.ParamConfigUri, cm.PutParamConfigToNF, nil) + Register("DELETE", cm.ParamConfigUri, cm.DeleteParamConfigToNF, nil) + + 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, nil) + Register("PUT", cm.UriNeInfo, cm.PutNeInfo, nil) + Register("DELETE", cm.UriNeInfo, cm.DeleteNeInfo, nil) + + // 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, nil) + Register("POST", mml.UriMMLDiscard, mml.PostMMLToNF, nil) + Register("POST", mml.UriOmMmlExt, mml.PostMMLToOMC, nil) + + Register("POST", mml.CustomUriMML, mml.PostMMLToNF, nil) + Register("POST", mml.CustomUriOmMmlExt, mml.PostMMLToOMC, nil) + // 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, nil) + + Register("GET", cm.UriNeCmFile, cm.DownloadNeBackupFile, nil) + Register("DELETE", cm.UriNeCmFile, cm.DeleteNeBackupFile, nil) + + 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, nil) + Register("DELETE", cm.UriSoftware, cm.DeleteSoftwareFile, nil) + + Register("POST", cm.UriSoftwareNE, cm.DistributeSoftwareToNF, nil) + Register("PUT", cm.UriSoftwareNE, cm.ActiveSoftwareToNF, nil) + Register("PATCH", cm.UriSoftwareNE, cm.RollBackSoftwareToNF, nil) + + 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("GET", cm.LicenseUri, cm.ExportCmFromNF, nil) + Register("POST", cm.LicenseUri, cm.ImportCmToNF, nil) + Register("DELETE", cm.LicenseUri, cm.ImportCmToNF, nil) + + Register("POST", cm.NeLicenseUri, cm.ExportCmFromNF, nil) + Register("PUT", cm.NeLicenseUri, cm.ImportCmToNF, nil) + Register("PATCH", cm.NeLicenseUri, cm.ImportCmToNF, nil) + + Register("POST", cm.CustomNeLicenseUri, cm.ExportCmFromNF, nil) + Register("PUT", cm.CustomNeLicenseUri, cm.ImportCmToNF, nil) + Register("PATCH", cm.CustomNeLicenseUri, cm.ImportCmToNF, nil) + + // Trace management + Register("POST", trace.UriTraceTask, trace.PostTraceTaskToNF, nil) + Register("PUT", trace.UriTraceTask, trace.PutTraceTaskToNF, nil) + Register("DELETE", trace.UriTraceTask, trace.DeleteTraceTaskToNF, nil) + 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.Authorize(nil)) + Register("POST", trace.CustomUriTcpdumpTask, trace.TcpdumpNeTask, midware.Authorize(nil)) + // 网元发送执行 抓包下载pcap文件 + Register("POST", trace.UriTcpdumpPcapDownload, trace.TcpdumpPcapDownload, midware.Authorize(nil)) + Register("POST", trace.CustomUriTcpdumpPcapDownload, trace.TcpdumpPcapDownload, midware.Authorize(nil)) + // 网元发送执行UPF pcap抓包 + Register("POST", trace.UriTcpdumpNeUPFTask, trace.TcpdumpNeUPFTask, nil) + Register("POST", trace.CustomUriTcpdumpNeUPFTask, trace.TcpdumpNeUPFTask, nil) + + // file management + Register("POST", file.UriFile, file.UploadFile, nil) + Register("GET", file.UriFile, file.DownloadFile, nil) + Register("DELETE", file.UriFile, file.DeleteFile, nil) + + 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 + Register("GET", ue.UriUEInfo, ue.GetUEInfoFromNF, nil) + Register("GET", ue.CustomUriUEInfo, ue.GetUEInfoFromNF, nil) + + // UEInfo + 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) + + // 进程网络 + 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..33d20d3 --- /dev/null +++ b/lib/run/exec_linux.go @@ -0,0 +1,56 @@ +//go:build linux +// +build linux + +package run + +import ( + "bytes" + "os/exec" + + "ems.agt/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..cf5bfd5 --- /dev/null +++ b/lib/run/exec_wasm.go @@ -0,0 +1,45 @@ +//go:build wasm +// +build wasm + +package run + +import ( + "os/exec" + + "ems.agt/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..c79d9a7 --- /dev/null +++ b/lib/run/exec_windows.go @@ -0,0 +1,45 @@ +//go:build windows +// +build windows + +package run + +import ( + "os/exec" + + "ems.agt/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..fef202d --- /dev/null +++ b/lib/services/file.go @@ -0,0 +1,423 @@ +package services + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" + "os" + "strconv" + "strings" + + "ems.agt/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, " 不支持的 Content-Type 类型", 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 == "" { + var limitError = "请求主体中非文件字段" + formName + "超出大小限制" + err = uploadSizeLimit(buf, part, ValuesMax, limitError) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + continue + } + + // 文件字段部分大小限制验证 + var limitError = "请求主体中文件字段" + 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 == "" { + http.Error(w, "文件"+fileName+"上传成功", http.StatusOK) + } else { + http.Error(w, "分片文件"+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 errors.New(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..44b3c6e --- /dev/null +++ b/lib/services/services.go @@ -0,0 +1,1000 @@ +package services + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "os" + "path/filepath" + + // "log" + "net/http" + "net/url" + "strconv" + "strings" + + "ems.agt/lib/dborm" + "ems.agt/lib/global" + "ems.agt/lib/log" + "ems.agt/lib/oauth" + "ems.agt/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 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 { + if strings.Contains(r.Header.Get("Content-Type"), "application/json") { + return true + } + return false +} + +func IsValidOAuthUri(r *http.Request) bool { + vars := mux.Vars(r) + apiVer := vars["apiVersion"] // 获取Uri + if apiVer != "v1" { + return false + } + + return true +} + +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 == false { + 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..1d18675 --- /dev/null +++ b/lib/session/session.go @@ -0,0 +1,169 @@ +package session + +import ( + "crypto/rand" + "encoding/base64" + "errors" + "io" + "net/http" + "strconv" + "strings" + "sync" + "time" + + "ems.agt/lib/log" + "ems.agt/lib/oauth" + "ems.agt/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 errors.New("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, errors.New("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..0a35778 --- /dev/null +++ b/lib/wsinfo/process_data.go @@ -0,0 +1,382 @@ +package wsinfo + +import ( + "encoding/json" + "fmt" + "sort" + "strings" + "sync" + "time" + + "ems.agt/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/restagent/config/config.go b/restagent/config/config.go new file mode 100644 index 0000000..77a543a --- /dev/null +++ b/restagent/config/config.go @@ -0,0 +1,344 @@ +package config + +import ( + "flag" + "fmt" + "os" + "strings" + + "ems.agt/lib/core/conf" + "ems.agt/lib/global" + "ems.agt/lib/log" + + "gopkg.in/yaml.v3" +) + +type DbConfig struct { + Type string `yaml:"type"` + User string `yaml:"user"` + Password string `yaml:"password"` + Host string `yaml:"host"` + Port string `yaml:"port"` + Name string `yaml:"name"` + Backup string `yaml:"backup"` +} + +// Yaml struct of config +type YamlConfig struct { + Logger struct { + File string `yaml:"file"` + Level string `yaml:"level"` + Duration int `yaml:"duration"` + Count int `yaml:"count"` + } `yaml:"logger"` + + Rest []struct { + IPv4 string `yaml:"ipv4"` + IPv6 string `yaml:"ipv6"` + Port uint16 `yaml:"port"` + Scheme string `yaml:"scheme"` + CaFile string `yaml:"caFile"` + CertFile string `yaml:"certFile"` + KeyFile string `yaml:"keyFile"` + } `yaml:"rest"` + + WebServer struct { + Enabled bool `yaml:"enabled"` + RootDir string `yaml:"rootDir"` + Listen []struct { + Addr string `yaml:"addr"` + Scheme string `yaml:"scheme"` + CaFile string `yaml:"caFile"` + CertFile string `yaml:"certFile"` + KeyFile string `yaml:"keyFile"` + } `yaml:"listen"` + } `yaml:"webServer"` + + Database DbConfig `yaml:"database"` + + OMC struct { + UriPrefix string `yaml:"uriPrefix"` + NeType string `yaml:"neType"` + NeId string `yaml:"neId"` + RmUID string `yaml:"rmUID"` + NeName string `yaml:"neName"` + Province string `yaml:"province"` + Vendor string `yaml:"vendor"` + Dn string `yaml:"dn"` + Chk2Ne bool `yaml:"chk2ne"` + Sn string `yaml:"sn"` + CheckSign bool `yaml:"checksign"` + Backup string `yaml:"backup"` + Upload string `yaml:"upload"` + FrontUpload string `yaml:"frontUpload"` + FrontTraceDir string `yaml:"frontTraceDir"` + Software string `yaml:"software"` + License string `yaml:"license"` + GtpUri string `yaml:"gtpUri"` + CheckContentType bool `yaml:"checkContentType"` + TestMode bool `yaml:"testMode"` + RBACMode bool `yaml:"rbacMode"` + RunDir string `yaml:"runDir"` + } `yaml:"omc"` + + Alarm struct { + ForwardAlarm bool `yaml:"forwardAlarm"` + Email struct { + Smtp string `yaml:"smtp"` + Port uint16 `yaml:"port"` + User string `yaml:"user"` + Password string `yaml:"password"` + } `json:"email"` + SMS struct { + ApiURL string `yaml:"apiURL"` + AccessKeyID string `yaml:"AccessKeyID"` + AccessKeySecret string `yaml:"accessKeySecret"` + SignName string `yaml:"signName"` + TemplateCode string `yaml:"templateCode"` + } `json:"sms"` + } `yaml:"alarm"` + + MML struct { + Port int `yaml:"port"` + Sleep int64 `yaml:"sleep"` + User string `yaml:"user"` + Password string `ymal:"password"` + MmlHome string `yaml:"mmlHome"` + Upload string `yaml:"upload"` + } `yaml:"mml"` + + NE struct { + Addr string `yaml:"addr"` + Port uint16 `yaml:"port"` + User string `yaml:"user"` + EtcDir string `yaml:"etcdir"` + BinDir string `yaml:"bindir"` + OmcDir string `yaml:"omcdir"` + ScpDir string `yaml:"scpdir"` + LicenseDir string `yaml:"licensedir"` + } `yaml:"ne"` + + Auth struct { + Crypt string `yaml:"crypt"` + Token bool `yaml:"token"` + Expires uint32 `yaml:"expires"` + Session string `yaml:"session"` + PublicKey string `yaml:"publicKey"` + PrivateKey string `yaml:"privateKey"` + } `yaml:"auth"` + + Params struct { + RmUIDMaxNum int `yaml:"rmuidmaxnum"` + AlarmIDMaxNum int `yaml:"alarmidmaxnum"` + PmIDMaxNum int `yaml:"pmidmaxnum"` + SubIDMaxNum int `yaml:"subidmaxnum"` + UriMaxLen int `yaml:"urimaxlen"` + RmUIDRegexp string `yaml:"rmuidregexp"` + } `yaml:"params"` + + TestConfig struct { + Enabled bool `yaml:"enabled"` + File string `yaml:"file"` + } `yaml:"testConfig"` +} + +type TestDatas struct { + UDM struct { + CapUsed uint32 `yaml:"capUsed"` + FeatureEnabled []string `yaml:"featureEnabled"` + } `yaml:"udm"` + AUSF struct { + CapUsed uint32 `yaml:"capUsed"` + FeatureEnabled []string `yaml:"featureEnabled"` + } `yaml:"ausf"` + AMF struct { + CapUsed uint32 `yaml:"capUsed"` + FeatureEnabled []string `yaml:"featureEnabled"` + } `yaml:"amf"` + SMF struct { + CapUsed uint32 `yaml:"capUsed"` + FeatureEnabled []string `yaml:"featureEnabled"` + } `yaml:"smf"` + UPF struct { + CapUsed uint32 `yaml:"capUsed"` + FeatureEnabled []string `yaml:"featureEnabled"` + } `yaml:"upf"` +} + +type NeTestData struct { + CapUsed uint32 `yaml:"capUsed"` + FeatureEnabled []string `yaml:"featureEnabled"` +} +type TestDataMap struct { + NeTestDatas []map[string]NeTestData +} + +var yamlConfig YamlConfig + +func ReadConfig(configFile string) { + yamlFile, err := os.ReadFile(configFile) + if err != nil { + fmt.Println("Read yaml config file error:", err) + os.Exit(2) + } + // fmt.Println("yamlfile:", string(yamlFile)) + + err = yaml.Unmarshal(yamlFile, &yamlConfig) + if err != nil { + fmt.Println("Unmarshal error:", err) + os.Exit(3) + } +} + +func WriteYamlConfig(newConfigData YamlConfig, configFile string) { + // 将配置转换回YAML数据 + newYamlData, err := yaml.Marshal(&newConfigData) + if err != nil { + log.Errorf("Failed to marshal YAML: %v", err) + } + + // 将新的YAML数据写入文件 + err = os.WriteFile(configFile, newYamlData, 0644) + if err != nil { + log.Errorf("Failed to write YAML file: %v", err) + } +} + +var mapYaml map[string]interface{} + +func ReadParamConfig(fileName string) *map[string]interface{} { + file, err := os.ReadFile(fileName) + if err != nil { + fmt.Println("Read yaml file error:", err) + } + + mapYaml = make(map[string]interface{}) + + err = yaml.Unmarshal(file, &mapYaml) + if err != nil { + fmt.Printf("yaml.Unmarshal: %v when to struct", err) + } + // fmt.Println("mapYaml:", mapYaml) + + return &mapYaml +} + +func GetYamlConfig() *YamlConfig { + return &yamlConfig +} + +func GetAuthFromConfig() interface{} { + return yamlConfig.Auth +} + +func GetExpiresFromConfig() uint32 { + return yamlConfig.Auth.Expires +} + +func GetRmUIDFromConfig() string { + return yamlConfig.OMC.RmUID +} + +func GetRmUIDRegexpFromConfig() string { + return yamlConfig.Params.RmUIDRegexp +} + +func GetRmUIDMaxNumFromConfig() int { + return yamlConfig.Params.RmUIDMaxNum +} + +func GetAlarmIDMaxNumFromConfig() int { + return yamlConfig.Params.AlarmIDMaxNum +} + +func GetPmIDMaxNumFromConfig() int { + return yamlConfig.Params.PmIDMaxNum +} + +func GetSubIDMaxNumFromConfig() int { + return yamlConfig.Params.SubIDMaxNum +} + +func GetUriMaxLenFromConfig() int { + return yamlConfig.Params.UriMaxLen +} + +func GetLogLevel() log.LogLevel { + var logLevel log.LogLevel + switch strings.ToLower(yamlConfig.Logger.Level) { + case "trace": + logLevel = log.LOG_TRACE + case "info": + logLevel = log.LOG_INFO + case "debug": + logLevel = log.LOG_DEBUG + case "warn": + logLevel = log.LOG_WARN + case "error": + logLevel = log.LOG_ERROR + case "fatal": + logLevel = log.LOG_FATAL + case "off": + logLevel = log.LOG_OFF + default: + logLevel = log.LOG_DEBUG + } + return logLevel +} + +var ( + DefaultUriPrefix string = "/api/rest" + UriPrefix string = "/api/rest" + //TestDataUDM []map[string]interface{} + TDatas map[string]NeTestData +) + +func ReadTestConfigYaml(pfile string) (ret error) { + file, err := os.ReadFile(pfile) + if err != nil { + return err + } + + err = yaml.Unmarshal(file, &TDatas) + if err != nil { + fmt.Println("Failed to Unmarshal:", err) + return err + } + + return nil +} + +func GetDefaultUserAgent() string { + return "OMC-restagent/" + global.Version +} + +const defaultConfigFile = "./etc/restconf.yaml" + +func init() { + cfile := flag.String("c", defaultConfigFile, "config file") + pv := flag.Bool("version", false, "print version") + ph := flag.Bool("help", false, "print help") + + global.BuildTime = "Wed May 31 18:24:04 CST 2023" + global.GoVer = "go version go1.15.7 linux/arm64" + flag.Parse() + if *pv { + fmt.Printf("OMC restagent version: %s\n%s\n%s\n\n", global.Version, global.BuildTime, global.GoVer) + os.Exit(0) + } + if *ph { + flag.Usage() + os.Exit(0) + } + + // 使用viper读取配置 + conf.InitConfig(*cfile) + + ReadConfig(*cfile) + if GetYamlConfig().OMC.UriPrefix != "" { + UriPrefix = GetYamlConfig().OMC.UriPrefix + } + if GetYamlConfig().TestConfig.Enabled { + ReadTestConfigYaml(GetYamlConfig().TestConfig.File) + } +} diff --git a/restagent/config/map.go b/restagent/config/map.go new file mode 100644 index 0000000..50a28c7 --- /dev/null +++ b/restagent/config/map.go @@ -0,0 +1,111 @@ +package config + +import ( + "io/ioutil" + "log" + + "gopkg.in/yaml.v3" + + "ems.agt/lib/global" +) + +type Uri2Object struct { + Uri string `yaml:"uri"` + Object []Object `yaml:"object"` +} + +type Object struct { + Name string `yaml:"name"` + Syntax string `yaml:"syntax"` + Oid string `yaml:"oid"` +} + +var uri2Object []Uri2Object + +func ReadMap(pfile string) (ret error) { + file, err := ioutil.ReadFile(pfile) + if err != nil { + log.Println(err) + return err + } + + err = yaml.Unmarshal(file, &uri2Object) + if err != nil { + log.Println(err) + return err + } + /* + for _, v := range uri2Object { + log.Println(v) + } + */ + return nil +} + +func GetOid(uri string, oids *[]string) *[]string { + for _, v := range uri2Object { + if uri == v.Uri { + for _, o := range v.Object { + *oids = append(*oids, o.Oid) + } + } + } + + return oids +} + +func GetOidByFileds(uri string, fields []string, oids *[]string) *[]string { + for _, v := range uri2Object { + if uri == v.Uri { + for _, o := range v.Object { + if global.IsContain(o.Name, fields) || len(fields) == 0 { + *oids = append(*oids, o.Oid) + } + } + } + } + + return oids +} + +type NameOid struct { + Name string + Oid string +} + +type NameValue struct { + Name string + Value string +} + +func GetDataOid(Uri string, nameOids *[]NameOid) *[]NameOid { + var nameOid NameOid + for _, v := range uri2Object { + if Uri == v.Uri { + for _, o := range v.Object { + nameOid.Name = o.Name + nameOid.Oid = o.Oid + *nameOids = append(*nameOids, nameOid) + } + } + } + + return nameOids +} + +func GetDataOidByFields(uri string, fields []string, nameOids *[]NameOid) *[]NameOid { + var nameOid NameOid + for _, v := range uri2Object { + if uri == v.Uri { + for _, o := range v.Object { + nameOid.Name = o.Name + nameOid.Oid = o.Oid + if len(fields) == 0 || global.IsContainP(nameOid.Name, &fields, len(fields)) { + *nameOids = append(*nameOids, nameOid) + } + } + } + } + + return nameOids +} diff --git a/restagent/etc/certs/ca_cert.pem b/restagent/etc/certs/ca_cert.pem new file mode 100644 index 0000000..828a843 --- /dev/null +++ b/restagent/etc/certs/ca_cert.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC4jCCAcqgAwIBAgICB+cwDQYJKoZIhvcNAQELBQAwEjEQMA4GA1UEAxMHUm9v +dCBDQTAeFw0yMzA4MTIxODA5MzZaFw0zMzA4MTIxODA5MzZaMBIxEDAOBgNVBAMT +B1Jvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCUTcFiT1GT +Zq9ROKTuWm6IfFthO6ysthEKevwkgJGDRCwF+K6rx3j7izTDfLkXum5OPbcd/yiE +B40Yrq9X2ckX260xG9PjDmGUaq8q5sza85Gg2hrW6wiLF9y8yYK3/v7716d0y6st +jt71pH554R98m0zHkbkmrFEagWR5cEoVM6MPZp2wdDnOFBBRiB1BrbHMpAFiKJ6s +oqm1yhUwCeeR/Hs09JF8KfOFhV4qAEVvE1cviHucCEvLBaG6xBzbKvYV2iOPu6u2 +o818wphqmSZYoj3/O4/EyVgvj1VdSqmkTSudiiIhLAa8/2JKrW1v7wFQjRkwNwdf +h6UR5liIUwk7AgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBRtEF/Jyw6Zz3v0K5uA6aMOr9LXRTANBgkqhkiG9w0BAQsF +AAOCAQEAErmFo6JjEoFZv8R2gwNUq7ln+YEh2hX4myLlNDfTpYeGnn2Ge70Kmb0o +dkSyanDd9tGImcbEOttWAYve3vetvN3g+GONbf5pV7ClIbVV5MsjN/aGwj/TQWhu +ttmfu9IV2b5HyFEM61eoHVrNoWiNpbNHTu5D/XF8sg5JkseTnJF06foykzAx28i6 +JcttunYn5SoWTIS9Ydu07X7uBcTeFBcKfH0xg4QlsNppiM5lIxBbp0WjQ8jyWw+b +Lyef4N6hmiaOf6P6qwaVZkEBhK6MVf1zadURuUSWAkhwGFjA7IbjbJ7OfYLXkYp3 +mIrXQzjxz6AfyOcY/FsgcaDLf5xbYw== +-----END CERTIFICATE----- diff --git a/restagent/etc/certs/omc_pri.key.aes_en b/restagent/etc/certs/omc_pri.key.aes_en new file mode 100644 index 0000000..cc22ab6 Binary files /dev/null and b/restagent/etc/certs/omc_pri.key.aes_en differ diff --git a/restagent/etc/certs/omc_pub.key b/restagent/etc/certs/omc_pub.key new file mode 100644 index 0000000..4f0922c --- /dev/null +++ b/restagent/etc/certs/omc_pub.key @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA10O52xwLqvwrrof3Ckso +QAXgQOMfIHB5HfrJYrd31tJlJbFw+bR1JHXIAdqh9Cz+H0ir8KfBXS/U2XgI5fbI +upkzg3cRv86nuksGtrKHzi9Q7lYYzGWVW9Tc/hcvdibG6XwLSNYemFN6JrOa5mZ7 +IabtZLuPRwFCBx2XC/dg45wxrGWIe7fbw2SpGId76erwSn/oKJtoy5Ash7LrOkp6 +lu8L0QQNfs9cRntjf4aQo99ppWvUF0H6OFBsGymwRfPRtWqLiwK824zrpXKMLFX3 +e4FjFO7WQNR1spOuL0bH+Ym1cEPYUn8LJN9OaiodjEXc1pM1E5MHWXGMhnhiE9q3 +jwIDAQAB +-----END PUBLIC KEY----- diff --git a/restagent/etc/certs/private_key.pem b/restagent/etc/certs/private_key.pem new file mode 100644 index 0000000..334940a --- /dev/null +++ b/restagent/etc/certs/private_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAlE3BYk9Rk2avUTik7lpuiHxbYTusrLYRCnr8JICRg0QsBfiu +q8d4+4s0w3y5F7puTj23Hf8ohAeNGK6vV9nJF9utMRvT4w5hlGqvKubM2vORoNoa +1usIixfcvMmCt/7++9endMurLY7e9aR+eeEffJtMx5G5JqxRGoFkeXBKFTOjD2ad +sHQ5zhQQUYgdQa2xzKQBYiierKKptcoVMAnnkfx7NPSRfCnzhYVeKgBFbxNXL4h7 +nAhLywWhusQc2yr2Fdojj7urtqPNfMKYapkmWKI9/zuPxMlYL49VXUqppE0rnYoi +ISwGvP9iSq1tb+8BUI0ZMDcHX4elEeZYiFMJOwIDAQABAoIBAFVJVhIsXVRwdBg4 +hBkS5ogVRBPp1obIeYpWadSwH36m4M5aUlE1eKzoRGK7wlIUA8V5FmroxysOkKUG +KI5UD6Jp0fLw9uyX46QPqkb4zgyIkFI5u7+nEJW1Flt3Y3Ze7dJ1FbdEPWAIzs3j +WFzNC2eEhCYs9pZ3+HhOuzJfUitYU6ts83tazSms+7E027e/v2ubtYOzluEoj4YK +MM+ocqJOlXUOhyTATDaLs4fM567bwwFeriODQqmMIhXLf3HiBPfPYhoAlwPUV51L +bAjIPwG2/jy0yXPenYOUqSMP1i+XkyTL4eVqOLLQUI+BsEaToS8M7Lf4KRQE0vQm +uAkEA9ECgYEAxCsKXtkYXrma8UziSJgQBj88TfACyuYZBPkpBFJhffPVOreYzqmn +gsE38oHlLhCrezsxvScivS/Cwt7kgBTE9r6xmH8y2f0P3PNmtazQCqovG1gsNZ1t +9PZQmxbVUfx/7KRJ1Q5zX3x5C0P2c6XYv/1h/SQQ8PYXDhRxWZFVSikCgYEAwYlt +AoP8j6yHDMk10vJd0fKT1lztcHcQLvH3HQn2kHci2GCxySvcac49hpXTDeNdUzcQ +I9UWo1MggNojME3WHUTiZD5DYUzuNUBTkc0kMHWy79/YNAzvF9312MSyJ0NlytBQ +ISh0z0sPSxDjW+XCi/8LGu1rrremBqvh0rNIrMMCgYEAt+C8VNHcZRZHpX3y8icP +hjuKFGgwxe3Pb/j5uKJb3ktMCUEFjFo8uXTSM5AMuhRIGTgQVIS1rG5zemSh/Wj6 +g2uWXyKEEQ+D6hGBqjP1wrlpdJE+x3btFdw5DFbn2HT9mF0bFAn6nXu4npWzEw5X +UQd67WT8OFIEpF8HLPTs5JkCgYBQlL6dmITU8Vm9mh1d+mnT23NwgnqeJATJ9xcT +sS1HE0Of4grEHw4Dw8pcOg+JLcyStE91C+kEEb1ryOdz2kS3JRI5+K6nWZeo9mKV +R1u1DZx2QbZMXcJDJriRC7y9mlNMsZVbaPPRx4fTknGHts9c6NEf4hC+y1pGVtSM +nPc6bwKBgQCrtb6srow9o0acH7mMmieDuqvT8DQ6rHWoizgneZTuvIo6SPO6nRnR +jfgfQkqr9IiLh9awSblQEN9fnddog7a1yS4cUZueiP5dmtFvp4B9Scnt+BoHpOLk +rrU2i7grAye4Cd003+pqhSERGCKSzb3fTSYiMypKsxeH2bs8nrRu2A== +-----END RSA PRIVATE KEY----- diff --git a/restagent/etc/certs/public_key.pem b/restagent/etc/certs/public_key.pem new file mode 100644 index 0000000..a85d9e4 --- /dev/null +++ b/restagent/etc/certs/public_key.pem @@ -0,0 +1,8 @@ +-----BEGIN RSA PUBLIC KEY----- +MIIBCgKCAQEAlE3BYk9Rk2avUTik7lpuiHxbYTusrLYRCnr8JICRg0QsBfiuq8d4 ++4s0w3y5F7puTj23Hf8ohAeNGK6vV9nJF9utMRvT4w5hlGqvKubM2vORoNoa1usI +ixfcvMmCt/7++9endMurLY7e9aR+eeEffJtMx5G5JqxRGoFkeXBKFTOjD2adsHQ5 +zhQQUYgdQa2xzKQBYiierKKptcoVMAnnkfx7NPSRfCnzhYVeKgBFbxNXL4h7nAhL +ywWhusQc2yr2Fdojj7urtqPNfMKYapkmWKI9/zuPxMlYL49VXUqppE0rnYoiISwG +vP9iSq1tb+8BUI0ZMDcHX4elEeZYiFMJOwIDAQAB +-----END RSA PUBLIC KEY----- diff --git a/restagent/etc/certs/rootca.crt b/restagent/etc/certs/rootca.crt new file mode 100644 index 0000000..2255c6b --- /dev/null +++ b/restagent/etc/certs/rootca.crt @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFKTCCAxGgAwIBAgIUIN48tRvspOjaIxbavyrZ/M7IvacwDQYJKoZIhvcNAQEL +BQAwHjELMAkGA1UEBhMCQ04xDzANBgNVBAMMBlJvb3RDQTAgFw0yMzA4MjIwMzQw +MDVaGA8yMTIzMDcyOTAzNDAwNVowHjELMAkGA1UEBhMCQ04xDzANBgNVBAMMBlJv +b3RDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALzyKbRc0hIDXsnI +Yo0DLjfGexwFmhgE6jFR6EWc4V2jVenkTrEahzpKYkhf2SrnEZuuZN/XU0JDAzUE +k8sNF6AUQOAQ6rwaHLB8ZZVEbl8F49F376RGAicg/AzIPytL1ebT1WR8YxoQgd6i ++B7H+6zcKDS8RREwpuVngNdVJ7/mUsxDFPVGpOsQQUsEdkZb/S7txRktHYBAXmiX +fZFC6aL/L9tXKJDcDiTQTyRuA08+uzFxgCF6/8XL8PD8+i8FzZLl0enadDy4ZiLA +pk+vrFmre+ETHHPf4lI1Ptz3fPNsvCS1HO73SJTgpGK0zpcvB32EqPuhPEKy33C0 +dsY+CQcuACvhEk1EQjsSXkuoaPCV/aCmBhS3AMMRuD+73rs9G2DbJQc2KTkyiqM6 +FO9MNGKUHDIEE9O8iBqagBZ7XXqwcf0zr+YdjT47RAM31uROKBECipc0BOSEHJ3w +sjMjLvqDHpP5NeoTnAIVt6djGQK8AWRKuICCtJ59llSmAMWjvsT4VD6ooVRln6ye +j6tLzlnmZvtxEmdOXL/Y7A/HnEb+uAtifsOvZUw/CQpdHRsrUdNUSOozTW/uXgp3 +5lHx9B6HWuxOGagxaaLhD8W5TsqjdSc2BqX23Up/p84RaLrwHjXaesTq7Y/Ckd8T +wIfUc2HtdtFt7YrBr3pkJeI/HjcNAgMBAAGjXTBbMB0GA1UdDgQWBBSr39jHWOmo +14F/m0G/Irn6oVwNTzAfBgNVHSMEGDAWgBSr39jHWOmo14F/m0G/Irn6oVwNTzAM +BgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAE+Br +fsui1ac9meqmgTPnwG91GhqITmVBKwLpwJ3vNHqU/6SOlsw31GZ689kxpOYsMh19 +PwYQ9OYQmz2LGbiVgf3m/mG9S4dnGQecqEAt0WBgYOOJ+tWwmtaDa1y1KEKJIGx2 +R4NYmlihSqobgl3f7/WWtwZM9BZCFAubh757gOiDFDgTukbJxfFVIRy8MVWg5Voz +KZK1toIVI617SignK65L4JbdEPgnD803vYsmn+Mk5PvKLVd1jB/M/Sqxd1W7gqr5 +nIT+WRJIs4SONQkxEP3e5aO2OQyify37a9P1IW8bPOtvZeQKnTSMIwVVyQV+Y6Az +4+a74ngLRAgk4ek9wycIHtv9ZzN0o6vwWBQ73DqyLLY2IVX/8LtrNO6LE9TpoMCa +tGVLZ1M5d1/eVYZC60y/lst/MRUyGgvB9tjiaNxo0Ni9quFifPAq/zx9AWo+7nIP +0RAahOvRZE5NM5LmgaVvMTXYflkfBXChDwL0gXa6rBt5y3/htZ8Cw1Tqi1v3Kv1s +hnD+zMD/Okrp76ZA/N2wib0p1Is1t+CFGdcmFOVEzkxXK37RreWay3cmdlTrzF5P +8kGivh2WApouy97rrhJo7R8rpUplD37Z5SiqtlOfBUaGIzpVapz19UTN5OTq10xo +yBSUGoQ/AGchVrHobdLPTgJNWU/L4QEPloEOGxg= +-----END CERTIFICATE----- diff --git a/restagent/etc/certs/rootca.pem b/restagent/etc/certs/rootca.pem new file mode 100644 index 0000000..2255c6b --- /dev/null +++ b/restagent/etc/certs/rootca.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFKTCCAxGgAwIBAgIUIN48tRvspOjaIxbavyrZ/M7IvacwDQYJKoZIhvcNAQEL +BQAwHjELMAkGA1UEBhMCQ04xDzANBgNVBAMMBlJvb3RDQTAgFw0yMzA4MjIwMzQw +MDVaGA8yMTIzMDcyOTAzNDAwNVowHjELMAkGA1UEBhMCQ04xDzANBgNVBAMMBlJv +b3RDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALzyKbRc0hIDXsnI +Yo0DLjfGexwFmhgE6jFR6EWc4V2jVenkTrEahzpKYkhf2SrnEZuuZN/XU0JDAzUE +k8sNF6AUQOAQ6rwaHLB8ZZVEbl8F49F376RGAicg/AzIPytL1ebT1WR8YxoQgd6i ++B7H+6zcKDS8RREwpuVngNdVJ7/mUsxDFPVGpOsQQUsEdkZb/S7txRktHYBAXmiX +fZFC6aL/L9tXKJDcDiTQTyRuA08+uzFxgCF6/8XL8PD8+i8FzZLl0enadDy4ZiLA +pk+vrFmre+ETHHPf4lI1Ptz3fPNsvCS1HO73SJTgpGK0zpcvB32EqPuhPEKy33C0 +dsY+CQcuACvhEk1EQjsSXkuoaPCV/aCmBhS3AMMRuD+73rs9G2DbJQc2KTkyiqM6 +FO9MNGKUHDIEE9O8iBqagBZ7XXqwcf0zr+YdjT47RAM31uROKBECipc0BOSEHJ3w +sjMjLvqDHpP5NeoTnAIVt6djGQK8AWRKuICCtJ59llSmAMWjvsT4VD6ooVRln6ye +j6tLzlnmZvtxEmdOXL/Y7A/HnEb+uAtifsOvZUw/CQpdHRsrUdNUSOozTW/uXgp3 +5lHx9B6HWuxOGagxaaLhD8W5TsqjdSc2BqX23Up/p84RaLrwHjXaesTq7Y/Ckd8T +wIfUc2HtdtFt7YrBr3pkJeI/HjcNAgMBAAGjXTBbMB0GA1UdDgQWBBSr39jHWOmo +14F/m0G/Irn6oVwNTzAfBgNVHSMEGDAWgBSr39jHWOmo14F/m0G/Irn6oVwNTzAM +BgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAE+Br +fsui1ac9meqmgTPnwG91GhqITmVBKwLpwJ3vNHqU/6SOlsw31GZ689kxpOYsMh19 +PwYQ9OYQmz2LGbiVgf3m/mG9S4dnGQecqEAt0WBgYOOJ+tWwmtaDa1y1KEKJIGx2 +R4NYmlihSqobgl3f7/WWtwZM9BZCFAubh757gOiDFDgTukbJxfFVIRy8MVWg5Voz +KZK1toIVI617SignK65L4JbdEPgnD803vYsmn+Mk5PvKLVd1jB/M/Sqxd1W7gqr5 +nIT+WRJIs4SONQkxEP3e5aO2OQyify37a9P1IW8bPOtvZeQKnTSMIwVVyQV+Y6Az +4+a74ngLRAgk4ek9wycIHtv9ZzN0o6vwWBQ73DqyLLY2IVX/8LtrNO6LE9TpoMCa +tGVLZ1M5d1/eVYZC60y/lst/MRUyGgvB9tjiaNxo0Ni9quFifPAq/zx9AWo+7nIP +0RAahOvRZE5NM5LmgaVvMTXYflkfBXChDwL0gXa6rBt5y3/htZ8Cw1Tqi1v3Kv1s +hnD+zMD/Okrp76ZA/N2wib0p1Is1t+CFGdcmFOVEzkxXK37RreWay3cmdlTrzF5P +8kGivh2WApouy97rrhJo7R8rpUplD37Z5SiqtlOfBUaGIzpVapz19UTN5OTq10xo +yBSUGoQ/AGchVrHobdLPTgJNWU/L4QEPloEOGxg= +-----END CERTIFICATE----- diff --git a/restagent/etc/certs/tsa-omc.crt b/restagent/etc/certs/tsa-omc.crt new file mode 100644 index 0000000..d119e0e --- /dev/null +++ b/restagent/etc/certs/tsa-omc.crt @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEYDCCAkigAwIBAgIUGZ67LDulO9kcRtwAwQGEC1oV5NUwDQYJKoZIhvcNAQEL +BQAwHjELMAkGA1UEBhMCQ04xDzANBgNVBAMMBlJvb3RDQTAeFw0yMzA4MjIwMzQw +MjVaFw0yMzA4MjMwMzQwMjVaMDwxCzAJBgNVBAYTAkNOMQ0wCwYDVQQKDARUZXN0 +MR4wHAYDVQQDDBVUaW1lc3RhbXAgQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQC1vnKPYHUYPQSvGmfgSxZQley/hvJZyY9dun3hYv1P +h5PydHqkwM6t6wS+f9WTm2RyCvfRJrwn356gKm3DoDePs7T2kIEwnljMFRT8HDEC +SujKz2MBBW/b9eU6Bz9sQ4XHmSTVE/mJF4DiMRxmflUPZ+Mis80XbpFO91tdhDHZ +f0RX5D9ihaS/WTnkOV+ISMxxK1PB/zITlxAk15irYsMM8dCq0iUy/B3+bcGH1YzL +aKqbFw+nrm6NTxYLo4q3ERtLl49l9UXXcoNMvB70fk6i4kXrn7+AEdb3SPCFX0hb +qxUbCrLLDWf5x5JM9+49tkyQ7N36TSj92Q+05jp/f1MdAgMBAAGjeDB2MB0GA1Ud +DgQWBBRghFzHbuyke/ayjjVJuSgPKzwfRTAfBgNVHSMEGDAWgBSr39jHWOmo14F/ +m0G/Irn6oVwNTzAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIGwDAWBgNVHSUB +Af8EDDAKBggrBgEFBQcDCDANBgkqhkiG9w0BAQsFAAOCAgEAAvKfbV0s2AGHKgEX +X33/N5nV8v109fjmQ0tzN1UvnamxYQLpgCLILN0yeWP4E5LKmzPWnTXG10woEasp +pAtXqYtgZZbvfqlkaryS5IJ73F6uO0hJK7iUswnpGxm9fxS04hhvsx3YRQnaaJ0m +Ek5JpXNsdQVN87iyGtnzp1uHY2csdyw9XDCQxDDXJ3hvLBthHAADeX+h8JX7G3xc +u85ON+w/rVIJIMpPQKg5gvNVzA/krebToSmOS6+f2O1UUj4PxwPPfBHugepzZYpF +grWy1vzz2DAZdNQs39c10a5/C5VrH77V++SoQBfYzCAI7YhA/L6ONIKW4nP3zFX3 +zMmevHYR/WzQlN8twJhyAx7Y6AEjZNnfS2CvZj6UWvVUFbcVD5N9qgLuZxdDezQt +RAsVcSN5q5FURl99oi3X9sGoq/rI47MEU96hSG4pfiBTlt6VbYyWvBLeRWLccLPi +KtvV3wd6F8bqp+U2d/XQFTyQ5jfZcrCVvIesv4euTq+kTypjjjLadgzsy56ivoAx +UvjcJFZ2HVoKpv1SusOMsgg5nZf4lhE5zxYQOtEwzhOXv4U81ctIVHOKYZdDefWm +wC7dTtH9NxaWQZu07Y27wihkBARdnW1azgWorwzKYcGFT+pwrAPRUScj/vqsJkUH +KagSCD6B6qh4zSj0UANX3V6QcfQ= +-----END CERTIFICATE----- diff --git a/restagent/etc/certs/tsa-omc.pem b/restagent/etc/certs/tsa-omc.pem new file mode 100644 index 0000000..d119e0e --- /dev/null +++ b/restagent/etc/certs/tsa-omc.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEYDCCAkigAwIBAgIUGZ67LDulO9kcRtwAwQGEC1oV5NUwDQYJKoZIhvcNAQEL +BQAwHjELMAkGA1UEBhMCQ04xDzANBgNVBAMMBlJvb3RDQTAeFw0yMzA4MjIwMzQw +MjVaFw0yMzA4MjMwMzQwMjVaMDwxCzAJBgNVBAYTAkNOMQ0wCwYDVQQKDARUZXN0 +MR4wHAYDVQQDDBVUaW1lc3RhbXAgQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQC1vnKPYHUYPQSvGmfgSxZQley/hvJZyY9dun3hYv1P +h5PydHqkwM6t6wS+f9WTm2RyCvfRJrwn356gKm3DoDePs7T2kIEwnljMFRT8HDEC +SujKz2MBBW/b9eU6Bz9sQ4XHmSTVE/mJF4DiMRxmflUPZ+Mis80XbpFO91tdhDHZ +f0RX5D9ihaS/WTnkOV+ISMxxK1PB/zITlxAk15irYsMM8dCq0iUy/B3+bcGH1YzL +aKqbFw+nrm6NTxYLo4q3ERtLl49l9UXXcoNMvB70fk6i4kXrn7+AEdb3SPCFX0hb +qxUbCrLLDWf5x5JM9+49tkyQ7N36TSj92Q+05jp/f1MdAgMBAAGjeDB2MB0GA1Ud +DgQWBBRghFzHbuyke/ayjjVJuSgPKzwfRTAfBgNVHSMEGDAWgBSr39jHWOmo14F/ +m0G/Irn6oVwNTzAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIGwDAWBgNVHSUB +Af8EDDAKBggrBgEFBQcDCDANBgkqhkiG9w0BAQsFAAOCAgEAAvKfbV0s2AGHKgEX +X33/N5nV8v109fjmQ0tzN1UvnamxYQLpgCLILN0yeWP4E5LKmzPWnTXG10woEasp +pAtXqYtgZZbvfqlkaryS5IJ73F6uO0hJK7iUswnpGxm9fxS04hhvsx3YRQnaaJ0m +Ek5JpXNsdQVN87iyGtnzp1uHY2csdyw9XDCQxDDXJ3hvLBthHAADeX+h8JX7G3xc +u85ON+w/rVIJIMpPQKg5gvNVzA/krebToSmOS6+f2O1UUj4PxwPPfBHugepzZYpF +grWy1vzz2DAZdNQs39c10a5/C5VrH77V++SoQBfYzCAI7YhA/L6ONIKW4nP3zFX3 +zMmevHYR/WzQlN8twJhyAx7Y6AEjZNnfS2CvZj6UWvVUFbcVD5N9qgLuZxdDezQt +RAsVcSN5q5FURl99oi3X9sGoq/rI47MEU96hSG4pfiBTlt6VbYyWvBLeRWLccLPi +KtvV3wd6F8bqp+U2d/XQFTyQ5jfZcrCVvIesv4euTq+kTypjjjLadgzsy56ivoAx +UvjcJFZ2HVoKpv1SusOMsgg5nZf4lhE5zxYQOtEwzhOXv4U81ctIVHOKYZdDefWm +wC7dTtH9NxaWQZu07Y27wihkBARdnW1azgWorwzKYcGFT+pwrAPRUScj/vqsJkUH +KagSCD6B6qh4zSj0UANX3V6QcfQ= +-----END CERTIFICATE----- diff --git a/restagent/etc/certs/tsa-omc_pri.key b/restagent/etc/certs/tsa-omc_pri.key new file mode 100644 index 0000000..530f70b --- /dev/null +++ b/restagent/etc/certs/tsa-omc_pri.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAtb5yj2B1GD0Erxpn4EsWUJXsv4byWcmPXbp94WL9T4eT8nR6 +pMDOresEvn/Vk5tkcgr30Sa8J9+eoCptw6A3j7O09pCBMJ5YzBUU/BwxAkroys9j +AQVv2/XlOgc/bEOFx5kk1RP5iReA4jEcZn5VD2fjIrPNF26RTvdbXYQx2X9EV+Q/ +YoWkv1k55DlfiEjMcStTwf8yE5cQJNeYq2LDDPHQqtIlMvwd/m3Bh9WMy2iqmxcP +p65ujU8WC6OKtxEbS5ePZfVF13KDTLwe9H5OouJF65+/gBHW90jwhV9IW6sVGwqy +yw1n+ceSTPfuPbZMkOzd+k0o/dkPtOY6f39THQIDAQABAoIBADgq2YDSEJ4jDXWI +NZ4t1oGlM/ulGvUAw9v1rL2hOCQCBIkj0ltkcvtw7JBrNXhex9Yu6ZOr4u53v7BE +LJQOyd0RbtXcpvB9n3fC9C9ODx9kQzmaikBgi8dw4Rzj7Ifq/kdHXwizGaVyrBQ7 +lUlfta4DL0EkTDf3pdS/wW7PUhCjLPEPH6GqkDilsIGGudDT5pqUVB/fkLq4rPl4 +QhQixDCwTtesPMeMMCy+bjB5hojRzENZ9ste4eSpF0BJQyUjKLWkyCPjFZKtM1l8 +C+fZT/tVeyMkGpOXywhtuYSaAeoeaqrPjvMzN1H3slx0i9J6lIS/T+94jXuA5Zbw +iXR6GAkCgYEA4D6K02Nnu1vk/NnhqK8AiP9gWUxArGaQ8BKkhAXEi7/oLhRfnJE0 +eNgmZLKk4pMZmC9tf5WiBXdcHDZwjxequi02AtdU4p0emStJ7L/if3YLdWKCOdjw +3jkMdtHmQRT3bUFppKkH4n+EAg08QOT1HLoFVmcfKkjZQ9igbfFoAx8CgYEAz3sn +G6ZpF7wbxdWyPxuko9O8r9V6xFa6pWpuy5X1rVXUEy4u7pwn3D7xgOT3v83HdkIG +aRlIR8YbEcekpO+hqRPljvm3zYDsEXs4I1fBmnxSrO9aX0IImEabTCTuRAUZRGMe +Vzrk2rqCmuO0zDmBN+/uxqlK5us3wAguO+vbPkMCgYAgSIC9/BKkA4/M4yU8nq0q +C/H4CSVKbJ2zIkvzAc2Q/PjVIXO+W509RIkKeDmKsPi/UxylgIpJ1Sw6l4/O2e5n +TTj4Yb3DvjD4hiXnYpyeEwWFScBLybKrm3Ty6jcG6ZjRbthhr0AGWKtNjFA/W3b6 +QsGf9cqj0McOoHJAQb/iqwKBgH9c0+bvQjflqw7IXUDMEUbX5U3yuAfqTWzZZuXm +Sgd7VlHn2btzdCyJHurGoybEhRqc8R52/J3CiD9j1DfDOfN2WUZpGGE4MLGSeCWJ +6ziP9Jhf3ZP4DxqazR3y1ag/kQEf7zIQ3wc90oqOGR/8dMwHpwWgSnByF6jz3J2/ +7LfNAoGBAMJsi2nJF3jzWwZLY9pQQ2/Icdojk0yUE0a9skdh5R6MjpzwQ3FydbB0 +PxUxfJlAjS06kV/URo11AdQZXm/QKyb8uJPyRppJ7DZ0vsiDWysElqUHX/E/H6kI +QaQvePQ59HcgkJVh9wFI5blHtTyMrGR76kmZN5ClL05ITkqdnWJp +-----END RSA PRIVATE KEY----- diff --git a/restagent/etc/certs/tsa-omc_pri.pem b/restagent/etc/certs/tsa-omc_pri.pem new file mode 100644 index 0000000..530f70b --- /dev/null +++ b/restagent/etc/certs/tsa-omc_pri.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAtb5yj2B1GD0Erxpn4EsWUJXsv4byWcmPXbp94WL9T4eT8nR6 +pMDOresEvn/Vk5tkcgr30Sa8J9+eoCptw6A3j7O09pCBMJ5YzBUU/BwxAkroys9j +AQVv2/XlOgc/bEOFx5kk1RP5iReA4jEcZn5VD2fjIrPNF26RTvdbXYQx2X9EV+Q/ +YoWkv1k55DlfiEjMcStTwf8yE5cQJNeYq2LDDPHQqtIlMvwd/m3Bh9WMy2iqmxcP +p65ujU8WC6OKtxEbS5ePZfVF13KDTLwe9H5OouJF65+/gBHW90jwhV9IW6sVGwqy +yw1n+ceSTPfuPbZMkOzd+k0o/dkPtOY6f39THQIDAQABAoIBADgq2YDSEJ4jDXWI +NZ4t1oGlM/ulGvUAw9v1rL2hOCQCBIkj0ltkcvtw7JBrNXhex9Yu6ZOr4u53v7BE +LJQOyd0RbtXcpvB9n3fC9C9ODx9kQzmaikBgi8dw4Rzj7Ifq/kdHXwizGaVyrBQ7 +lUlfta4DL0EkTDf3pdS/wW7PUhCjLPEPH6GqkDilsIGGudDT5pqUVB/fkLq4rPl4 +QhQixDCwTtesPMeMMCy+bjB5hojRzENZ9ste4eSpF0BJQyUjKLWkyCPjFZKtM1l8 +C+fZT/tVeyMkGpOXywhtuYSaAeoeaqrPjvMzN1H3slx0i9J6lIS/T+94jXuA5Zbw +iXR6GAkCgYEA4D6K02Nnu1vk/NnhqK8AiP9gWUxArGaQ8BKkhAXEi7/oLhRfnJE0 +eNgmZLKk4pMZmC9tf5WiBXdcHDZwjxequi02AtdU4p0emStJ7L/if3YLdWKCOdjw +3jkMdtHmQRT3bUFppKkH4n+EAg08QOT1HLoFVmcfKkjZQ9igbfFoAx8CgYEAz3sn +G6ZpF7wbxdWyPxuko9O8r9V6xFa6pWpuy5X1rVXUEy4u7pwn3D7xgOT3v83HdkIG +aRlIR8YbEcekpO+hqRPljvm3zYDsEXs4I1fBmnxSrO9aX0IImEabTCTuRAUZRGMe +Vzrk2rqCmuO0zDmBN+/uxqlK5us3wAguO+vbPkMCgYAgSIC9/BKkA4/M4yU8nq0q +C/H4CSVKbJ2zIkvzAc2Q/PjVIXO+W509RIkKeDmKsPi/UxylgIpJ1Sw6l4/O2e5n +TTj4Yb3DvjD4hiXnYpyeEwWFScBLybKrm3Ty6jcG6ZjRbthhr0AGWKtNjFA/W3b6 +QsGf9cqj0McOoHJAQb/iqwKBgH9c0+bvQjflqw7IXUDMEUbX5U3yuAfqTWzZZuXm +Sgd7VlHn2btzdCyJHurGoybEhRqc8R52/J3CiD9j1DfDOfN2WUZpGGE4MLGSeCWJ +6ziP9Jhf3ZP4DxqazR3y1ag/kQEf7zIQ3wc90oqOGR/8dMwHpwWgSnByF6jz3J2/ +7LfNAoGBAMJsi2nJF3jzWwZLY9pQQ2/Icdojk0yUE0a9skdh5R6MjpzwQ3FydbB0 +PxUxfJlAjS06kV/URo11AdQZXm/QKyb8uJPyRppJ7DZ0vsiDWysElqUHX/E/H6kI +QaQvePQ59HcgkJVh9wFI5blHtTyMrGR76kmZN5ClL05ITkqdnWJp +-----END RSA PRIVATE KEY----- diff --git a/restagent/etc/restconf-t.yaml b/restagent/etc/restconf-t.yaml new file mode 100644 index 0000000..b53e780 --- /dev/null +++ b/restagent/etc/restconf-t.yaml @@ -0,0 +1,134 @@ +# file: log file name +# level: /trace/debug/info/warn/error/fatal, default: debug +# duration: rotation time with xx hours, example: 1/12/24 hours +# count: rotation count of log, default is 30 rotation +logger: + file: d:/local.git/ems.agt/restagent/log/restagent-t.log + level: trace + duration: 24 + count: 2 + +# rest agent listen ipv4/v6 and port, support multiple routines +# ip: 0.0.0.0 or ::0, support IPv4/v6 +rest: + - ipv4: 0.0.0.0 + ipv6: fe80::f6bb:7d5f:bcb2:763b%7 + port: 3030 + - ipv4: 0.0.0.0 + ipv6: fe80::f6bb:7d5f:bcb2:763b%7 + port: 6060 + +database: + type: mysql + user: root + password: 1000omc@kp! + host: 127.0.0.1 + port: 33066 + name: omc_db + backup: d:/local.git/ems.agt/restagent/database + +# 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" + +mml: + port: 4100 + sleep: 200 + user: admin + password: admin + mmlHome: ./mmlhome + +ne: + user: root + etcdir: /usr/local/etc + bindir: /usr/local/bin + omcdir: /usr/local/omc + scpdir: /tmp + licensedir: /usr/local/etc/{neType}/license + +# chk2ne: true/false, if put OmcNeConfig parameters to NE +omc: + uriPrefix: /api/rest + neType: OMC + neId: 001 + rmUID: 4400HX101 + neName: OMC + province: GD + vendor: "" + dn: 4600 + chk2ne: false + sn: 13750650 + checksign: false + backup: ./backup + upload: ./upload + frontUpload: d:/local.git/fe.ems/upload + frontTraceDir: d:/local.git/fe.ems/trace + software: ./software + license: ./license + gtpUri: gtp:192.168.2.119:2152 + checkContentType: false + testMode: false + rbacMode: true + runDir: + +# Alarm module setting +# Forward interface: +# email/sms +alarm: + forwardAlarm: true + email: + smtp: smtp@xxx.com.cn + port: 25 + user: smtpuser + password: smtpuser@omc + sms: + apiURL: http://smsc.xxx.com.cn/ + accessKeyID: xxxx + accessKeySecret: xxxx + signName: xxx SMSC + templateCode: 1000 + +#User authorized information +# crypt: mysql/md5/bcrypt +# token: true/false to check accessToken +# expires for session, unit: second +# Support single/multiple session of user +auth: + crypt: bcrypt + token: true + expires: 1800 + session: multiple + publicKey: ./etc/certs/omc_pub.key + privateKey: ./etc/certs/omc_pri.key + +# Parameter for limit number +# rmuid_maxnum: the max number of rmUID, default: 50 +# alarmid_maxnum: the max number of AlarmID, default: 50 +# pmid_maxnum: the max number of pmID, default: 50 +# subid_maxnum: the max number of subscription ID, default: 20 +# uri_maxlen: the max length of uri, default: 8192 +# rmuid_regexp: regexp pattern of rmUID +params: + rmuidmaxnum: 50 + alarmidmaxnum: 50 + pmidmaxnum: 50 + subidmaxnum: 20 + urimaxlen: 2100000 + rmuidregexp: "[0-9]{4}[A-Z]{2}[A-Z]{2}[0-9A-Z]{1}[0-9A-Z]{3}[0-9A-Z]{1,16}" +testConfig: + enabled: true + file: ./etc/testconfig.yaml \ No newline at end of file diff --git a/restagent/etc/restconf.yaml b/restagent/etc/restconf.yaml new file mode 100644 index 0000000..f0e6fea --- /dev/null +++ b/restagent/etc/restconf.yaml @@ -0,0 +1,156 @@ +# file: log file name +# level: /trace/debug/info/warn/error/fatal, default: debug +# duration: rotation time with xx hours, example: 1/12/24 hours +# count: rotation count of log, default is 30 rotation +logger: + file: d:/local.git/ems.agt/restagent/log/restagent.log + level: trace + duration: 24 + count: 2 + +# rest agent listen ipv4/v6 and port, support multiple routines +# ip: 0.0.0.0 or ::0, support IPv4/v6 +rest: + - ipv4: 0.0.0.0 + ipv6: + port: 3040 + - ipv4: 0.0.0.0 + ipv6: + port: 4443 + scheme: https + caFile: ./etc/certs/rootca.crt + certFile: ./etc/certs/tsa-omc.crt + keyFile: ./etc/certs/tsa-omc_pri.key + +webServer: + enabled: true + rootDir: d:/local.git/fe.ems + listen: + - addr: :8080 + schema: http + - addr: :8443 + scheme: https + caFile: ./etc/certs/rootca.crt + certFile: ./etc/certs/tsa-omc.crt + keyFile: ./etc/certs/tsa-omc_pri.key + +database: + type: mysql + user: root + # password: 1000omc@kp! + # host: "192.168.2.119" + # port: 33066 + password: "root@1234" + host: "192.168.30.244" + port: 3306 + name: omc_db + backup: d:/local.git/ems.agt/restagent/database + +# 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" + +mml: + port: 4100 + sleep: 200 + user: admin + password: admin + mmlHome: ./mmlhome + upload: /home/agtuser + +ne: + user: root + etcdir: /usr/local/etc + bindir: /usr/local/bin + omcdir: /usr/local/omc + scpdir: /tmp + licensedir: /usr/local/etc/{neType}/license + +# chk2ne: true/false, if put OmcNeConfig parameters to NE +omc: + uriPrefix: /api/rest/oam + neType: OMC + neId: 001 + rmUID: 4400HX101 + neName: OMC + province: GD + vendor: "" + dn: 4600 + chk2ne: false + sn: 13750650 + checksign: false + backup: ./backup + upload: ./upload + frontUpload: C:\AMP\Probject\ems_frontend\upload + frontTraceDir: d:/local.git/fe.ems/trace + software: ./software + license: ./license + gtpUri: gtp:192.168.2.119:2152 + checkContentType: false + testMode: false + rbacMode: true + runDir: + +# Alarm module setting +# Forward interface: +# email/sms +alarm: + forwardAlarm: true + email: + smtp: smtp@xxx.com.cn + port: 25 + user: smtpuser + password: smtpuser@omc + sms: + apiURL: http://smsc.xxx.com.cn/ + accessKeyID: xxxx + accessKeySecret: xxxx + signName: xxx SMSC + templateCode: 1000 + +#User authorized information +# crypt: mysql/md5/bcrypt +# token: true/false to check accessToken +# expires for session, unit: second +# Support single/multiple session of user +# +auth: + crypt: bcrypt + token: true + expires: 1800 + session: multiple + publicKey: ./etc/certs/omc_pub.key + privateKey: ./etc/certs/omc_pri.key + +# Parameter for limit number +# rmuid_maxnum: the max number of rmUID, default: 50 +# alarmid_maxnum: the max number of AlarmID, default: 50 +# pmid_maxnum: the max number of pmID, default: 50 +# subid_maxnum: the max number of subscription ID, default: 20 +# uri_maxlen: the max length of uri, default: 8192 +# rmuid_regexp: regexp pattern of rmUID +params: + rmuidmaxnum: 50 + alarmidmaxnum: 50 + pmidmaxnum: 50 + subidmaxnum: 20 + urimaxlen: 2100000 + rmuidregexp: "[0-9]{4}[A-Z]{2}[A-Z]{2}[0-9A-Z]{1}[0-9A-Z]{3}[0-9A-Z]{1,16}" + +testConfig: + enabled: true + file: ./etc/testconfig.yaml \ No newline at end of file diff --git a/restagent/etc/testconfig.yaml b/restagent/etc/testconfig.yaml new file mode 100644 index 0000000..b001290 --- /dev/null +++ b/restagent/etc/testconfig.yaml @@ -0,0 +1,19 @@ +UDM: + capUsed: 16 + featureEnabled: [N8,N10,N13] +AUSF: + capUsed: 16 + featureEnabled: [N12] +AMF: + capUsed: 16 + featureEnabled: [N1,N2,N8,N11,N12,N14,N15] +SMF: + capUsed: 16 + featureEnabled: [N4,N7,N10,N11] +UPF: + capUsed: 16 + featureEnabled: [N3,N4,N6,N9] +OMC: + capUsed: 0 + featureEnabled: [] + diff --git a/restagent/makefile b/restagent/makefile new file mode 100644 index 0000000..015071e --- /dev/null +++ b/restagent/makefile @@ -0,0 +1,26 @@ +# Makefile for rest agent project + +PROJECT = OMC +VERSION = 1.6.2 +PLATFORM = amd64 +ARMPLATFORM = aarch64 +BUILDDIR = ../../build +DEBBUILDDIR = ../../debbuild +RPMBUILDDIR = $(HOME)/goprojects/rpmbuild +INSTALLDIR = /usr/local/omc +RELEASEDIR = ../../release +LIBDIR = ems.agt/lib +BINNAME = restagent + +.PHONY: build $(BINNAME) +build $(BINNAME): + go build -o $(BINNAME) -v -ldflags "-X '$(LIBDIR)/global.Version=$(VERSION)' \ + -X '$(LIBDIR)/global.BuildTime=`date`' \ + -X '$(LIBDIR)/global.GoVer=`go version`'" + +run: $(BINNAME) + ./$(BINNAME) + +clean: + rm ./$(BINNAME) + diff --git a/restagent/restagent.go b/restagent/restagent.go new file mode 100644 index 0000000..c8e2708 --- /dev/null +++ b/restagent/restagent.go @@ -0,0 +1,211 @@ +package main + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "net" + "net/http" + "os" + "strconv" + "strings" + + "ems.agt/lib/core/redis" + "ems.agt/lib/dborm" + "ems.agt/lib/global" + "ems.agt/lib/log" + "ems.agt/lib/routes" + + "ems.agt/features/dbrest" + "ems.agt/features/fm" + "ems.agt/features/lm" + "ems.agt/features/pm" + "ems.agt/restagent/config" +) + +// const defaultConfigFile = "./etc/restconf.yaml" + +// func init() { +// cfile := flag.String("c", defaultConfigFile, "config file") +// pv := flag.Bool("v", false, "print version") +// ph := flag.Bool("h", false, "print help") + +// flag.Parse() +// if *pv { +// fmt.Printf("OMC restagent version: %s\n%s\n%s\n\n", global.Version, global.BuildTime, global.GoVer) +// os.Exit(0) +// } +// if *ph { +// flag.Usage() +// os.Exit(0) +// } + +// config.ReadConfig(*cfile) +// config.UriPrefix = config.GetYamlConfig().OMC.UriPrefix +// //fmt.Println(config.UriPrefix) +// } + +func listenIPv6(ipv6 string, port int) { + // + addr := &net.TCPAddr{ + IP: net.ParseIP(ipv6), + Port: port, + } + + listener, err := net.ListenTCP("tcp6", addr) + if err != nil { + fmt.Println("Failed to listen:", err) + return + } + + server := &http.Server{} + err = server.Serve(listener) + if err != nil { + fmt.Println("Failed to serve:", err) + } +} + +func HttpListen(addr string, router http.Handler) { + err := http.ListenAndServe(addr, router) + if err != nil { + fmt.Println("ListenAndServe err:", err) + os.Exit(5) + } +} + +func HttpListenTLS(addr, certFile, keyFile string, router http.Handler) { + err := http.ListenAndServeTLS(addr, certFile, keyFile, router) + if err != nil { + fmt.Println("ListenAndServeTLS err:", err) + os.Exit(6) + } +} + +func HttpListenConfigTLS(addr, caFile, certFile, keyFile string, router http.Handler) { + // 加载根证书 + caCert, err := os.ReadFile(caFile) + if err != nil { + log.Fatal(err) + } + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + + // 创建自定义的TLS配置 + tlsConfig := &tls.Config{ + ClientCAs: caCertPool, + ClientAuth: tls.RequireAndVerifyClientCert, + } + + // 创建HTTP服务器 + server := &http.Server{ + Addr: addr, + Handler: router, + TLSConfig: tlsConfig, + } + + err = server.ListenAndServeTLS(certFile, keyFile) + if err != nil { + fmt.Println("ListenAndServeTLS err:", err) + os.Exit(6) + } +} + +func HttpListenWebServerTLS(addr, certFile, keyFile string) { + err := http.ListenAndServeTLS(addr, certFile, keyFile, nil) + if err != nil { + fmt.Println("ListenAndServeTLS err:", err) + os.Exit(7) + } +} + +func HttpListenWebServer(addr string) { + err := http.ListenAndServe(addr, nil) + if err != nil { + fmt.Println("ListenAndServe err:", err) + os.Exit(7) + } +} + +func main() { + conf := config.GetYamlConfig() + + log.InitLogger(conf.Logger.File, conf.Logger.Duration, conf.Logger.Count, "omc:restagent", config.GetLogLevel()) + fmt.Printf("OMC restagent version: %s\n", global.Version) + log.Infof("========================= OMC restagent startup =========================") + log.Infof("OMC restagent version: %s %s %s", global.Version, global.BuildTime, global.GoVer) + + 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) + os.Exit(4) + } + err = fm.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) + os.Exit(4) + } + err = pm.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) + os.Exit(4) + } + err = dbrest.InitDbClient(conf.Database.Type, conf.Database.User, conf.Database.Password, + conf.Database.Host, conf.Database.Port, conf.Database.Name) + if err != nil { + fmt.Println("rests.initDbClient err:", err) + os.Exit(4) + } + err = lm.InitDbClient(conf.Database.Type, conf.Database.User, conf.Database.Password, + conf.Database.Host, conf.Database.Port, conf.Database.Name) + if err != nil { + fmt.Println("lm.initDbClient err:", err) + os.Exit(4) + } + + // 连接redis + redis.Connect() + + router := routes.NewRouter() + + // 开启监控采集 + // monitor.StartMonitor(false, "") + + for _, rest := range conf.Rest { + // ipv4 goroutines + if rest.IPv4 != "" { + listen := rest.IPv4 + ":" + strconv.Itoa(int(rest.Port)) + if strings.ToLower(rest.Scheme) == "https" { + go HttpListenTLS(listen, rest.CertFile, rest.KeyFile, router) + } else { + go HttpListen(listen, router) + } + + } + // ipv6 goroutines + if rest.IPv6 != "" { + listenv6 := "[" + rest.IPv6 + "]" + ":" + strconv.Itoa(int(rest.Port)) + if strings.ToLower(rest.Scheme) == "https" { + go HttpListenTLS(listenv6, rest.CertFile, rest.KeyFile, router) + } else { + go HttpListen(listenv6, router) + } + } + } + + if conf.WebServer.Enabled { + fs := http.FileServer(http.Dir(conf.WebServer.RootDir)) + http.Handle("/", fs) + for _, listen := range conf.WebServer.Listen { + if strings.ToLower(listen.Scheme) == "https" { + go HttpListenWebServerTLS(listen.Addr, listen.CertFile, listen.KeyFile) + } else { + go HttpListenWebServer(listen.Addr) + } + } + } + + select {} +}