From f4741a1e07ed2df600b5b11e23b1589fd0e406d0 Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Fri, 9 May 2025 18:34:40 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20UDM-Voip/volte-ims=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../network_data/controller/udm_voip.go | 561 +++++++++++++++++ .../network_data/controller/udm_volte_ims.go | 590 ++++++++++++++++++ src/modules/network_data/model/udm_voip.go | 15 + .../network_data/model/udm_volte_ims.go | 17 + .../network_data/repository/udm_voip.go | 138 ++++ .../network_data/repository/udm_volte_ims.go | 150 +++++ src/modules/network_data/service/udm_voip.go | 172 +++++ .../network_data/service/udm_volte_ims.go | 194 ++++++ 8 files changed, 1837 insertions(+) create mode 100644 src/modules/network_data/controller/udm_voip.go create mode 100644 src/modules/network_data/controller/udm_volte_ims.go create mode 100644 src/modules/network_data/model/udm_voip.go create mode 100644 src/modules/network_data/model/udm_volte_ims.go create mode 100644 src/modules/network_data/repository/udm_voip.go create mode 100644 src/modules/network_data/repository/udm_volte_ims.go create mode 100644 src/modules/network_data/service/udm_voip.go create mode 100644 src/modules/network_data/service/udm_volte_ims.go diff --git a/src/modules/network_data/controller/udm_voip.go b/src/modules/network_data/controller/udm_voip.go new file mode 100644 index 00000000..6e09c532 --- /dev/null +++ b/src/modules/network_data/controller/udm_voip.go @@ -0,0 +1,561 @@ +package controller + +import ( + "fmt" + "path/filepath" + "strings" + "time" + + "be.ems/src/framework/constants" + "be.ems/src/framework/i18n" + "be.ems/src/framework/reqctx" + "be.ems/src/framework/resp" + "be.ems/src/framework/telnet" + "be.ems/src/framework/utils/file" + "be.ems/src/framework/utils/parse" + "be.ems/src/modules/network_data/model" + neDataService "be.ems/src/modules/network_data/service" + neService "be.ems/src/modules/network_element/service" + + "github.com/gin-gonic/gin" +) + +// 实例化控制层 UDMVOIPController 结构体 +var NewUDMVOIP = &UDMVOIPController{ + udmVOIPService: neDataService.NewUDMVOIPUser, + neInfoService: neService.NewNeInfo, +} + +// UDMVOIP用户 +// +// PATH /udm/voip +type UDMVOIPController struct { + udmVOIPService *neDataService.UDMVOIPUser // UDMVOIP信息服务 + neInfoService *neService.NeInfo // 网元信息服务 +} + +// UDMVOIP用户重载数据 +// +// PUT /resetData/:neId +// +// @Tags network_data/udm/voip +// @Accept json +// @Produce json +// @Param neId path string true "NE ID" default(001) +// @Success 200 {object} object "Response Results" +// @Security TokenAuth +// @Summary UDM VOIP User Data Refresh +// @Description UDM VOIP User Data List Refresh Synchronization Latest +// @Router /neData/udm/voip/resetData/{neId} [put] +func (s *UDMVOIPController) ResetData(c *gin.Context) { + neId := c.Param("neId") + if neId == "" { + c.JSON(422, resp.CodeMsg(422002, "bind err: neId is empty")) + return + } + + data := s.udmVOIPService.ResetData(neId) + c.JSON(200, resp.OkData(data)) +} + +// UDMVOIP用户列表 +// +// GET /list +// +// @Tags network_data/udm/voip +// @Accept json +// @Produce json +// @Param neId query string true "NE ID" default(001) +// @Param username query string false "User Name" +// @Param pageNum query number true "pageNum" default(1) +// @Param pageSize query number true "pageSize" default(10) +// @Success 200 {object} object "Response Results" +// @Security TokenAuth +// @Summary UDM VOIP User List +// @Description UDM VOIP User List +// @Router /neData/udm/voip/list [get] +func (s *UDMVOIPController) List(c *gin.Context) { + query := reqctx.QueryMap(c) + total, rows := s.udmVOIPService.FindByPage(query) + c.JSON(200, resp.OkData(map[string]any{"total": total, "rows": rows})) +} + +// UDMVOIP用户信息 +// +// GET /:neId/:username +// +// @Tags network_data/udm/voip +// @Accept json +// @Produce json +// @Param neId path string true "NE ID" default(001) +// @Param value path string true "User Name" +// @Success 200 {object} object "Response Results" +// @Security TokenAuth +// @Summary UDM VOIP User Information +// @Description UDM VOIP User Information +// @Router /neData/udm/voip/{neId}/{value} [get] +func (s *UDMVOIPController) Info(c *gin.Context) { + language := reqctx.AcceptLanguage(c) + neId := c.Param("neId") + username := c.Param("username") + if neId == "" || username == "" { + c.JSON(422, resp.CodeMsg(422002, "bind err: neId or username is empty")) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + // 发送MML + cmd := fmt.Sprintf("dsp voip:username=%s", username) + data, err := telnet.ConvertToMap(telnetClient, cmd) + if err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + + if len(data) == 0 { + c.JSON(200, resp.ErrMsg("No VOIP Data")) + return + } + + // 解析返回的数据 + u := s.udmVOIPService.ParseInfo(neId, data) + if u.ID != "" { + s.udmVOIPService.Insert(neId, u.UserName) + c.JSON(200, resp.OkData(u)) + return + } + c.JSON(200, resp.ErrMsg("No VOIP Data")) +} + +// UDMVOIP用户新增 +// +// POST /:neId +// +// @Tags network_data/udm/voip +// @Accept json +// @Produce json +// @Param neId path string true "NE ID" default(001) +// @Param data body object true "Request Param" +// @Success 200 {object} object "Response Results" +// @Security TokenAuth +// @Summary UDM VOIP User Added +// @Description UDM VOIP User Added +// @Router /neData/udm/voip/{neId} [post] +func (s *UDMVOIPController) Add(c *gin.Context) { + language := reqctx.AcceptLanguage(c) + neId := c.Param("neId") + if neId == "" { + c.JSON(422, resp.CodeMsg(422002, "bind err: neId is empty")) + return + } + + var body model.UDMVOIPUser + if err := c.ShouldBindBodyWithJSON(&body); err != nil { + errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err)) + c.JSON(422, resp.CodeMsg(422001, errMsgs)) + return + } + if body.UserName == "" { + c.JSON(422, resp.CodeMsg(422002, "bind err: username is empty")) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + // 发送MML + cmd := fmt.Sprintf("add voip:username=%s,password=%s", body.UserName, body.Password) + data, err := telnet.ConvertToStr(telnetClient, cmd) + if err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + + // 命令ok时 + if strings.Contains(data, "ok") { + s.udmVOIPService.Insert(neId, body.UserName) + } + c.JSON(200, resp.OkData(data)) +} + +// UDMVOIP用户批量新增 +// +// POST /:neId/:num +// +// @Tags network_data/udm/voip +// @Accept json +// @Produce json +// @Param neId path string true "NE ID" default(001) +// @Param value path number true "Number of releases, value includes start username" +// @Param data body object true "Request Param" +// @Success 200 {object} object "Response Results" +// @Security TokenAuth +// @Summary UDM VOIP User Batch Add +// @Description UDM VOIP User Batch Add +// @Router /neData/udm/voip/{neId}/{value} [post] +func (s *UDMVOIPController) Adds(c *gin.Context) { + language := reqctx.AcceptLanguage(c) + neId := c.Param("neId") + num := c.Param("num") + if neId == "" || num == "" { + c.JSON(422, resp.CodeMsg(422002, "bind err: neId or num is empty")) + return + } + + var body model.UDMVOIPUser + if err := c.ShouldBindBodyWithJSON(&body); err != nil { + errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err)) + c.JSON(422, resp.CodeMsg(422001, errMsgs)) + return + } + if body.UserName == "" { + c.JSON(422, resp.CodeMsg(422002, "bind err: username is empty")) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + // 发送MML + cmd := fmt.Sprintf("baa voip:sub_num=%s,start_username=%s,password=%s", num, body.UserName, body.Password) + data, err := telnet.ConvertToStr(telnetClient, cmd) + if err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + + // 命令ok时 + if strings.Contains(data, "ok") { + s.udmVOIPService.LoadData(neId, body.UserName, num) + } + c.JSON(200, resp.OkData(data)) +} + +// UDMVOIP用户删除 +// +// DELETE /:neId/:username +// +// @Tags network_data/udm/voip +// @Accept json +// @Produce json +// @Param neId path string true "NE ID" default(001) +// @Param value path string true "User Name, multiple separated by a , sign" +// @Success 200 {object} object "Response Results" +// @Security TokenAuth +// @Summary UDM VOIP User Deletion +// @Description UDM VOIP User Deletion +// @Router /neData/udm/voip/{neId}/{value} [delete] +func (s *UDMVOIPController) Remove(c *gin.Context) { + language := reqctx.AcceptLanguage(c) + neId := c.Param("neId") + username := c.Param("username") + if neId == "" || username == "" { + c.JSON(422, resp.CodeMsg(422002, "bind err: neId or username is empty")) + return + } + + // 处理字符转id数组后去重 + usernameArr := strings.Split(username, ",") + uniqueIDs := parse.RemoveDuplicates(usernameArr) + if len(uniqueIDs) <= 0 { + c.JSON(200, resp.Err(nil)) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + resultData := map[string]string{} + for _, v := range uniqueIDs { + // 发送MML + cmd := fmt.Sprintf("del voip:username=%s", v) + data, err := telnet.ConvertToStr(telnetClient, cmd) + if err != nil { + resultData[v] = err.Error() + continue + } + // 命令ok时 + if strings.Contains(data, "ok") { + s.udmVOIPService.Delete(v, neId) + } + resultData[v] = data + } + + c.JSON(200, resp.OkData(resultData)) +} + +// UDMVOIP用户批量删除 +// +// DELETE /:neId/:username/:num +// +// @Tags network_data/udm/voip +// @Accept json +// @Produce json +// @Param neId path string true "NE ID" default(001) +// @Param username path string true "User Name" +// @Param num path number true "Number of releases, value includes start username" +// @Success 200 {object} object "Response Results" +// @Security TokenAuth +// @Summary UDM VOIP User Batch Deletion +// @Description UDM VOIP User Batch Deletion +// @Router /neData/udm/voip/{neId}/{username}/{num} [delete] +func (s *UDMVOIPController) Removes(c *gin.Context) { + language := reqctx.AcceptLanguage(c) + neId := c.Param("neId") + username := c.Param("username") + num := c.Param("num") + if neId == "" || username == "" || num == "" { + c.JSON(422, resp.CodeMsg(422002, "bind err: neId/username/num is empty")) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + // 发送MML + cmd := fmt.Sprintf("bde voip:start_username=%s,sub_num=%s", username, num) + data, err := telnet.ConvertToStr(telnetClient, cmd) + if err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + + // 命令ok时 + if strings.Contains(data, "ok") { + s.udmVOIPService.LoadData(neId, username, num) + } + c.JSON(200, resp.OkData(data)) +} + +// UDMVOIP用户导出 +// +// GET /export +// +// @Tags network_data/udm/voip +// @Accept json +// @Produce json +// @Param neId query string true "NE ID" default(001) +// @Param type query string true "File Type" Enums(csv,txt) default(txt) +// @Param username query string false "User Name" +// @Param pageNum query number true "pageNum" default(1) +// @Param pageSize query number true "pageSize" default(10) +// @Success 200 {object} object "Response Results" +// @Security TokenAuth +// @Summary UDM VOIP User Export +// @Description UDM VOIP User Export +// @Router /neData/udm/voip/export [get] +func (s *UDMVOIPController) Export(c *gin.Context) { + language := reqctx.AcceptLanguage(c) + // 查询结果,根据查询条件结果,单页最大值限制 + neId := c.Query("neId") + fileType := c.Query("type") + if neId == "" { + c.JSON(422, resp.CodeMsg(422002, "bind err: neId is empty")) + return + } + if !(fileType == "csv" || fileType == "txt") { + c.JSON(200, resp.ErrMsg("file type error, only support csv,txt")) + return + } + + query := reqctx.QueryMap(c) + total, rows := s.udmVOIPService.FindByPage(query) + if total == 0 { + // 导出数据记录为空 + c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.exportEmpty"))) + return + } + + // rows := s.udmVOIPService.SelectList(model.UDMVOIPUser{NeId: neId}) + if len(rows) <= 0 { + // 导出数据记录为空 + c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.exportEmpty"))) + return + } + + // 文件名 + fileName := fmt.Sprintf("udm_voip_user_export_%s_%d.%s", neId, time.Now().UnixMilli(), fileType) + filePath := filepath.Join(file.ParseUploadFileDir(constants.UPLOAD_EXPORT), fileName) + + if fileType == "csv" { + // 转换数据 + data := [][]string{} + data = append(data, []string{"username", "password"}) + for _, v := range rows { + data = append(data, []string{v.UserName, v.Password}) + } + // 输出到文件 + if err := file.WriterFileCSV(data, filePath); err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + } + + if fileType == "txt" { + // 转换数据 + data := [][]string{} + for _, v := range rows { + data = append(data, []string{v.UserName, v.Password}) + } + // 输出到文件 + if err := file.WriterFileTXT(data, ",", filePath); err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + } + + c.FileAttachment(filePath, fileName) +} + +// UDMVOIP用户导入 +// +// POST /import +// +// @Tags network_data/udm/voip +// @Accept json +// @Produce json +// @Param data body object true "Request Param" +// @Success 200 {object} object "Response Results" +// @Security TokenAuth +// @Summary UDM VOIP User Import +// @Description UDM VOIP User Import +// @Router /neData/udm/voip/import [post] +func (s *UDMVOIPController) Import(c *gin.Context) { + language := reqctx.AcceptLanguage(c) + var body struct { + NeId string `json:"neId" binding:"required"` // 网元ID + UploadPath string `json:"uploadPath" binding:"required"` // 上传文件路径 + } + if err := c.ShouldBindBodyWithJSON(&body); err != nil { + errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err)) + c.JSON(422, resp.CodeMsg(422001, errMsgs)) + return + } + + // 判断文件名 + if !(strings.HasSuffix(body.UploadPath, ".csv") || strings.HasSuffix(body.UploadPath, ".txt")) { + c.JSON(200, resp.ErrMsg(i18n.TKey(language, "ne.udm.errImportUserAuthFileFormat"))) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", body.NeId) + if neInfo.NeId != body.NeId || neInfo.IP == "" { + c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + + // 网元主机的SSH客户端 + sshClient, err := s.neInfoService.NeRunSSHClient(neInfo.NeType, neInfo.NeId) + if err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + defer sshClient.Close() + // 网元主机的SSH客户端进行文件传输 + sftpClient, err := sshClient.NewClientSFTP() + if err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + defer sftpClient.Close() + + // 本地文件 + localFilePath := file.ParseUploadFilePath(body.UploadPath) + neFilePath := fmt.Sprintf("/tmp/%s", filepath.Base(localFilePath)) + // 复制到远程 + if err = sftpClient.CopyFileLocalToRemote(localFilePath, neFilePath); err != nil { + c.JSON(200, resp.ErrMsg("error uploading file")) + return + } + + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient(neInfo.NeType, neInfo.NeId, 1) + if err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + // 结果信息 + var resultMsg string + var resultErr error + + // 发送MML + cmd := fmt.Sprintf("import voip:path=%s", neFilePath) + resultMsg, resultErr = telnet.ConvertToStr(telnetClient, cmd) + if resultErr != nil { + c.JSON(200, resp.ErrMsg(resultErr.Error())) + return + } + + // 命令ok时 + if strings.Contains(resultMsg, "ok") { + if strings.HasSuffix(body.UploadPath, ".csv") { + data := file.ReadFileCSV(localFilePath) + go s.udmVOIPService.InsertData(neInfo.NeId, "csv", data) + } + if strings.HasSuffix(body.UploadPath, ".txt") { + data := file.ReadFileTXT(",", localFilePath) + go s.udmVOIPService.InsertData(neInfo.NeId, "txt", data) + } + } + c.JSON(200, resp.OkMsg(resultMsg)) +} diff --git a/src/modules/network_data/controller/udm_volte_ims.go b/src/modules/network_data/controller/udm_volte_ims.go new file mode 100644 index 00000000..16f19099 --- /dev/null +++ b/src/modules/network_data/controller/udm_volte_ims.go @@ -0,0 +1,590 @@ +package controller + +import ( + "fmt" + "path/filepath" + "strings" + "time" + + "be.ems/src/framework/constants" + "be.ems/src/framework/i18n" + "be.ems/src/framework/reqctx" + "be.ems/src/framework/resp" + "be.ems/src/framework/telnet" + "be.ems/src/framework/utils/file" + "be.ems/src/framework/utils/parse" + "be.ems/src/modules/network_data/model" + neDataService "be.ems/src/modules/network_data/service" + neService "be.ems/src/modules/network_element/service" + + "github.com/gin-gonic/gin" +) + +// 实例化控制层 UDMVolteIMSController 结构体 +var NewUDMVolteIMS = &UDMVolteIMSController{ + udmVolteIMSService: neDataService.NewUDMVolteIMSUser, + neInfoService: neService.NewNeInfo, +} + +// UDMVolteIMS用户 +// +// PATH /udm/volte-ims +type UDMVolteIMSController struct { + udmVolteIMSService *neDataService.UDMVolteIMSUser // UDMVolteIMS信息服务 + neInfoService *neService.NeInfo // 网元信息服务 +} + +// UDMVolteIMS用户重载数据 +// +// PUT /resetData/:neId +// +// @Tags network_data/udm/volte-ims +// @Accept json +// @Produce json +// @Param neId path string true "NE ID" default(001) +// @Success 200 {object} object "Response Results" +// @Security TokenAuth +// @Summary UDM VolteIMS User Data Refresh +// @Description UDM Authenticated User Data List Refresh Synchronization Latest +// @Router /neData/udm/volte-ims/resetData/{neId} [put] +func (s *UDMVolteIMSController) ResetData(c *gin.Context) { + neId := c.Param("neId") + if neId == "" { + c.JSON(422, resp.CodeMsg(422002, "bind err: neId is empty")) + return + } + + data := s.udmVolteIMSService.ResetData(neId) + c.JSON(200, resp.OkData(data)) +} + +// UDMVolteIMS用户列表 +// +// GET /list +// +// @Tags network_data/udm/volte-ims +// @Accept json +// @Produce json +// @Param neId query string true "NE ID" default(001) +// @Param imsi query string false "IMSI" +// @Param pageNum query number true "pageNum" default(1) +// @Param pageSize query number true "pageSize" default(10) +// @Success 200 {object} object "Response Results" +// @Security TokenAuth +// @Summary UDM VolteIMS User List +// @Description UDM VolteIMS User List +// @Router /neData/udm/volte-ims/list [get] +func (s *UDMVolteIMSController) List(c *gin.Context) { + query := reqctx.QueryMap(c) + total, rows := s.udmVolteIMSService.FindByPage(query) + c.JSON(200, resp.OkData(map[string]any{"total": total, "rows": rows})) +} + +// UDMVolteIMS用户信息 +// +// GET /:neId/:imsi +// +// @Tags network_data/udm/volte-ims +// @Accept json +// @Produce json +// @Param neId path string true "NE ID" default(001) +// @Param value path string true "IMSI" +// @Param msisdn query string true "MSISDN" +// @Success 200 {object} object "Response Results" +// @Security TokenAuth +// @Summary UDM VolteIMS User Information +// @Description UDM VolteIMS User Information +// @Router /neData/udm/volte-ims/{neId}/{value} [get] +func (s *UDMVolteIMSController) Info(c *gin.Context) { + language := reqctx.AcceptLanguage(c) + neId := c.Param("neId") + imsi := c.Param("imsi") + msisdn := c.Query("msisdn") + if neId == "" || imsi == "" { + c.JSON(422, resp.CodeMsg(422002, "bind err: neId or imsi is empty")) + return + } + if msisdn == "" { + c.JSON(422, resp.CodeMsg(422002, "bind err: msisdn is required")) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + // 发送MML + cmd := fmt.Sprintf("dsp imsuser:imsi=%s,msisdn=%s", imsi, msisdn) + data, err := telnet.ConvertToMap(telnetClient, cmd) + if err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + + if len(data) == 0 { + c.JSON(200, resp.ErrMsg("No Volte IMS Data")) + return + } + + // 解析返回的数据 + u := s.udmVolteIMSService.ParseInfo(neId, data) + if u.ID != "" { + s.udmVolteIMSService.InsertByIMSI(imsi, neId) + c.JSON(200, resp.OkData(u)) + return + } + c.JSON(200, resp.ErrMsg("No Volte IMS Data")) +} + +// UDMVolteIMS用户新增 +// +// POST /:neId +// +// @Tags network_data/udm/volte-ims +// @Accept json +// @Produce json +// @Param neId path string true "NE ID" default(001) +// @Param data body object true "Request Param" +// @Success 200 {object} object "Response Results" +// @Security TokenAuth +// @Summary UDM VolteIMS User Added +// @Description UDM VolteIMS User Added If VoIP tag=0, then MSISDN and IMSI need to be the same. +// @Router /neData/udm/volte-ims/{neId} [post] +func (s *UDMVolteIMSController) Add(c *gin.Context) { + language := reqctx.AcceptLanguage(c) + neId := c.Param("neId") + if neId == "" { + c.JSON(422, resp.CodeMsg(422002, "bind err: neId is empty")) + return + } + + var body model.UDMVolteIMSUser + if err := c.ShouldBindBodyWithJSON(&body); err != nil { + errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err)) + c.JSON(422, resp.CodeMsg(422001, errMsgs)) + return + } + if body.IMSI == "" { + c.JSON(422, resp.CodeMsg(422002, "bind err: imsi is empty")) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + // 检查同IMSI下msisdn是否存在 + hasMsisdns := s.udmVolteIMSService.Find(model.UDMVolteIMSUser{IMSI: body.IMSI, MSISDN: body.MSISDN, NeId: neId}) + if len(hasMsisdns) > 0 { + c.JSON(200, resp.ErrMsg("IMSI and MSISDN already exist")) + return + } + + // 发送MML + cmd := fmt.Sprintf("add imsuser:imsi=%s,msisdn=%s,volte=%s,vni=%s", body.IMSI, body.MSISDN, body.Tag, body.VNI) + data, err := telnet.ConvertToStr(telnetClient, cmd) + if err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + + // 命令ok时 + if strings.Contains(data, "ok") { + s.udmVolteIMSService.InsertByIMSI(body.IMSI, neId) + } + c.JSON(200, resp.OkData(data)) +} + +// UDMVolteIMS用户批量新增 +// +// POST /:neId/:num +// +// @Tags network_data/udm/volte-ims +// @Accept json +// @Produce json +// @Param neId path string true "NE ID" default(001) +// @Param value path number true "Number of releases, value includes start imsi" +// @Param data body object true "Request Param" +// @Success 200 {object} object "Response Results" +// @Security TokenAuth +// @Summary UDM VolteIMS User Batch Add +// @Description UDM VolteIMS User Batch Add +// @Router /neData/udm/volte-ims/{neId}/{value} [post] +func (s *UDMVolteIMSController) Adds(c *gin.Context) { + language := reqctx.AcceptLanguage(c) + neId := c.Param("neId") + num := c.Param("num") + if neId == "" || num == "" { + c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + var body model.UDMVolteIMSUser + if err := c.ShouldBindBodyWithJSON(&body); err != nil { + errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err)) + c.JSON(422, resp.CodeMsg(422001, errMsgs)) + return + } + if body.IMSI == "" { + c.JSON(422, resp.CodeMsg(422002, "bind err: imsi is empty")) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + // 发送MML + cmd := fmt.Sprintf("baa imsuser:sub_num=%s,start_imsi=%s,start_msisdn=%s,volte=%s,vni=%s", num, body.IMSI, body.MSISDN, body.Tag, body.VNI) + data, err := telnet.ConvertToStr(telnetClient, cmd) + if err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + + // 命令ok时 + if strings.Contains(data, "ok") { + s.udmVolteIMSService.LoadData(neId, body.IMSI, num) + } + c.JSON(200, resp.OkData(data)) +} + +// UDMVolteIMS用户删除 +// +// DELETE /:neId/:imsi +// +// @Tags network_data/udm/volte-ims +// @Accept json +// @Produce json +// @Param neId path string true "NE ID" default(001) +// @Param value path string true "IMSI, multiple separated by a , sign" +// @Param msisdn query string false "MSISDN" +// @Success 200 {object} object "Response Results" +// @Security TokenAuth +// @Summary UDM Authenticated User Deletion +// @Description UDM Authenticated User Deletion +// @Router /neData/udm/volte-ims/{neId}/{value} [delete] +func (s *UDMVolteIMSController) Remove(c *gin.Context) { + language := reqctx.AcceptLanguage(c) + neId := c.Param("neId") + imsi := c.Param("imsi") + msisdn := c.Query("msisdn") + if neId == "" || imsi == "" { + c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + imsiArr := strings.Split(imsi, ",") + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + // 精确msisdn删除 + if msisdn != "" { + // 发送MML + cmd := fmt.Sprintf("del imsuser:imsi=%s,msisdn=%s", imsiArr[0], msisdn) + data, err := telnet.ConvertToStr(telnetClient, cmd) + if err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + // 命令ok时 + if strings.Contains(data, "ok") { + s.udmVolteIMSService.Delete(imsi, neId) + } + c.JSON(200, resp.OkData(data)) + return + } else { + // 处理字符转id数组后去重 + uniqueIDs := parse.RemoveDuplicates(imsiArr) + if len(uniqueIDs) <= 0 { + c.JSON(200, resp.Err(nil)) + return + } + resultData := map[string]string{} + for _, imsi := range uniqueIDs { + // 发送MML + cmd := fmt.Sprintf("del imsuser:imsi=%s", imsi) + data, err := telnet.ConvertToStr(telnetClient, cmd) + if err != nil { + resultData[imsi] = err.Error() + continue + } + // 命令ok时 + if strings.Contains(data, "ok") { + s.udmVolteIMSService.Delete(imsi, neId) + } + resultData[imsi] = data + } + c.JSON(200, resp.OkData(resultData)) + return + } +} + +// UDMVolteIMS用户批量删除 +// +// DELETE /:neId/:imsi/:num +// +// @Tags network_data/udm/volte-ims +// @Accept json +// @Produce json +// @Param neId path string true "NE ID" default(001) +// @Param imsi path string true "IMSI" +// @Param num path number true "Number of releases, value includes start imsi" +// @Success 200 {object} object "Response Results" +// @Security TokenAuth +// @Summary UDM VolteIMS User Batch Deletion +// @Description UDM VolteIMS User Batch Deletion +// @Router /neData/udm/volte-ims/{neId}/{imsi}/{num} [delete] +func (s *UDMVolteIMSController) Removes(c *gin.Context) { + language := reqctx.AcceptLanguage(c) + neId := c.Param("neId") + imsi := c.Param("imsi") + num := c.Param("num") + if neId == "" || imsi == "" || num == "" { + c.JSON(422, resp.CodeMsg(422002, "bind err: neId/imsi/num is empty")) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + // 发送MML + cmd := fmt.Sprintf("bde imsuser:start_imsi=%s,sub_num=%s", imsi, num) + data, err := telnet.ConvertToStr(telnetClient, cmd) + if err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + + // 命令ok时 + if strings.Contains(data, "ok") { + s.udmVolteIMSService.LoadData(neId, imsi, num) + } + c.JSON(200, resp.OkData(data)) +} + +// UDMVolteIMS用户导出 +// +// GET /export +// +// @Tags network_data/udm/volte-ims +// @Accept json +// @Produce json +// @Param neId query string true "NE ID" default(001) +// @Param type query string true "File Type" Enums(csv,txt) default(txt) +// @Param imsi query string false "IMSI" +// @Param pageNum query number true "pageNum" default(1) +// @Param pageSize query number true "pageSize" default(10) +// @Success 200 {object} object "Response Results" +// @Security TokenAuth +// @Summary UDM Authenticated User Export +// @Description UDM Authenticated User Export +// @Router /neData/udm/volte-ims/export [get] +func (s *UDMVolteIMSController) Export(c *gin.Context) { + language := reqctx.AcceptLanguage(c) + // 查询结果,根据查询条件结果,单页最大值限制 + neId := c.Query("neId") + fileType := c.Query("type") + if neId == "" { + c.JSON(422, resp.CodeMsg(422002, "bind err: neId is empty")) + return + } + if !(fileType == "csv" || fileType == "txt") { + c.JSON(200, resp.ErrMsg(i18n.TKey(language, "ne.udm.errImportUserSubFileFormat"))) + return + } + + query := reqctx.QueryMap(c) + total, rows := s.udmVolteIMSService.FindByPage(query) + if total == 0 { + // 导出数据记录为空 + c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.exportEmpty"))) + return + } + if len(rows) <= 0 { + // 导出数据记录为空 + c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.exportEmpty"))) + return + } + + // 文件名 + fileName := fmt.Sprintf("udm_volte_user_export_%s_%d.%s", neId, time.Now().UnixMilli(), fileType) + filePath := filepath.Join(file.ParseUploadFileDir(constants.UPLOAD_EXPORT), fileName) + + if fileType == "csv" { + // 转换数据 + data := [][]string{} + data = append(data, []string{"IMSI", "MSISDN", "TAG", "VNI"}) + for _, v := range rows { + data = append(data, []string{v.IMSI, v.MSISDN, v.Tag, v.VNI}) + } + // 输出到文件 + if err := file.WriterFileCSV(data, filePath); err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + } + + if fileType == "txt" { + // 转换数据 + data := [][]string{} + for _, v := range rows { + data = append(data, []string{v.IMSI, v.MSISDN, v.Tag, v.VNI}) + } + // 输出到文件 + if err := file.WriterFileTXT(data, ",", filePath); err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + } + + c.FileAttachment(filePath, fileName) +} + +// UDMVolteIMS用户导入 +// +// POST /import +// +// @Tags network_data/udm/volte-ims +// @Accept json +// @Produce json +// @Param data body object true "Request Param" +// @Success 200 {object} object "Response Results" +// @Security TokenAuth +// @Summary UDM Authenticated User Import +// @Description UDM Authenticated User Import +// @Router /neData/udm/volte-ims/import [post] +func (s *UDMVolteIMSController) Import(c *gin.Context) { + language := reqctx.AcceptLanguage(c) + var body struct { + NeId string `json:"neId" binding:"required"` // 网元ID + UploadPath string `json:"uploadPath" binding:"required"` // 上传文件路径 + } + if err := c.ShouldBindBodyWithJSON(&body); err != nil { + errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err)) + c.JSON(422, resp.CodeMsg(422001, errMsgs)) + return + } + + // 判断文件名 + if !(strings.HasSuffix(body.UploadPath, ".csv") || strings.HasSuffix(body.UploadPath, ".txt")) { + c.JSON(200, resp.ErrMsg(i18n.TKey(language, "ne.udm.errImportUserAuthFileFormat"))) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", body.NeId) + if neInfo.NeId != body.NeId || neInfo.IP == "" { + c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + + // 网元主机的SSH客户端 + sshClient, err := s.neInfoService.NeRunSSHClient(neInfo.NeType, neInfo.NeId) + if err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + defer sshClient.Close() + // 网元主机的SSH客户端进行文件传输 + sftpClient, err := sshClient.NewClientSFTP() + if err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + defer sftpClient.Close() + + // 本地文件 + localFilePath := file.ParseUploadFilePath(body.UploadPath) + neFilePath := fmt.Sprintf("/tmp/%s", filepath.Base(localFilePath)) + // 复制到远程 + if err = sftpClient.CopyFileLocalToRemote(localFilePath, neFilePath); err != nil { + c.JSON(200, resp.ErrMsg("error uploading file")) + return + } + + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient(neInfo.NeType, neInfo.NeId, 1) + if err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + // 结果信息 + var resultMsg string + var resultErr error + + // 发送MML + cmd := fmt.Sprintf("import imsuser:path=%s", neFilePath) + resultMsg, resultErr = telnet.ConvertToStr(telnetClient, cmd) + if resultErr != nil { + c.JSON(200, resp.ErrMsg(resultErr.Error())) + return + } + + // 命令ok时 + if strings.Contains(resultMsg, "ok") { + if strings.HasSuffix(body.UploadPath, ".csv") { + data := file.ReadFileCSV(localFilePath) + go s.udmVolteIMSService.InsertData(neInfo.NeId, "csv", data) + } + if strings.HasSuffix(body.UploadPath, ".txt") { + data := file.ReadFileTXT(",", localFilePath) + go s.udmVolteIMSService.InsertData(neInfo.NeId, "txt", data) + } + } + c.JSON(200, resp.OkMsg(resultMsg)) +} diff --git a/src/modules/network_data/model/udm_voip.go b/src/modules/network_data/model/udm_voip.go new file mode 100644 index 00000000..39e59501 --- /dev/null +++ b/src/modules/network_data/model/udm_voip.go @@ -0,0 +1,15 @@ +package model + +// UDMVOIPUser UDMVOIP用户 udm_voip +type UDMVOIPUser struct { + ID string `json:"id" gorm:"column:id;primaryKey;autoIncrement"` // 主键 + NeId string `json:"neId" gorm:"column:ne_id"` // UDM网元标识 + + UserName string `json:"username" gorm:"column:user_name"` // 用户名 + Password string `json:"password" gorm:"column:password"` // 密码 +} + +// TableName 表名称 +func (*UDMVOIPUser) TableName() string { + return "udm_voip" +} diff --git a/src/modules/network_data/model/udm_volte_ims.go b/src/modules/network_data/model/udm_volte_ims.go new file mode 100644 index 00000000..acd19bb0 --- /dev/null +++ b/src/modules/network_data/model/udm_volte_ims.go @@ -0,0 +1,17 @@ +package model + +// UDMVolteIMSUser UDMVolteIMS用户 u_ims_user +type UDMVolteIMSUser struct { + ID string `json:"id" gorm:"column:id;primaryKey;autoIncrement"` // 主键 + IMSI string `json:"imsi" gorm:"column:imsi"` // SIM卡/USIM卡ID + MSISDN string `json:"msisdn" gorm:"column:msisdn"` // 用户电话号码 + NeId string `json:"neId" gorm:"column:ne_id"` // UDM网元标识 + + Tag string `json:"tag" gorm:"column:tag"` // 0=VoIP, 1=VoLTE + VNI string `json:"vni" gorm:"column:vni"` // VNI +} + +// TableName 表名称 +func (*UDMVolteIMSUser) TableName() string { + return "u_ims_user" +} diff --git a/src/modules/network_data/repository/udm_voip.go b/src/modules/network_data/repository/udm_voip.go new file mode 100644 index 00000000..d5d4007b --- /dev/null +++ b/src/modules/network_data/repository/udm_voip.go @@ -0,0 +1,138 @@ +package repository + +import ( + "fmt" + "strings" + + "be.ems/src/framework/database/db" + "be.ems/src/framework/logger" + "be.ems/src/modules/network_data/model" +) + +// 实例化数据层 UDMVOIPUser 结构体 +var NewUDMVOIPUser = &UDMVOIPUser{} + +// UDMVOIPUser UDMVOIP用户信息表 数据层处理 +type UDMVOIPUser struct{} + +// ClearAndInsert 清空ne_id后新增实体 +func (r UDMVOIPUser) ClearAndInsert(neId string, uArr []model.UDMVOIPUser) int64 { + // 不指定neID时,用 TRUNCATE 清空表快 + // _, err := datasource.ExecDB("", "TRUNCATE TABLE udm_voip", nil) + result := db.DB("").Where("ne_id = ?", neId).Unscoped().Delete(&model.UDMVOIPUser{}) + if result.Error != nil { + logger.Errorf("Delete err => %v", result.Error) + } + return r.Inserts(uArr) +} + +// SelectPage 根据条件分页查询 +func (r UDMVOIPUser) SelectPage(query map[string]string) (int64, []model.UDMVOIPUser) { + tx := db.DB("").Model(&model.UDMVOIPUser{}) + // 查询条件拼接 + if v, ok := query["username"]; ok && v != "" { + tx = tx.Where("user_name like ?", fmt.Sprintf("%%%s%%", v)) + } + if v, ok := query["neId"]; ok && v != "" { + tx = tx.Where("ne_id = ?", v) + } + if v, ok := query["usernames"]; ok && v != "" { + arr := strings.Split(v, ",") + tx = tx.Where("user_name in ?", arr) + // 勾选时,pageSize为勾选的数量 + query["pageSize"] = fmt.Sprint(len(arr)) + } + + var total int64 = 0 + rows := []model.UDMVOIPUser{} + + // 查询数量 长度为0直接返回 + if err := tx.Count(&total).Error; err != nil || total <= 0 { + return total, rows + } + + // 分页 + pageNum, pageSize := db.PageNumSize(query["pageNum"], query["pageSize"]) + tx = tx.Offset(int(pageNum * pageSize)).Limit(int(pageSize)) + + // 排序 + if v, ok := query["sortField"]; ok && v != "" { + sortSql := v + if o, ok := query["sortOrder"]; ok && o != "" { + if o == "desc" { + sortSql += " desc " + } else { + sortSql += " asc " + } + } + tx = tx.Order(sortSql) + } else { + tx = tx.Order("username asc") + } + + // 查询数据 + if err := tx.Find(&rows).Error; err != nil { + logger.Errorf("query err => %v", err) + } + + return total, rows +} + +// SelectList 根据实体查询 +func (r UDMVOIPUser) SelectList(u model.UDMVOIPUser) []model.UDMVOIPUser { + tx := db.DB("").Model(&model.UDMVOIPUser{}) + // 查询条件拼接 + if u.UserName != "" { + tx = tx.Where("username = ?", u.UserName) + } + if u.NeId != "" { + tx = tx.Where("ne_id = ?", u.NeId) + } + + // 查询数据 + arr := []model.UDMVOIPUser{} + if err := tx.Order("username asc").Find(&arr).Error; err != nil { + logger.Errorf("query err => %v", err) + } + return arr +} + +// SelectByUserNameAndNeID 通过username和ne_id查询 +func (r UDMVOIPUser) SelectByUserNameAndNeID(username, neId string) model.UDMVOIPUser { + tx := db.DB("").Model(&model.UDMVOIPUser{}) + item := model.UDMVOIPUser{} + // 查询条件拼接 + tx = tx.Where("username = ? and ne_id = ?", username, neId) + // 查询数据 + if err := tx.Order("username asc").Limit(1).Find(&item).Error; err != nil { + logger.Errorf("query err => %v", err) + } + return item +} + +// Insert 批量添加 +func (r UDMVOIPUser) Inserts(uArr []model.UDMVOIPUser) int64 { + tx := db.DB("").CreateInBatches(uArr, 500) + if err := tx.Error; err != nil { + logger.Errorf("CreateInBatches err => %v", err) + } + return tx.RowsAffected +} + +// Delete 删除实体 +func (r UDMVOIPUser) Delete(username, neId string) int64 { + tx := db.DB("").Where("username = ? and ne_id = ?", username, neId).Delete(&model.UDMVOIPUser{}) + if err := tx.Error; err != nil { + logger.Errorf("Delete err => %v", err) + } + return tx.RowsAffected +} + +// DeletePrefixByUserName 删除前缀匹配的实体 +func (r UDMVOIPUser) DeletePrefixByUserName(username, neId string) int64 { + tx := db.DB("").Where("username like ? and ne_id = ?", fmt.Sprintf("%s%%", username), neId).Delete(&model.UDMVOIPUser{}) + if err := tx.Error; err != nil { + logger.Errorf("DeletePrefixByUserName err => %v", err) + } + return tx.RowsAffected +} diff --git a/src/modules/network_data/repository/udm_volte_ims.go b/src/modules/network_data/repository/udm_volte_ims.go new file mode 100644 index 00000000..b4fc935d --- /dev/null +++ b/src/modules/network_data/repository/udm_volte_ims.go @@ -0,0 +1,150 @@ +package repository + +import ( + "fmt" + "strings" + + "be.ems/src/framework/database/db" + "be.ems/src/framework/logger" + "be.ems/src/modules/network_data/model" +) + +// 实例化数据层 UDMVolteIMSUser 结构体 +var NewUDMVolteIMSUser = &UDMVolteIMSUser{} + +// UDMVolteIMSUser UDMVOIP用户信息表 数据层处理 +type UDMVolteIMSUser struct{} + +// ClearAndInsert 清空ne_id后新增实体 +func (r UDMVolteIMSUser) ClearAndInsert(neId string, uArr []model.UDMVolteIMSUser) int64 { + // 不指定neID时,用 TRUNCATE 清空表快 + // _, err := datasource.ExecDB("", "TRUNCATE TABLE udm_volte_ims", nil) + result := db.DB("").Where("ne_id = ?", neId).Unscoped().Delete(&model.UDMVolteIMSUser{}) + if result.Error != nil { + logger.Errorf("Delete err => %v", result.Error) + } + return r.Inserts(uArr) +} + +// SelectPage 根据条件分页查询 +func (r UDMVolteIMSUser) SelectPage(query map[string]string) (int64, []model.UDMVolteIMSUser) { + tx := db.DB("").Model(&model.UDMVolteIMSUser{}) + // 查询条件拼接 + if v, ok := query["imsi"]; ok && v != "" { + tx = tx.Where("imsi like ?", fmt.Sprintf("%%%s%%", v)) + } + if v, ok := query["msisdn"]; ok && v != "" { + tx = tx.Where("msisdn like ?", fmt.Sprintf("%%%s%%", v)) + } + if v, ok := query["neId"]; ok && v != "" { + tx = tx.Where("ne_id = ?", v) + } + if v, ok := query["tag"]; ok && v != "" { + tx = tx.Where("tag = ?", v) + } + if v, ok := query["vni"]; ok && v != "" { + tx = tx.Where("vni like ?", fmt.Sprintf("%%%s%%", v)) + } + if v, ok := query["imsis"]; ok && v != "" { + arr := strings.Split(v, ",") + tx = tx.Where("imsi in ?", arr) + // 勾选时,pageSize为勾选的数量 + query["pageSize"] = fmt.Sprint(len(arr)) + } + + var total int64 = 0 + rows := []model.UDMVolteIMSUser{} + + // 查询数量 长度为0直接返回 + if err := tx.Count(&total).Error; err != nil || total <= 0 { + return total, rows + } + + // 分页 + pageNum, pageSize := db.PageNumSize(query["pageNum"], query["pageSize"]) + tx = tx.Offset(int(pageNum * pageSize)).Limit(int(pageSize)) + + // 排序 + if v, ok := query["sortField"]; ok && v != "" { + sortSql := v + if o, ok := query["sortOrder"]; ok && o != "" { + if o == "desc" { + sortSql += " desc " + } else { + sortSql += " asc " + } + } + tx = tx.Order(sortSql) + } else { + tx = tx.Order("imsi asc") + } + + // 查询数据 + if err := tx.Find(&rows).Error; err != nil { + logger.Errorf("query err => %v", err) + } + + return total, rows +} + +// SelectList 根据实体查询 +func (r UDMVolteIMSUser) SelectList(u model.UDMVolteIMSUser) []model.UDMVolteIMSUser { + tx := db.DB("").Model(&model.UDMVolteIMSUser{}) + // 查询条件拼接 + if u.IMSI != "" { + tx = tx.Where("imsi = ?", u.IMSI) + } + if u.NeId != "" { + tx = tx.Where("ne_id = ?", u.NeId) + } + if u.Tag != "" { + tx = tx.Where("tag = ?", u.Tag) + } + + // 查询数据 + arr := []model.UDMVolteIMSUser{} + if err := tx.Order("imsi asc").Find(&arr).Error; err != nil { + logger.Errorf("query err => %v", err) + } + return arr +} + +// SelectByIMSIAndMSISDNAndNeID 通过imsi,msisdn,ne_id查询 +func (r UDMVolteIMSUser) SelectByIMSIAndMSISDNAndNeID(imsi, msisdn, neId string) model.UDMVolteIMSUser { + tx := db.DB("").Model(&model.UDMVolteIMSUser{}) + item := model.UDMVolteIMSUser{} + // 查询条件拼接 + tx = tx.Where("imsi = ? and msisdn = ? and ne_id = ?", imsi, msisdn, neId) + // 查询数据 + if err := tx.Order("imsi asc").Limit(1).Find(&item).Error; err != nil { + logger.Errorf("query err => %v", err) + } + return item +} + +// Insert 批量添加 +func (r UDMVolteIMSUser) Inserts(uArr []model.UDMVolteIMSUser) int64 { + tx := db.DB("").CreateInBatches(uArr, 500) + if err := tx.Error; err != nil { + logger.Errorf("CreateInBatches err => %v", err) + } + return tx.RowsAffected +} + +// Delete 删除实体 +func (r UDMVolteIMSUser) Delete(imsi, neId string) int64 { + tx := db.DB("").Where("imsi = ? and ne_id = ?", imsi, neId).Delete(&model.UDMVolteIMSUser{}) + if err := tx.Error; err != nil { + logger.Errorf("Delete err => %v", err) + } + return tx.RowsAffected +} + +// DeletePrefixByIMSI 删除前缀匹配的实体 +func (r UDMVolteIMSUser) DeletePrefixByIMSI(imsi, neId string) int64 { + tx := db.DB("").Where("imsi like ? and ne_id = ?", fmt.Sprintf("%s%%", imsi), neId).Delete(&model.UDMVolteIMSUser{}) + if err := tx.Error; err != nil { + logger.Errorf("DeletePrefixByIMSI err => %v", err) + } + return tx.RowsAffected +} diff --git a/src/modules/network_data/service/udm_voip.go b/src/modules/network_data/service/udm_voip.go new file mode 100644 index 00000000..10c95d92 --- /dev/null +++ b/src/modules/network_data/service/udm_voip.go @@ -0,0 +1,172 @@ +package service + +import ( + "fmt" + "strconv" + "strings" + + "be.ems/src/framework/database/redis" + "be.ems/src/modules/network_data/model" + "be.ems/src/modules/network_data/repository" + neService "be.ems/src/modules/network_element/service" +) + +// 实例化服务层 UDMVOIPUser 结构体 +var NewUDMVOIPUser = &UDMVOIPUser{ + udmVOIPRepository: repository.NewUDMVOIPUser, +} + +// UDMVOIP信息 服务层处理 +type UDMVOIPUser struct { + // UDMVOIP信息数据信息 + udmVOIPRepository *repository.UDMVOIPUser +} + +// dataByRedis UDMVOIP用户 db:0 中 voip:* +func (r UDMVOIPUser) dataByRedis(username, neId string) []model.UDMVOIPUser { + arr := []model.UDMVOIPUser{} + key := fmt.Sprintf("voip:%s", username) + source := fmt.Sprintf("UDM_%s", neId) + + // 网元主机的Redis客户端 + redisClient, err := neService.NewNeInfo.NeRunRedisClient("UDM", neId) + if err != nil { + return arr + } + defer func() { + redisClient.Close() + redis.ConnectPush(source, nil) + }() + redis.ConnectPush(source, redisClient.Client) + + voipArr, err := redis.GetKeys(source, key) + if err != nil { + return arr + } + mkv, err := redis.GetHashBatch(source, voipArr) + if err != nil { + return arr + } + + for k, m := range mkv { + // 跳过-号数据 voip:360000100000130 + username, hasPrefix := strings.CutPrefix(k, "voip:") + if strings.Contains(username, "-") || !hasPrefix { + continue + } + + a := model.UDMVOIPUser{ + NeId: neId, + UserName: username, + Password: m["password"], + } + arr = append(arr, a) + } + return arr +} + +// ResetData 重置VOIP用户数据,清空数据库重新同步Redis数据 +func (r UDMVOIPUser) ResetData(neId string) int64 { + arr := r.dataByRedis("*", neId) + // 数据清空后添加 + go r.udmVOIPRepository.ClearAndInsert(neId, arr) + return int64(len(arr)) +} + +// ParseInfo 解析单个用户userName信息 data从命令MML得到的结果 +func (r UDMVOIPUser) ParseInfo(neId string, data map[string]string) model.UDMVOIPUser { + u := model.UDMVOIPUser{ + NeId: neId, + UserName: data["username"], + Password: data["password"], + } + // 赋予ID + item := r.udmVOIPRepository.SelectByUserNameAndNeID(u.UserName, neId) + if item.ID != "" { + u.ID = item.ID + } + return u +} + +// FindByPage 分页查询数据库 +func (r UDMVOIPUser) FindByPage(query map[string]string) (int64, []model.UDMVOIPUser) { + return r.udmVOIPRepository.SelectPage(query) +} + +// Find 查询数据库 +func (r UDMVOIPUser) Find(u model.UDMVOIPUser) []model.UDMVOIPUser { + return r.udmVOIPRepository.SelectList(u) +} + +// Insert 从数据中读取后删除username再存入数据库 +func (r UDMVOIPUser) Insert(neId string, username string) int64 { + uArr := r.dataByRedis(username, neId) + if len(uArr) > 0 { + r.udmVOIPRepository.Delete(username, neId) + return r.udmVOIPRepository.Inserts(uArr) + } + return 0 +} + +// InsertData 导入文件数据 dataType目前两种:txt/csv +func (r UDMVOIPUser) InsertData(neId, dataType string, data any) int64 { + // imsi截取前缀,重新获取部分数据 + prefixes := make(map[string]struct{}) + + if dataType == "csv" { + for _, v := range data.([]map[string]string) { + username := v["username"] + if len(username) < 4 { + continue + } + prefix := username[:len(username)-3] + prefixes[prefix] = struct{}{} + } + } + if dataType == "txt" { + for _, v := range data.([][]string) { + username := v[0] + if len(username) < 4 { + continue + } + prefix := username[:len(username)-3] + prefixes[prefix] = struct{}{} + } + } + + // 根据前缀重新加载插入 + var num int64 = 0 + for prefix := range prefixes { + // 直接删除前缀的记录 + r.udmVOIPRepository.DeletePrefixByUserName(prefix, neId) + // keys voip:4600001000004* + arr := r.dataByRedis(prefix+"*", neId) + if len(arr) > 0 { + num += r.udmVOIPRepository.Inserts(arr) + } + } + return num +} + +// Delete 删除单个不重新加载 +func (r UDMVOIPUser) Delete(username, neId string) int64 { + return r.udmVOIPRepository.Delete(username, neId) +} + +// LoadData 重新加载从username开始num的数据 +func (r UDMVOIPUser) LoadData(neId, username, num string) { + startUserName, _ := strconv.ParseInt(username, 10, 64) + subNum, _ := strconv.ParseInt(num, 10, 64) + var i int64 + for i = 0; i < subNum; i++ { + keyUserName := fmt.Sprintf("%d", startUserName+i) + // 删除原数据 + r.udmVOIPRepository.Delete(keyUserName, neId) + // 加载数据 + arr := r.dataByRedis(keyUserName, neId) + if len(arr) < 1 { + continue + } + r.udmVOIPRepository.Inserts(arr) + } +} diff --git a/src/modules/network_data/service/udm_volte_ims.go b/src/modules/network_data/service/udm_volte_ims.go new file mode 100644 index 00000000..dbdb6146 --- /dev/null +++ b/src/modules/network_data/service/udm_volte_ims.go @@ -0,0 +1,194 @@ +package service + +import ( + "fmt" + "strconv" + "strings" + + "be.ems/src/framework/database/redis" + "be.ems/src/modules/network_data/model" + "be.ems/src/modules/network_data/repository" + neService "be.ems/src/modules/network_element/service" +) + +// 实例化服务层 UDMVolteIMSUser 结构体 +var NewUDMVolteIMSUser = &UDMVolteIMSUser{ + udmVolteIMSRepository: repository.NewUDMVolteIMSUser, +} + +// UDMVolteIMS信息 服务层处理 +type UDMVolteIMSUser struct { + // UDMVolteIMS信息数据信息 + udmVolteIMSRepository *repository.UDMVolteIMSUser +} + +// dataByRedis UDMVolteIMS用户 db:0 中 volte:* +func (r UDMVolteIMSUser) dataByRedis(imsi, neId string) []model.UDMVolteIMSUser { + arr := []model.UDMVolteIMSUser{} + key := fmt.Sprintf("volte:%s", imsi) + source := fmt.Sprintf("UDM_%s", neId) + + // 网元主机的Redis客户端 + redisClient, err := neService.NewNeInfo.NeRunRedisClient("UDM", neId) + if err != nil { + return arr + } + defer func() { + redisClient.Close() + redis.ConnectPush(source, nil) + }() + redis.ConnectPush(source, redisClient.Client) + + volteArr, err := redis.GetKeys(source, key) + if err != nil { + return arr + } + mkv, err := redis.GetHashBatch(source, volteArr) + if err != nil { + return arr + } + + for k, m := range mkv { + // volte:360000100000130:8612300000130 + keys := strings.Split(k, ":") + if len(keys) != 3 { + continue + } + + // "110011200004217@ims.mnc001.mcc110.3gppnetwork.org" + vni := "" + impiParts := strings.Split(m["impi"], "@") + if len(impiParts) > 1 { + vni = impiParts[1] + } + + a := model.UDMVolteIMSUser{ + NeId: neId, + IMSI: keys[1], + MSISDN: keys[2], + Tag: m["tag"], // volte = tag + VNI: vni, + } + arr = append(arr, a) + } + return arr +} + +// ResetData 重置VolteIMS用户数据,清空数据库重新同步Redis数据 +func (r UDMVolteIMSUser) ResetData(neId string) int64 { + authArr := r.dataByRedis("*", neId) + // 数据清空后添加 + go r.udmVolteIMSRepository.ClearAndInsert(neId, authArr) + return int64(len(authArr)) +} + +// ParseInfo 解析单个用户imsi信息 data从命令MML得到的结果 +func (r UDMVolteIMSUser) ParseInfo(neId string, data map[string]string) model.UDMVolteIMSUser { + // "110011200004217@ims.mnc001.mcc110.3gppnetwork.org" + vni := "" + impiParts := strings.Split(data["impi"], "@") + if len(impiParts) > 1 { + vni = impiParts[1] + } + if vni == "" { + return model.UDMVolteIMSUser{} + } + + u := model.UDMVolteIMSUser{ + NeId: neId, + IMSI: data["imsi"], + MSISDN: data["msisdn"], + Tag: data["volte_tag"], + VNI: vni, + } + // 赋予ID + item := r.udmVolteIMSRepository.SelectByIMSIAndMSISDNAndNeID(u.IMSI, u.MSISDN, neId) + if item.ID != "" { + u.ID = item.ID + } + return u +} + +// FindByPage 分页查询数据库 +func (r UDMVolteIMSUser) FindByPage(query map[string]string) (int64, []model.UDMVolteIMSUser) { + return r.udmVolteIMSRepository.SelectPage(query) +} + +// Find 查询数据库 +func (r UDMVolteIMSUser) Find(u model.UDMVolteIMSUser) []model.UDMVolteIMSUser { + return r.udmVolteIMSRepository.SelectList(u) +} + +// InsertByIMSI 从数据中读取后删除imsi再存入数据库 +// imsi长度15 +func (r UDMVolteIMSUser) InsertByIMSI(imsi, neId string) int64 { + uArr := r.dataByRedis(imsi+":*", neId) + if len(uArr) > 0 { + r.udmVolteIMSRepository.Delete(imsi, neId) + return r.udmVolteIMSRepository.Inserts(uArr) + } + return 0 +} + +// InsertData 导入文件数据 dataType目前两种:txt/csv +func (r UDMVolteIMSUser) InsertData(neId, dataType string, data any) int64 { + // imsi截取前缀,重新获取部分数据 + prefixes := make(map[string]struct{}) + + if dataType == "csv" { + for _, v := range data.([]map[string]string) { + imsi := v["imsi"] + if len(imsi) < 6 { + continue + } + prefix := imsi[:len(imsi)-4] + prefixes[prefix] = struct{}{} + } + } + if dataType == "txt" { + for _, v := range data.([][]string) { + imsi := v[0] + if len(imsi) < 6 { + continue + } + prefix := imsi[:len(imsi)-4] + prefixes[prefix] = struct{}{} + } + } + + // 根据前缀重新加载插入 + var num int64 = 0 + for prefix := range prefixes { + // 直接删除前缀的记录 + r.udmVolteIMSRepository.DeletePrefixByIMSI(prefix, neId) + // keys voip:4600001000004* + arr := r.dataByRedis(prefix+"*", neId) + if len(arr) > 0 { + num += r.udmVolteIMSRepository.Inserts(arr) + } + } + return num +} + +// Delete 删除单个不重新加载 +func (r UDMVolteIMSUser) Delete(imsi, neId string) int64 { + return r.udmVolteIMSRepository.Delete(imsi, neId) +} + +// LoadData 重新加载从imsi开始num的数据 +func (r UDMVolteIMSUser) LoadData(neId, imsiOrMsisdn, num string) { + startIMSIOrMsisdn, _ := strconv.ParseInt(imsiOrMsisdn, 10, 64) + subNum, _ := strconv.ParseInt(num, 10, 64) + var i int64 + for i = 0; i < subNum; i++ { + keyIMSI := fmt.Sprintf("%d", startIMSIOrMsisdn+i) + // 删除原数据 + r.udmVolteIMSRepository.Delete(keyIMSI, neId) + // 加载数据 + arr := r.dataByRedis(keyIMSI+":*", neId) + if len(arr) < 1 { + continue + } + r.udmVolteIMSRepository.Inserts(arr) + } +}