From 7e5a73ffa7d7e26e1e8cbc199cbb8982f233dcb5 Mon Sep 17 00:00:00 2001 From: zhangsz Date: Wed, 9 Apr 2025 14:16:35 +0800 Subject: [PATCH] feat: support ims user, voip auth data and backup UE data --- database/install/sys_dict_data1_i18n_zh.sql | 5 +- database/install/sys_dict_data2_i18n_en.sql | 5 +- database/install/sys_job.sql | 3 +- database/install/sys_menu.sql | 4 +- database/install/u_ims_user.sql | 36 ++ database/install/u_voip_auth.sql | 34 ++ .../upgrade/upg_sys_dict_data1_i18n_zh.sql | 5 +- .../upgrade/upg_sys_dict_data2_i18n_en.sql | 5 +- database/upgrade/upg_sys_job.sql | 3 +- database/upgrade/upg_sys_menu.sql | 4 +- database/upgrade/upg_u_ims_user.sql | 35 ++ database/upgrade/upg_u_voip_auth.sql | 33 ++ features/ue/api.go | 25 + features/ue/api/file_export.go | 40 ++ features/ue/api/ims_user.go | 68 +++ features/ue/api/voip_auth.go | 68 +++ features/ue/controller/file_export.go | 95 ++++ features/ue/controller/file_source.go | 57 ++ features/ue/controller/ims_user.go | 488 ++++++++++++++++++ features/ue/controller/voip_auth.go | 486 +++++++++++++++++ .../ue/file_export/{route.go => router.go} | 0 features/ue/ims_user/controller.go | 486 +++++++++++++++++ features/ue/ims_user/model.go | 26 + features/ue/ims_user/repository.go | 272 ++++++++++ features/ue/ims_user/router.go | 67 +++ features/ue/ims_user/service.go | 209 ++++++++ features/ue/model/file_export.go | 26 + features/ue/model/ims_user.go | 26 + features/ue/model/voip_auth.go | 17 + features/ue/repository/ims_user.go | 273 ++++++++++ features/ue/repository/voip_auth.go | 268 ++++++++++ features/ue/service.go | 17 - features/ue/service/ims_user.go | 211 ++++++++ features/ue/service/voip_auth.go | 170 ++++++ .../processor/exportUEData/exportUEData.go | 35 +- src/modules/network_data/network_data.go | 29 -- 36 files changed, 3569 insertions(+), 62 deletions(-) create mode 100755 database/install/u_ims_user.sql create mode 100755 database/install/u_voip_auth.sql create mode 100755 database/upgrade/upg_u_ims_user.sql create mode 100755 database/upgrade/upg_u_voip_auth.sql create mode 100644 features/ue/api.go create mode 100644 features/ue/api/file_export.go create mode 100644 features/ue/api/ims_user.go create mode 100644 features/ue/api/voip_auth.go create mode 100644 features/ue/controller/file_export.go create mode 100644 features/ue/controller/file_source.go create mode 100644 features/ue/controller/ims_user.go create mode 100644 features/ue/controller/voip_auth.go rename features/ue/file_export/{route.go => router.go} (100%) create mode 100644 features/ue/ims_user/controller.go create mode 100644 features/ue/ims_user/model.go create mode 100644 features/ue/ims_user/repository.go create mode 100644 features/ue/ims_user/router.go create mode 100644 features/ue/ims_user/service.go create mode 100644 features/ue/model/file_export.go create mode 100644 features/ue/model/ims_user.go create mode 100644 features/ue/model/voip_auth.go create mode 100644 features/ue/repository/ims_user.go create mode 100644 features/ue/repository/voip_auth.go delete mode 100644 features/ue/service.go create mode 100644 features/ue/service/ims_user.go create mode 100644 features/ue/service/voip_auth.go diff --git a/database/install/sys_dict_data1_i18n_zh.sql b/database/install/sys_dict_data1_i18n_zh.sql index af464e91..bddb2cef 100644 --- a/database/install/sys_dict_data1_i18n_zh.sql +++ b/database/install/sys_dict_data1_i18n_zh.sql @@ -703,10 +703,11 @@ INSERT INTO `sys_dict_data` VALUES (2192, 2192, 'menu.dashboard.overview.gnbBase INSERT INTO `sys_dict_data` VALUES (2193, 2193, 'menu.dashboard.overview.enbBase', '展示4G基站在线信息', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); INSERT INTO `sys_dict_data` VALUES (2194, 2194, 'menu.ueUser.imsUDM', 'IMS签约用户', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); INSERT INTO `sys_dict_data` VALUES (2195, 2195, 'menu.ueUser.imsUDMRemark', 'IMS签约用户菜单', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); -INSERT INTO `sys_dict_data` VALUES (2196, 2196, 'menu.ueUser.voipUDM', 'VoIP签约用户', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); -INSERT INTO `sys_dict_data` VALUES (2197, 2197, 'menu.ueUser.voipUDMRemark', 'VoIP签约用户菜单', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +INSERT INTO `sys_dict_data` VALUES (2196, 2196, 'menu.ueUser.voipUDM', 'VoIP鉴权数据', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +INSERT INTO `sys_dict_data` VALUES (2197, 2197, 'menu.ueUser.voipUDMRemark', 'VoIP鉴权数据菜单', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); INSERT INTO `sys_dict_data` VALUES (2198, 2198, 'menu.ueUser.exportFile', '导出文件管理', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); INSERT INTO `sys_dict_data` VALUES (2199, 2199, 'menu.ueUser.exportFileRemark', '导出文件管理菜单', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +INSERT INTO `sys_dict_data` VALUES (2200, 2200, 'job.backup.ue.data', '定期从UE用户和数据表导出文件至指定目录', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); -- multi-tenancy INSERT INTO `sys_dict_data` VALUES (11000, 11000, 'menu.security.tenant', '租户管理', 'i18n_zh', NULL, NULL, '1', 'supervisor', 1700000000000, NULL, 0, NULL); diff --git a/database/install/sys_dict_data2_i18n_en.sql b/database/install/sys_dict_data2_i18n_en.sql index a710064e..eec0cb2a 100644 --- a/database/install/sys_dict_data2_i18n_en.sql +++ b/database/install/sys_dict_data2_i18n_en.sql @@ -703,10 +703,11 @@ INSERT INTO `sys_dict_data` VALUES (4192, 4192, 'menu.dashboard.overview.gnbBase INSERT INTO `sys_dict_data` VALUES (4193, 4193, 'menu.dashboard.overview.enbBase', 'Display 4G base station online information', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); INSERT INTO `sys_dict_data` VALUES (4194, 4194, 'menu.ueUser.imsUDM', 'IMS Subscribers', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); INSERT INTO `sys_dict_data` VALUES (4195, 4195, 'menu.ueUser.imsUDMRemark', 'IMS Subscribers Menu', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); -INSERT INTO `sys_dict_data` VALUES (4196, 4196, 'menu.ueUser.voipUDM', 'VoIP Subscribers', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); -INSERT INTO `sys_dict_data` VALUES (4197, 4197, 'menu.ueUser.voipUDMRemark', 'VoIP Subscribers Menu', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +INSERT INTO `sys_dict_data` VALUES (4196, 4196, 'menu.ueUser.voipUDM', 'VoIP Authentication', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +INSERT INTO `sys_dict_data` VALUES (4197, 4197, 'menu.ueUser.voipUDMRemark', 'VoIP Authentication Menu', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); INSERT INTO `sys_dict_data` VALUES (4198, 4198, 'menu.ueUser.exportFile', 'Exported File Management', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); INSERT INTO `sys_dict_data` VALUES (4199, 4199, 'menu.ueUser.exportFileRemark', 'Exported File Management Menu', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +INSERT INTO `sys_dict_data` VALUES (4200, 4200, 'job.backup.ue.data', 'Regularly export files from UE users and data tables to a specified directory', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); -- 多租户 INSERT INTO `sys_dict_data` VALUES (14000, 14000, 'menu.security.tenant', 'Tenant Management', 'i18n_en', '', '', '1', 'supervisor', 1705550000000, '', 0, ''); diff --git a/database/install/sys_job.sql b/database/install/sys_job.sql index ac6be1dc..ffc8a2f0 100644 --- a/database/install/sys_job.sql +++ b/database/install/sys_job.sql @@ -42,7 +42,8 @@ INSERT INTO `sys_job` VALUES (11, 'job.exportOperateLog', 'SYSTEM', 'exportTable INSERT INTO `sys_job` VALUES (12, 'job.exportIMSCDR', 'SYSTEM', 'exportTable', '{\"duration\":1,\"tableName\":\"cdr_event_ims\",\"columns\":\"id,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.recordType\')) as record_type,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.callType\')) as call_type,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.callerParty\')) as caller_party,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.calledParty\')) as called_party,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.callDuration\')) as call_duration,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.serviceResult\')) as service_result,DATE_FORMAT(FROM_UNIXTIME(timestamp), \'%Y-%m-%d %H:%i:%s\') AS timestamp\",\"timeCol\":\"timestamp\",\"timeUnit\":\"second\",\"extras\":\"\",\"filePath\":\"/usr/local/omc/backup/ims_cdr\"}', '0 0 0/1 * * ?', '3', '0', '1', '1', 'supervisor', 1698478134842, 'admin', 1722224659251, ''); INSERT INTO `sys_job` VALUES (13, 'job.exportSMFCDR', 'SYSTEM', 'exportTable', '{\"duration\":1,\"tableName\":\"cdr_event_smf\",\"columns\":\"id,ne_type,ne_name,rm_uid,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.recordType\')) AS record_type,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.chargingID\')) AS charging_id,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.subscriberIdentifier.subscriptionIDType\')) AS subscriber_id_type,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.subscriberIdentifier.subscriptionIDData\')) AS subscriber_id_data,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.duration\')) AS duration,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.invocationTimestamp\')) as invocationTimestamp,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.listOfMultipleUnitUsage[*].usedUnitContainer[*].dataVolumeUplink\')) AS data_volume_uplink,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.listOfMultipleUnitUsage[*].usedUnitContainer[*].dataVolumeDownlink\')) AS data_volume_downlink,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.listOfMultipleUnitUsage[*].usedUnitContainer[*].dataTotalVolume\')) AS data_total_volume,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.pDUSessionChargingInformation.pDUAddress.pDUIPv4Address\')) AS pdu_ipv4_address,timestamp\",\"timeCol\":\"timestamp\",\"timeUnit\":\"second\",\"extras\":\"\",\"filePath\":\"/usr/local/omc/backup/smf_cdr\"}', '0 0 0/1 * * ?', '3', '0', '1', '1', 'supervisor', 1698478134842, 'admin', 1724309047797, ''); INSERT INTO `sys_job` VALUES (14, 'job.exportSMSCCDR', 'SYSTEM', 'exportTable', '{\"duration\":1,\"tableName\":\"cdr_event_smsc\",\"columns\":\"id,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.recordType\')) as record_type,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.serviceType\')) as service_type,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.callerParty\')) as caller_party,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.calledParty\')) as called_party,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.result\')) as result,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.updateTime\')) as update_time\",\"timeCol\":\"timestamp\",\"timeUnit\":\"second\",\"extras\":\"\",\"filePath\":\"/usr/local/omc/backup/smsc_cdr\"}', '0 0 0/1 * * ?', '3', '0', '1', '1', 'supervisor', 1698478134842, 'admin', 1724309047797, ''); -INSERT INTO `sys_job` VALUES (15, 'job.removeExportedFiles', 'SYSTEM', 'removeFile', '[{\"filePath\":\"/usr/local/omc/backup/operate_log\",\"maxDays\":30},{\"filePath\":\"/usr/local/omc/backup/ims_cdr\",\"maxDays\":30},{\"filePath\":\"/usr/local/omc/backup/smf_cdr\",\"maxDays\":30},{\"filePath\":\"/usr/local/omc/backup/smsc_cdr\",\"maxDays\":30}]', '0 10 0 * * ?', '3', '0', '1', '1', 'supervisor', 1698478134842, 'admin', 1728634085631, ''); +INSERT INTO `sys_job` VALUES (15, 'job.removeExportedFiles', 'SYSTEM', 'removeFile', '[{\"filePath\":\"/usr/local/omc/backup/operate_log\",\"maxDays\":30},{\"filePath\":\"/usr/local/omc/backup/ims_cdr\",\"maxDays\":30},{\"filePath\":\"/usr/local/omc/backup/smf_cdr\",\"maxDays\":30},{\"filePath\":\"/usr/local/omc/backup/smsc_cdr\",\"maxDays\":30},{\"filePath\":\"/usr/local/omc/backup/u_auth_user\",\"maxDays\":30},{\"filePath\":\"/usr/local/omc/backup/u_sub_user\",\"maxDays\":30},{\"filePath\":\"/usr/local/omc/backup/u_voip_auth\",\"maxDays\":30},{\"filePath\":\"/usr/local/omc/backup/u_ims_user\",\"maxDays\":30}]', '0 10 0 * * ?', '3', '0', '1', '1', 'supervisor', 1698478134842, 'admin', 1728634085631, ''); INSERT INTO `sys_job` VALUES (16, 'job.exportSGWCCDR', 'SYSTEM', 'exportTable', '{\"duration\":1,\"tableName\":\"cdr_event_sgwc\",\"columns\":\"id,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.recordType\')) as recordType,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.accessPointNameNI\')) as accessPointNameNI,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.servedIMSI\')) as IMSI,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.servedMSISDN\')) as MSISDN,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.servedPDPPDNAddress\')) as PdpAddress,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.duration\')) as duration,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.recordOpeningTime\')) as recordOpeningTime,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.chargingID\')) as chargingID,JSON_UNQUOTE(JSON_EXTRACT(cdr_json, \'$.listOfTrafficVolumes[0].dataVolumeGPRSDownlink\')) AS dataVolumeGPRSDownlink,JSON_UNQUOTE(JSON_EXTRACT(cdr_json, \'$.listOfTrafficVolumes[0].dataVolumeGPRsUplink\')) as dataVolumeGPRsUplink,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.userLocationInformation.tai.tac\')) as tac,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.userLocationInformation.ecgi.eutraCellId\')) as cellID\",\"timeCol\":\"timestamp\",\"timeUnit\":\"second\",\"extras\":\"\",\"filePath\":\"/usr/local/omc/backup/sgwc_cdr\"}', '0 0 0/1 * * ?', '3', '0', '1', '1', 'supervisor', 1698478134842, 'admin', 1724309047797, ''); +INSERT INTO `sys_job` VALUES (17, 'job.backup.ue.data', 'SYSTEM', 'exportUEData', '[{\"tableName\":\"u_auth_user\",\"columns\":\"imsi,ki,algo,amf,opc\",\"extras\":\"\",\"serviceName\":\"UDMAuthUser\",\"orderBy\":\"imsi\",\"orderType\":\"desc\", \"fileType\":\"txt\",\"filePath\":\"/usr/local/omc/backup/u_auth_user\"},{\"tableName\":\"u_sub_user\",\"columns\":\"imsi,msisdn,ambr,nssai,arfb,sar,rat,cn_type,smf_sel,sm_data,eps_flag,eps_odb,hplmn_odb,ard,epstpl,context_id,apn_context,static_ip\",\"extras\":\"\",\"serviceName\":\"UDMSubUser\",\"orderBy\":\"imsi\",\"orderType\":\"desc\", \"fileType\":\"txt\",\"filePath\":\"/usr/local/omc/backup/u_sub_user\"},{\"tableName\":\"u_voip_auth\",\"columns\":\"user_name,password\",\"extras\":\"\",\"serviceName\":\"UDMVoIPAuth\",\"orderBy\":\"user_name\",\"orderType\":\"desc\", \"fileType\":\"txt\",\"filePath\":\"/usr/local/omc/backup/u_voip_auth\"},{\"tableName\":\"u_ims_user\",\"columns\":\"imsi,msisdn,volte,vni\",\"extras\":\"\",\"serviceName\":\"UDMIMSUser\",\"orderBy\":\"imsi\",\"orderType\":\"desc\", \"fileType\":\"txt\",\"filePath\":\"/usr/local/omc/backup/u_ims_user\"}]', '0 35 0 * * ?', '3', '0', '1', '1', 'supervisor', 1698478134842, 'admin', 1743479268652, ''); SET FOREIGN_KEY_CHECKS = 1; diff --git a/database/install/sys_menu.sql b/database/install/sys_menu.sql index 3e1f5572..79464e5b 100644 --- a/database/install/sys_menu.sql +++ b/database/install/sys_menu.sql @@ -129,8 +129,8 @@ INSERT INTO `sys_menu` VALUES (1055, 'menu.common.edit', 116, 5, '#', NULL, '1', INSERT INTO `sys_menu` VALUES (1056, 'menu.common.export', 116, 6, '#', NULL, '1', '1', 'B', '1', '1', 'monitor:job:export', '#', 'supervisor', 1700000000000, NULL, 0, NULL); INSERT INTO `sys_menu` VALUES (2009, 'menu.ueUser.authUDM', 5, 1, 'auth', 'neUser/auth/index', '1', '1', 'M', '1', '1', 'neUser:auth:index', 'icon-xiangmuchengyuan', 'supervisor', 1700000000000, NULL, 0, 'menu.ueUser.authUDMRemark'); INSERT INTO `sys_menu` VALUES (2010, 'menu.ueUser.subUDM', 5, 2, 'sub', 'neUser/sub/index', '1', '1', 'M', '1', '1', 'neUser:sub:index', 'icon-xiangmuchengyuan', 'supervisor', 1700000000000, NULL, 0, 'menu.ueUser.subUDMRemark'); -INSERT INTO `sys_menu` VALUES (2011, 'menu.ueUser.imsUDM', 5, 3, 'imsUDM', 'neUser/imsUDM/index', '1', '1', 'M', '1', '1', 'neUser:imsUDM:index', 'icon-xiangmuchengyuan', 'supervisor', 1700000000000, NULL, 0, 'menu.ueUser.imsUDMRemark'); -INSERT INTO `sys_menu` VALUES (2012, 'menu.ueUser.voipUDM', 5, 4, 'voip', 'neUser/voip/index', '1', '1', 'M', '1', '1', 'neUser:voip:index', 'icon-xiangmuchengyuan', 'supervisor', 1700000000000, NULL, 0, 'menu.ueUser.voipUDMRemark'); +INSERT INTO `sys_menu` VALUES (2011, 'menu.ueUser.voipUDM', 5, 3, 'voip', 'neUser/voip/index', '1', '1', 'M', '1', '1', 'neUser:voip:index', 'icon-xiangmuchengyuan', 'supervisor', 1700000000000, NULL, 0, 'menu.ueUser.voipUDMRemark'); +INSERT INTO `sys_menu` VALUES (2012, 'menu.ueUser.imsUDM', 5, 4, 'imsUDM', 'neUser/imsUDM/index', '1', '1', 'M', '1', '1', 'neUser:imsUDM:index', 'icon-xiangmuchengyuan', 'supervisor', 1700000000000, NULL, 0, 'menu.ueUser.imsUDMRemark'); INSERT INTO `sys_menu` VALUES (2079, 'menu.ueUser.exportFile', 5, 20, 'exportFile', 'neUser/exportFile/index', '1', '1', 'M', '1', '1', 'neUser:exportFile:index', 'icon-xiangmuchengyuan', 'supervisor', 1700000000000, NULL, 0, 'menu.ueUser.exportFileRemark'); -- INSERT INTO `sys_menu` VALUES (2075, 'menu.config.neManage', 4, 1, 'neManage', 'configManage/neManage/index', '1', '0', 'M', '1', '0', 'configManage:neManage:index', 'icon-biaoqing', 'supervisor', 1700000000000, NULL, 0, 'menu.config.neManageRemark'); -- INSERT INTO `sys_menu` VALUES (2078, 'menu.config.backupManage', 4, 100, 'backupManage', 'configManage/backupManage/index', '1', '0', 'M', '1', '0', 'configManage:backupManage:index', 'icon-soutubiao', 'supervisor', 1700000000000, NULL, 0, 'menu.config.backupManageRemark'); diff --git a/database/install/u_ims_user.sql b/database/install/u_ims_user.sql new file mode 100755 index 00000000..98cf7227 --- /dev/null +++ b/database/install/u_ims_user.sql @@ -0,0 +1,36 @@ +/* + Navicat Premium Data Transfer + + Source Server : omc@192.168.2.211 + Source Server Type : MariaDB + Source Server Version : 100621 (10.6.21-MariaDB-0ubuntu0.22.04.2) + Source Host : 192.168.2.211:33066 + Source Schema : tenants_db + + Target Server Type : MariaDB + Target Server Version : 100621 (10.6.21-MariaDB-0ubuntu0.22.04.2) + File Encoding : 65001 + + Date: 09/04/2025 09:22:00 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for u_ims_user +-- ---------------------------- +DROP TABLE IF EXISTS `u_ims_user`; +CREATE TABLE `u_ims_user` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `ne_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `imsi` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'SIM/USIMID', + `msisdn` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '', + `volte` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '', + `vni` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '', + `tenant_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'multi-tenancy refer to sys_tenant.tenant_id', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_imsi_ne`(`imsi`, `ne_id`) USING BTREE COMMENT 'imsi_neid' +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'UDM' ROW_FORMAT = Dynamic; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/database/install/u_voip_auth.sql b/database/install/u_voip_auth.sql new file mode 100755 index 00000000..07fd2748 --- /dev/null +++ b/database/install/u_voip_auth.sql @@ -0,0 +1,34 @@ +/* + Navicat Premium Data Transfer + + Source Server : omc@192.168.2.211 + Source Server Type : MariaDB + Source Server Version : 100621 (10.6.21-MariaDB-0ubuntu0.22.04.2) + Source Host : 192.168.2.211:33066 + Source Schema : tenants_db + + Target Server Type : MariaDB + Target Server Version : 100621 (10.6.21-MariaDB-0ubuntu0.22.04.2) + File Encoding : 65001 + + Date: 09/04/2025 09:26:02 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for u_voip_auth +-- ---------------------------- +DROP TABLE IF EXISTS `u_voip_auth`; +CREATE TABLE `u_voip_auth` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `ne_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `user_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `password` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '', + `tenant_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'multi-tenancy refer to sys_tenant.tenant_id', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_user_name`(`user_name`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'UDM' ROW_FORMAT = Dynamic; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/database/upgrade/upg_sys_dict_data1_i18n_zh.sql b/database/upgrade/upg_sys_dict_data1_i18n_zh.sql index 93552d69..0f414c71 100644 --- a/database/upgrade/upg_sys_dict_data1_i18n_zh.sql +++ b/database/upgrade/upg_sys_dict_data1_i18n_zh.sql @@ -710,10 +710,11 @@ REPLACE INTO `sys_dict_data` VALUES (2192, 2192, 'menu.dashboard.overview.gnbBas REPLACE INTO `sys_dict_data` VALUES (2193, 2193, 'menu.dashboard.overview.enbBase', '展示4G基站在线信息', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); REPLACE INTO `sys_dict_data` VALUES (2194, 2194, 'menu.ueUser.imsUDM', 'IMS签约用户', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); REPLACE INTO `sys_dict_data` VALUES (2195, 2195, 'menu.ueUser.imsUDMRemark', 'IMS签约用户菜单', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); -REPLACE INTO `sys_dict_data` VALUES (2196, 2196, 'menu.ueUser.voipUDM', 'VoIP签约用户', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); -REPLACE INTO `sys_dict_data` VALUES (2197, 2197, 'menu.ueUser.voipUDMRemark', 'VoIP签约用户菜单', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +REPLACE INTO `sys_dict_data` VALUES (2196, 2196, 'menu.ueUser.voipUDM', 'VoIP鉴权数据', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +REPLACE INTO `sys_dict_data` VALUES (2197, 2197, 'menu.ueUser.voipUDMRemark', 'VoIP鉴权数据菜单', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); REPLACE INTO `sys_dict_data` VALUES (2198, 2198, 'menu.ueUser.exportFile', '导出文件管理', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); REPLACE INTO `sys_dict_data` VALUES (2199, 2199, 'menu.ueUser.exportFileRemark', '导出文件管理菜单', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +REPLACE INTO `sys_dict_data` VALUES (2200, 2200, 'job.backup.ue.data', '定期备份UE用户和数据表', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); -- multi-tenancy REPLACE INTO `sys_dict_data` VALUES (11000, 11000, 'menu.security.tenant', '租户管理', 'i18n_zh', NULL, NULL, '1', 'supervisor', 1700000000000, NULL, 0, NULL); diff --git a/database/upgrade/upg_sys_dict_data2_i18n_en.sql b/database/upgrade/upg_sys_dict_data2_i18n_en.sql index a1c397d9..bdd92ec5 100644 --- a/database/upgrade/upg_sys_dict_data2_i18n_en.sql +++ b/database/upgrade/upg_sys_dict_data2_i18n_en.sql @@ -709,10 +709,11 @@ REPLACE INTO `sys_dict_data` VALUES (4192, 4192, 'menu.dashboard.overview.gnbBas REPLACE INTO `sys_dict_data` VALUES (4193, 4193, 'menu.dashboard.overview.enbBase', 'Display 4G base station online information', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); REPLACE INTO `sys_dict_data` VALUES (4194, 4194, 'menu.ueUser.imsUDM', 'IMS Subscribers', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); REPLACE INTO `sys_dict_data` VALUES (4195, 4195, 'menu.ueUser.imsUDMRemark', 'IMS Subscribers Menu', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); -REPLACE INTO `sys_dict_data` VALUES (4196, 4196, 'menu.ueUser.voipUDM', 'VoIP Subscribers', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); -REPLACE INTO `sys_dict_data` VALUES (4197, 4197, 'menu.ueUser.voipUDMRemark', 'VoIP Subscribers Menu', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +REPLACE INTO `sys_dict_data` VALUES (4196, 4196, 'menu.ueUser.voipUDM', 'VoIP Authentication', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +REPLACE INTO `sys_dict_data` VALUES (4197, 4197, 'menu.ueUser.voipUDMRemark', 'VoIP Authentication Menu', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); REPLACE INTO `sys_dict_data` VALUES (4198, 4198, 'menu.ueUser.exportFile', 'Exported File Management', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); REPLACE INTO `sys_dict_data` VALUES (4199, 4199, 'menu.ueUser.exportFileRemark', 'Exported File Management Menu', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +REPLACE INTO `sys_dict_data` VALUES (4200, 4200, 'job.backup.ue.data', 'Backup regularly UE users and data', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); -- 多租户 REPLACE INTO `sys_dict_data` VALUES (14000, 14000, 'menu.security.tenant', 'Tenant Management', 'i18n_en', '', '', '1', 'supervisor', 1705550000000, '', 0, ''); diff --git a/database/upgrade/upg_sys_job.sql b/database/upgrade/upg_sys_job.sql index 5ffd8f59..79d02fb7 100644 --- a/database/upgrade/upg_sys_job.sql +++ b/database/upgrade/upg_sys_job.sql @@ -46,7 +46,8 @@ REPLACE INTO `sys_job` VALUES (11, 'job.exportOperateLog', 'SYSTEM', 'exportTabl REPLACE INTO `sys_job` VALUES (12, 'job.exportIMSCDR', 'SYSTEM', 'exportTable', '{\"duration\":1,\"tableName\":\"cdr_event_ims\",\"columns\":\"id,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.recordType\')) as record_type,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.callType\')) as call_type,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.callerParty\')) as caller_party,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.calledParty\')) as called_party,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.callDuration\')) as call_duration,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.serviceResult\')) as service_result,DATE_FORMAT(FROM_UNIXTIME(timestamp), \'%Y-%m-%d %H:%i:%s\') AS timestamp\",\"timeCol\":\"timestamp\",\"timeUnit\":\"second\",\"extras\":\"\",\"filePath\":\"/usr/local/omc/backup/ims_cdr\"}', '0 0 0/1 * * ?', '3', '0', '1', '1', 'supervisor', 1698478134842, 'admin', 1722224659251, ''); REPLACE INTO `sys_job` VALUES (13, 'job.exportSMFCDR', 'SYSTEM', 'exportTable', '{\"duration\":1,\"tableName\":\"cdr_event_smf\",\"columns\":\"id,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.recordType\')) AS record_type,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.chargingID\')) AS charging_id,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.subscriberIdentifier.subscriptionIDType\')) AS subscriber_id_type,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.subscriberIdentifier.subscriptionIDData\')) AS subscriber_id_data,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.duration\')) AS duration,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.invocationTimestamp\')) as invocationTimestamp,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.listOfMultipleUnitUsage[*].usedUnitContainer[*].dataVolumeUplink\')) AS data_volume_uplink,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.listOfMultipleUnitUsage[*].usedUnitContainer[*].dataVolumeDownlink\')) AS data_volume_downlink,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.listOfMultipleUnitUsage[*].usedUnitContainer[*].dataTotalVolume\')) AS data_total_volume,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.pDUSessionChargingInformation.pDUAddress.pDUIPv4Address\')) AS pdu_ipv4_address,timestamp\",\"timeCol\":\"timestamp\",\"timeUnit\":\"second\",\"extras\":\"\",\"filePath\":\"/usr/local/omc/backup/smf_cdr\"}', '0 0 0/1 * * ?', '3', '0', '1', '1', 'supervisor', 1698478134842, 'admin', 1724309047797, ''); REPLACE INTO `sys_job` VALUES (14, 'job.exportSMSCCDR', 'SYSTEM', 'exportTable', '{\"duration\":1,\"tableName\":\"cdr_event_smsc\",\"columns\":\"id,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.recordType\')) as record_type,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.serviceType\')) as service_type,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.callerParty\')) as caller_party,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.calledParty\')) as called_party,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.result\')) as result,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.updateTime\')) as update_time\",\"timeCol\":\"timestamp\",\"timeUnit\":\"second\",\"extras\":\"\",\"filePath\":\"/usr/local/omc/backup/smsc_cdr\"}', '0 0 0/1 * * ?', '3', '0', '1', '1', 'supervisor', 1698478134842, 'admin', 1724309047797, ''); -REPLACE INTO `sys_job` VALUES (15, 'job.removeExportedFiles', 'SYSTEM', 'removeFile', '[{\"filePath\":\"/usr/local/omc/backup/operate_log\",\"maxDays\":30},{\"filePath\":\"/usr/local/omc/backup/ims_cdr\",\"maxDays\":30},{\"filePath\":\"/usr/local/omc/backup/smf_cdr\",\"maxDays\":30},{\"filePath\":\"/usr/local/omc/backup/smsc_cdr\",\"maxDays\":30}]', '0 10 0 * * ?', '3', '0', '1', '1', 'supervisor', 1698478134842, 'admin', 1728634085631, ''); +REPLACE INTO `sys_job` VALUES (15, 'job.removeExportedFiles', 'SYSTEM', 'removeFile', '[{\"filePath\":\"/usr/local/omc/backup/operate_log\",\"maxDays\":30},{\"filePath\":\"/usr/local/omc/backup/ims_cdr\",\"maxDays\":30},{\"filePath\":\"/usr/local/omc/backup/smf_cdr\",\"maxDays\":30},{\"filePath\":\"/usr/local/omc/backup/smsc_cdr\",\"maxDays\":30},{\"filePath\":\"/usr/local/omc/backup/u_auth_user\",\"maxDays\":30},{\"filePath\":\"/usr/local/omc/backup/u_sub_user\",\"maxDays\":30},{\"filePath\":\"/usr/local/omc/backup/u_voip_auth\",\"maxDays\":30},{\"filePath\":\"/usr/local/omc/backup/u_ims_user\",\"maxDays\":30}]', '0 10 0 * * ?', '3', '0', '1', '1', 'supervisor', 1698478134842, 'admin', 1728634085631, ''); REPLACE INTO `sys_job` VALUES (16, 'job.exportSGWCCDR', 'SYSTEM', 'exportTable', '{\"duration\":1,\"tableName\":\"cdr_event_sgwc\",\"columns\":\"id,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.recordType\')) as recordType,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.accessPointNameNI\')) as accessPointNameNI,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.servedIMSI\')) as IMSI,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.servedMSISDN\')) as MSISDN,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.servedPDPPDNAddress\')) as PdpAddress,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.duration\')) as duration,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.recordOpeningTime\')) as recordOpeningTime,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.chargingID\')) as chargingID,JSON_UNQUOTE(JSON_EXTRACT(cdr_json, \'$.listOfTrafficVolumes[0].dataVolumeGPRSDownlink\')) AS dataVolumeGPRSDownlink,JSON_UNQUOTE(JSON_EXTRACT(cdr_json, \'$.listOfTrafficVolumes[0].dataVolumeGPRsUplink\')) as dataVolumeGPRsUplink,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.userLocationInformation.tai.tac\')) as tac,JSON_UNQUOTE(JSON_EXTRACT(cdr_json,\'$.userLocationInformation.ecgi.eutraCellId\')) as cellID\",\"timeCol\":\"timestamp\",\"timeUnit\":\"second\",\"extras\":\"\",\"filePath\":\"/usr/local/omc/backup/sgwc_cdr\"}', '0 0 0/1 * * ?', '3', '0', '1', '1', 'supervisor', 1698478134842, 'admin', 1724309047797, ''); +REPLACE INTO `sys_job` VALUES (17, 'job.backup.ue.data', 'SYSTEM', 'exportUEData', '[{\"tableName\":\"u_auth_user\",\"columns\":\"imsi,ki,algo,amf,opc\",\"extras\":\"\",\"serviceName\":\"UDMAuthUser\",\"orderBy\":\"imsi\",\"orderType\":\"desc\", \"fileType\":\"txt\",\"filePath\":\"/usr/local/omc/backup/u_auth_user\"},{\"tableName\":\"u_sub_user\",\"columns\":\"imsi,msisdn,ambr,nssai,arfb,sar,rat,cn_type,smf_sel,sm_data,eps_flag,eps_odb,hplmn_odb,ard,epstpl,context_id,apn_context,static_ip\",\"extras\":\"\",\"serviceName\":\"UDMSubUser\",\"orderBy\":\"imsi\",\"orderType\":\"desc\", \"fileType\":\"txt\",\"filePath\":\"/usr/local/omc/backup/u_sub_user\"},{\"tableName\":\"u_voip_auth\",\"columns\":\"user_name,password\",\"extras\":\"\",\"serviceName\":\"UDMVoIPAuth\",\"orderBy\":\"user_name\",\"orderType\":\"desc\", \"fileType\":\"txt\",\"filePath\":\"/usr/local/omc/backup/u_voip_auth\"},{\"tableName\":\"u_ims_user\",\"columns\":\"imsi,msisdn,volte,vni\",\"extras\":\"\",\"serviceName\":\"UDMIMSUser\",\"orderBy\":\"imsi\",\"orderType\":\"desc\", \"fileType\":\"txt\",\"filePath\":\"/usr/local/omc/backup/u_ims_user\"}]', '0 35 0 * * ?', '3', '0', '1', '1', 'supervisor', 1698478134842, 'admin', 1743479268652, ''); SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/database/upgrade/upg_sys_menu.sql b/database/upgrade/upg_sys_menu.sql index 0adc6337..31b68ef8 100644 --- a/database/upgrade/upg_sys_menu.sql +++ b/database/upgrade/upg_sys_menu.sql @@ -112,8 +112,8 @@ REPLACE INTO `sys_menu` VALUES (1055, 'menu.common.edit', 116, 5, '#', NULL, '1' REPLACE INTO `sys_menu` VALUES (1056, 'menu.common.export', 116, 6, '#', NULL, '1', '1', 'B', '1', '1', 'monitor:job:export', '#', 'supervisor', 1700000000000, NULL, 0, NULL); REPLACE INTO `sys_menu` VALUES (2009, 'menu.ueUser.authUDM', 5, 1, 'auth', 'neUser/auth/index', '1', '1', 'M', '1', '1', 'neUser:auth:index', 'icon-xiangmuchengyuan', 'supervisor', 1700000000000, NULL, 0, 'menu.ueUser.authUDMRemark'); REPLACE INTO `sys_menu` VALUES (2010, 'menu.ueUser.subUDM', 5, 2, 'sub', 'neUser/sub/index', '1', '1', 'M', '1', '1', 'neUser:sub:index', 'icon-xiangmuchengyuan', 'supervisor', 1700000000000, NULL, 0, 'menu.ueUser.subUDMRemark'); -REPLACE INTO `sys_menu` VALUES (2011, 'menu.ueUser.imsUDM', 5, 3, 'imsUDM', 'neUser/imsUDM/index', '1', '1', 'M', '1', '1', 'neUser:imsUDM:index', 'icon-xiangmuchengyuan', 'supervisor', 1700000000000, NULL, 0, 'menu.ueUser.imsUDMRemark'); -REPLACE INTO `sys_menu` VALUES (2012, 'menu.ueUser.voipUDM', 5, 4, 'voip', 'neUser/voip/index', '1', '1', 'M', '1', '1', 'neUser:voip:index', 'icon-xiangmuchengyuan', 'supervisor', 1700000000000, NULL, 0, 'menu.ueUser.voipUDMRemark'); +REPLACE INTO `sys_menu` VALUES (2011, 'menu.ueUser.voipUDM', 5, 3, 'voip', 'neUser/voip/index', '1', '1', 'M', '1', '1', 'neUser:voip:index', 'icon-xiangmuchengyuan', 'supervisor', 1700000000000, NULL, 0, 'menu.ueUser.voipUDMRemark'); +REPLACE INTO `sys_menu` VALUES (2012, 'menu.ueUser.imsUDM', 5, 4, 'imsUDM', 'neUser/imsUDM/index', '1', '1', 'M', '1', '1', 'neUser:imsUDM:index', 'icon-xiangmuchengyuan', 'supervisor', 1700000000000, NULL, 0, 'menu.ueUser.imsUDMRemark'); REPLACE INTO `sys_menu` VALUES (2079, 'menu.ueUser.exportFile', 5, 20, 'exportFile', 'neUser/exportFile/index', '1', '1', 'M', '1', '1', 'neUser:exportFile:index', 'icon-xiangmuchengyuan', 'supervisor', 1700000000000, NULL, 0, 'menu.ueUser.exportFileRemark'); -- REPLACE INTO `sys_menu` VALUES (2075, 'menu.config.neManage', 4, 1, 'neManage', 'configManage/neManage/index', '1', '0', 'M', '1', '0', 'configManage:neManage:index', 'icon-biaoqing', 'supervisor', 1700000000000, NULL, 0, 'menu.config.neManageRemark'); -- REPLACE INTO `sys_menu` VALUES (2078, 'menu.config.backupManage', 4, 100, 'backupManage', 'configManage/backupManage/index', '1', '0', 'M', '1', '0', 'configManage:backupManage:index', 'icon-soutubiao', 'supervisor', 1700000000000, NULL, 0, 'menu.config.backupManageRemark'); diff --git a/database/upgrade/upg_u_ims_user.sql b/database/upgrade/upg_u_ims_user.sql new file mode 100755 index 00000000..e0eea667 --- /dev/null +++ b/database/upgrade/upg_u_ims_user.sql @@ -0,0 +1,35 @@ +/* + Navicat Premium Data Transfer + + Source Server : omc@192.168.2.211 + Source Server Type : MariaDB + Source Server Version : 100621 (10.6.21-MariaDB-0ubuntu0.22.04.2) + Source Host : 192.168.2.211:33066 + Source Schema : tenants_db + + Target Server Type : MariaDB + Target Server Version : 100621 (10.6.21-MariaDB-0ubuntu0.22.04.2) + File Encoding : 65001 + + Date: 09/04/2025 09:22:00 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for u_ims_user +-- ---------------------------- +CREATE TABLE IF NOT EXISTS `u_ims_user` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `ne_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `imsi` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'SIM/USIMID', + `msisdn` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '', + `volte` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '', + `vni` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '', + `tenant_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'multi-tenancy refer to sys_tenant.tenant_id', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_imsi_ne`(`imsi`, `ne_id`) USING BTREE COMMENT 'imsi_neid' +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'UDM' ROW_FORMAT = Dynamic; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/database/upgrade/upg_u_voip_auth.sql b/database/upgrade/upg_u_voip_auth.sql new file mode 100755 index 00000000..687cfa89 --- /dev/null +++ b/database/upgrade/upg_u_voip_auth.sql @@ -0,0 +1,33 @@ +/* + Navicat Premium Data Transfer + + Source Server : omc@192.168.2.211 + Source Server Type : MariaDB + Source Server Version : 100621 (10.6.21-MariaDB-0ubuntu0.22.04.2) + Source Host : 192.168.2.211:33066 + Source Schema : tenants_db + + Target Server Type : MariaDB + Target Server Version : 100621 (10.6.21-MariaDB-0ubuntu0.22.04.2) + File Encoding : 65001 + + Date: 09/04/2025 09:26:56 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for u_voip_auth +-- ---------------------------- +CREATE TABLE IF NOT EXISTS `u_voip_auth` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `ne_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `user_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `password` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '', + `tenant_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'multi-tenancy refer to sys_tenant.tenant_id', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_user_name`(`user_name`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 19570 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'UDM' ROW_FORMAT = Dynamic; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/features/ue/api.go b/features/ue/api.go new file mode 100644 index 00000000..191612de --- /dev/null +++ b/features/ue/api.go @@ -0,0 +1,25 @@ +// log management package + +package ue + +import ( + // "be.ems/features/ue/file_export" + // "be.ems/features/ue/ims_user" + "be.ems/features/ue/api" + "be.ems/lib/log" + "github.com/gin-gonic/gin" +) + +func InitSubServiceRoute(r *gin.Engine) { + log.Info("======init UE management group gin.Engine") + + ueGroup := r.Group("/ue") + // register sub modules routes + // file_export.Register(ueGroup) + // ims_user.Register(ueGroup) + api.UDMIMSUserRegister(ueGroup) + api.FileExportRegister(ueGroup) + api.VoIPAuthRegister(ueGroup) + // register other sub modules routes +} + diff --git a/features/ue/api/file_export.go b/features/ue/api/file_export.go new file mode 100644 index 00000000..2ebfd384 --- /dev/null +++ b/features/ue/api/file_export.go @@ -0,0 +1,40 @@ +package api + +import ( + "be.ems/src/framework/middleware" + "be.ems/features/ue/controller" + "github.com/gin-gonic/gin" +) + +// Register Routes for file_export +func FileExportRegister(r *gin.RouterGroup) { + + ueTable := r.Group("/table") + { + var m *controller.FileSource + ueTable.GET("/list", + middleware.PreAuthorize(nil), + m.GetFileExportTable, + ) + } + ueFile := r.Group("/file") + { + var f *controller.FileExport + ueFile.GET("/list", + middleware.PreAuthorize(nil), + f.GetFileList, + ) + ueFile.GET("/total", + middleware.PreAuthorize(nil), + f.Total, + ) + ueFile.GET("/:fileName", + middleware.PreAuthorize(nil), + f.DownloadHandler, + ) + ueFile.DELETE("/:fileName", + middleware.PreAuthorize(nil), + f.Delete, + ) + } +} diff --git a/features/ue/api/ims_user.go b/features/ue/api/ims_user.go new file mode 100644 index 00000000..aeaf1a8f --- /dev/null +++ b/features/ue/api/ims_user.go @@ -0,0 +1,68 @@ +package api + +import ( + "be.ems/src/framework/middleware" + "be.ems/src/framework/middleware/collectlogs" + "be.ems/src/framework/middleware/repeat" + "be.ems/features/ue/controller" + + "github.com/gin-gonic/gin" +) + +// @Description Register Routes for ims_user +func UDMIMSUserRegister(r *gin.RouterGroup) { + + udmIMSUserGroup := r.Group("/udm/imsuser") + { + udmIMSUserGroup.PUT("/resetData/:neId", + repeat.RepeatSubmit(5), + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmIMSUser", collectlogs.BUSINESS_TYPE_CLEAN)), + controller.NewIMSUserController.ResetData, + ) + udmIMSUserGroup.GET("/list", + middleware.PreAuthorize(nil), + controller.NewIMSUserController.List, + ) + udmIMSUserGroup.GET("/:neId/:imsi", + middleware.PreAuthorize(nil), + controller.NewIMSUserController.Info, + ) + udmIMSUserGroup.POST("/:neId", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmIMSUser", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewIMSUserController.Add, + ) + udmIMSUserGroup.POST("/:neId/:num", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmIMSUser", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewIMSUserController.Adds, + ) + udmIMSUserGroup.PUT("/:neId", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmIMSUser", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewIMSUserController.Edit, + ) + udmIMSUserGroup.DELETE("/:neId/:imsi", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmIMSUser", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewIMSUserController.Remove, + ) + udmIMSUserGroup.DELETE("/:neId/:imsi/:num", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmIMSUser", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewIMSUserController.Removes, + ) + udmIMSUserGroup.POST("/export", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmIMSUser", collectlogs.BUSINESS_TYPE_EXPORT)), + controller.NewIMSUserController.Export, + ) + udmIMSUserGroup.POST("/import", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmIMSUser", collectlogs.BUSINESS_TYPE_IMPORT)), + controller.NewIMSUserController.Import, + ) + } +} + diff --git a/features/ue/api/voip_auth.go b/features/ue/api/voip_auth.go new file mode 100644 index 00000000..d1abecb8 --- /dev/null +++ b/features/ue/api/voip_auth.go @@ -0,0 +1,68 @@ +package api + +import ( + "be.ems/src/framework/middleware" + "be.ems/src/framework/middleware/collectlogs" + "be.ems/src/framework/middleware/repeat" + "be.ems/features/ue/controller" + + "github.com/gin-gonic/gin" +) + +// @Description Register Routes for ims_user +func VoIPAuthRegister(r *gin.RouterGroup) { + + voipAuthGroup := r.Group("/udm/voipauth") + { + voipAuthGroup.PUT("/resetData/:neId", + repeat.RepeatSubmit(5), + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.voipAuth", collectlogs.BUSINESS_TYPE_CLEAN)), + controller.NewVoIPAuthController.ResetData, + ) + voipAuthGroup.GET("/list", + middleware.PreAuthorize(nil), + controller.NewVoIPAuthController.List, + ) + voipAuthGroup.GET("/:neId/:userName", + middleware.PreAuthorize(nil), + controller.NewVoIPAuthController.Info, + ) + voipAuthGroup.POST("/:neId", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.voipAuth", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewVoIPAuthController.Add, + ) + voipAuthGroup.POST("/:neId/:num", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.voipAuth", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewVoIPAuthController.Adds, + ) + voipAuthGroup.PUT("/:neId", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.voipAuth", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewVoIPAuthController.Edit, + ) + voipAuthGroup.DELETE("/:neId/:userName", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.voipAuth", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewVoIPAuthController.Remove, + ) + voipAuthGroup.DELETE("/:neId/:userName/:num", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.voipAuth", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewVoIPAuthController.Removes, + ) + voipAuthGroup.POST("/export", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.voipAuth", collectlogs.BUSINESS_TYPE_EXPORT)), + controller.NewVoIPAuthController.Export, + ) + voipAuthGroup.POST("/import", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.voipAuth", collectlogs.BUSINESS_TYPE_IMPORT)), + controller.NewVoIPAuthController.Import, + ) + } +} + diff --git a/features/ue/controller/file_export.go b/features/ue/controller/file_export.go new file mode 100644 index 00000000..6e38633b --- /dev/null +++ b/features/ue/controller/file_export.go @@ -0,0 +1,95 @@ +package controller + +import ( + "net/http" + "os" + "path/filepath" + + "be.ems/lib/file" + "be.ems/lib/log" + "be.ems/lib/services" + "be.ems/features/ue/model" + "github.com/gin-gonic/gin" +) + +type FileExport struct { + file.FileInfo +} + +func (m *FileExport) GetFileList(c *gin.Context) { + var querys model.FileExportQuery + + if err := c.ShouldBindQuery(&querys); err != nil { + c.JSON(http.StatusOK, services.ErrResp(err.Error())) + return + } + + files, err := file.GetFileInfo(querys.Path, querys.Suffix) + if err != nil { + log.Error("failed to GetFileInfo:", err) + c.JSON(http.StatusOK, services.ErrResp(err.Error())) + return + } + + // split files list + lenNum := int64(len(files)) + start := (querys.PageNum - 1) * querys.PageSize + end := start + querys.PageSize + var splitList []file.FileInfo + if start >= lenNum { + splitList = []file.FileInfo{} + } else if end >= lenNum { + splitList = files[start:] + } else { + splitList = files[start:end] + } + total := len(files) + c.JSON(http.StatusOK, services.TotalDataResp(splitList, total)) +} + +func (m *FileExport) Total(c *gin.Context) { + dir := c.Query("path") + + fileCount, dirCount, err := file.GetFileAndDirCount(dir) + if err != nil { + log.Error("failed to GetFileAndDirCount:", err) + c.JSON(http.StatusOK, services.ErrResp(err.Error())) + return + } + total := fileCount + dirCount + c.JSON(http.StatusOK, services.TotalResp(int64(total))) +} + +func (m *FileExport) DownloadHandler(c *gin.Context) { + dir := c.Query("path") + fileName := c.Param("fileName") + filePath := filepath.Join(dir, fileName) + + file, err := os.Open(filePath) + if err != nil { + c.JSON(http.StatusOK, services.ErrResp(err.Error())) + return + } + defer file.Close() + + if _, err := os.Stat(filePath); os.IsNotExist(err) { + c.JSON(http.StatusOK, services.ErrResp(err.Error())) + return + } + + c.Header("Content-Disposition", "attachment; filename="+fileName) + c.Header("Content-Type", "application/octet-stream") + c.File(filePath) +} + +func (m *FileExport) Delete(c *gin.Context) { + fileName := c.Param("fileName") + dir := c.Query("path") + filePath := filepath.Join(dir, fileName) + + if err := os.Remove(filePath); err != nil { + c.JSON(http.StatusOK, services.ErrResp(err.Error())) + return + } + c.JSON(http.StatusNoContent, nil) // 204 No Content +} diff --git a/features/ue/controller/file_source.go b/features/ue/controller/file_source.go new file mode 100644 index 00000000..6767bfc6 --- /dev/null +++ b/features/ue/controller/file_source.go @@ -0,0 +1,57 @@ +package controller + +import ( + "encoding/json" + "net/http" + + "be.ems/lib/services" + "be.ems/src/framework/datasource" + "be.ems/src/framework/i18n" + "be.ems/src/framework/utils/ctx" + "be.ems/src/modules/crontask/processor/exportUEData" + "be.ems/features/ue/model" + "github.com/gin-gonic/gin" +) + +type FileSource struct { + SysJob model.SysJob + TableName string `json:"tableName"` + TableDisplay string `json:"tableDisplay"` + FilePath string `json:"filePath"` +} + +// GetFileExportTable 获取文件导出任务列表 +func (m *FileSource) GetFileExportTable(c *gin.Context) { + var results []model.SysJob + + err := datasource.DefaultDB().Table(m.SysJob.TableName()). + Where("invoke_target=? and status=1", model.INVOKE_FILE_EXPORT). + Find(&results).Error + if err != nil { + c.JSON(http.StatusOK, services.ErrResp(err.Error())) + return + } + language := ctx.AcceptLanguage(c) + var response []FileSource + for _, job := range results { + params := make([]exportUEData.BarParams, 0) + if err := json.Unmarshal([]byte(job.TargetParams), ¶ms); err != nil { + c.JSON(http.StatusOK, services.ErrResp(err.Error())) + return + } + for _, param := range params { + TableDisplay := i18n.TKey(language, "table."+param.TableName) + if TableDisplay == "" { + TableDisplay = param.TableName + } + response = append(response, FileSource{ + SysJob: job, + TableName: param.TableName, + TableDisplay: TableDisplay, + FilePath: param.FilePath, + }) + } + } + c.JSON(http.StatusOK, services.DataResp(response)) +} + diff --git a/features/ue/controller/ims_user.go b/features/ue/controller/ims_user.go new file mode 100644 index 00000000..20efea73 --- /dev/null +++ b/features/ue/controller/ims_user.go @@ -0,0 +1,488 @@ +package controller + +import ( + "fmt" + "path/filepath" + "strings" + "time" + + "be.ems/src/framework/constants/uploadsubpath" + "be.ems/src/framework/i18n" + "be.ems/src/framework/telnet" + "be.ems/src/framework/utils/ctx" + "be.ems/src/framework/utils/file" + "be.ems/src/framework/utils/parse" + "be.ems/src/framework/vo/result" + neService "be.ems/src/modules/network_element/service" + "be.ems/features/ue/service" + "be.ems/features/ue/model" + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 IMSUserController 结构体 +var NewIMSUserController = &IMSUserController{ + imsUserService: service.NewIMSUserService, + neInfoService: neService.NewNeInfo, +} + +// IMS用户信息 控制层处理 +// +// @Description IMS用户信息 控制层处理 +type IMSUserController struct { + imsUserService *service.IMSUserService // IMS User信息服务 + neInfoService *neService.NeInfo // 网元信息服务 +} + +func (s *IMSUserController) ResetData(c *gin.Context) { + language := ctx.AcceptLanguage(c) + neId := c.Param("neId") + if neId == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + data := s.imsUserService.ResetData(neId) + c.JSON(200, result.OkData(data)) +} + + +func (s *IMSUserController) List(c *gin.Context) { + querys := ctx.QueryMap(c) + // querys["userName"] = ctx.LoginUserToUserName(c) + // 判断租户角色 + loginUser, _ := ctx.LoginUser(c) + if len(loginUser.User.Roles) > 0 { + for _, v := range loginUser.User.Roles { + if v.RoleKey == "tenant" { + querys["tenantName"] = loginUser.User.UserName + break + } + } + } + data := s.imsUserService.SelectPage(querys) + c.JSON(200, result.Ok(data)) +} + +func (s *IMSUserController) Info(c *gin.Context) { + language := ctx.AcceptLanguage(c) + neId := c.Param("neId") + imsi := c.Param("imsi") + if neId == "" || imsi == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + // 发送MML + cmd := fmt.Sprintf("dsp imsuser:imsi=%s", imsi) + data, err := telnet.ConvertToMap(telnetClient, cmd) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + if len(data) == 0 { + c.JSON(200, result.ErrMsg("Not found IMS User data")) + return + } + + // 解析返回的数据 + u := s.imsUserService.ParseInfo(imsi, neId, data) + s.imsUserService.Insert(neId, u) + c.JSON(200, result.OkData(u)) +} + +func (s *IMSUserController) Add(c *gin.Context) { + language := ctx.AcceptLanguage(c) + neId := c.Param("neId") + if neId == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + var body model.IMSUser + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || len(body.IMSI) < model.IMSI_MAX_LENGTH { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + // 发送MML + cmd := fmt.Sprintf("add imsuser:imsi=%s,", body.IMSI) + cmd += s.imsUserService.ParseCommandParams(body) + data, err := telnet.ConvertToStr(telnetClient, cmd) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + // 命令ok时 + if strings.Contains(data, "ok") { + body.NeId = neId + s.imsUserService.Insert(neId, body) + } + c.JSON(200, result.OkData(data)) +} + +func (s *IMSUserController) Adds(c *gin.Context) { + language := ctx.AcceptLanguage(c) + neId := c.Param("neId") + num := c.Param("num") + if neId == "" || num == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + var body model.IMSUser + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || len(body.IMSI) < model.IMSI_MAX_LENGTH { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + // 发送MML + cmd := fmt.Sprintf("baa imsuser:start_imsi=%s,start_msisdn=%s,sub_num=%s,", body.IMSI, body.MSISDN, num) + cmd += s.imsUserService.ParseCommandParams(body) + // 去除msisdn参数,避免重复 + omemsisdn := fmt.Sprintf(",msisdn=%s,", body.MSISDN) + cmd = strings.Replace(cmd, omemsisdn, ",", 1) + data, err := telnet.ConvertToStr(telnetClient, cmd) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.JSON(200, result.OkData(data)) +} + +func (s *IMSUserController) Edit(c *gin.Context) { + language := ctx.AcceptLanguage(c) + neId := c.Param("neId") + if neId == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + var body model.IMSUser + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || len(body.IMSI) < model.IMSI_MAX_LENGTH { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + // 发送MML + cmd := fmt.Sprintf("mod imsuser:imsi=%s,", body.IMSI) + cmd += s.imsUserService.ParseCommandParams(body) + data, err := telnet.ConvertToStr(telnetClient, cmd) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + // 命令ok时 + if strings.Contains(data, "ok") { + body.NeId = neId + s.imsUserService.Insert(neId, body) + } + c.JSON(200, result.OkData(data)) +} + +func (s *IMSUserController) Remove(c *gin.Context) { + language := ctx.AcceptLanguage(c) + neId := c.Param("neId") + imsi := c.Param("imsi") + if neId == "" || len(imsi) < model.IMSI_MAX_LENGTH { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 处理字符转id数组后去重 + imsiArr := strings.Split(imsi, ",") + uniqueIDs := parse.RemoveDuplicates(imsiArr) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + 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.imsUserService.Delete(neId, imsi) + } + resultData[imsi] = data + } + + c.JSON(200, result.OkData(resultData)) +} + +func (s *IMSUserController) Removes(c *gin.Context) { + language := ctx.AcceptLanguage(c) + neId := c.Param("neId") + imsi := c.Param("imsi") + num := c.Param("num") + if neId == "" || len(imsi) < model.IMSI_MAX_LENGTH || num == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, result.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, result.ErrMsg(err.Error())) + return + } + + // 命令ok时 + if strings.Contains(data, "ok") { + s.imsUserService.LoadData(neId, imsi, num) + } + c.JSON(200, result.OkData(data)) +} + +func (s *IMSUserController) Export(c *gin.Context) { + language := ctx.AcceptLanguage(c) + // 查询结果,根据查询条件结果,单页最大值限制 + querys := ctx.BodyJSONMap(c) + neId := querys["neId"].(string) + fileType := querys["type"].(string) + if neId == "" || fileType == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + if !(fileType == "csv" || fileType == "txt") { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "ne.udm.errImportUserSubFileFormat"))) + return + } + + // 判断租户角色 + loginUser, _ := ctx.LoginUser(c) + if len(loginUser.User.Roles) > 0 { + for _, v := range loginUser.User.Roles { + if v.RoleKey == "tenant" { + querys["tenantName"] = loginUser.User.UserName + break + } + } + } + + querys["pageNum"] = 1 + querys["pageSize"] = 10000 + data := s.imsUserService.SelectPage(querys) + if parse.Number(data["total"]) == 0 { + // 导出数据记录为空 + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.exportEmpty"))) + return + } + rows := data["rows"].([]model.IMSUser) + + // rows := s.imsUserService.SelectList(model.IMSUser{NeId: neId}) + if len(rows) <= 0 { + // 导出数据记录为空 + c.JSON(200, result.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(uploadsubpath.EXPORT), fileName) + + if fileType == "csv" { + // 转换数据 + data := [][]string{} + data = append(data, []string{"IMSI", "MSISDN", "VoLTE", "VNI"}) + for _, v := range rows { + data = append(data, []string{v.IMSI, v.MSISDN, v.VoLTE, v.VNI}) + } + // 输出到文件 + if err := file.WriterFileCSV(data, filePath); err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + } + + if fileType == "txt" { + // 转换数据 + data := [][]string{} + for _, v := range rows { + data = append(data, []string{v.IMSI, v.MSISDN, v.VoLTE, v.VNI}) + } + // 输出到文件 + if err := file.WriterFileTXT(data, ",", filePath); err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + } + + c.FileAttachment(filePath, fileName) +} + +func (s *IMSUserController) Import(c *gin.Context) { + language := ctx.AcceptLanguage(c) + var body struct { + NeId string `json:"neId" binding:"required"` + UploadPath string `json:"uploadPath" binding:"required"` + } + if err := c.ShouldBindBodyWith(&body, binding.JSON); err != nil { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 判断文件名 + if !(strings.HasSuffix(body.UploadPath, ".csv") || strings.HasSuffix(body.UploadPath, ".txt")) { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "ne.udm.errImportUserSubFileFormat"))) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", body.NeId) + if neInfo.NeId != body.NeId || neInfo.IP == "" { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + + // 网元主机的SSH客户端 + sshClient, err := s.neInfoService.NeRunSSHClient(neInfo.NeType, neInfo.NeId) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + defer sshClient.Close() + // 网元主机的SSH客户端进行文件传输 + sftpClient, err := sshClient.NewClientSFTP() + if err != nil { + c.JSON(200, result.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, result.ErrMsg("error uploading file")) + return + } + + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient(neInfo.NeType, neInfo.NeId, 1) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + // 发送MML + cmd := fmt.Sprintf("import imsuser:path=%s", neFilePath) + data, err := telnet.ConvertToStr(telnetClient, cmd) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + // 命令ok时 + if strings.Contains(data, "ok") { + if strings.HasSuffix(body.UploadPath, ".csv") { + data := file.ReadFileCSV(localFilePath) + go s.imsUserService.InsertData(neInfo.NeId, "csv", data) + } + if strings.HasSuffix(body.UploadPath, ".txt") { + data := file.ReadFileTXT(",", localFilePath) + go s.imsUserService.InsertData(neInfo.NeId, "txt", data) + } + } + c.JSON(200, result.OkMsg(data)) +} diff --git a/features/ue/controller/voip_auth.go b/features/ue/controller/voip_auth.go new file mode 100644 index 00000000..ffbe9559 --- /dev/null +++ b/features/ue/controller/voip_auth.go @@ -0,0 +1,486 @@ +package controller + +import ( + "fmt" + "path/filepath" + "strings" + "time" + + "be.ems/src/framework/constants/uploadsubpath" + "be.ems/src/framework/i18n" + "be.ems/src/framework/telnet" + "be.ems/src/framework/utils/ctx" + "be.ems/src/framework/utils/file" + "be.ems/src/framework/utils/parse" + "be.ems/src/framework/vo/result" + neService "be.ems/src/modules/network_element/service" + "be.ems/features/ue/service" + "be.ems/features/ue/model" + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 VoIPAuthController 结构体 +var NewVoIPAuthController = &VoIPAuthController{ + voipAuthService: service.NewVoIPAuthService, + neInfoService: neService.NewNeInfo, +} + +// IMS用户信息 控制层处理 +// +// @Description IMS用户信息 控制层处理 +type VoIPAuthController struct { + voipAuthService *service.VoIPAuthService // VoIP Auth信息服务 + neInfoService *neService.NeInfo // 网元信息服务 +} + +func (s *VoIPAuthController) ResetData(c *gin.Context) { + language := ctx.AcceptLanguage(c) + neId := c.Param("neId") + if neId == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + data := s.voipAuthService.ResetData(neId) + c.JSON(200, result.OkData(data)) +} + + +func (s *VoIPAuthController) List(c *gin.Context) { + querys := ctx.QueryMap(c) + // querys["userName"] = ctx.LoginUserToUserName(c) + // 判断租户角色 + loginUser, _ := ctx.LoginUser(c) + if len(loginUser.User.Roles) > 0 { + for _, v := range loginUser.User.Roles { + if v.RoleKey == "tenant" { + querys["tenantName"] = loginUser.User.UserName + break + } + } + } + data := s.voipAuthService.SelectPage(querys) + c.JSON(200, result.Ok(data)) +} + +func (s *VoIPAuthController) Info(c *gin.Context) { + language := ctx.AcceptLanguage(c) + neId := c.Param("neId") + userName := c.Param("userName") + if neId == "" || userName == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, result.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, result.ErrMsg(err.Error())) + return + } + + if len(data) == 0 { + c.JSON(200, result.ErrMsg("Not found VoIP Auth data")) + return + } + + // 解析返回的数据 + u := s.voipAuthService.ParseInfo(userName, neId, data) + s.voipAuthService.Insert(neId, u) + c.JSON(200, result.OkData(u)) +} + +func (s *VoIPAuthController) Add(c *gin.Context) { + language := ctx.AcceptLanguage(c) + neId := c.Param("neId") + if neId == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + var body model.VoIPAuth + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + // 发送MML + cmd := fmt.Sprintf("add voip:username=%s,", body.UserName) + cmd += s.voipAuthService.ParseCommandParams(body) + data, err := telnet.ConvertToStr(telnetClient, cmd) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + // 命令ok时 + if strings.Contains(data, "ok") { + body.NeId = neId + s.voipAuthService.Insert(neId, body) + } + c.JSON(200, result.OkData(data)) +} + +func (s *VoIPAuthController) Adds(c *gin.Context) { + language := ctx.AcceptLanguage(c) + neId := c.Param("neId") + num := c.Param("num") + if neId == "" || num == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + var body model.VoIPAuth + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + // 发送MML + cmd := fmt.Sprintf("baa voip:start_username=%s,password=%s,sub_num=%s,", + body.UserName, body.Password, num) + cmd += s.voipAuthService.ParseCommandParams(body) + data, err := telnet.ConvertToStr(telnetClient, cmd) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.JSON(200, result.OkData(data)) +} + +func (s *VoIPAuthController) Edit(c *gin.Context) { + language := ctx.AcceptLanguage(c) + neId := c.Param("neId") + if neId == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + var body model.VoIPAuth + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + // 发送MML + cmd := fmt.Sprintf("mod voip:username=%s,", body.UserName) + cmd += s.voipAuthService.ParseCommandParams(body) + data, err := telnet.ConvertToStr(telnetClient, cmd) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + // 命令ok时 + if strings.Contains(data, "ok") { + body.NeId = neId + s.voipAuthService.Insert(neId, body) + } + c.JSON(200, result.OkData(data)) +} + +func (s *VoIPAuthController) Remove(c *gin.Context) { + language := ctx.AcceptLanguage(c) + neId := c.Param("neId") + userName := c.Param("userName") + if neId == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 处理字符转id数组后去重 + userNameArr := strings.Split(userName, ",") + uniqueIDs := parse.RemoveDuplicates(userNameArr) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + resultData := map[string]string{} + for _, userName := range uniqueIDs { + // 发送MML + cmd := fmt.Sprintf("del voip:username=%s", userName) + data, err := telnet.ConvertToStr(telnetClient, cmd) + if err != nil { + resultData[userName] = err.Error() + continue + } + // 命令ok时 + if strings.Contains(data, "ok") { + s.voipAuthService.Delete(neId, userName) + } + resultData[userName] = data + } + + c.JSON(200, result.OkData(resultData)) +} + +func (s *VoIPAuthController) Removes(c *gin.Context) { + language := ctx.AcceptLanguage(c) + neId := c.Param("neId") + userName := c.Param("userName") + num := c.Param("num") + if neId == "" || num == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, result.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, result.ErrMsg(err.Error())) + return + } + + // 命令ok时 + if strings.Contains(data, "ok") { + s.voipAuthService.LoadData(neId, userName, num) + } + c.JSON(200, result.OkData(data)) +} + +func (s *VoIPAuthController) Export(c *gin.Context) { + language := ctx.AcceptLanguage(c) + // 查询结果,根据查询条件结果,单页最大值限制 + querys := ctx.BodyJSONMap(c) + neId := querys["neId"].(string) + fileType := querys["type"].(string) + if neId == "" || fileType == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + if !(fileType == "csv" || fileType == "txt") { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "ne.udm.errImportUserSubFileFormat"))) + return + } + + // 判断租户角色 + loginUser, _ := ctx.LoginUser(c) + if len(loginUser.User.Roles) > 0 { + for _, v := range loginUser.User.Roles { + if v.RoleKey == "tenant" { + querys["tenantName"] = loginUser.User.UserName + break + } + } + } + + querys["pageNum"] = 1 + querys["pageSize"] = 10000 + data := s.voipAuthService.SelectPage(querys) + if parse.Number(data["total"]) == 0 { + // 导出数据记录为空 + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.exportEmpty"))) + return + } + rows := data["rows"].([]model.VoIPAuth) + + // rows := s.voipAuthService.SelectList(model.VoIPAuth{NeId: neId}) + if len(rows) <= 0 { + // 导出数据记录为空 + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.exportEmpty"))) + return + } + + // 文件名 + fileName := fmt.Sprintf("u_voip_auth_export_%s_%d.%s", neId, time.Now().UnixMilli(), fileType) + filePath := filepath.Join(file.ParseUploadFileDir(uploadsubpath.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, result.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, result.ErrMsg(err.Error())) + return + } + } + + c.FileAttachment(filePath, fileName) +} + +func (s *VoIPAuthController) Import(c *gin.Context) { + language := ctx.AcceptLanguage(c) + var body struct { + NeId string `json:"neId" binding:"required"` + UploadPath string `json:"uploadPath" binding:"required"` + } + if err := c.ShouldBindBodyWith(&body, binding.JSON); err != nil { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 判断文件名 + if !(strings.HasSuffix(body.UploadPath, ".csv") || strings.HasSuffix(body.UploadPath, ".txt")) { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "ne.udm.errImportUserSubFileFormat"))) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", body.NeId) + if neInfo.NeId != body.NeId || neInfo.IP == "" { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + + // 网元主机的SSH客户端 + sshClient, err := s.neInfoService.NeRunSSHClient(neInfo.NeType, neInfo.NeId) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + defer sshClient.Close() + // 网元主机的SSH客户端进行文件传输 + sftpClient, err := sshClient.NewClientSFTP() + if err != nil { + c.JSON(200, result.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, result.ErrMsg("error uploading file")) + return + } + + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient(neInfo.NeType, neInfo.NeId, 1) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + // 发送MML + cmd := fmt.Sprintf("import voip:path=%s", neFilePath) + data, err := telnet.ConvertToStr(telnetClient, cmd) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + // 命令ok时 + if strings.Contains(data, "ok") { + if strings.HasSuffix(body.UploadPath, ".csv") { + data := file.ReadFileCSV(localFilePath) + go s.voipAuthService.InsertData(neInfo.NeId, "csv", data) + } + if strings.HasSuffix(body.UploadPath, ".txt") { + data := file.ReadFileTXT(",", localFilePath) + go s.voipAuthService.InsertData(neInfo.NeId, "txt", data) + } + } + c.JSON(200, result.OkMsg(data)) +} diff --git a/features/ue/file_export/route.go b/features/ue/file_export/router.go similarity index 100% rename from features/ue/file_export/route.go rename to features/ue/file_export/router.go diff --git a/features/ue/ims_user/controller.go b/features/ue/ims_user/controller.go new file mode 100644 index 00000000..a7bb2286 --- /dev/null +++ b/features/ue/ims_user/controller.go @@ -0,0 +1,486 @@ +package ims_user + +import ( + "fmt" + "path/filepath" + "strings" + "time" + + "be.ems/src/framework/constants/uploadsubpath" + "be.ems/src/framework/i18n" + "be.ems/src/framework/telnet" + "be.ems/src/framework/utils/ctx" + "be.ems/src/framework/utils/file" + "be.ems/src/framework/utils/parse" + "be.ems/src/framework/vo/result" + neService "be.ems/src/modules/network_element/service" + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 Controller 结构体 +var NewController = &Controller{ + volteUserService: NewVoLTEService, + neInfoService: neService.NewNeInfo, +} + +// IMS用户信息 控制层处理 +// +// @Description IMS用户信息 控制层处理 +type Controller struct { + volteUserService *Service // IMS User信息服务 + neInfoService *neService.NeInfo // 网元信息服务 +} + +func (s *Controller) ResetData(c *gin.Context) { + language := ctx.AcceptLanguage(c) + neId := c.Param("neId") + if neId == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + data := s.volteUserService.ResetData(neId) + c.JSON(200, result.OkData(data)) +} + + +func (s *Controller) List(c *gin.Context) { + querys := ctx.QueryMap(c) + // querys["userName"] = ctx.LoginUserToUserName(c) + // 判断租户角色 + loginUser, _ := ctx.LoginUser(c) + if len(loginUser.User.Roles) > 0 { + for _, v := range loginUser.User.Roles { + if v.RoleKey == "tenant" { + querys["tenantName"] = loginUser.User.UserName + break + } + } + } + data := s.volteUserService.SelectPage(querys) + c.JSON(200, result.Ok(data)) +} + +func (s *Controller) Info(c *gin.Context) { + language := ctx.AcceptLanguage(c) + neId := c.Param("neId") + imsi := c.Param("imsi") + if neId == "" || imsi == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + // 发送MML + cmd := fmt.Sprintf("dsp imsuser:imsi=%s", imsi) + data, err := telnet.ConvertToMap(telnetClient, cmd) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + if len(data) == 0 { + c.JSON(200, result.ErrMsg("Not found VoLTE user data")) + return + } + + // 解析返回的数据 + u := s.volteUserService.ParseInfo(imsi, neId, data) + s.volteUserService.Insert(neId, u) + c.JSON(200, result.OkData(u)) +} + +func (s *Controller) Add(c *gin.Context) { + language := ctx.AcceptLanguage(c) + neId := c.Param("neId") + if neId == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + var body VoLTEUser + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || len(body.IMSI) < IMSI_MAX_LENGTH { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + // 发送MML + cmd := fmt.Sprintf("add imsuser:imsi=%s,", body.IMSI) + cmd += s.volteUserService.ParseCommandParams(body) + data, err := telnet.ConvertToStr(telnetClient, cmd) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + // 命令ok时 + if strings.Contains(data, "ok") { + body.NeId = neId + s.volteUserService.Insert(neId, body) + } + c.JSON(200, result.OkData(data)) +} + +func (s *Controller) Adds(c *gin.Context) { + language := ctx.AcceptLanguage(c) + neId := c.Param("neId") + num := c.Param("num") + if neId == "" || num == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + var body VoLTEUser + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || len(body.IMSI) < IMSI_MAX_LENGTH { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + // 发送MML + cmd := fmt.Sprintf("baa imsuser:start_imsi=%s,start_msisdn=%s,sub_num=%s,", body.IMSI, body.MSISDN, num) + cmd += s.volteUserService.ParseCommandParams(body) + // 去除msisdn参数,避免重复 + omemsisdn := fmt.Sprintf(",msisdn=%s,", body.MSISDN) + cmd = strings.Replace(cmd, omemsisdn, ",", 1) + data, err := telnet.ConvertToStr(telnetClient, cmd) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.JSON(200, result.OkData(data)) +} + +func (s *Controller) Edit(c *gin.Context) { + language := ctx.AcceptLanguage(c) + neId := c.Param("neId") + if neId == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + var body VoLTEUser + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || len(body.IMSI) < IMSI_MAX_LENGTH { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + // 发送MML + cmd := fmt.Sprintf("mod imsuser:imsi=%s,", body.IMSI) + cmd += s.volteUserService.ParseCommandParams(body) + data, err := telnet.ConvertToStr(telnetClient, cmd) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + // 命令ok时 + if strings.Contains(data, "ok") { + body.NeId = neId + s.volteUserService.Insert(neId, body) + } + c.JSON(200, result.OkData(data)) +} + +func (s *Controller) Remove(c *gin.Context) { + language := ctx.AcceptLanguage(c) + neId := c.Param("neId") + imsi := c.Param("imsi") + if neId == "" || len(imsi) < IMSI_MAX_LENGTH { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 处理字符转id数组后去重 + imsiArr := strings.Split(imsi, ",") + uniqueIDs := parse.RemoveDuplicates(imsiArr) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + 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.volteUserService.Delete(neId, imsi) + } + resultData[imsi] = data + } + + c.JSON(200, result.OkData(resultData)) +} + +func (s *Controller) Removes(c *gin.Context) { + language := ctx.AcceptLanguage(c) + neId := c.Param("neId") + imsi := c.Param("imsi") + num := c.Param("num") + if neId == "" || len(imsi) < IMSI_MAX_LENGTH || num == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", neId) + if neInfo.NeId != neId || neInfo.IP == "" { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1) + if err != nil { + c.JSON(200, result.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, result.ErrMsg(err.Error())) + return + } + + // 命令ok时 + if strings.Contains(data, "ok") { + s.volteUserService.LoadData(neId, imsi, num) + } + c.JSON(200, result.OkData(data)) +} + +func (s *Controller) Export(c *gin.Context) { + language := ctx.AcceptLanguage(c) + // 查询结果,根据查询条件结果,单页最大值限制 + querys := ctx.BodyJSONMap(c) + neId := querys["neId"].(string) + fileType := querys["type"].(string) + if neId == "" || fileType == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + if !(fileType == "csv" || fileType == "txt") { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "ne.udm.errImportUserSubFileFormat"))) + return + } + + // 判断租户角色 + loginUser, _ := ctx.LoginUser(c) + if len(loginUser.User.Roles) > 0 { + for _, v := range loginUser.User.Roles { + if v.RoleKey == "tenant" { + querys["tenantName"] = loginUser.User.UserName + break + } + } + } + + querys["pageNum"] = 1 + querys["pageSize"] = 10000 + data := s.volteUserService.SelectPage(querys) + if parse.Number(data["total"]) == 0 { + // 导出数据记录为空 + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.exportEmpty"))) + return + } + rows := data["rows"].([]VoLTEUser) + + // rows := s.volteUserService.SelectList(VoLTEUser{NeId: neId}) + if len(rows) <= 0 { + // 导出数据记录为空 + c.JSON(200, result.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(uploadsubpath.EXPORT), fileName) + + if fileType == "csv" { + // 转换数据 + data := [][]string{} + data = append(data, []string{"IMSI", "MSISDN", "VoLTE", "VNI"}) + for _, v := range rows { + data = append(data, []string{v.IMSI, v.MSISDN, v.VoLTE, v.VNI}) + } + // 输出到文件 + if err := file.WriterFileCSV(data, filePath); err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + } + + if fileType == "txt" { + // 转换数据 + data := [][]string{} + for _, v := range rows { + data = append(data, []string{v.IMSI, v.MSISDN, v.VoLTE, v.VNI}) + } + // 输出到文件 + if err := file.WriterFileTXT(data, ",", filePath); err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + } + + c.FileAttachment(filePath, fileName) +} + +func (s *Controller) Import(c *gin.Context) { + language := ctx.AcceptLanguage(c) + var body struct { + NeId string `json:"neId" binding:"required"` + UploadPath string `json:"uploadPath" binding:"required"` + } + if err := c.ShouldBindBodyWith(&body, binding.JSON); err != nil { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 判断文件名 + if !(strings.HasSuffix(body.UploadPath, ".csv") || strings.HasSuffix(body.UploadPath, ".txt")) { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "ne.udm.errImportUserSubFileFormat"))) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID("UDM", body.NeId) + if neInfo.NeId != body.NeId || neInfo.IP == "" { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + + // 网元主机的SSH客户端 + sshClient, err := s.neInfoService.NeRunSSHClient(neInfo.NeType, neInfo.NeId) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + defer sshClient.Close() + // 网元主机的SSH客户端进行文件传输 + sftpClient, err := sshClient.NewClientSFTP() + if err != nil { + c.JSON(200, result.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, result.ErrMsg("error uploading file")) + return + } + + // 网元主机的Telnet客户端 + telnetClient, err := s.neInfoService.NeRunTelnetClient(neInfo.NeType, neInfo.NeId, 1) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + defer telnetClient.Close() + + // 发送MML + cmd := fmt.Sprintf("import imsuser:path=%s", neFilePath) + data, err := telnet.ConvertToStr(telnetClient, cmd) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + // 命令ok时 + if strings.Contains(data, "ok") { + if strings.HasSuffix(body.UploadPath, ".csv") { + data := file.ReadFileCSV(localFilePath) + go s.volteUserService.InsertData(neInfo.NeId, "csv", data) + } + if strings.HasSuffix(body.UploadPath, ".txt") { + data := file.ReadFileTXT(",", localFilePath) + go s.volteUserService.InsertData(neInfo.NeId, "txt", data) + } + } + c.JSON(200, result.OkMsg(data)) +} diff --git a/features/ue/ims_user/model.go b/features/ue/ims_user/model.go new file mode 100644 index 00000000..d89b411d --- /dev/null +++ b/features/ue/ims_user/model.go @@ -0,0 +1,26 @@ +package ims_user + +const ( + // IMSI 号码长度 + IMSI_MAX_LENGTH = 15 + // MSISDN 号码长度 + MSISDN_MAX_LENGTH = 15 +) + +// @Description VoLTE用户信息 +type VoLTEUser struct { + ID string `json:"id" gorm:"column:id;primaryKey;autoIncrement"` // 主键 + NeId string `json:"neId" gorm:"column:ne_id"` // UDM网元标识 + IMSI string `json:"imsi" gorm:"column:imsi"` // SIM卡/USIM卡ID + MSISDN string `json:"msisdn" gorm:"column:msisdn"` // 用户电话号码 + VoLTE string `json:"volte" gorm:"column:volte"` // VoLTE + VNI string `json:"vni" gorm:"column:vni"` // VNI + + TenantID string `json:"tenantID" gorm:"column:tenant_id"` + TenantName string `json:"tenantName" gorm:"-"` +} + +// TableName 表名称 +func (*VoLTEUser) TableName() string { + return "u_ims_user" +} diff --git a/features/ue/ims_user/repository.go b/features/ue/ims_user/repository.go new file mode 100644 index 00000000..b43385a3 --- /dev/null +++ b/features/ue/ims_user/repository.go @@ -0,0 +1,272 @@ +package ims_user + +import ( + "fmt" + "strings" + + dborm "be.ems/lib/core/datasource" + "be.ems/lib/log" + "be.ems/src/framework/datasource" + "be.ems/src/framework/logger" + "be.ems/src/framework/utils/parse" + "be.ems/src/framework/utils/repo" +) + +// 实例化数据层 Repository 结构体 +var NewVoLTERepository = &Repository{ + selectSql: `select + s.id, s.ne_id, s.imsi, s.msisdn, s.volte, s.vni, + t.tenant_id, t.tenant_name + from u_ims_user s + left join sys_tenant t on t.tenant_id = s.tenant_id and t.status = 1`, + + resultMap: map[string]string{ + "id": "ID", + "ne_id": "NeId", + "imsi": "IMSI", + "msisdn": "MSISDN", + "volte": "VoLTE", + "vni": "VNI", + + "tenant_id": "TenantID", + "tenant_name": "TenantName", // Tenant name for multi-tenancy + }, +} + +// Repository UDM签约信息表 数据层处理 +type Repository struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *Repository) convertResultRows(rows []map[string]any) []VoLTEUser { + arr := make([]VoLTEUser, 0) + for _, row := range rows { + item := VoLTEUser{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&item, keyMapper, value) + } + } + arr = append(arr, item) + } + return arr +} + +// ClearAndInsert 清空ne_id后新增实体 +func (r *Repository) ClearAndInsert(neId string, u []VoLTEUser) int64 { + // 不指定neID时,用 TRUNCATE 清空表快 + // _, err := datasource.ExecDB("", "TRUNCATE TABLE u_ims_user", nil) + _, err := datasource.ExecDB("", "DELETE FROM u_ims_user WHERE ne_id = ?", []any{neId}) + if err != nil { + logger.Errorf("TRUNCATE err => %v", err) + } + + return r.Inserts(u) +} + +// SelectPage 根据条件分页查询字典类型 +func (r *Repository) SelectPage(query map[string]any) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["imsi"]; ok && v != "" { + conditions = append(conditions, "imsi like concat(concat('%', ?), '%')") + params = append(params, strings.Trim(v.(string), " ")) + } + if v, ok := query["msisdn"]; ok && v != "" { + conditions = append(conditions, "msisdn like concat(concat('%', ?), '%')") + params = append(params, strings.Trim(v.(string), " ")) + } + if v, ok := query["imsis"]; ok && v != "" { + placeholder := repo.KeyPlaceholderByQuery(len(v.([]any))) + conditions = append(conditions, fmt.Sprintf("imsi in (%s)", placeholder)) + for _, v := range v.([]any) { + params = append(params, v.(string)) + } + } + + // for multi-tenancy solution + if v, ok := query["tenantName"]; ok && v != "" { + var tenantID []string + err := dborm.DefaultDB().Table("sys_tenant"). + Where("tenant_name=? and status=1", v).Cols("tenant_id").Distinct().Find(&tenantID) + if err != nil { + log.Errorf("Find tenant_id from sys_user err => %v", err) + } + log.Tracef("userName=%v, tenantID=%v", v, tenantID) + if len(tenantID) > 0 { + conditions = append(conditions, "s.tenant_id = ?") + params = append(params, tenantID[0]) + } + } else if v, ok := query["userName"]; ok && v != "" { + var tenantID string + _, err := dborm.DefaultDB().Table("sys_user"). + Where("user_name=?", v).Cols("tenant_id").Distinct().Get(&tenantID) + if err != nil { + log.Errorf("Find tenant_id from sys_user err => %v", err) + } + log.Tracef("userName=%v, tenantID=%v", v, tenantID) + if tenantID != "" { + conditions = append(conditions, "s.tenant_id = ?") + params = append(params, tenantID) + query["neId"] = "" + } + } + 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": []VoLTEUser{}, + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from u_ims_user s" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 排序 + orderSql := "" + if v, ok := query["sortField"]; ok && v != "" { + sortSql := v.(string) + if o, ok := query["sortOrder"]; ok && o != nil && v != "" { + if o == "desc" { + sortSql += " desc " + } else { + sortSql += " asc " + } + } + orderSql = fmt.Sprintf(" order by %s ", sortSql) + } + + // 查询数据 + querySql := r.selectSql + whereSql + orderSql + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return result + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectList 根据实体查询 +func (r *Repository) SelectList(u VoLTEUser) []VoLTEUser { + // 查询条件拼接 + var conditions []string + var params []any + if u.IMSI != "" { + conditions = append(conditions, "imsi = ?") + params = append(params, u.IMSI) + } + if u.NeId != "" { + conditions = append(conditions, "ne_id = ?") + params = append(params, u.NeId) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询数据 + querySql := r.selectSql + whereSql + " order by imsi asc " + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + } + + // 转换实体 + return r.convertResultRows(results) +} + +// SelectByIMSIAndNeID 通过imsi和ne_id查询 +func (r *Repository) SelectByIMSIAndNeID(imsi, neId string) VoLTEUser { + querySql := r.selectSql + " where imsi = ? and ne_id = ?" + results, err := datasource.RawDB("", querySql, []any{imsi, neId}) + if err != nil { + logger.Errorf("query err => %v", err) + return VoLTEUser{} + } + // 转换实体 + rows := r.convertResultRows(results) + if len(rows) > 0 { + return rows[0] + } + return VoLTEUser{} +} + +// Insert 批量添加 +func (r *Repository) Inserts(uArr []VoLTEUser) int64 { + // multi-tenancy + r.SetTenantID(&uArr) + + tx := datasource.DefaultDB().CreateInBatches(uArr, 2000) + if err := tx.Error; err != nil { + logger.Errorf("CreateInBatches err => %v", err) + } + return tx.RowsAffected +} + +// Delete 删除实体 +func (r *Repository) Delete(imsi, neId string) int64 { + tx := datasource.DefaultDB().Where("imsi = ? and ne_id = ?", imsi, neId).Delete(&Repository{}) + if err := tx.Error; err != nil { + logger.Errorf("Delete err => %v", err) + } + return tx.RowsAffected +} + +// DeletePrefixByIMSI 删除前缀匹配的实体 +func (r *Repository) DeletePrefixByIMSI(imsiPrefix, neId string) int64 { + tx := datasource.DefaultDB().Where("imsi like concat(?, '%') and ne_id = ?", imsiPrefix, neId).Delete(&Repository{}) + if err := tx.Error; err != nil { + logger.Errorf("DeletePrefixByIMSI err => %v", err) + } + return tx.RowsAffected +} + +func (r *Repository) SetTenantID(subArr *[]VoLTEUser) { + for s := 0; s < len(*subArr); s++ { + var tenantID []string + err := dborm.DefaultDB().Table("sys_tenant"). + Where("status='1' and tenancy_type='IMSI' and ? like tenancy_key", (*subArr)[s].IMSI). + Cols("parent_id").Distinct().Find(&tenantID) + if err != nil { + logger.Errorf("Find tenant_id err => %v", err) + continue + } + if len(tenantID) > 0 { + (*subArr)[s].TenantID = tenantID[0] + } + } +} diff --git a/features/ue/ims_user/router.go b/features/ue/ims_user/router.go new file mode 100644 index 00000000..451f11ec --- /dev/null +++ b/features/ue/ims_user/router.go @@ -0,0 +1,67 @@ +package ims_user + +import ( + "be.ems/src/framework/middleware" + "be.ems/src/framework/middleware/collectlogs" + "be.ems/src/framework/middleware/repeat" + + "github.com/gin-gonic/gin" +) + +// @Description Register Routes for ims_user +func Register(r *gin.RouterGroup) { + + volteUserGroup := r.Group("/volte/user") + { + volteUserGroup.PUT("/resetData/:neId", + repeat.RepeatSubmit(5), + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.volteUser", collectlogs.BUSINESS_TYPE_CLEAN)), + NewController.ResetData, + ) + volteUserGroup.GET("/list", + middleware.PreAuthorize(nil), + NewController.List, + ) + volteUserGroup.GET("/:neId/:imsi", + middleware.PreAuthorize(nil), + NewController.Info, + ) + volteUserGroup.POST("/:neId", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.volteUser", collectlogs.BUSINESS_TYPE_INSERT)), + NewController.Add, + ) + volteUserGroup.POST("/:neId/:num", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.volteUser", collectlogs.BUSINESS_TYPE_INSERT)), + NewController.Adds, + ) + volteUserGroup.PUT("/:neId", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.volteUser", collectlogs.BUSINESS_TYPE_UPDATE)), + NewController.Edit, + ) + volteUserGroup.DELETE("/:neId/:imsi", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.volteUser", collectlogs.BUSINESS_TYPE_DELETE)), + NewController.Remove, + ) + volteUserGroup.DELETE("/:neId/:imsi/:num", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.volteUser", collectlogs.BUSINESS_TYPE_DELETE)), + NewController.Removes, + ) + volteUserGroup.POST("/export", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.volteUser", collectlogs.BUSINESS_TYPE_EXPORT)), + NewController.Export, + ) + volteUserGroup.POST("/import", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.volteUser", collectlogs.BUSINESS_TYPE_IMPORT)), + NewController.Import, + ) + } +} + diff --git a/features/ue/ims_user/service.go b/features/ue/ims_user/service.go new file mode 100644 index 00000000..fb5cde2a --- /dev/null +++ b/features/ue/ims_user/service.go @@ -0,0 +1,209 @@ +package ims_user + +import ( + "fmt" + "strconv" + "strings" + + "be.ems/src/framework/redis" + neService "be.ems/src/modules/network_element/service" +) + +// 实例化服务层 Service 结构体 +var NewVoLTEService = &Service{ + volteRepository: NewVoLTERepository, +} + +// VoLTE用户信息 服务层处理 +type Service struct { + volteRepository *Repository // VoLTE用户信息数据信息 +} + +// dataByRedis UDM签约用户 db:0 中 volte:* +func (r *Service) dataByRedis(imsi, neId string) []VoLTEUser { + arr := []VoLTEUser{} + 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) + + udmsdArr, err := redis.GetKeys(source, key) + if err != nil { + return arr + } + mkv, err := redis.GetHashBatch(source, udmsdArr) + if err != nil { + return arr + } + + for k, m := range mkv { + var imsi, msisdn string + KeyParts := strings.Split(k, ":") + switch len(KeyParts) { + case 0, 1: + // 处理单个部分的情况 + continue + case 2: + // 处理两个部分的情况 + imsi = KeyParts[1] + msisdn = "-" + case 3: + // 处理三个部分的情况 + imsi = KeyParts[1] + msisdn = KeyParts[2] + default: + // 处理更多部分的情况 + imsi = KeyParts[1] + msisdn = KeyParts[2] + } + + var vni string = "-" + impiParts := strings.Split(m["impi"], "@") + if len(impiParts) > 1 { + vni = impiParts[1] // 输出: ims.mnc001.mcc110.3gppnetwork.org + } + a := VoLTEUser{ + NeId: neId, + IMSI: imsi, // volte:360000100000130:8612300000130 + MSISDN: msisdn, // 8612300000130 + VoLTE: m["tag"], // volte = tag + VNI: vni, // ims.mnc001.mcc110.3gppnetwork.org + } + arr = append(arr, a) + } + return arr +} + +// ResetData 重置鉴权用户数据,清空数据库重新同步Redis数据 +func (r *Service) ResetData(neId string) int64 { + subArr := r.dataByRedis("*", neId) + // 数据清空后添加 + go r.volteRepository.ClearAndInsert(neId, subArr) + return int64(len(subArr)) +} + +// ParseInfo 解析单个用户imsi签约信息 data从命令MML得到的结果 +func (r *Service) ParseInfo(imsi, neId string, data map[string]string) VoLTEUser { + u := r.volteRepository.SelectByIMSIAndNeID(imsi, neId) + + msisdn := data["MSISDN"] + if imsMsisdnLen := strings.Index(msisdn, ","); imsMsisdnLen != -1 { + msisdn = msisdn[:imsMsisdnLen] + } + + // 用于更新 + u.NeId = neId + u.IMSI = imsi + u.MSISDN = msisdn + u.VoLTE = data["VoLTE"] + u.VNI = data["VNI"] + return u +} + +// SelectPage 分页查询数据库 +func (r *Service) SelectPage(query map[string]any) map[string]any { + return r.volteRepository.SelectPage(query) +} + +// SelectList 查询数据库 +func (r *Service) SelectList(u VoLTEUser) []VoLTEUser { + return r.volteRepository.SelectList(u) +} + +// Insert 从数据中读取后删除imsi再存入数据库 +// imsi长度15,ki长度32,opc长度0或者32 +func (r *Service) Insert(neId string, u VoLTEUser) int64 { + uArr := r.dataByRedis(u.IMSI, neId) + if len(uArr) > 0 { + r.volteRepository.Delete(u.IMSI, neId) + return r.volteRepository.Inserts(uArr) + } + return 0 +} + +// InsertData 导入文件数据 dataType目前两种:txt/csv +func (r *Service) 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 { + // keys volte:4600001000004* + arr := r.dataByRedis(prefix+"*", neId) + if len(arr) > 0 { + r.volteRepository.DeletePrefixByIMSI(prefix, neId) + num += r.volteRepository.Inserts(arr) + } + } + return num +} + +// Delete 删除单个不重新加载 +func (r *Service) Delete(neId, imsi string) int64 { + return r.volteRepository.Delete(imsi, neId) +} + +// LoadData 重新加载从imsi开始num的数据 +func (r *Service) LoadData(neId, imsi, num string) { + startIMSI, _ := strconv.ParseInt(imsi, 10, 64) + subNum, _ := strconv.ParseInt(num, 10, 64) + var i int64 + for i = 0; i < subNum; i++ { + keyIMSI := fmt.Sprintf("%015d", startIMSI+i) + // 删除原数据 + r.volteRepository.Delete(keyIMSI, neId) + arr := r.dataByRedis(keyIMSI, neId) + if len(arr) < 1 { + continue + } + r.volteRepository.Inserts(arr) + } +} + +// ParseCommandParams 解析数据组成命令参数 msisdn=xx,xx=xx,... +func (r *Service) ParseCommandParams(item VoLTEUser) string { + var conditions []string + if item.MSISDN != "" { + conditions = append(conditions, fmt.Sprintf("msisdn=%s", item.MSISDN)) + } + + if item.VoLTE != "" { + conditions = append(conditions, fmt.Sprintf("volte=%s", item.VoLTE)) + } + if item.VNI != "" { + conditions = append(conditions, fmt.Sprintf("vni=%s", item.VNI)) + } + + return strings.Join(conditions, ",") +} diff --git a/features/ue/model/file_export.go b/features/ue/model/file_export.go new file mode 100644 index 00000000..3ba9c432 --- /dev/null +++ b/features/ue/model/file_export.go @@ -0,0 +1,26 @@ +package model + +import ( + +) + +const ( + INVOKE_FILE_EXPORT = "exportUEData" // 调用目标字符串 +) + +type SysJob struct { + JobID int64 `gorm:"column:job_id;primary_key;auto_increment" json:"job_id"` //任务ID + InvokeTarget string `gorm:"column:invoke_target" json:"invoke_target"` //调用目标字符串 + TargetParams string `gorm:"column:target_params;type:json" json:"target_params,omitempty"` //调用目标传入参数 +} + +func (m *SysJob) TableName() string { + return "sys_job" +} + +type FileExportQuery struct { + Path string `form:"path" binding:"required"` + Suffix string `form:"suffix"` + PageNum int64 `form:"pageNum" binding:"required"` + PageSize int64 `form:"pageSize" binding:"required"` +} diff --git a/features/ue/model/ims_user.go b/features/ue/model/ims_user.go new file mode 100644 index 00000000..e2407549 --- /dev/null +++ b/features/ue/model/ims_user.go @@ -0,0 +1,26 @@ +package model + +const ( + // IMSI 号码长度 + IMSI_MAX_LENGTH = 15 + // MSISDN 号码长度 + MSISDN_MAX_LENGTH = 15 +) + +// @Description VoLTE用户信息 +type IMSUser struct { + ID string `json:"id" gorm:"column:id;primaryKey;autoIncrement"` // 主键 + NeId string `json:"neId" gorm:"column:ne_id"` // UDM网元标识 + IMSI string `json:"imsi" gorm:"column:imsi"` // SIM卡/USIM卡ID + MSISDN string `json:"msisdn" gorm:"column:msisdn"` // 用户电话号码 + VoLTE string `json:"volte" gorm:"column:volte"` // VoLTE + VNI string `json:"vni" gorm:"column:vni"` // VNI + + TenantID string `json:"tenantID" gorm:"column:tenant_id"` + TenantName string `json:"tenantName" gorm:"-"` +} + +// TableName 表名称 +func (*IMSUser) TableName() string { + return "u_ims_user" +} diff --git a/features/ue/model/voip_auth.go b/features/ue/model/voip_auth.go new file mode 100644 index 00000000..0e07514e --- /dev/null +++ b/features/ue/model/voip_auth.go @@ -0,0 +1,17 @@ +package model + +// @Description VoLTE用户信息 +type VoIPAuth 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"` // SIM卡/USIM卡ID + Password string `json:"password" gorm:"column:password"` // 用户电话号码 + + TenantID string `json:"tenantID" gorm:"column:tenant_id"` + TenantName string `json:"tenantName" gorm:"-"` +} + +// TableName 表名称 +func (*VoIPAuth) TableName() string { + return "u_voip_auth" +} diff --git a/features/ue/repository/ims_user.go b/features/ue/repository/ims_user.go new file mode 100644 index 00000000..0c5cacac --- /dev/null +++ b/features/ue/repository/ims_user.go @@ -0,0 +1,273 @@ +package repository + +import ( + "fmt" + "strings" + + dborm "be.ems/lib/core/datasource" + "be.ems/lib/log" + "be.ems/src/framework/datasource" + "be.ems/src/framework/logger" + "be.ems/src/framework/utils/parse" + "be.ems/src/framework/utils/repo" + "be.ems/features/ue/model" +) + +// 实例化数据层 IMSUserRepository 结构体 +var NewIMSUserRepository = &IMSUserRepository{ + selectSql: `select + s.id, s.ne_id, s.imsi, s.msisdn, s.volte, s.vni, + t.tenant_id, t.tenant_name + from u_ims_user s + left join sys_tenant t on t.tenant_id = s.tenant_id and t.status = 1`, + + resultMap: map[string]string{ + "id": "ID", + "ne_id": "NeId", + "imsi": "IMSI", + "msisdn": "MSISDN", + "volte": "VoLTE", + "vni": "VNI", + + "tenant_id": "TenantID", + "tenant_name": "TenantName", // Tenant name for multi-tenancy + }, +} + +// IMSUserRepository UDM签约信息表 数据层处理 +type IMSUserRepository struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *IMSUserRepository) convertResultRows(rows []map[string]any) []model.IMSUser { + arr := make([]model.IMSUser, 0) + for _, row := range rows { + item := model.IMSUser{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&item, keyMapper, value) + } + } + arr = append(arr, item) + } + return arr +} + +// ClearAndInsert 清空ne_id后新增实体 +func (r *IMSUserRepository) ClearAndInsert(neId string, u []model.IMSUser) int64 { + // 不指定neID时,用 TRUNCATE 清空表快 + // _, err := datasource.ExecDB("", "TRUNCATE TABLE u_ims_user", nil) + _, err := datasource.ExecDB("", "DELETE FROM u_ims_user WHERE ne_id = ?", []any{neId}) + if err != nil { + logger.Errorf("TRUNCATE err => %v", err) + } + + return r.Inserts(u) +} + +// SelectPage 根据条件分页查询字典类型 +func (r *IMSUserRepository) SelectPage(query map[string]any) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["imsi"]; ok && v != "" { + conditions = append(conditions, "imsi like concat(concat('%', ?), '%')") + params = append(params, strings.Trim(v.(string), " ")) + } + if v, ok := query["msisdn"]; ok && v != "" { + conditions = append(conditions, "msisdn like concat(concat('%', ?), '%')") + params = append(params, strings.Trim(v.(string), " ")) + } + if v, ok := query["imsis"]; ok && v != "" { + placeholder := repo.KeyPlaceholderByQuery(len(v.([]any))) + conditions = append(conditions, fmt.Sprintf("imsi in (%s)", placeholder)) + for _, v := range v.([]any) { + params = append(params, v.(string)) + } + } + + // for multi-tenancy solution + if v, ok := query["tenantName"]; ok && v != "" { + var tenantID []string + err := dborm.DefaultDB().Table("sys_tenant"). + Where("tenant_name=? and status=1", v).Cols("tenant_id").Distinct().Find(&tenantID) + if err != nil { + log.Errorf("Find tenant_id from sys_user err => %v", err) + } + log.Tracef("userName=%v, tenantID=%v", v, tenantID) + if len(tenantID) > 0 { + conditions = append(conditions, "s.tenant_id = ?") + params = append(params, tenantID[0]) + } + } else if v, ok := query["userName"]; ok && v != "" { + var tenantID string + _, err := dborm.DefaultDB().Table("sys_user"). + Where("user_name=?", v).Cols("tenant_id").Distinct().Get(&tenantID) + if err != nil { + log.Errorf("Find tenant_id from sys_user err => %v", err) + } + log.Tracef("userName=%v, tenantID=%v", v, tenantID) + if tenantID != "" { + conditions = append(conditions, "s.tenant_id = ?") + params = append(params, tenantID) + query["neId"] = "" + } + } + 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.IMSUser{}, + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from u_ims_user s" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 排序 + orderSql := "" + if v, ok := query["sortField"]; ok && v != "" { + sortSql := v.(string) + if o, ok := query["sortOrder"]; ok && o != nil && v != "" { + if o == "desc" { + sortSql += " desc " + } else { + sortSql += " asc " + } + } + orderSql = fmt.Sprintf(" order by %s ", sortSql) + } + + // 查询数据 + querySql := r.selectSql + whereSql + orderSql + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return result + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectList 根据实体查询 +func (r *IMSUserRepository) SelectList(u model.IMSUser) []model.IMSUser { + // 查询条件拼接 + var conditions []string + var params []any + if u.IMSI != "" { + conditions = append(conditions, "imsi = ?") + params = append(params, u.IMSI) + } + if u.NeId != "" { + conditions = append(conditions, "ne_id = ?") + params = append(params, u.NeId) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询数据 + querySql := r.selectSql + whereSql + " order by imsi asc " + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + } + + // 转换实体 + return r.convertResultRows(results) +} + +// SelectByIMSIAndNeID 通过imsi和ne_id查询 +func (r *IMSUserRepository) SelectByIMSIAndNeID(imsi, neId string) model.IMSUser { + querySql := r.selectSql + " where imsi = ? and ne_id = ?" + results, err := datasource.RawDB("", querySql, []any{imsi, neId}) + if err != nil { + logger.Errorf("query err => %v", err) + return model.IMSUser{} + } + // 转换实体 + rows := r.convertResultRows(results) + if len(rows) > 0 { + return rows[0] + } + return model.IMSUser{} +} + +// Insert 批量添加 +func (r *IMSUserRepository) Inserts(uArr []model.IMSUser) int64 { + // multi-tenancy + r.SetTenantID(&uArr) + + tx := datasource.DefaultDB().CreateInBatches(uArr, 2000) + if err := tx.Error; err != nil { + logger.Errorf("CreateInBatches err => %v", err) + } + return tx.RowsAffected +} + +// Delete 删除实体 +func (r *IMSUserRepository) Delete(imsi, neId string) int64 { + tx := datasource.DefaultDB().Where("imsi = ? and ne_id = ?", imsi, neId).Delete(&IMSUserRepository{}) + if err := tx.Error; err != nil { + logger.Errorf("Delete err => %v", err) + } + return tx.RowsAffected +} + +// DeletePrefixByIMSI 删除前缀匹配的实体 +func (r *IMSUserRepository) DeletePrefixByIMSI(imsiPrefix, neId string) int64 { + tx := datasource.DefaultDB().Where("imsi like concat(?, '%') and ne_id = ?", imsiPrefix, neId).Delete(&IMSUserRepository{}) + if err := tx.Error; err != nil { + logger.Errorf("DeletePrefixByIMSI err => %v", err) + } + return tx.RowsAffected +} + +func (r *IMSUserRepository) SetTenantID(subArr *[]model.IMSUser) { + for s := 0; s < len(*subArr); s++ { + var tenantID []string + err := dborm.DefaultDB().Table("sys_tenant"). + Where("status='1' and tenancy_type='IMSI' and ? like tenancy_key", (*subArr)[s].IMSI). + Cols("parent_id").Distinct().Find(&tenantID) + if err != nil { + logger.Errorf("Find tenant_id err => %v", err) + continue + } + if len(tenantID) > 0 { + (*subArr)[s].TenantID = tenantID[0] + } + } +} diff --git a/features/ue/repository/voip_auth.go b/features/ue/repository/voip_auth.go new file mode 100644 index 00000000..bbe263ab --- /dev/null +++ b/features/ue/repository/voip_auth.go @@ -0,0 +1,268 @@ +package repository + +import ( + "fmt" + "strings" + + dborm "be.ems/lib/core/datasource" + "be.ems/lib/log" + "be.ems/src/framework/datasource" + "be.ems/src/framework/logger" + "be.ems/src/framework/utils/parse" + "be.ems/src/framework/utils/repo" + "be.ems/features/ue/model" +) + +// 实例化数据层 VoIPAuthRepository 结构体 +var NewVoIPAuthRepository = &VoIPAuthRepository{ + selectSql: `select + s.id, s.ne_id, s.user_name, s.password, + t.tenant_id, t.tenant_name + from u_voip_auth s + left join sys_tenant t on t.tenant_id = s.tenant_id and t.status = 1`, + + resultMap: map[string]string{ + "id": "ID", + "ne_id": "NeId", + "user_name": "UserName", + "password": "Password", + + + "tenant_id": "TenantID", + "tenant_name": "TenantName", // Tenant name for multi-tenancy + }, +} + +// VoIPAuthRepository UDM签约信息表 数据层处理 +type VoIPAuthRepository struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *VoIPAuthRepository) convertResultRows(rows []map[string]any) []model.VoIPAuth { + arr := make([]model.VoIPAuth, 0) + for _, row := range rows { + item := model.VoIPAuth{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&item, keyMapper, value) + } + } + arr = append(arr, item) + } + return arr +} + +// ClearAndInsert 清空ne_id后新增实体 +func (r *VoIPAuthRepository) ClearAndInsert(neId string, u []model.VoIPAuth) int64 { + // 不指定neID时,用 TRUNCATE 清空表快 + // _, err := datasource.ExecDB("", "TRUNCATE TABLE u_voip_auth", nil) + _, err := datasource.ExecDB("", "DELETE FROM u_voip_auth WHERE ne_id = ?", []any{neId}) + if err != nil { + logger.Errorf("TRUNCATE err => %v", err) + } + + return r.Inserts(u) +} + +// SelectPage 根据条件分页查询字典类型 +func (r *VoIPAuthRepository) SelectPage(query map[string]any) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["userName"]; ok && v != "" { + conditions = append(conditions, "user_name like concat(concat('%', ?), '%')") + params = append(params, strings.Trim(v.(string), " ")) + } + if v, ok := query["userNames"]; ok && v != "" { + placeholder := repo.KeyPlaceholderByQuery(len(v.([]any))) + conditions = append(conditions, fmt.Sprintf("user_name in (%s)", placeholder)) + for _, v := range v.([]any) { + params = append(params, v.(string)) + } + } + + // for multi-tenancy solution + if v, ok := query["tenantName"]; ok && v != "" { + var tenantID []string + err := dborm.DefaultDB().Table("sys_tenant"). + Where("tenant_name=? and status=1", v).Cols("tenant_id").Distinct().Find(&tenantID) + if err != nil { + log.Errorf("Find tenant_id from sys_user err => %v", err) + } + log.Tracef("userName=%v, tenantID=%v", v, tenantID) + if len(tenantID) > 0 { + conditions = append(conditions, "s.tenant_id = ?") + params = append(params, tenantID[0]) + } + } else if v, ok := query["userName"]; ok && v != "" { + var tenantID string + _, err := dborm.DefaultDB().Table("sys_user"). + Where("user_name=?", v).Cols("tenant_id").Distinct().Get(&tenantID) + if err != nil { + log.Errorf("Find tenant_id from sys_user err => %v", err) + } + log.Tracef("userName=%v, tenantID=%v", v, tenantID) + if tenantID != "" { + conditions = append(conditions, "s.tenant_id = ?") + params = append(params, tenantID) + query["neId"] = "" + } + } + 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.VoIPAuth{}, + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from u_voip_auth s" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 排序 + orderSql := "" + if v, ok := query["sortField"]; ok && v != "" { + sortSql := v.(string) + if o, ok := query["sortOrder"]; ok && o != nil && v != "" { + if o == "desc" { + sortSql += " desc " + } else { + sortSql += " asc " + } + } + orderSql = fmt.Sprintf(" order by %s ", sortSql) + } + + // 查询数据 + querySql := r.selectSql + whereSql + orderSql + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return result + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectList 根据实体查询 +func (r *VoIPAuthRepository) SelectList(u model.VoIPAuth) []model.VoIPAuth { + // 查询条件拼接 + var conditions []string + var params []any + if u.UserName != "" { + conditions = append(conditions, "user_name = ?") + params = append(params, u.UserName) + } + if u.NeId != "" { + conditions = append(conditions, "ne_id = ?") + params = append(params, u.NeId) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询数据 + querySql := r.selectSql + whereSql + " order by user_name asc " + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + } + + // 转换实体 + return r.convertResultRows(results) +} + +// SelectByUserNameAndNeID 根据用户名和网元ID查询 +func (r *VoIPAuthRepository) SelectByUserNameAndNeID(userName, neId string) model.VoIPAuth { + querySql := r.selectSql + " where user_name = ? and ne_id = ?" + results, err := datasource.RawDB("", querySql, []any{userName, neId}) + if err != nil { + logger.Errorf("query err => %v", err) + return model.VoIPAuth{} + } + // 转换实体 + rows := r.convertResultRows(results) + if len(rows) > 0 { + return rows[0] + } + return model.VoIPAuth{} +} + +// Insert 批量添加 +func (r *VoIPAuthRepository) Inserts(uArr []model.VoIPAuth) int64 { + // multi-tenancy + r.SetTenantID(&uArr) + + tx := datasource.DefaultDB().CreateInBatches(uArr, 2000) + if err := tx.Error; err != nil { + logger.Errorf("CreateInBatches err => %v", err) + } + return tx.RowsAffected +} + +// Delete 删除实体 +func (r *VoIPAuthRepository) Delete(userName, neId string) int64 { + tx := datasource.DefaultDB().Where("user_name = ? and ne_id = ?", userName, neId).Delete(&VoIPAuthRepository{}) + if err := tx.Error; err != nil { + logger.Errorf("Delete err => %v", err) + } + return tx.RowsAffected +} + +// DeletePrefixByUserName 删除以指定前缀的用户名 +func (r *VoIPAuthRepository) DeletePrefixByUserName(userNamePrefix, neId string) int64 { + tx := datasource.DefaultDB().Where("user_name like concat(?, '%') and ne_id = ?", userNamePrefix, neId).Delete(&VoIPAuthRepository{}) + if err := tx.Error; err != nil { + logger.Errorf("DeletePrefixByUserName err => %v", err) + } + return tx.RowsAffected +} + +func (r *VoIPAuthRepository) SetTenantID(subArr *[]model.VoIPAuth) { + for s := 0; s < len(*subArr); s++ { + var tenantID []string + err := dborm.DefaultDB().Table("sys_tenant"). + Where("status='1' and tenancy_type='MSISDN' and ? like tenancy_key", (*subArr)[s].UserName). + Cols("parent_id").Distinct().Find(&tenantID) + if err != nil { + logger.Errorf("Find tenant_id err => %v", err) + continue + } + if len(tenantID) > 0 { + (*subArr)[s].TenantID = tenantID[0] + } + } +} diff --git a/features/ue/service.go b/features/ue/service.go deleted file mode 100644 index 014fdc4d..00000000 --- a/features/ue/service.go +++ /dev/null @@ -1,17 +0,0 @@ -// log management package - -package ue - -import ( - "be.ems/features/ue/file_export" - "be.ems/lib/log" - "github.com/gin-gonic/gin" -) - -func InitSubServiceRoute(r *gin.Engine) { - log.Info("======init UE management group gin.Engine") - - ueGroup := r.Group("/ue") - // register sub modules routes - file_export.Register(ueGroup) -} diff --git a/features/ue/service/ims_user.go b/features/ue/service/ims_user.go new file mode 100644 index 00000000..365d8e62 --- /dev/null +++ b/features/ue/service/ims_user.go @@ -0,0 +1,211 @@ +package service + +import ( + "fmt" + "strconv" + "strings" + + "be.ems/src/framework/redis" + neService "be.ems/src/modules/network_element/service" + "be.ems/features/ue/model" + "be.ems/features/ue/repository" +) + +// 实例化服务层 IMSUserService 结构体 +var NewIMSUserService = &IMSUserService{ + imsUserRepository: repository.NewIMSUserRepository, +} + +// VoLTE用户信息 服务层处理 +type IMSUserService struct { + imsUserRepository *repository.IMSUserRepository // VoLTE用户信息数据信息 +} + +// dataByRedis UDM签约用户 db:0 中 volte:* +func (r *IMSUserService) dataByRedis(imsi, neId string) []model.IMSUser { + arr := []model.IMSUser{} + 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) + + udmsdArr, err := redis.GetKeys(source, key) + if err != nil { + return arr + } + mkv, err := redis.GetHashBatch(source, udmsdArr) + if err != nil { + return arr + } + + for k, m := range mkv { + var imsi, msisdn string + KeyParts := strings.Split(k, ":") + switch len(KeyParts) { + case 0, 1: + // 处理单个部分的情况 + continue + case 2: + // 处理两个部分的情况 + imsi = KeyParts[1] + msisdn = "-" + case 3: + // 处理三个部分的情况 + imsi = KeyParts[1] + msisdn = KeyParts[2] + default: + // 处理更多部分的情况 + imsi = KeyParts[1] + msisdn = KeyParts[2] + } + + var vni string = "-" + impiParts := strings.Split(m["impi"], "@") + if len(impiParts) > 1 { + vni = impiParts[1] // 输出: ims.mnc001.mcc110.3gppnetwork.org + } + a := model.IMSUser{ + NeId: neId, + IMSI: imsi, // volte:360000100000130:8612300000130 + MSISDN: msisdn, // 8612300000130 + VoLTE: m["tag"], // volte = tag + VNI: vni, // ims.mnc001.mcc110.3gppnetwork.org + } + arr = append(arr, a) + } + return arr +} + +// ResetData 重置鉴权用户数据,清空数据库重新同步Redis数据 +func (r *IMSUserService) ResetData(neId string) int64 { + subArr := r.dataByRedis("*", neId) + // 数据清空后添加 + go r.imsUserRepository.ClearAndInsert(neId, subArr) + return int64(len(subArr)) +} + +// ParseInfo 解析单个用户imsi签约信息 data从命令MML得到的结果 +func (r *IMSUserService) ParseInfo(imsi, neId string, data map[string]string) model.IMSUser { + u := r.imsUserRepository.SelectByIMSIAndNeID(imsi, neId) + + msisdn := data["MSISDN"] + if imsMsisdnLen := strings.Index(msisdn, ","); imsMsisdnLen != -1 { + msisdn = msisdn[:imsMsisdnLen] + } + + // 用于更新 + u.NeId = neId + u.IMSI = imsi + u.MSISDN = msisdn + u.VoLTE = data["VoLTE"] + u.VNI = data["VNI"] + return u +} + +// SelectPage 分页查询数据库 +func (r *IMSUserService) SelectPage(query map[string]any) map[string]any { + return r.imsUserRepository.SelectPage(query) +} + +// SelectList 查询数据库 +func (r *IMSUserService) SelectList(u model.IMSUser) []model.IMSUser { + return r.imsUserRepository.SelectList(u) +} + +// Insert 从数据中读取后删除imsi再存入数据库 +// imsi长度15,ki长度32,opc长度0或者32 +func (r *IMSUserService) Insert(neId string, u model.IMSUser) int64 { + uArr := r.dataByRedis(u.IMSI, neId) + if len(uArr) > 0 { + r.imsUserRepository.Delete(u.IMSI, neId) + return r.imsUserRepository.Inserts(uArr) + } + return 0 +} + +// InsertData 导入文件数据 dataType目前两种:txt/csv +func (r *IMSUserService) 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 { + // keys volte:4600001000004* + arr := r.dataByRedis(prefix+"*", neId) + if len(arr) > 0 { + r.imsUserRepository.DeletePrefixByIMSI(prefix, neId) + num += r.imsUserRepository.Inserts(arr) + } + } + return num +} + +// Delete 删除单个不重新加载 +func (r *IMSUserService) Delete(neId, imsi string) int64 { + return r.imsUserRepository.Delete(imsi, neId) +} + +// LoadData 重新加载从imsi开始num的数据 +func (r *IMSUserService) LoadData(neId, imsi, num string) { + startIMSI, _ := strconv.ParseInt(imsi, 10, 64) + subNum, _ := strconv.ParseInt(num, 10, 64) + var i int64 + for i = 0; i < subNum; i++ { + keyIMSI := fmt.Sprintf("%015d", startIMSI+i) + // 删除原数据 + r.imsUserRepository.Delete(keyIMSI, neId) + arr := r.dataByRedis(keyIMSI, neId) + if len(arr) < 1 { + continue + } + r.imsUserRepository.Inserts(arr) + } +} + +// ParseCommandParams 解析数据组成命令参数 msisdn=xx,xx=xx,... +func (r *IMSUserService) ParseCommandParams(item model.IMSUser) string { + var conditions []string + if item.MSISDN != "" { + conditions = append(conditions, fmt.Sprintf("msisdn=%s", item.MSISDN)) + } + + if item.VoLTE != "" { + conditions = append(conditions, fmt.Sprintf("volte=%s", item.VoLTE)) + } + if item.VNI != "" { + conditions = append(conditions, fmt.Sprintf("vni=%s", item.VNI)) + } + + return strings.Join(conditions, ",") +} diff --git a/features/ue/service/voip_auth.go b/features/ue/service/voip_auth.go new file mode 100644 index 00000000..6aff3b30 --- /dev/null +++ b/features/ue/service/voip_auth.go @@ -0,0 +1,170 @@ +package service + +import ( + "fmt" + "strconv" + "strings" + + "be.ems/src/framework/redis" + neService "be.ems/src/modules/network_element/service" + "be.ems/features/ue/model" + "be.ems/features/ue/repository" +) + +// 实例化服务层 VoIPAuthService 结构体 +var NewVoIPAuthService = &VoIPAuthService{ + voipAuthRepository: repository.NewVoIPAuthRepository, +} + +// VoLTE用户信息 服务层处理 +type VoIPAuthService struct { + voipAuthRepository *repository.VoIPAuthRepository // VoLTE用户信息数据信息 +} + +// dataByRedis UDM签约用户 db:0 中 volte:* +func (r *VoIPAuthService) dataByRedis(userName, neId string) []model.VoIPAuth { + arr := []model.VoIPAuth{} + 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) + + udmsdArr, err := redis.GetKeys(source, key) + if err != nil { + return arr + } + mkv, err := redis.GetHashBatch(source, udmsdArr) + if err != nil { + return arr + } + + for k, m := range mkv { + var userName string + KeyParts := strings.Split(k, ":") + if len(KeyParts) > 1 { + userName = KeyParts[1] + } + + a := model.VoIPAuth{ + NeId: neId, + UserName: userName, // userName + Password: m["password"], // + } + arr = append(arr, a) + } + return arr +} + +// ResetData 重置鉴权用户数据,清空数据库重新同步Redis数据 +func (r *VoIPAuthService) ResetData(neId string) int64 { + subArr := r.dataByRedis("*", neId) + // 数据清空后添加 + go r.voipAuthRepository.ClearAndInsert(neId, subArr) + return int64(len(subArr)) +} + +// ParseInfo 解析单个用户userName信息 data从命令MML得到的结果 +func (r *VoIPAuthService) ParseInfo(userName, neId string, data map[string]string) model.VoIPAuth { + u := r.voipAuthRepository.SelectByUserNameAndNeID(userName, neId) + password := data["password"] + + // 用于更新 + u.NeId = neId + u.UserName = userName + u.Password = password + return u +} + +// SelectPage 分页查询数据库 +func (r *VoIPAuthService) SelectPage(query map[string]any) map[string]any { + return r.voipAuthRepository.SelectPage(query) +} + +// SelectList 查询数据库 +func (r *VoIPAuthService) SelectList(u model.VoIPAuth) []model.VoIPAuth { + return r.voipAuthRepository.SelectList(u) +} + +// Insert 从数据中读取后删除userName再存入数据库 +func (r *VoIPAuthService) Insert(neId string, u model.VoIPAuth) int64 { + uArr := r.dataByRedis(u.UserName, neId) + if len(uArr) > 0 { + r.voipAuthRepository.Delete(u.UserName, neId) + return r.voipAuthRepository.Inserts(uArr) + } + return 0 +} + +// InsertData 导入文件数据 dataType目前两种:txt/csv +func (r *VoIPAuthService) InsertData(neId, dataType string, data any) int64 { + // userName截取前缀,重新获取部分数据 + prefixes := make(map[string]struct{}) + + if dataType == "csv" { + for _, v := range data.([]map[string]string) { + userName := v["userName"] + prefix := userName[:len(userName)-4] + prefixes[prefix] = struct{}{} + } + } + if dataType == "txt" { + for _, v := range data.([][]string) { + userName := v[0] + prefix := userName[:len(userName)-4] + prefixes[prefix] = struct{}{} + } + } + + // 根据前缀重新加载插入 + var num int64 = 0 + for prefix := range prefixes { + // keys volte:4600001000004* + arr := r.dataByRedis(prefix+"*", neId) + if len(arr) > 0 { + r.voipAuthRepository.DeletePrefixByUserName(prefix, neId) + num += r.voipAuthRepository.Inserts(arr) + } + } + return num +} + +// Delete 删除单个不重新加载 +func (r *VoIPAuthService) Delete(neId, userName string) int64 { + return r.voipAuthRepository.Delete(userName, neId) +} + +// LoadData 重新加载从userName开始num的数据 +func (r *VoIPAuthService) 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.voipAuthRepository.Delete(keyUserName, neId) + arr := r.dataByRedis(keyUserName, neId) + if len(arr) < 1 { + continue + } + r.voipAuthRepository.Inserts(arr) + } +} + +// ParseCommandParams 解析数据组成命令参数 msisdn=xx,xx=xx,... +func (r *VoIPAuthService) ParseCommandParams(item model.VoIPAuth) string { + var conditions []string + if item.Password != "" { + conditions = append(conditions, fmt.Sprintf("password=%s", item.Password)) + } + + return strings.Join(conditions, ",") +} diff --git a/src/modules/crontask/processor/exportUEData/exportUEData.go b/src/modules/crontask/processor/exportUEData/exportUEData.go index b4fb1d51..e4093b48 100644 --- a/src/modules/crontask/processor/exportUEData/exportUEData.go +++ b/src/modules/crontask/processor/exportUEData/exportUEData.go @@ -13,7 +13,8 @@ import ( "be.ems/lib/dborm" "be.ems/lib/log" "be.ems/src/framework/cron" - networkdata "be.ems/src/modules/network_data" + ueService "be.ems/features/ue/service" + neService "be.ems/src/modules/network_data/service" ) var NewProcessor = &BarProcessor{ @@ -103,7 +104,7 @@ func (s *BarProcessor) exportUEData(param BarParams) (map[string]any, error) { for _, neID := range neIDs { log.Trace("ne_id:", neID) // 1. 加载最新数据, 如果数据服务存在,则重新加载数据 - dataService, err := networkdata.GetService(param.ServiceName) + dataService, err := GetService(param.ServiceName) if err != nil { log.Warn("failed to get data service:", err) } else if dataService != nil { @@ -224,3 +225,33 @@ func (s *BarProcessor) exportData(query, filePath string, fileType string) (int6 return affected, nil } + +// ResettableService 接口定义 +type ResettableService interface { + ResetData(neID string) int64 +} + +// 服务注册表 +var serviceRegistry = make(map[string]ResettableService) +func RegisterService(name string, service ResettableService) { + serviceRegistry[name] = service +} + +// 获取服务 +func GetService(name string) (ResettableService, error) { + service, exists := serviceRegistry[name] + if !exists { + return nil, fmt.Errorf("service %s not found", name) + } + return service, nil +} + +// 初始化注册表 +func init() { + RegisterService("UDMAuthUser", neService.NewUDMAuthUser) + RegisterService("UDMSubUser", neService.NewUDMAuthUser) + RegisterService("UDMVoIPAuth", ueService.NewVoIPAuthService) + RegisterService("UDMIMSUser", ueService.NewIMSUserService) + + // 这里注册更多服务 +} diff --git a/src/modules/network_data/network_data.go b/src/modules/network_data/network_data.go index abe55e62..88b2758b 100644 --- a/src/modules/network_data/network_data.go +++ b/src/modules/network_data/network_data.go @@ -9,7 +9,6 @@ import ( "be.ems/src/modules/network_data/service" "github.com/gin-gonic/gin" - "fmt" ) // 模块路由注册 @@ -324,34 +323,6 @@ func Setup(router *gin.Engine) { } } -// ResettableService 接口定义 -type ResettableService interface { - ResetData(neID string) int64 -} - -// 服务注册表 -var serviceRegistry = make(map[string]ResettableService) -func RegisterService(name string, service ResettableService) { - serviceRegistry[name] = service -} - -// 获取服务 -func GetService(name string) (ResettableService, error) { - service, exists := serviceRegistry[name] - if !exists { - return nil, fmt.Errorf("service %s not found", name) - } - return service, nil -} - -// 初始化注册表 -func init() { - RegisterService("UDMAuthUser", service.NewUDMAuthUser) - RegisterService("UDMSubUser", service.NewUDMSubUser) - - // 这里注册更多服务 -} - // InitLoad 初始参数 func InitLoad() { // 启动时,加载UPF上下行流量