From cd7e5693f5d9fb91e636f45946beed52296d4151 Mon Sep 17 00:00:00 2001 From: simonzhangsz Date: Thu, 29 Aug 2024 11:46:45 +0800 Subject: [PATCH] add: custom kpi and export log&cdr file --- database/install/function.sql | 16 + database/install/sys_dict_data1_i18n_zh.sql | 9 +- database/install/sys_dict_data2_i18n_en.sql | 7 + database/install/sys_job.sql | 5 +- database/install/sys_job_log.sql | 71 ++-- database/upgrade/upg_function.sql | 16 + .../upgrade/upg_sys_dict_data1_i18n_zh.sql | 9 +- .../upgrade/upg_sys_dict_data2_i18n_en.sql | 7 + database/upgrade/upg_sys_job.sql | 7 + database/upgrade/upg_sys_job_log.sql | 72 ++-- features/features.go | 19 + features/lm/file_export/controller.go | 141 ++++++++ features/lm/file_export/model.go | 30 ++ features/lm/file_export/route.go | 40 +++ features/lm/service.go | 17 + features/pm/kpi_c_report/controller.go | 329 ++++++++++++++++++ features/pm/kpi_c_report/model.go | 74 ++++ features/pm/kpi_c_report/route.go | 43 +++ features/pm/kpi_c_title/controller.go | 174 +++++++++ features/pm/kpi_c_title/model.go | 30 ++ features/pm/kpi_c_title/route.go | 39 +++ features/pm/performance.go | 59 +++- features/pm/service.go | 19 + lib/eval/evaluate.go | 111 ++++++ lib/file/file.go | 27 ++ lib/file/file_linux.go | 63 ++++ lib/file/file_windows.go | 61 ++++ lib/services/response.go | 35 ++ misc/setomc.sh | 10 +- restagent/restagent.go | 4 + src/modules/ws/service/ws_send.impl.go | 2 + 31 files changed, 1459 insertions(+), 87 deletions(-) create mode 100644 database/install/function.sql create mode 100644 database/upgrade/upg_function.sql create mode 100644 features/features.go create mode 100644 features/lm/file_export/controller.go create mode 100644 features/lm/file_export/model.go create mode 100644 features/lm/file_export/route.go create mode 100644 features/lm/service.go create mode 100644 features/pm/kpi_c_report/controller.go create mode 100644 features/pm/kpi_c_report/model.go create mode 100644 features/pm/kpi_c_report/route.go create mode 100644 features/pm/kpi_c_title/controller.go create mode 100644 features/pm/kpi_c_title/model.go create mode 100644 features/pm/kpi_c_title/route.go create mode 100644 features/pm/service.go create mode 100644 lib/eval/evaluate.go create mode 100644 lib/file/file.go create mode 100644 lib/file/file_linux.go create mode 100644 lib/file/file_windows.go create mode 100644 lib/services/response.go diff --git a/database/install/function.sql b/database/install/function.sql new file mode 100644 index 00000000..4dbfface --- /dev/null +++ b/database/install/function.sql @@ -0,0 +1,16 @@ +DELIMITER // + +CREATE FUNCTION IF NOT EXISTS omc_get_dict_value(field_value VARCHAR(255), type VARCHAR(255)) +RETURNS VARCHAR(255) +DETERMINISTIC +BEGIN +DECLARE result VARCHAR(255); + +SELECT `dict_value` INTO result +FROM `sys_dict_data` +WHERE `dict_label` = field_value AND `dict_type` = type limit 1; + +RETURN result; +END // + +DELIMITER; \ No newline at end of file diff --git a/database/install/sys_dict_data1_i18n_zh.sql b/database/install/sys_dict_data1_i18n_zh.sql index c521c6c3..e1d49f16 100644 --- a/database/install/sys_dict_data1_i18n_zh.sql +++ b/database/install/sys_dict_data1_i18n_zh.sql @@ -542,7 +542,7 @@ INSERT INTO `sys_dict_data` VALUES (2031, 2031, 'sys.account.captchaType', '账 INSERT INTO `sys_dict_data` VALUES (2032, 2032, 'sys.account.captchaTypeRemark', '使用验证码类型(math数值计算,char字符验证)', 'i18n_zh', NULL, NULL, '1', 'supervisor', 1700000000000, NULL, 0, NULL); INSERT INTO `sys_dict_data` VALUES (2033, 2033, 'menu.dashboard', '仪表盘', 'i18n_zh', NULL, NULL, '1', 'supervisor', 1705550000000, NULL, 0, NULL); INSERT INTO `sys_dict_data` VALUES (2034, 2034, 'menu.dashboard.overview', '总览', 'i18n_zh', NULL, NULL, '1', 'supervisor', 1705550000000, NULL, 0, NULL); -INSERT INTO `sys_dict_data` VALUES (2035, 2035, 'menu.dashboard.imsCDR', '通话话单', 'i18n_zh', NULL, NULL, '1', 'supervisor', 1705550000000, NULL, 0, NULL); +INSERT INTO `sys_dict_data` VALUES (2035, 2035, 'menu.dashboard.imsCDR', '语音话单', 'i18n_zh', NULL, NULL, '1', 'supervisor', 1705550000000, NULL, 0, NULL); INSERT INTO `sys_dict_data` VALUES (2036, 2036, 'dictType.cdr_sip_code', 'CDR SIP响应代码类别类型', 'i18n_zh', NULL, NULL, '1', 'supervisor', 1705550000000, NULL, 0, NULL); INSERT INTO `sys_dict_data` VALUES (2037, 2037, 'dictType.cdr_call_type', 'CDR 呼叫类型', 'i18n_zh', NULL, NULL, '1', 'supervisor', 1705550000000, NULL, 0, NULL); INSERT INTO `sys_dict_data` VALUES (2038, 2038, 'dictType.ue_auth_code', 'UE 事件认证代码类型', 'i18n_zh', NULL, NULL, '1', 'supervisor', 1705550000000, NULL, 0, NULL); @@ -664,5 +664,12 @@ INSERT INTO `sys_dict_data` VALUES (2153, 2153, 'menu.system.setting.lock', '锁 INSERT INTO `sys_dict_data` VALUES (2154, 2154, 'menu.ne.neConfigBackup', '网元配置备份', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); INSERT INTO `sys_dict_data` VALUES (2155, 2155, 'job.ne_config_backup', '网元-配置文件定期备份', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); INSERT INTO `sys_dict_data` VALUES (2156, 2156, 'job.ne_config_backup_remark', '网元配置文件定期备份到网管服务器\r\n可查看网元配置备份记录进行下载或通过网元信息操作导入配置', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +INSERT INTO `sys_dict_data` VALUES (2157, 2157, 'job.exportOperateLog', '定期从操作日志表导出文件到指定目录', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +INSERT INTO `sys_dict_data` VALUES (2158, 2158, 'job.exportIMSCDR', '定期从语音话单表导出文件至指定目录', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +INSERT INTO `sys_dict_data` VALUES (2159, 2159, 'job.exportSMFCDR', '定期从数据话单表导出文件至指定目录', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +INSERT INTO `sys_dict_data` VALUES (2160, 2160, 'table.sys_log_operate', '操作日志', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +INSERT INTO `sys_dict_data` VALUES (2161, 2161, 'table.cdr_event_ims', '语音话单', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +INSERT INTO `sys_dict_data` VALUES (2162, 2162, 'table.cdr_event_smf', '数据话单', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +INSERT INTO `sys_dict_data` VALUES (2163, 2163, 'table.cdr_event_smsc', '短信话单', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); SET FOREIGN_KEY_CHECKS = 1; diff --git a/database/install/sys_dict_data2_i18n_en.sql b/database/install/sys_dict_data2_i18n_en.sql index 3aebbb34..8d72e765 100644 --- a/database/install/sys_dict_data2_i18n_en.sql +++ b/database/install/sys_dict_data2_i18n_en.sql @@ -664,5 +664,12 @@ INSERT INTO `sys_dict_data` VALUES (4153, 4153, 'menu.system.setting.lock', 'Loc INSERT INTO `sys_dict_data` VALUES (4154, 4154, 'menu.ne.neConfigBackup', 'NE Config Backups', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); INSERT INTO `sys_dict_data` VALUES (4155, 4155, 'job.ne_config_backup', 'NE-Config Backup Regularly', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); INSERT INTO `sys_dict_data` VALUES (4156, 4156, 'job.ne_config_backup_remark', 'Network Element Configuration files are regularly backed up to the OMC\r\nView network element configuration backup records for downloading or importing configurations through network element information operations.', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +INSERT INTO `sys_dict_data` VALUES (4157, 4157, 'job.exportOperateLog', 'Export regularly from operation log table', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +INSERT INTO `sys_dict_data` VALUES (4158, 4158, 'job.exportIMSCDR', 'Export regularly from IMS CDR table', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +INSERT INTO `sys_dict_data` VALUES (4159, 4159, 'job.exportSMFCDR', 'Export regularly from SMF CDR table', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +INSERT INTO `sys_dict_data` VALUES (4160, 4160, 'table.sys_log_operate', 'Operation Log', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +INSERT INTO `sys_dict_data` VALUES (4161, 4161, 'table.cdr_event_ims', 'Voice CDR', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +INSERT INTO `sys_dict_data` VALUES (4162, 4162, 'table.cdr_event_smf', 'Data CDR', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +INSERT INTO `sys_dict_data` VALUES (4163, 4163, 'table.cdr_event_smsc', 'SMS CDR', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); SET FOREIGN_KEY_CHECKS = 1; diff --git a/database/install/sys_job.sql b/database/install/sys_job.sql index 7e2243ac..131ad511 100644 --- a/database/install/sys_job.sql +++ b/database/install/sys_job.sql @@ -10,7 +10,7 @@ CREATE TABLE `sys_job` ( `job_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '任务名称', `job_group` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'DEFAULT' COMMENT '任务组名', `invoke_target` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '调用目标字符串', - `target_params` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '调用目标传入参数', + `target_params` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '调用目标传入参数', `cron_expression` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'cron执行表达式', `misfire_policy` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '3' COMMENT '计划执行错误策略(1立即执行 2执行一次 3放弃执行)', `concurrent` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '0' COMMENT '是否并发执行(0禁止 1允许)', @@ -37,5 +37,8 @@ INSERT INTO `sys_job` VALUES (7, 'job.backupEtcFromNE', 'SYSTEM', 'backupEtcFrom INSERT INTO `sys_job` VALUES (8, 'job.deleteExpiredNeStateRecord', 'SYSTEM', 'deleteExpiredRecord', '{\"duration\":1,\"tableName\":\"ne_state\",\"colName\":\"timestamp\"}', '0 25 0 * * ?', '3', '0', '1', '1', 'supervisor', 1698478134842, 'admin', 1703668901929, 'job.deleteExpiredNeStateRecordRemark'); INSERT INTO `sys_job` VALUES (9, 'job.getStateFromNE', 'SYSTEM', 'getStateFromNE', '', '0/10 * * * * ?', '3', '0', '0', '0', 'supervisor', 1698478134842, 'admin', 1713231120503, 'job.getStateFromNERemark'); INSERT INTO `sys_job` VALUES (10, 'job.genNeStateAlarm', 'SYSTEM', 'genNeStateAlarm', '{\"alarmID\":\"HXEMSSM10000\",\"alarmCode\":10000,\"alarmTitle\":\"The system state is abnormal\",\"neType\":\"OMC\",\"alarmType\":\"EquipmentAlarm\",\"origSeverity\": \"Major\",\"objectName\":\"EMS;SystemManagement;Heartbeat\",\"objectType\":\"SystemState\",\"specificProblem\":\"Alarm cause: the system state of target NE has not been received for {threshold} seconds\", \"specificProblemID\":\"AC10000\",\"threshold\":30}', '0/5 * * * * ?', '3', '0', '0', '0', 'supervisor', 1698478134842, 'admin', 1713781643031, 'job.genNeStateAlarmRemark'); +INSERT INTO `sys_job` VALUES (11, 'job.exportOperateLog', 'SYSTEM', 'exportTable', '{\"duration\":1,\"tableName\":\"sys_log_operate\",\"timeCol\":\"oper_time\",\"timeUnit\":\"milli\",\"columns\":\"oper_id,omc_get_dict_value(title, \\\"i18n_en\\\") as title,business_type,method,request_method,operator_type,oper_name,dept_name,oper_url,oper_ip,oper_location,oper_param,oper_msg,status,oper_time,cost_time,tenant_id\",\"extras\":\"\",\"filePath\":\"/usr/local/omc/backup/operate_log\"}', '0 0 0/1 * * ?', '3', '0', '1', '1', 'supervisor', 1698478134842, 'admin', 1724833786290, 'job.exportOperateLog'); +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, ''); SET FOREIGN_KEY_CHECKS = 1; diff --git a/database/install/sys_job_log.sql b/database/install/sys_job_log.sql index 082d51cf..4d175d39 100644 --- a/database/install/sys_job_log.sql +++ b/database/install/sys_job_log.sql @@ -1,48 +1,37 @@ --- MariaDB dump 10.19 Distrib 10.6.16-MariaDB, for debian-linux-gnu (x86_64) --- --- Host: 192.168.2.219 Database: omc_db --- ------------------------------------------------------ --- Server version 10.3.38-MariaDB +/* + Navicat Premium Data Transfer -/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; -/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; -/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8mb4 */; -/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; -/*!40103 SET TIME_ZONE='+00:00' */; -/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; -/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; -/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; -/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + Source Server : local_mariadb + Source Server Type : MariaDB + Source Server Version : 100338 (10.3.38-MariaDB) + Source Host : localhost:33066 + Source Schema : omc_db --- --- Table structure for table `sys_job_log` --- + Target Server Type : MariaDB + Target Server Version : 100338 (10.3.38-MariaDB) + File Encoding : 65001 + Date: 26/08/2024 09:51:25 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for sys_job_log +-- ---------------------------- DROP TABLE IF EXISTS `sys_job_log`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `sys_job_log` ( +CREATE TABLE `sys_job_log` ( `job_log_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务日志ID', - `job_name` varchar(64) NOT NULL COMMENT '任务名称', - `job_group` varchar(64) NOT NULL COMMENT '任务组名', - `invoke_target` varchar(64) NOT NULL COMMENT '调用目标字符串', - `target_params` varchar(500) DEFAULT '' COMMENT '调用目标传入参数', - `job_msg` varchar(500) DEFAULT '' COMMENT '日志信息', - `status` char(1) DEFAULT '0' COMMENT '执行状态(0失败 1正常)', - `create_time` bigint(20) DEFAULT 0 COMMENT '创建时间', - `cost_time` bigint(20) DEFAULT 0 COMMENT '消耗时间(毫秒)', + `job_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '任务名称', + `job_group` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '任务组名', + `invoke_target` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '调用目标字符串', + `target_params` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '调用目标传入参数', + `job_msg` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '日志信息', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '执行状态(0失败 1正常)', + `create_time` bigint(20) NULL DEFAULT 0 COMMENT '创建时间', + `cost_time` bigint(20) NULL DEFAULT 0 COMMENT '消耗时间(毫秒)', PRIMARY KEY (`job_log_id`) USING BTREE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='调度任务调度日志表'; -/*!40101 SET character_set_client = @saved_cs_client */; -/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; +) ENGINE = InnoDB AUTO_INCREMENT = 421 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '调度任务调度日志表' ROW_FORMAT = Dynamic; -/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; -/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; -/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; -/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; -/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; -/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; -/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; - --- Dump completed on 2024-03-06 17:26:58 +SET FOREIGN_KEY_CHECKS = 1; diff --git a/database/upgrade/upg_function.sql b/database/upgrade/upg_function.sql new file mode 100644 index 00000000..3c426344 --- /dev/null +++ b/database/upgrade/upg_function.sql @@ -0,0 +1,16 @@ +DELIMITER / / + +CREATE FUNCTION IF NOT EXISTS omc_get_dict_value(field_value VARCHAR(255), type VARCHAR(255)) +RETURNS VARCHAR(255) +DETERMINISTIC +BEGIN +DECLARE result VARCHAR(255); + +SELECT `dict_value` INTO result +FROM `sys_dict_data` +WHERE `dict_label` = field_value AND `dict_type` = type limit 1; + +RETURN result; +END // + +DELIMITER; \ No newline at end of file diff --git a/database/upgrade/upg_sys_dict_data1_i18n_zh.sql b/database/upgrade/upg_sys_dict_data1_i18n_zh.sql index aa4887be..7d65b94b 100644 --- a/database/upgrade/upg_sys_dict_data1_i18n_zh.sql +++ b/database/upgrade/upg_sys_dict_data1_i18n_zh.sql @@ -549,7 +549,7 @@ REPLACE INTO `sys_dict_data` VALUES (2031, 2031, 'sys.account.captchaType', '账 REPLACE INTO `sys_dict_data` VALUES (2032, 2032, 'sys.account.captchaTypeRemark', '使用验证码类型(math数值计算,char字符验证)', 'i18n_zh', NULL, NULL, '1', 'supervisor', 1700000000000, NULL, 0, NULL); REPLACE INTO `sys_dict_data` VALUES (2033, 2033, 'menu.dashboard', '仪表盘', 'i18n_zh', NULL, NULL, '1', 'supervisor', 1705550000000, NULL, 0, NULL); REPLACE INTO `sys_dict_data` VALUES (2034, 2034, 'menu.dashboard.overview', '总览', 'i18n_zh', NULL, NULL, '1', 'supervisor', 1705550000000, NULL, 0, NULL); -REPLACE INTO `sys_dict_data` VALUES (2035, 2035, 'menu.dashboard.imsCDR', '通话话单', 'i18n_zh', NULL, NULL, '1', 'supervisor', 1705550000000, NULL, 0, NULL); +REPLACE INTO `sys_dict_data` VALUES (2035, 2035, 'menu.dashboard.imsCDR', '语音话单', 'i18n_zh', NULL, NULL, '1', 'supervisor', 1705550000000, NULL, 0, NULL); REPLACE INTO `sys_dict_data` VALUES (2036, 2036, 'dictType.cdr_sip_code', 'CDR SIP响应代码类别类型', 'i18n_zh', NULL, NULL, '1', 'supervisor', 1705550000000, NULL, 0, NULL); REPLACE INTO `sys_dict_data` VALUES (2037, 2037, 'dictType.cdr_call_type', 'CDR 呼叫类型', 'i18n_zh', NULL, NULL, '1', 'supervisor', 1705550000000, NULL, 0, NULL); REPLACE INTO `sys_dict_data` VALUES (2038, 2038, 'dictType.ue_auth_code', 'UE 事件认证代码类型', 'i18n_zh', NULL, NULL, '1', 'supervisor', 1705550000000, NULL, 0, NULL); @@ -671,5 +671,12 @@ REPLACE INTO `sys_dict_data` VALUES (2153, 2153, 'menu.system.setting.lock', ' REPLACE INTO `sys_dict_data` VALUES (2154, 2154, 'menu.ne.neConfigBackup', '网元配置备份', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); REPLACE INTO `sys_dict_data` VALUES (2155, 2155, 'job.ne_config_backup', '网元-配置文件定期备份', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); REPLACE INTO `sys_dict_data` VALUES (2156, 2156, 'job.ne_config_backup_remark', '网元配置文件定期备份到网管服务器\r\n可查看网元配置备份记录进行下载或通过网元信息操作导入配置', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +REPLACE INTO `sys_dict_data` VALUES (2157, 2157, 'job.exportOperateLog', '定期从操作日志表导出文件到指定目录', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +REPLACE INTO `sys_dict_data` VALUES (2158, 2158, 'job.exportIMSCDR', '定期从语音话单表导出文件至指定目录', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +REPLACE INTO `sys_dict_data` VALUES (2159, 2159, 'job.exportSMFCDR', '定期从数据话单表导出文件至指定目录', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +REPLACE INTO `sys_dict_data` VALUES (2160, 2160, 'table.sys_log_operate', '操作日志', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +REPLACE INTO `sys_dict_data` VALUES (2161, 2161, 'table.cdr_event_ims', '语音话单', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +REPLACE INTO `sys_dict_data` VALUES (2162, 2162, 'table.cdr_event_smf', '数据话单', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +REPLACE INTO `sys_dict_data` VALUES (2163, 2163, 'table.cdr_event_smsc', '短信话单', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); SET FOREIGN_KEY_CHECKS = 1; diff --git a/database/upgrade/upg_sys_dict_data2_i18n_en.sql b/database/upgrade/upg_sys_dict_data2_i18n_en.sql index 7309cce7..6b2c5755 100644 --- a/database/upgrade/upg_sys_dict_data2_i18n_en.sql +++ b/database/upgrade/upg_sys_dict_data2_i18n_en.sql @@ -666,5 +666,12 @@ REPLACE INTO `sys_dict_data` VALUES (4153, 4153, 'menu.system.setting.lock', 'Lo REPLACE INTO `sys_dict_data` VALUES (4154, 4154, 'menu.ne.neConfigBackup', 'NE Config Backups', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); REPLACE INTO `sys_dict_data` VALUES (4155, 4155, 'job.ne_config_backup', 'NE-Config Backup Regularly', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); REPLACE INTO `sys_dict_data` VALUES (4156, 4156, 'job.ne_config_backup_remark', 'Network Element Configuration files are regularly backed up to the OMC\r\nView network element configuration backup records for downloading or importing configurations through network element information operations.', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +REPLACE INTO `sys_dict_data` VALUES (4157, 4157, 'job.exportOperateLog', 'Export regularly from operation log table', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +REPLACE INTO `sys_dict_data` VALUES (4158, 4158, 'job.exportIMSCDR', 'Export regularly from IMS CDR table', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +REPLACE INTO `sys_dict_data` VALUES (4159, 4159, 'job.exportSMFCDR', 'Export regularly from SMF CDR table', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +REPLACE INTO `sys_dict_data` VALUES (4160, 4160, 'table.sys_log_operate', 'Operation Log', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +REPLACE INTO `sys_dict_data` VALUES (4161, 4161, 'table.cdr_event_ims', 'Voice CDR', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +REPLACE INTO `sys_dict_data` VALUES (4162, 4162, 'table.cdr_event_smf', 'Data CDR', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); +REPLACE INTO `sys_dict_data` VALUES (4163, 4163, 'table.cdr_event_smsc', 'SMS CDR', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, ''); SET FOREIGN_KEY_CHECKS = 1; diff --git a/database/upgrade/upg_sys_job.sql b/database/upgrade/upg_sys_job.sql index 023d7f86..48332084 100644 --- a/database/upgrade/upg_sys_job.sql +++ b/database/upgrade/upg_sys_job.sql @@ -24,6 +24,10 @@ CREATE TABLE IF NOT EXISTS `sys_job` ( UNIQUE KEY `idx_uni_name_group` (`job_name`,`job_group`) USING BTREE COMMENT 'unique index for job_name and job_group' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='调度任务调度表'; + +ALTER TABLE `sys_job` +MODIFY COLUMN `target_params` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '调用目标传入参数' AFTER `invoke_target`; + -- ---------------------------- -- Records of sys_job -- ---------------------------- @@ -37,5 +41,8 @@ REPLACE INTO `sys_job` VALUES (7, 'job.backupEtcFromNE', 'SYSTEM', 'backupEtcFro REPLACE INTO `sys_job` VALUES (8, 'job.deleteExpiredNeStateRecord', 'SYSTEM', 'deleteExpiredRecord', '{\"duration\":1,\"tableName\":\"ne_state\",\"colName\":\"timestamp\"}', '0 25 0 * * ?', '3', '0', '1', '1', 'supervisor', 1698478134842, 'admin', 1703668901929, 'job.deleteExpiredNeStateRecordRemark'); REPLACE INTO `sys_job` VALUES (9, 'job.getStateFromNE', 'SYSTEM', 'getStateFromNE', '', '0/10 * * * * ?', '3', '0', '0', '0', 'supervisor', 1698478134842, 'admin', 1713231120503, 'job.getStateFromNERemark'); REPLACE INTO `sys_job` VALUES (10, 'job.genNeStateAlarm', 'SYSTEM', 'genNeStateAlarm', '{\"alarmID\":\"HXEMSSM10000\",\"alarmCode\":10000,\"alarmTitle\":\"The system state is abnormal\",\"neType\":\"OMC\",\"alarmType\":\"EquipmentAlarm\",\"origSeverity\": \"Major\",\"objectName\":\"EMS;SystemManagement;Heartbeat\",\"objectType\":\"SystemState\",\"specificProblem\":\"Alarm cause: the system state of target NE has not been received for {threshold} seconds\", \"specificProblemID\":\"AC10000\",\"threshold\":30}', '0/5 * * * * ?', '3', '0', '0', '0', 'supervisor', 1698478134842, 'admin', 1713781643031, 'job.genNeStateAlarmRemark'); +REPLACE INTO `sys_job` VALUES (11, 'job.exportOperateLog', 'SYSTEM', 'exportTable', '{\"duration\":1,\"tableName\":\"sys_log_operate\",\"timeCol\":\"oper_time\",\"timeUnit\":\"milli\",\"columns\":\"oper_id,omc_get_dict_value(title, \\\"i18n_en\\\") as title,business_type,method,request_method,operator_type,oper_name,dept_name,oper_url,oper_ip,oper_location,oper_param,oper_msg,status,oper_time,cost_time,tenant_id\",\"extras\":\"\",\"filePath\":\"/usr/local/omc/backup/operate_log\"}', '0 0 0/1 * * ?', '3', '0', '1', '1', 'supervisor', 1698478134842, 'admin', 1724833786290, 'job.exportOperateLog'); +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,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, ''); SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/database/upgrade/upg_sys_job_log.sql b/database/upgrade/upg_sys_job_log.sql index b35d00e1..96d55b8b 100644 --- a/database/upgrade/upg_sys_job_log.sql +++ b/database/upgrade/upg_sys_job_log.sql @@ -1,47 +1,39 @@ --- MariaDB dump 10.19 Distrib 10.6.16-MariaDB, for debian-linux-gnu (x86_64) --- --- Host: 192.168.2.219 Database: omc_db --- ------------------------------------------------------ --- Server version 10.3.38-MariaDB +/* + Navicat Premium Data Transfer -/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; -/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; -/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8mb4 */; -/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; -/*!40103 SET TIME_ZONE='+00:00' */; -/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; -/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; -/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; -/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + Source Server : local_mariadb + Source Server Type : MariaDB + Source Server Version : 100338 (10.3.38-MariaDB) + Source Host : localhost:33066 + Source Schema : omc_db --- --- Table structure for table `sys_job_log` --- + Target Server Type : MariaDB + Target Server Version : 100338 (10.3.38-MariaDB) + File Encoding : 65001 -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE IF NOT EXISTS `sys_job_log` ( + Date: 26/08/2024 09:52:51 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for sys_job_log +-- ---------------------------- +CREATE TABLE IF NOT EXISTS `sys_job_log` ( `job_log_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务日志ID', - `job_name` varchar(64) NOT NULL COMMENT '任务名称', - `job_group` varchar(64) NOT NULL COMMENT '任务组名', - `invoke_target` varchar(64) NOT NULL COMMENT '调用目标字符串', - `target_params` varchar(500) DEFAULT '' COMMENT '调用目标传入参数', - `job_msg` varchar(500) DEFAULT '' COMMENT '日志信息', - `status` char(1) DEFAULT '0' COMMENT '执行状态(0失败 1正常)', - `create_time` bigint(20) DEFAULT 0 COMMENT '创建时间', - `cost_time` bigint(20) DEFAULT 0 COMMENT '消耗时间(毫秒)', + `job_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '任务名称', + `job_group` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '任务组名', + `invoke_target` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '调用目标字符串', + `target_params` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '调用目标传入参数', + `job_msg` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '日志信息', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '执行状态(0失败 1正常)', + `create_time` bigint(20) NULL DEFAULT 0 COMMENT '创建时间', + `cost_time` bigint(20) NULL DEFAULT 0 COMMENT '消耗时间(毫秒)', PRIMARY KEY (`job_log_id`) USING BTREE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='调度任务调度日志表'; -/*!40101 SET character_set_client = @saved_cs_client */; -/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; +) ENGINE = InnoDB AUTO_INCREMENT = 421 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '调度任务调度日志表' ROW_FORMAT = Dynamic; -/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; -/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; -/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; -/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; -/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; -/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; -/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; +ALTER TABLE `sys_job_log` +MODIFY COLUMN `target_params` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '调用目标传入参数' AFTER `invoke_target`; --- Dump completed on 2024-03-06 17:26:58 +SET FOREIGN_KEY_CHECKS = 1; diff --git a/features/features.go b/features/features.go new file mode 100644 index 00000000..70510558 --- /dev/null +++ b/features/features.go @@ -0,0 +1,19 @@ +package features + +import ( + "be.ems/features/lm" + "be.ems/features/pm" + "be.ems/lib/log" + "github.com/gin-gonic/gin" +) + +func InitServiceEngine(r *gin.Engine) { + log.Info("======init feature group gin.Engine") + + // featuresGroup := r.Group("/") + // 注册 各个features 模块的路由 + pm.InitSubServiceRoute(r) + lm.InitSubServiceRoute(r) + + // return featuresGroup +} diff --git a/features/lm/file_export/controller.go b/features/lm/file_export/controller.go new file mode 100644 index 00000000..5fba6abb --- /dev/null +++ b/features/lm/file_export/controller.go @@ -0,0 +1,141 @@ +package file_export + +import ( + "encoding/json" + "net/http" + "os" + "path/filepath" + + "be.ems/lib/file" + "be.ems/lib/log" + "be.ems/lib/services" + "be.ems/src/framework/datasource" + "be.ems/src/framework/i18n" + "be.ems/src/framework/utils/ctx" + "github.com/gin-gonic/gin" +) + +type SysJobResponse struct { + SysJob + TableName string `json:"tableName"` + TableDisplay string `json:"tableDisplay"` + FilePath string `json:"filePath"` +} + +type TargetParams struct { + Duration int `json:"duration"` + TableName string `json:"tableName"` + Columns string `json:"columns"` // exported column name of time string + TimeCol string `json:"timeCol"` // time stamp of column name + TimeUnit string `json:"timeUnit"` // timestamp unit: second/micro/milli + Extras string `json:"extras"` // extras condition for where + FilePath string `json:"filePath"` // file path +} + +func (m *SysJob) GetFileExportTable(c *gin.Context) { + var results []SysJob + + err := datasource.DefaultDB().Table(m.TableName()).Where("invoke_target=? and status=1", INVOKE_FILE_EXPORT). + Find(&results).Error + if err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + language := ctx.AcceptLanguage(c) + var response []SysJobResponse + for _, job := range results { + var params TargetParams + if err := json.Unmarshal([]byte(job.TargetParams), ¶ms); err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + TableDisplay := i18n.TKey(language, "table."+params.TableName) + if TableDisplay == "" { + TableDisplay = params.TableName + } + response = append(response, SysJobResponse{ + SysJob: job, + TableName: params.TableName, + TableDisplay: TableDisplay, + FilePath: params.FilePath, + }) + } + c.JSON(http.StatusOK, services.DataResp(response)) +} + +func (m *FileExport) GetFileList(c *gin.Context) { + var querys FileExportQuery + + if err := c.ShouldBindQuery(&querys); err != nil { + c.JSON(http.StatusBadRequest, services.ErrResp(err.Error())) + return + } + + files, err := file.GetFileInfo(querys.Path, querys.Suffix) + if err != nil { + log.Error(err) + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + } + + // 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(err) + c.JSON(http.StatusInternalServerError, 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.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + defer file.Close() + + if _, err := os.Stat(filePath); os.IsNotExist(err) { + c.JSON(http.StatusNotFound, 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.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + c.JSON(http.StatusNoContent, nil) // 204 No Content +} diff --git a/features/lm/file_export/model.go b/features/lm/file_export/model.go new file mode 100644 index 00000000..33a611f9 --- /dev/null +++ b/features/lm/file_export/model.go @@ -0,0 +1,30 @@ +package file_export + +import ( + "be.ems/lib/file" +) + +const ( + INVOKE_FILE_EXPORT = "exportTable" +) + +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 FileExport struct { + file.FileInfo +} + +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/lm/file_export/route.go b/features/lm/file_export/route.go new file mode 100644 index 00000000..d6caba91 --- /dev/null +++ b/features/lm/file_export/route.go @@ -0,0 +1,40 @@ +package file_export + +import ( + "be.ems/src/framework/middleware" + "github.com/gin-gonic/gin" +) + +// Register Routes for file_export +func Register(r *gin.RouterGroup) { + + lmTable := r.Group("/table") + { + var m *SysJob + lmTable.GET("/list", + middleware.PreAuthorize(nil), + m.GetFileExportTable, + ) + + } + lmFile := r.Group("/file") + { + var f *FileExport + lmFile.GET("/list", + middleware.PreAuthorize(nil), + f.GetFileList, + ) + lmFile.GET("/total", + middleware.PreAuthorize(nil), + f.Total, + ) + lmFile.GET("/:fileName", + middleware.PreAuthorize(nil), + f.DownloadHandler, + ) + lmFile.DELETE("/:fileName", + middleware.PreAuthorize(nil), + f.Delete, + ) + } +} diff --git a/features/lm/service.go b/features/lm/service.go new file mode 100644 index 00000000..5cdaa943 --- /dev/null +++ b/features/lm/service.go @@ -0,0 +1,17 @@ +// log management package + +package lm + +import ( + "be.ems/features/lm/file_export" + "be.ems/lib/log" + "github.com/gin-gonic/gin" +) + +func InitSubServiceRoute(r *gin.Engine) { + log.Info("======init Log management group gin.Engine") + + lmGroup := r.Group("/lm") + // register sub modules routes + file_export.Register(lmGroup) +} diff --git a/features/pm/kpi_c_report/controller.go b/features/pm/kpi_c_report/controller.go new file mode 100644 index 00000000..85818b48 --- /dev/null +++ b/features/pm/kpi_c_report/controller.go @@ -0,0 +1,329 @@ +package kpi_c_report + +import ( + "fmt" + "net/http" + "strings" + + "be.ems/lib/services" + "be.ems/src/framework/datasource" + "github.com/gin-gonic/gin" +) + +func (k *KpiCReport) Get(c *gin.Context) { + var reports []KpiCReport + var conditions []string + var params []any + + var querys KpiCReportQuery + if err := c.ShouldBindQuery(&querys); err != nil { + c.JSON(http.StatusBadRequest, services.ErrResp(err.Error())) + return + } + + // construct condition to get + if querys.NeType != "" { + conditions = append(conditions, "ne_type = ?") + params = append(params, strings.ToUpper(querys.NeType)) + } else { + c.JSON(http.StatusBadRequest, services.ErrResp("Not found NE type")) + return + } + tableName := TableName() + "_" + strings.ToLower(querys.NeType) + dborm := datasource.DefaultDB().Table(tableName) + + if querys.NeID != "" { + conditions = append(conditions, "rm_uid = (select n.rm_uid from ne_info n where n.ne_type=? and n.ne_id=? and n.status=1)") + params = append(params, strings.ToUpper(querys.NeType), querys.NeID) + } else { + c.JSON(http.StatusBadRequest, services.ErrResp("Not found required parameter NE ID")) + return + } + if querys.StartTime != "" { + conditions = append(conditions, "created_at >= ?") + params = append(params, querys.StartTime) + } + if querys.EndTime != "" { + conditions = append(conditions, "created_at <= ?") + params = append(params, querys.EndTime) + } + + whereSql := "" + if len(conditions) > 0 { + whereSql += strings.Join(conditions, " and ") + dborm = dborm.Where(whereSql, params...) + } + // page number and size + if pageSize := querys.PageSize; pageSize > 0 { + dborm = dborm.Limit(pageSize) + if pageNum := querys.PageNum; pageNum > 0 { + dborm = dborm.Offset((pageNum - 1) * pageSize) + } + } + + // order by + if sortField, sortOrder := querys.SortField, querys.SortOrder; sortField != "" && sortOrder != "" { + orderBy := fmt.Sprintf("%s %s", sortField, sortOrder) + dborm = dborm.Order(orderBy) + } + + //err := datasource.DefaultDB().Table(tableName).Where(whereSql, params...).Find(&reports).Error + err := dborm.Find(&reports).Error + if err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + c.JSON(http.StatusOK, services.DataResp(reports)) + //c.JSON(http.StatusOK, reports) +} + +func (k *KpiCReport) GetReport2FE(c *gin.Context) { + var results []KpiCReport + var conditions []string + var params []any + + var querys KpiCReportQuery + if err := c.ShouldBindQuery(&querys); err != nil { + c.JSON(http.StatusBadRequest, services.ErrResp(err.Error())) + return + } + + // construct condition to get + if querys.NeType != "" { + conditions = append(conditions, "ne_type = ?") + params = append(params, strings.ToUpper(querys.NeType)) + } else { + c.JSON(http.StatusBadRequest, services.ErrResp("Not found required parameter NE type")) + return + } + tableName := TableName() + "_" + strings.ToLower(querys.NeType) + dborm := datasource.DefaultDB().Table(tableName) + + if querys.NeID != "" { + conditions = append(conditions, "rm_uid = (select n.rm_uid from ne_info n where n.ne_type=? and n.ne_id=? and n.status=1)") + params = append(params, querys.NeType, querys.NeID) + } else { + c.JSON(http.StatusBadRequest, services.ErrResp("Not found required parameter NE ID")) + return + } + if querys.StartTime != "" { + conditions = append(conditions, "created_at >= ?") + params = append(params, querys.StartTime) + } + if querys.EndTime != "" { + conditions = append(conditions, "created_at <= ?") + params = append(params, querys.EndTime) + } + + whereSql := "" + if len(conditions) > 0 { + whereSql += strings.Join(conditions, " and ") + dborm = dborm.Where(whereSql, params...) + } + // page number and size + if pageSize := querys.PageSize; pageSize > 0 { + dborm = dborm.Limit(pageSize) + if pageNum := querys.PageNum; pageNum > 0 { + dborm = dborm.Offset((pageNum - 1) * pageSize) + } + } + + // order by + if sortField, sortOrder := querys.SortField, querys.SortOrder; sortField != "" && sortOrder != "" { + orderBy := fmt.Sprintf("%s %s", sortField, sortOrder) + dborm = dborm.Order(orderBy) + } + + //err := datasource.DefaultDB().Table(tableName).Where(whereSql, params...).Find(&reports).Error + err := dborm.Find(&results).Error + if err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + + reports := []map[string]any{} + for _, r := range results { + report := map[string]any{ + // kip_id ... + "neType": *r.NeType, + "neId": querys.NeID, + "neName": *r.NeName, + "rmUID": *r.RmUID, + "startIndex": r.Index, + "timeGroup": r.Date[:10] + " " + *r.StartTime, + "createdAt": r.CreatedAt, + "granularity": r.Granularity, + "tenantID": r.TenantID, + } + + for _, k := range r.KpiValues { + report[k.KPIID] = k.Value + } + reports = append(reports, report) + } + c.JSON(http.StatusOK, services.DataResp(reports)) +} + +func (k *KpiCReport) GetTotalList(c *gin.Context) { + var reports []KpiCReport + var conditions []string + var params []any + + var querys KpiCReportQuery + if err := c.ShouldBindQuery(&querys); err != nil { + c.JSON(http.StatusBadRequest, services.ErrResp(err.Error())) + return + } + + // construct condition to get + if querys.NeType != "" { + conditions = append(conditions, "ne_type = ?") + params = append(params, strings.ToUpper(querys.NeType)) + } else { + c.JSON(http.StatusBadRequest, services.ErrResp("Not found NE type")) + return + } + tableName := TableName() + "_" + strings.ToLower(querys.NeType) + dborm := datasource.DefaultDB().Table(tableName) + + if querys.StartTime != "" { + conditions = append(conditions, "created_at >= ?") + params = append(params, querys.StartTime) + } + if querys.EndTime != "" { + conditions = append(conditions, "created_at <= ?") + params = append(params, querys.EndTime) + } + + whereSql := "" + if len(conditions) > 0 { + whereSql += strings.Join(conditions, " and ") + dborm = dborm.Where(whereSql, params...) + } + + // get total number + var total int64 = 0 + err := dborm.Count(&total).Error + if err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + + // page number and size + if pageSize := querys.PageSize; pageSize > 0 { + dborm = dborm.Limit(pageSize) + if pageNum := querys.PageNum; pageNum > 0 { + dborm = dborm.Offset((pageNum - 1) * pageSize) + } + } + + // order by + if sortField, sortOrder := querys.SortField, querys.SortOrder; sortField != "" && sortOrder != "" { + orderBy := fmt.Sprintf("%s %s", sortField, sortOrder) + dborm = dborm.Order(orderBy) + } + + //err := datasource.DefaultDB().Table(tableName).Where(whereSql, params...).Find(&reports).Error + err = dborm.Find(&reports).Error + if err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + + c.JSON(http.StatusOK, services.TotalDataResp(reports, total)) + //c.JSON(http.StatusOK, reports) +} + +func (k *KpiCReport) Total(c *gin.Context) { + var conditions []string + var params []any + + var querys KpiCReportQuery + if err := c.ShouldBindQuery(&querys); err != nil { + c.JSON(http.StatusBadRequest, services.ErrResp(err.Error())) + return + } + + // construct condition to get + if querys.NeType != "" { + conditions = append(conditions, "ne_type = ?") + params = append(params, strings.ToUpper(querys.NeType)) + } else { + c.JSON(http.StatusBadRequest, services.ErrResp("Not found NE type")) + return + } + tableName := TableName() + "_" + strings.ToLower(querys.NeType) + dborm := datasource.DefaultDB().Table(tableName) + + if querys.StartTime != "" { + conditions = append(conditions, "created_at >= ?") + params = append(params, querys.StartTime) + } + if querys.EndTime != "" { + conditions = append(conditions, "created_at <= ?") + params = append(params, querys.EndTime) + } + + whereSql := "" + if len(conditions) > 0 { + whereSql += strings.Join(conditions, " and ") + dborm = dborm.Where(whereSql, params...) + } + var total int64 = 0 + err := dborm.Count(&total).Error + if err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + + c.JSON(http.StatusOK, services.TotalResp(total)) +} + +func (k *KpiCReport) Post(c *gin.Context) { + var report KpiCReport + + if err := c.ShouldBindJSON(&report); err != nil { + c.JSON(http.StatusBadRequest, services.ErrResp(err.Error())) + return + } + if err := datasource.DefaultDB().Create(&report).Error; err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + c.JSON(http.StatusCreated, services.DataResp(report)) +} + +func (k *KpiCReport) Put(c *gin.Context) { + var report KpiCReport + id := c.Param("id") + + if err := datasource.DefaultDB().First(&report, id).Error; err != nil { + c.JSON(http.StatusNotFound, services.ErrResp("KPI report not found")) + return + } + + if err := c.ShouldBindJSON(&report); err != nil { + c.JSON(http.StatusBadRequest, services.ErrResp(err.Error())) + return + } + datasource.DefaultDB().Save(&report) + c.JSON(http.StatusOK, services.DataResp(report)) +} + +func (k *KpiCReport) Delete(c *gin.Context) { + id := c.Param("id") + + if err := datasource.DefaultDB().Delete(&KpiCReport{}, id).Error; err != nil { + c.JSON(http.StatusNotFound, services.ErrResp("KPI report not found")) + return + } + + c.JSON(http.StatusNoContent, nil) // 204 No Content +} + +func InsertKpiCReport(neType string, report KpiCReport) { + tableName := TableName() + "_" + strings.ToLower(neType) + if err := datasource.DefaultDB().Table(tableName).Create(&report).Error; err != nil { + return + } +} diff --git a/features/pm/kpi_c_report/model.go b/features/pm/kpi_c_report/model.go new file mode 100644 index 00000000..512fb342 --- /dev/null +++ b/features/pm/kpi_c_report/model.go @@ -0,0 +1,74 @@ +package kpi_c_report + +import ( + "database/sql/driver" + "encoding/json" + "fmt" + "time" +) + +type KpiCVal struct { + KPIID string `json:"kpi_id" gorm:"column:kpi_id"` + Value float64 `json:"value" gorm:"column:value"` + Err string `json:"err" gorm:"column:err"` +} + +type KpiCValues []KpiCVal + +type KpiCReport struct { + ID int `gorm:"column:id;primary_key;auto_increment" json:"id"` + NeType *string `gorm:"column:ne_type;default:NULL" json:"neType,omitempty"` + NeName *string `gorm:"column:ne_name;default:" json:"neName,omitempty"` + RmUID *string `gorm:"column:rm_uid;default:NULL" json:"rmUid,omitempty"` + Date string `gorm:"column:date" json:"date"` // time.Time `gorm:"column:date" json:"date"` + StartTime *string `gorm:"column:start_time;default:NULL" json:"startTime,omitempty"` + EndTime *string `gorm:"column:end_time;default:NULL" json:"endTime,omitempty"` + Index int16 `gorm:"column:index" json:"index"` + Granularity *int8 `gorm:"column:granularity;default:60" json:"granularity,omitempty"` //Time granualarity: 5/10/.../60/300 (second) + KpiValues KpiCValues `gorm:"column:kpi_values;type:json" json:"kpiValues,omitempty"` + CreatedAt *time.Time `gorm:"column:created_at;default:current_timestamp()" json:"createdAt,omitempty"` + TenantID *string `gorm:"column:tenant_id;default:NULL" json:"tenantID,omitempty"` +} + +type KpiCReportQuery struct { + NeType string `json:"neType" form:"neType" binding:"required"` + NeID string `json:"neId" form:"neId" binding:"required"` + RmUID string `json:"rmUID" form:"rmUID"` + StartTime string `json:"startTime" form:"startTime"` + EndTime string `json:"endTime" form:"endTime"` + TenantName string `json:"tenantName" form:"tenantName"` + UserName string `json:"userName" form:"userName"` + SortField string `json:"sortField" form:"sortField" binding:"omitempty,oneof=created_at"` // 排序字段,填写结果字段 + SortOrder string `json:"sortOrder" form:"sortOrder" binding:"omitempty,oneof=asc desc"` // 排序升降序,asc desc + PageNum int `json:"pageNum" form:"pageNum"` + PageSize int `json:"pageSize" form:"pageSize"` +} + +type KpiCReport2FE struct { + NeType string `json:"neType" gorm:"column:ne_type"` + NeId string `json:"neId"` + NeName string `json:"neName" gorm:"column:ne_name"` + RmUID string `json:"rmUid" gorm:"column:rm_uid"` + TimeGroup string `json:"timeGroup"` + StartIndex int16 `json:"startIndex" gorm:"column:index"` + Granularity int8 `json:"granularity" gorm:"column:granularity"` + TenantID string `json:"tenantID" gorm:"column:tenant_id"` +} + +func TableName() string { + return "kpi_c_report" +} + +// 将 KpiCValues 转换为 JSON 字节 +func (k KpiCValues) Value() (driver.Value, error) { + return json.Marshal(k) +} + +// 从字节中扫描 KpiCValues +func (k *KpiCValues) Scan(value interface{}) error { + b, ok := value.([]byte) + if !ok { + return fmt.Errorf("failed to scan value: %v", value) + } + return json.Unmarshal(b, k) +} diff --git a/features/pm/kpi_c_report/route.go b/features/pm/kpi_c_report/route.go new file mode 100644 index 00000000..7e64f95b --- /dev/null +++ b/features/pm/kpi_c_report/route.go @@ -0,0 +1,43 @@ +package kpi_c_report + +import ( + "be.ems/src/framework/middleware" + "github.com/gin-gonic/gin" +) + +// Register Routes for kpi_c_report +func Register(r *gin.RouterGroup) { + + pmKPIC := r.Group("/kpiC") + { + var k *KpiCReport + pmKPIC.GET("/report", + middleware.PreAuthorize(nil), + k.GetReport2FE, + ) + pmKPIC.GET("/report/list", + middleware.PreAuthorize(nil), + k.Get, + ) + pmKPIC.GET("/report/totalList", + middleware.PreAuthorize(nil), + k.Total, + ) + pmKPIC.GET("/report/total", + middleware.PreAuthorize(nil), + k.Total, + ) + pmKPIC.POST("/report", + middleware.PreAuthorize(nil), + k.Post, + ) + pmKPIC.PUT("/report/:id", + middleware.PreAuthorize(nil), + k.Put, + ) + pmKPIC.DELETE("/report/:id", + middleware.PreAuthorize(nil), + k.Delete, + ) + } +} diff --git a/features/pm/kpi_c_title/controller.go b/features/pm/kpi_c_title/controller.go new file mode 100644 index 00000000..1c509470 --- /dev/null +++ b/features/pm/kpi_c_title/controller.go @@ -0,0 +1,174 @@ +package kpi_c_title + +import ( + "fmt" + "net/http" + "strings" + + "be.ems/lib/services" + "be.ems/src/framework/datasource" + "github.com/gin-gonic/gin" +) + +func (k *KpiCTitle) GetToalList(c *gin.Context) { + var titles []KpiCTitle + var conditions []string + var params []any + + var querys KpiCTitleQuery + if err := c.ShouldBindQuery(&querys); err != nil { + c.JSON(http.StatusBadRequest, services.ErrResp(err.Error())) + return + } + + dborm := datasource.DefaultDB().Table(k.TableName()) + // construct condition to get + if neType := querys.NeType; neType != "" { + conditions = append(conditions, "ne_type = ?") + params = append(params, strings.ToUpper(neType)) + } + if status := querys.Status; status != "" { + conditions = append(conditions, "status = ?") + params = append(params, status) + } + whereSql := "" + if len(conditions) > 0 { + whereSql += strings.Join(conditions, " and ") + dborm = dborm.Where(whereSql, params...) + } + + // Get total number + var total int64 = 0 + if err := dborm.Count(&total).Error; err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + + // page number and size + if pageSize := querys.PageSize; pageSize > 0 { + dborm = dborm.Limit(pageSize) + if pageNum := querys.PageNum; pageNum > 0 { + dborm = dborm.Offset((pageNum - 1) * pageSize) + } + } + + // order by + if sortField, sortOrder := querys.SortField, querys.SortOrder; sortField != "" && sortOrder != "" { + orderBy := fmt.Sprintf("%s %s", sortField, sortOrder) + dborm = dborm.Order(orderBy) + } + if err := dborm.Find(&titles).Error; err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + + c.JSON(http.StatusOK, services.TotalDataResp(titles, total)) + //c.JSON(http.StatusOK, titles) +} + +func (k *KpiCTitle) Get(c *gin.Context) { + var titles []KpiCTitle + var conditions []string + var params []any + + // construct condition to get + if neType := c.Query("neType"); neType != "" { + conditions = append(conditions, "ne_type = ?") + params = append(params, strings.ToUpper(neType)) + } + if status := c.Query("status"); status != "" { + conditions = append(conditions, "status = ?") + params = append(params, status) + } + whereSql := "" + if len(conditions) > 0 { + whereSql += strings.Join(conditions, " and ") + } + if err := datasource.DefaultDB().Where(whereSql, params...).Find(&titles).Error; err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + + c.JSON(http.StatusOK, services.DataResp(titles)) + //c.JSON(http.StatusOK, titles) +} + +func (k *KpiCTitle) Total(c *gin.Context) { + var conditions []string + var params []any + + // construct condition to get + if neType := c.Query("neType"); neType != "" { + conditions = append(conditions, "ne_type = ?") + params = append(params, strings.ToUpper(neType)) + } + if status := c.Query("status"); status != "" { + conditions = append(conditions, "status = ?") + params = append(params, status) + } + whereSql := "" + if len(conditions) > 0 { + whereSql += strings.Join(conditions, " and ") + } + var total int64 = 0 + if err := datasource.DefaultDB().Table(k.TableName()).Where(whereSql, params...).Count(&total).Error; err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + + c.JSON(http.StatusOK, services.TotalResp(total)) +} + +func (k *KpiCTitle) Post(c *gin.Context) { + var title KpiCTitle + + if err := c.ShouldBindJSON(&title); err != nil { + c.JSON(http.StatusBadRequest, services.ErrResp(err.Error())) + return + } + if err := datasource.DefaultDB().Create(&title).Error; err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + + c.JSON(http.StatusCreated, services.DataResp(title)) +} + +func (k *KpiCTitle) Put(c *gin.Context) { + var title KpiCTitle + id := c.Param("id") + + if err := datasource.DefaultDB().First(&title, id).Error; err != nil { + c.JSON(http.StatusNotFound, services.ErrResp("KPIC Title not found")) + return + } + + if err := c.ShouldBindJSON(&title); err != nil { + c.JSON(http.StatusBadRequest, services.ErrResp(err.Error())) + return + } + datasource.DefaultDB().Save(&title) + + c.JSON(http.StatusOK, services.DataResp(title)) +} + +func (k *KpiCTitle) Delete(c *gin.Context) { + id := c.Param("id") + + if err := datasource.DefaultDB().Delete(&KpiCTitle{}, id).Error; err != nil { + c.JSON(http.StatusNotFound, services.ErrResp("KPIC Title not found")) + return + } + + c.JSON(http.StatusNoContent, nil) // 204 No Content +} + +func GetActiveKPICList(neType string) []KpiCTitle { + k := new([]KpiCTitle) + + err := datasource.DefaultDB().Where("`ne_type` = ? and `status` = 'Active'", neType).Find(&k).Error + if err != nil { + return nil + } + return *k +} diff --git a/features/pm/kpi_c_title/model.go b/features/pm/kpi_c_title/model.go new file mode 100644 index 00000000..1bfbfcbc --- /dev/null +++ b/features/pm/kpi_c_title/model.go @@ -0,0 +1,30 @@ +package kpi_c_title + +import "time" + +type KpiCTitle struct { + ID int `gorm:"column:id;primary_key;auto_increment" json:"id"` + NeType *string `gorm:"column:ne_type;default:NULL," json:"neType,omitempty"` + KpiID *string `gorm:"column:kpi_id;default:NULL," json:"kpiId,omitempty"` + Title *string `gorm:"column:title;default:NULL," json:"title,omitempty"` + Expression *string `gorm:"column:expression;default:NULL," json:"expression,omitempty"` + Status *string `gorm:"column:status" json:"status,omitempty"` + Unit *string `gorm:"column:unit" json:"unit,omitempty"` + Description *string `gorm:"column:description;default:NULL," json:"description,omitempty"` + CreatedBy *string `gorm:"column:created_by;default:NULL," json:"createdBy,omitempty"` + UpdatedAt *time.Time `gorm:"column:updated_at;default:current_timestamp()," json:"updatedAt,omitempty"` +} + +type KpiCTitleQuery struct { + ID int `json:"id" form:"id"` + NeType string `json:"neType" form:"neType"` + Status string `json:"status" form:"status"` + SortField string `json:"sortField" form:"sortField" binding:"omitempty,oneof=created_at"` // 排序字段,填写结果字段 + SortOrder string `json:"sortOrder" form:"sortOrder" binding:"omitempty,oneof=asc desc"` // 排序升降序,asc desc + PageNum int `json:"pageNum" form:"pageNum"` + PageSize int `json:"pageSize" form:"pageSize"` +} + +func (k *KpiCTitle) TableName() string { + return "kpi_c_title" +} diff --git a/features/pm/kpi_c_title/route.go b/features/pm/kpi_c_title/route.go new file mode 100644 index 00000000..f058dbe0 --- /dev/null +++ b/features/pm/kpi_c_title/route.go @@ -0,0 +1,39 @@ +package kpi_c_title + +import ( + "be.ems/src/framework/middleware" + "github.com/gin-gonic/gin" +) + +// Register Routes for kpi_c_title +func Register(r *gin.RouterGroup) { + + pmKPIC := r.Group("/kpiC") + { + var k *KpiCTitle + pmKPIC.GET("/title", + middleware.PreAuthorize(nil), + k.Get, + ) + pmKPIC.GET("/title/total", + middleware.PreAuthorize(nil), + k.Total, + ) + pmKPIC.GET("/title/totalList", + middleware.PreAuthorize(nil), + k.GetToalList, + ) + pmKPIC.POST("/title", + middleware.PreAuthorize(nil), + k.Post, + ) + pmKPIC.PUT("/title/:id", + middleware.PreAuthorize(nil), + k.Put, + ) + pmKPIC.DELETE("/title/:id", + middleware.PreAuthorize(nil), + k.Delete, + ) + } +} diff --git a/features/pm/performance.go b/features/pm/performance.go index 1613e3f6..f331020c 100644 --- a/features/pm/performance.go +++ b/features/pm/performance.go @@ -10,7 +10,10 @@ import ( "strings" "time" + "be.ems/features/pm/kpi_c_report" + "be.ems/features/pm/kpi_c_title" "be.ems/lib/dborm" + evaluate "be.ems/lib/eval" "be.ems/lib/global" "be.ems/lib/log" "be.ems/lib/services" @@ -246,9 +249,13 @@ func PostKPIReportFromNF(w http.ResponseWriter, r *http.Request) { // kip_id ... "neType": kpiReport.Task.NE.NeType, "neName": kpiReport.Task.NE.NEName, + "rmUID": kpiReport.Task.NE.RmUID, "startIndex": kpiIndex, "timeGroup": kpiData.CreatedAt, } + + // for custom kpi + kpiValMap := map[string]any{} for _, k := range kpiReport.Task.NE.KPIs { kpiEvent[k.KPIID] = k.Value // kip_id @@ -256,7 +263,9 @@ func PostKPIReportFromNF(w http.ResponseWriter, r *http.Request) { kpiVal.Value = int64(k.Value) kpiVal.Err = k.Err kpiData.KPIValues = append(kpiData.KPIValues, *kpiVal) + kpiValMap[k.KPIID] = k.Value } + kpiValMap["granularity"] = kpiData.Granularity // insert kpi_report table, no session tableName := "kpi_report_" + strings.ToLower(kpiReport.Task.NE.NeType) @@ -267,13 +276,60 @@ func PostKPIReportFromNF(w http.ResponseWriter, r *http.Request) { return } + report := kpi_c_report.KpiCReport{ + NeType: &kpiData.NEType, + NeName: &kpiData.NEName, + RmUID: &kpiData.RmUid, + Date: kpiData.Date, + StartTime: &kpiData.StartTime, + EndTime: &kpiData.EndTime, + Index: int16(kpiData.Index), + Granularity: &kpiData.Granularity, + } + // 发送到匹配的网元 neInfo := neService.NewNeInfoImpl.SelectNeInfoByRmuid(kpiData.RmUid) + // custom kpi report to FE + kpiCEvent := map[string]any{ + // kip_id ... + "neType": kpiData.NEType, + "neId": neInfo.NeId, + "neName": kpiData.NEName, + "rmUID": kpiData.RmUid, + "startIndex": kpiData.Index, + "timeGroup": kpiData.Date[:10] + " " + kpiData.StartTime, + "createdAt": kpiData.CreatedAt, + "granularity": kpiData.Granularity, + } + kpiCList := kpi_c_title.GetActiveKPICList(kpiData.NEType) + for _, k := range kpiCList { + result, err := evaluate.CalcExpr(*k.Expression, kpiValMap) + kpiCVal := new(kpi_c_report.KpiCVal) + kpiCVal.KPIID = *k.KpiID + if err != nil { + kpiCVal.Value = 0.0 + kpiCVal.Err = err.Error() + } else { + kpiCVal.Value = result + } + + report.KpiValues = append(report.KpiValues, *kpiCVal) + + // set KPIC event kpiid and value + kpiCEvent[kpiCVal.KPIID] = kpiCVal.Value + } + + // KPI自定义指标入库 + kpi_c_report.InsertKpiCReport(kpiData.NEType, report) + if neInfo.RmUID == kpiData.RmUid { // 推送到ws订阅组 wsService.NewWSSendImpl.ByGroupID(fmt.Sprintf("%s%s_%s", wsService.GROUP_KPI, neInfo.NeType, neInfo.NeId), kpiEvent) + // 推送自定义KPI到ws订阅组 + wsService.NewWSSendImpl.ByGroupID(fmt.Sprintf("%s%s_%s", wsService.GROUP_KPI_C, neInfo.NeType, neInfo.NeId), kpiCEvent) if neInfo.NeType == "UPF" { - wsService.NewWSSendImpl.ByGroupID(wsService.GROUP_KPI_UPF+neInfo.NeId, kpiEvent) + // 推送标识为:12_RMUID, exp: 12_4400HXUPF001 + wsService.NewWSSendImpl.ByGroupID(wsService.GROUP_KPI_UPF+kpiReport.Task.NE.RmUID, kpiEvent) } } @@ -328,6 +384,7 @@ func PostGoldKPIFromNF(w http.ResponseWriter, r *http.Request) { // kip_id ... "neType": goldKpi.NEType, "neName": goldKpi.NEName, + "rmUID": goldKpi.RmUid, "startIndex": goldKpi.Index, "timeGroup": goldKpi.StartTime, } diff --git a/features/pm/service.go b/features/pm/service.go new file mode 100644 index 00000000..ff2a6539 --- /dev/null +++ b/features/pm/service.go @@ -0,0 +1,19 @@ +package pm + +import ( + "be.ems/features/pm/kpi_c_report" + "be.ems/features/pm/kpi_c_title" + "be.ems/lib/log" + "github.com/gin-gonic/gin" +) + +func InitSubServiceRoute(r *gin.Engine) { + log.Info("======init PM group gin.Engine") + + pmGroup := r.Group("/pm") + // register sub modules routes + kpi_c_title.Register(pmGroup) + kpi_c_report.Register(pmGroup) + + // return featuresGroup +} diff --git a/lib/eval/evaluate.go b/lib/eval/evaluate.go new file mode 100644 index 00000000..d0ff37e0 --- /dev/null +++ b/lib/eval/evaluate.go @@ -0,0 +1,111 @@ +package evaluate + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "regexp" + "strconv" + "strings" +) + +// Parse and caculate expression +func CalcExpr(expr string, paramValues map[string]any) (float64, error) { + // match parameter with '' + re := regexp.MustCompile(`'([^']+)'`) + matches := re.FindAllStringSubmatch(expr, -1) + + // replace to value + for _, match := range matches { + paramName := match[1] + value, exists := paramValues[paramName] + if !exists { + return 0, fmt.Errorf("parameter '%s' not found", paramName) + } + + expr = strings.Replace(expr, match[0], fmt.Sprintf("%v", value), 1) + } + + // expression to evaluate + result, err := evalExpr(expr) + return result, err +} + +// eval 解析和计算表达式 +func evalExpr(expr string) (float64, error) { + //fset := token.NewFileSet() + node, err := parser.ParseExpr(expr) + if err != nil { + return 0, err + } + return evalNode(node) +} + +// EvaluateExpr 解析并计算给定的表达式 +func EvalExpr(expr string, values map[string]any) (float64, error) { + // 解析表达式 + node, err := parser.ParseExpr(expr) + if err != nil { + return 0, err + } + + // 遍历 AST 并替换变量 + ast.Inspect(node, func(n ast.Node) bool { + if ident, ok := n.(*ast.Ident); ok { + if val, ok := values[ident.Name]; ok { + // 替换标识符为对应值 + ident.Name = fmt.Sprintf("%v", val) + } + } + return true + }) + + // 计算表达式 + return evalNode(node) +} + +// eval 递归计算 AST 节点 +func evalNode(node ast.Node) (float64, error) { + var result float64 + + switch n := node.(type) { + case *ast.BinaryExpr: + left, err := evalNode(n.X) + if err != nil { + return 0, err + } + right, err := evalNode(n.Y) + if err != nil { + return 0, err + } + switch n.Op { + case token.ADD: + result = left + right + case token.SUB: + result = left - right + case token.MUL: + result = left * right + case token.QUO: + result = left / right + } + case *ast.BasicLit: + var err error + result, err = strconv.ParseFloat(n.Value, 64) + if err != nil { + return 0, err + } + case *ast.Ident: + val, err := strconv.ParseFloat(n.Name, 64) + if err != nil { + return 0, fmt.Errorf("unsupported expression: %s", n.Name) + } + result = val + case *ast.ParenExpr: + return evalNode(n.X) // 递归评估括号中的表达式 + default: + return 0, fmt.Errorf("unsupported expression: %T", n) + } + + return result, nil +} diff --git a/lib/file/file.go b/lib/file/file.go new file mode 100644 index 00000000..7bc2a57e --- /dev/null +++ b/lib/file/file.go @@ -0,0 +1,27 @@ +package file + +import ( + "os" + "path/filepath" +) + +func GetFileAndDirCount(dir string) (int, int, error) { + var fileCount, dirCount int + + err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if path == dir { + return nil // 跳过当前目录 + } + if info.IsDir() { + dirCount++ + } else { + fileCount++ + } + return nil + }) + + return fileCount, dirCount, err +} diff --git a/lib/file/file_linux.go b/lib/file/file_linux.go new file mode 100644 index 00000000..9e459ef5 --- /dev/null +++ b/lib/file/file_linux.go @@ -0,0 +1,63 @@ +//go:build linux +// +build linux + +package file + +import ( + "fmt" + "os" + "path/filepath" + "syscall" +) + +type FileInfo struct { + FileType string `json:"fileType"` // 文件类型 + FileMode string `json:"fileMode"` // 文件的权限 + LinkCount int64 `json:"linkCount"` // 硬链接数目 + Owner string `json:"owner"` // 所属用户 + Group string `json:"group"` // 所属组 + Size int64 `json:"size"` // 文件的大小 + ModifiedTime int64 `json:"modifiedTime"` // 最后修改时间,单位为秒 + FileName string `json:"fileName"` // 文件的名称 +} + +func GetFileInfo(dir, suffix string) ([]FileInfo, error) { + var files []FileInfo + + err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if path == dir { + return nil // 跳过当前目录 + } + + fileType := "file" + if info.IsDir() { + fileType = "directory" + } else if info.Mode()&os.ModeSymlink != 0 { + fileType = "symlink" + } + + // check if match suffix + if (suffix != "" && filepath.Ext(path) == suffix) || suffix == "" { + fileInfo := FileInfo{ + FileType: fileType, + FileMode: info.Mode().String(), + LinkCount: int64(info.Sys().(*syscall.Stat_t).Nlink), + Owner: fmt.Sprintf("%d", info.Sys().(*syscall.Stat_t).Uid), + Group: fmt.Sprintf("%d", info.Sys().(*syscall.Stat_t).Gid), + Size: info.Size(), + ModifiedTime: info.ModTime().Unix(), + FileName: info.Name(), + } + files = append(files, fileInfo) + } + return nil + }) + if err != nil { + return nil, err + } + return files, nil +} diff --git a/lib/file/file_windows.go b/lib/file/file_windows.go new file mode 100644 index 00000000..5371d44e --- /dev/null +++ b/lib/file/file_windows.go @@ -0,0 +1,61 @@ +//go:build windows +// +build windows + +package file + +import ( + "os" + "path/filepath" +) + +type FileInfo struct { + FileType string `json:"fileType"` // 文件类型 + FileMode string `json:"fileMode"` // 文件的权限 + LinkCount int64 `json:"linkCount"` // 硬链接数目 + Owner string `json:"owner"` // 所属用户 + Group string `json:"group"` // 所属组 + Size int64 `json:"size"` // 文件的大小 + ModifiedTime int64 `json:"modifiedTime"` // 最后修改时间,单位为秒 + FileName string `json:"fileName"` // 文件的名称 +} + +func GetFileInfo(dir, suffix string) ([]FileInfo, error) { + var files []FileInfo + + err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if path == dir { + return nil // 跳过当前目录 + } + + fileType := "file" + if info.IsDir() { + fileType = "directory" + } else if info.Mode()&os.ModeSymlink != 0 { + fileType = "symlink" + } + + // check if match suffix + if (suffix != "" && filepath.Ext(path) == suffix) || suffix == "" { + fileInfo := FileInfo{ + FileType: fileType, + FileMode: info.Mode().String(), + LinkCount: 0, + Owner: "N/A", + Group: "N/A", + Size: info.Size(), + ModifiedTime: info.ModTime().Unix(), + FileName: info.Name(), + } + files = append(files, fileInfo) + } + return nil + }) + if err != nil { + return nil, err + } + return files, nil +} diff --git a/lib/services/response.go b/lib/services/response.go new file mode 100644 index 00000000..93f24425 --- /dev/null +++ b/lib/services/response.go @@ -0,0 +1,35 @@ +package services + +const ( + CODE_FAIL = 0 + CODE_SUCC = 1 +) + +func ErrResp(msg string) map[string]any { + return map[string]any{"code": CODE_FAIL, "message": msg} +} + +func DataResp(data any) map[string]any { + return map[string]any{"code": CODE_SUCC, "data": data} +} + +func SuccMessageResp() map[string]any { + return map[string]any{"code": CODE_SUCC, "message": "success"} +} + +func TotalResp(total int64) map[string]any { + return map[string]any{"code": CODE_SUCC, "total": total} +} + +func TotalDataResp(data any, total any) map[string]any { + return map[string]any{"code": CODE_SUCC, "data": data, "total": total} +} + +func SuccResp(va map[string]any) map[string]any { + resp := make(map[string]any) + resp["code"] = CODE_SUCC + for k, v := range va { + resp[k] = v + } + return resp +} diff --git a/misc/setomc.sh b/misc/setomc.sh index 7af2bdcb..d3fd3bf1 100644 --- a/misc/setomc.sh +++ b/misc/setomc.sh @@ -59,7 +59,7 @@ case "${M_ARG}" in echo "Not found ${C_ARG_UPPER} customized directory, nothing to be done" exit 1 fi - echo "Setting ${C_ARG_UPPER} customized OMC ..." + echo -n "Setting ${C_ARG_UPPER} customized OMC ..." for SQL in ${CustomizedDir}/db/*.sql; do mysql -u${USER} -p${PASSWORD} -P ${PORT} --protocol tcp -D ${DBNAME} < ${SQL}; done @@ -67,6 +67,9 @@ case "${M_ARG}" in cp -rf ${CustomizedDir}/doc/* ${OMCStaticDir}/helpDoc #perl -0777 -i -pe 's/omcuser/bluearcus/g' ${OMCRootDir}/etc/default/restconf.yaml #perl -0777 -i -pe 's/omcuser/bluearcus/g' ${OMCBinDir}/nehosts + if [ $? = 0 ]; then + echo "done" + fi fi ;; upgrade | upgvue3) @@ -79,12 +82,15 @@ case "${M_ARG}" in echo "Not found ${C_ARG_UPPER} customized directory, nothing to be done" exit 1 fi - echo "Setting ${C_ARG_UPPER} customized OMC ..." + echo -n "Setting ${C_ARG_UPPER} customized OMC ..." for SQL in ${CustomizedDir}/db/*.sql; do mysql -u${USER} -p${PASSWORD} -P ${PORT} --protocol tcp -D ${DBNAME} < ${SQL}; done cp -rf ${CustomizedDir}/logo/* ${OMCStaticDir}/logo cp -rf ${CustomizedDir}/doc/* ${OMCStaticDir}/helpDoc + if [ $? = 0 ]; then + echo "done" + fi fi ;; *) diff --git a/restagent/restagent.go b/restagent/restagent.go index 0ea6dcd5..723b37f4 100644 --- a/restagent/restagent.go +++ b/restagent/restagent.go @@ -11,6 +11,7 @@ import ( _ "net/http/pprof" + "be.ems/features" "be.ems/features/dbrest" "be.ems/features/event" "be.ems/features/fm" @@ -251,6 +252,9 @@ func main() { // AMF上报的UE事件, 无前缀,暂时特殊处理 app.POST(event.UriUEEventAMF, event.PostUEEventFromAMF) + // register feature service gin.Engine + features.InitServiceEngine(app) + // var listenLocalhost bool = false for _, rest := range conf.Rest { // ipv4 goroutines diff --git a/src/modules/ws/service/ws_send.impl.go b/src/modules/ws/service/ws_send.impl.go index cdf5b079..ee440800 100644 --- a/src/modules/ws/service/ws_send.impl.go +++ b/src/modules/ws/service/ws_send.impl.go @@ -16,6 +16,8 @@ const ( GROUP_KPI = "10_" // 组号-指标UPF 12_neId GROUP_KPI_UPF = "12_" + // 组号-自定义KPI指标20_neType_neId + GROUP_KPI_C = "20_" // 组号-IMS_CDR会话事件 1005_neId GROUP_IMS_CDR = "1005_" // 组号-SMF_CDR会话事件 1006_neId