diff --git a/build/database/std/install/cbc_message.sql b/build/database/std/install/cbc_message.sql new file mode 100755 index 00000000..59910b5a --- /dev/null +++ b/build/database/std/install/cbc_message.sql @@ -0,0 +1,38 @@ +/* + Navicat Premium Data Transfer + + Source Server : root@192.168.2.242 + Source Server Type : MariaDB + Source Server Version : 100622 (10.6.22-MariaDB-0ubuntu0.22.04.1) + Source Host : 192.168.2.242:33066 + Source Schema : omc_db + + Target Server Type : MariaDB + Target Server Version : 100622 (10.6.22-MariaDB-0ubuntu0.22.04.1) + File Encoding : 65001 + + Date: 01/08/2025 10:07:00 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for cbc_message +-- ---------------------------- +DROP TABLE IF EXISTS `cbc_message`; +CREATE TABLE `cbc_message` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `ne_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `ne_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `message_json` varchar(10240) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `status` enum('ACTIVE','INACTIVE') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'INACTIVE', + `detail` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `created_at` bigint(20) NULL DEFAULT current_timestamp(), + `updated_at` bigint(20) NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `id`(`id`) USING BTREE, + INDEX `idx_ne_time`(`ne_type`, `ne_id`, `created_at`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 64 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'CDR事件_MF' ROW_FORMAT = Dynamic; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/build/database/std/install/sys_i18n.sql b/build/database/std/install/sys_i18n.sql index 83045948..04c356ff 100644 --- a/build/database/std/install/sys_i18n.sql +++ b/build/database/std/install/sys_i18n.sql @@ -775,4 +775,23 @@ INSERT INTO `sys_i18n` VALUES (757, 'dictData.trace_interfaces.5', 'N5', 'N5'); INSERT INTO `sys_i18n` VALUES (758, "alarm.export.alarmCode", "告警编码", "Alarm Code"); INSERT INTO `sys_i18n` VALUES (759, "config.sys.user.fristPasswdChangeRemark", "关闭改为false 开启改为true, 建议同时设置密码有效期", "Off to false On to true, it is recommended to set the password expiration date at the same time."); +INSERT INTO `sys_i18n` VALUES (2000, 'menu.psap.agent', '座席', 'Agent'); +INSERT INTO `sys_i18n` VALUES (2001, 'menu.psap.agent.callings', '并行话务', 'Calling Information'); +INSERT INTO `sys_i18n` VALUES (2002, 'menu.psap.agent.callback', '回拨管理', 'Callback Management'); +INSERT INTO `sys_i18n` VALUES (2003, 'callback.status.NEW', '新建', 'New'); +INSERT INTO `sys_i18n` VALUES (2004, 'callback.status.IN_PROGRESS', '处理中', 'In Progress'); +INSERT INTO `sys_i18n` VALUES (2005, 'callback.status.NO_ANSWER_1', '未应答1', 'No Answer 1'); +INSERT INTO `sys_i18n` VALUES (2006, 'callback.status.NO_ANSWER_2', '未应答2', 'No Answer 2'); +INSERT INTO `sys_i18n` VALUES (2007, 'callback.status.TIMEOUT', '超时', 'Timeout'); +INSERT INTO `sys_i18n` VALUES (2008, 'callback.status.PENDING', '挂起', 'Pending'); +INSERT INTO `sys_i18n` VALUES (2009, 'callback.status.CLOSED', '关闭', 'Closed'); +INSERT INTO `sys_i18n` VALUES (2010, 'job.export.cdr.mf', '定期导出MF话单', 'Periodic Export of MF Call Records'); +INSERT INTO `sys_i18n` VALUES (2011, 'job.psap.ticket.monitor', '回拨工单监控', 'Callback Ticket Monitoring'); +INSERT INTO `sys_i18n` VALUES (2012, 'menu.omc.cdr', '话单', 'Call Records'); +INSERT INTO `sys_i18n` VALUES (2013, 'menu.omc.cdr.mf', '紧急呼叫话单', 'Emergency Call Records'); +INSERT INTO `sys_i18n` VALUES (2014, 'menu.omc.cdr.crbt', '彩铃话单', 'Color Ring Back Tone Records'); +INSERT INTO `sys_i18n` VALUES (2015, 'menu.omc.cdr.mms', '彩信话单', 'Multimedia Message Service Records'); +INSERT INTO `sys_i18n` VALUES (2016, 'menu.ue.cbc.cbe', '广播', 'Broadcast'); +INSERT INTO `sys_i18n` VALUES (2017, 'log.operate.title.cbcMessage', '广播事件', 'Broadcast Event'); + -- Dump completed on 2025-02-14 15:26:56 diff --git a/build/database/std/install/sys_menu.sql b/build/database/std/install/sys_menu.sql index c5025ce0..f6d8f08b 100644 --- a/build/database/std/install/sys_menu.sql +++ b/build/database/std/install/sys_menu.sql @@ -209,6 +209,8 @@ INSERT INTO `sys_menu` VALUES (2167, 'menu.dashboard.overview.imsUeNum', 2132, 2 INSERT INTO `sys_menu` VALUES (2168, 'menu.dashboard.overview.gnbBase', 2132, 6, '', '', '1', '1', 'B', '1', '1', 'dashboard:overview:gnbBase', '#', '0', 'system', 1728641403588, 'system', 1728641403588, ''); INSERT INTO `sys_menu` VALUES (2169, 'menu.dashboard.overview.enbBase', 2132, 8, '', '', '1', '1', 'B', '1', '1', 'dashboard:overview:enbBase', '#', '0', 'system', 1728641403588, 'system', 1728641403588, ''); +INSERT INTO `sys_menu` VALUES (20000, 'menu.ue.cbc.cbe', 5, 20, 'cbe', 'cbc/cbe/index', '1', '0', 'M', '1', '1', 'cbc#dashboard:cdr:index', 'icon-tubiaoku', '0', 'system', 1711352709786, 'system', 1747796007372, ''); + SET FOREIGN_KEY_CHECKS = 1; -- Dump completed on 2025-02-14 15:26:56 diff --git a/build/database/std/install/sys_role_menu.sql b/build/database/std/install/sys_role_menu.sql index 62a310de..099ecc5f 100644 --- a/build/database/std/install/sys_role_menu.sql +++ b/build/database/std/install/sys_role_menu.sql @@ -163,6 +163,7 @@ INSERT IGNORE INTO `sys_role_menu` VALUES (2, 2166); INSERT IGNORE INTO `sys_role_menu` VALUES (2, 2167); INSERT IGNORE INTO `sys_role_menu` VALUES (2, 2168); INSERT IGNORE INTO `sys_role_menu` VALUES (2, 2169); +INSERT IGNORE INTO `sys_role_menu` VALUES (2, 20000); INSERT IGNORE INTO `sys_role_menu` VALUES (3, 1); INSERT IGNORE INTO `sys_role_menu` VALUES (3, 4); @@ -238,6 +239,7 @@ INSERT IGNORE INTO `sys_role_menu` VALUES (3, 2166); INSERT IGNORE INTO `sys_role_menu` VALUES (3, 2167); INSERT IGNORE INTO `sys_role_menu` VALUES (3, 2168); INSERT IGNORE INTO `sys_role_menu` VALUES (3, 2169); +INSERT IGNORE INTO `sys_role_menu` VALUES (3, 20000); INSERT IGNORE INTO `sys_role_menu` VALUES (4, 1); INSERT IGNORE INTO `sys_role_menu` VALUES (4, 4); diff --git a/build/database/std/upgrade/upg_cbc_message.sql b/build/database/std/upgrade/upg_cbc_message.sql new file mode 100755 index 00000000..281c7bcb --- /dev/null +++ b/build/database/std/upgrade/upg_cbc_message.sql @@ -0,0 +1,37 @@ +/* + Navicat Premium Data Transfer + + Source Server : root@192.168.2.242 + Source Server Type : MariaDB + Source Server Version : 100622 (10.6.22-MariaDB-0ubuntu0.22.04.1) + Source Host : 192.168.2.242:33066 + Source Schema : omc_db + + Target Server Type : MariaDB + Target Server Version : 100622 (10.6.22-MariaDB-0ubuntu0.22.04.1) + File Encoding : 65001 + + Date: 01/08/2025 10:07:00 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for cbc_message +-- ---------------------------- +CREATE TABLE IF NOT EXISTS `cbc_message` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `ne_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `ne_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `message_json` varchar(10240) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `status` enum('ACTIVE','INACTIVE') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'INACTIVE', + `detail` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `created_at` bigint(20) NULL DEFAULT current_timestamp(), + `updated_at` bigint(20) NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `id`(`id`) USING BTREE, + INDEX `idx_ne_time`(`ne_type`, `ne_id`, `created_at`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 64 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'CDR事件_MF' ROW_FORMAT = Dynamic; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/build/database/std/upgrade/upg_sys_i18n.sql b/build/database/std/upgrade/upg_sys_i18n.sql index ef55f345..105bd632 100644 --- a/build/database/std/upgrade/upg_sys_i18n.sql +++ b/build/database/std/upgrade/upg_sys_i18n.sql @@ -772,4 +772,23 @@ REPLACE INTO `sys_i18n` VALUES (757, 'dictData.trace_interfaces.5', 'N5', 'N5'); REPLACE INTO `sys_i18n` VALUES (758, "alarm.export.alarmCode", "告警编码", "Alarm Code"); REPLACE INTO `sys_i18n` VALUES (759, "config.sys.user.fristPasswdChangeRemark", "关闭改为false 开启改为true, 建议同时设置密码有效期", "Off to false On to true, it is recommended to set the password expiration date at the same time."); +REPLACE INTO `sys_i18n` VALUES (2000, 'menu.psap.agent', '座席', 'Agent'); +REPLACE INTO `sys_i18n` VALUES (2001, 'menu.psap.agent.callings', '并行话务', 'Calling Information'); +REPLACE INTO `sys_i18n` VALUES (2002, 'menu.psap.agent.callback', '回拨管理', 'Callback Management'); +REPLACE INTO `sys_i18n` VALUES (2003, 'callback.status.NEW', '新建', 'New'); +REPLACE INTO `sys_i18n` VALUES (2004, 'callback.status.IN_PROGRESS', '处理中', 'In Progress'); +REPLACE INTO `sys_i18n` VALUES (2005, 'callback.status.NO_ANSWER_1', '未应答1', 'No Answer 1'); +REPLACE INTO `sys_i18n` VALUES (2006, 'callback.status.NO_ANSWER_2', '未应答2', 'No Answer 2'); +REPLACE INTO `sys_i18n` VALUES (2007, 'callback.status.TIMEOUT', '超时', 'Timeout'); +REPLACE INTO `sys_i18n` VALUES (2008, 'callback.status.PENDING', '挂起', 'Pending'); +REPLACE INTO `sys_i18n` VALUES (2009, 'callback.status.CLOSED', '关闭', 'Closed'); +REPLACE INTO `sys_i18n` VALUES (2010, 'job.export.cdr.mf', '定期导出MF话单', 'Periodic Export of MF Call Records'); +REPLACE INTO `sys_i18n` VALUES (2011, 'job.psap.ticket.monitor', '回拨工单监控', 'Callback Ticket Monitoring'); +REPLACE INTO `sys_i18n` VALUES (2012, 'menu.omc.cdr', '话单', 'Call Records'); +REPLACE INTO `sys_i18n` VALUES (2013, 'menu.omc.cdr.mf', '紧急呼叫话单', 'Emergency Call Records'); +REPLACE INTO `sys_i18n` VALUES (2014, 'menu.omc.cdr.crbt', '彩铃话单', 'Color Ring Back Tone Records'); +REPLACE INTO `sys_i18n` VALUES (2015, 'menu.omc.cdr.mms', '彩信话单', 'Multimedia Message Service Records'); +REPLACE INTO `sys_i18n` VALUES (2016, 'menu.ue.cbc.cbe', '广播', 'Broadcast'); +REPLACE INTO `sys_i18n` VALUES (2017, 'log.operate.title.cbcMessage', '广播事件', 'Broadcast Event'); + -- Dump completed on 2025-02-14 15:26:56 diff --git a/build/database/std/upgrade/upg_sys_menu.sql b/build/database/std/upgrade/upg_sys_menu.sql index 555014cc..352bccca 100644 --- a/build/database/std/upgrade/upg_sys_menu.sql +++ b/build/database/std/upgrade/upg_sys_menu.sql @@ -231,6 +231,8 @@ REPLACE INTO `sys_menu` VALUES (2167, 'menu.dashboard.overview.imsUeNum', 2132, REPLACE INTO `sys_menu` VALUES (2168, 'menu.dashboard.overview.gnbBase', 2132, 6, '', '', '1', '1', 'B', '1', '1', 'dashboard:overview:gnbBase', '#', '0', 'system', 1728641403588, 'system', 1728641403588, ''); REPLACE INTO `sys_menu` VALUES (2169, 'menu.dashboard.overview.enbBase', 2132, 8, '', '', '1', '1', 'B', '1', '1', 'dashboard:overview:enbBase', '#', '0', 'system', 1728641403588, 'system', 1728641403588, ''); +REPLACE INTO `sys_menu` VALUES (20000, 'menu.ue.cbc.cbe', 5, 20, 'cbe', 'cbc/cbe/index', '1', '0', 'M', '1', '1', 'cbc#dashboard:cdr:index', 'icon-tubiaoku', '0', 'system', 1711352709786, 'system', 1747796007372, ''); + SET FOREIGN_KEY_CHECKS = 1; -- Dump completed on 2025-02-14 15:26:56 diff --git a/build/database/std/upgrade/upg_sys_role_menu.sql b/build/database/std/upgrade/upg_sys_role_menu.sql index d8e3385b..87e16b35 100644 --- a/build/database/std/upgrade/upg_sys_role_menu.sql +++ b/build/database/std/upgrade/upg_sys_role_menu.sql @@ -162,6 +162,7 @@ INSERT IGNORE INTO `sys_role_menu` VALUES (2, 2166); INSERT IGNORE INTO `sys_role_menu` VALUES (2, 2167); INSERT IGNORE INTO `sys_role_menu` VALUES (2, 2168); INSERT IGNORE INTO `sys_role_menu` VALUES (2, 2169); +INSERT IGNORE INTO `sys_role_menu` VALUES (2, 20000); INSERT IGNORE INTO `sys_role_menu` VALUES (3, 1); INSERT IGNORE INTO `sys_role_menu` VALUES (3, 4); @@ -237,6 +238,7 @@ INSERT IGNORE INTO `sys_role_menu` VALUES (3, 2166); INSERT IGNORE INTO `sys_role_menu` VALUES (3, 2167); INSERT IGNORE INTO `sys_role_menu` VALUES (3, 2168); INSERT IGNORE INTO `sys_role_menu` VALUES (3, 2169); +INSERT IGNORE INTO `sys_role_menu` VALUES (3, 20000); INSERT IGNORE INTO `sys_role_menu` VALUES (4, 1); INSERT IGNORE INTO `sys_role_menu` VALUES (4, 4); diff --git a/src/modules/network_data/controller/cbc.go b/src/modules/network_data/controller/cbc.go new file mode 100644 index 00000000..a5935875 --- /dev/null +++ b/src/modules/network_data/controller/cbc.go @@ -0,0 +1,316 @@ +package controller + +import ( + "encoding/json" + "fmt" + "strconv" + "time" + + "be.ems/src/framework/i18n" + "be.ems/src/framework/reqctx" + "be.ems/src/framework/resp" + "be.ems/src/modules/network_data/model" + neDataService "be.ems/src/modules/network_data/service" + neService "be.ems/src/modules/network_element/service" + "github.com/gin-gonic/gin" +) + +const ( + neType = "CBC" // 网元类型 +) + +// 实例化控制层 CBCController 结构体 +var NewCBC = &CBCController{ + neInfoService: neService.NewNeInfo, + neCBCMessageService: neDataService.NewCBCMessage, +} + +// 网元CBC +type CBCController struct { + neInfoService *neService.NeInfo // 网元信息服务 + neCBCMessageService *neDataService.CBCMessage // CBC消息服务 +} + +func (m *CBCController) List(c *gin.Context) { + language := reqctx.AcceptLanguage(c) + neId := c.Query("neId") + if neId == "" { + c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + var query model.CBCMessageQuery + if err := c.ShouldBindQuery(&query); err != nil { + c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + query.NeType = neType + query.NeId = neId + + data, total, err := neDataService.NewCBCMessage.SelectByPage(query) + if err != nil { + c.JSON(500, resp.ErrMsg(err.Error())) + return + } + + // 转换数据格式,确保 MessageJson 正确序列化 + processedData := make([]map[string]interface{}, len(data)) + for i, msg := range data { + var messageJson interface{} + if len(msg.MessageJson) > 0 { + // 尝试解析为 JSON 对象 + if err := json.Unmarshal(msg.MessageJson, &messageJson); err != nil { + // 如果解析失败,就作为字符串 + messageJson = string(msg.MessageJson) + } + } + + processedData[i] = map[string]interface{}{ + "id": msg.Id, + "neType": msg.NeType, + "neId": msg.NeId, + "messageJson": messageJson, // 这里是解析后的 JSON 对象 + "status": msg.Status.Enum(), + "detail": msg.Detail, + "createdAt": msg.CreatedAt, + "updatedAt": msg.UpdatedAt, + } + } + c.JSON(200, resp.Ok(gin.H{ + "total": total, + "data": processedData, + })) +} + +// Update 更新CB消息 +func (m *CBCController) Insert(c *gin.Context) { + language := reqctx.AcceptLanguage(c) + // 绑定请求体 + var msg model.CBCMessage + msg.NeType = neType + msg.NeId = c.Query("neId") + msg.Status = model.CBCEventStatusInactive // 默认状态为 INACTIVE + if msg.NeId == "" { + c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + msg.CreatedAt = time.Now().Local().UnixMicro() + msg.UpdatedAt = nil // 新增时更新时间为nil + + // 使用 ShouldBindBodyWithJSON 读取请求体 + var jsonData interface{} + if err := c.ShouldBindBodyWithJSON(&jsonData); err != nil { + c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 将绑定的数据转换为 JSON + jsonBytes, err := json.Marshal(jsonData) + if err != nil { + c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + msg.MessageJson = json.RawMessage(jsonBytes) + + if err := neDataService.NewCBCMessage.Insert(msg); err != nil { + c.JSON(500, resp.ErrMsg(err.Error())) + return + } + + c.JSON(200, resp.Ok(nil)) +} + +// Update 更新CB消息 +func (m *CBCController) Update(c *gin.Context) { + language := reqctx.AcceptLanguage(c) + + // 获取路径参数 + messageId := c.Param("id") + if messageId == "" { + c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + id, err := strconv.ParseInt(messageId, 10, 64) + if err != nil { + c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + // 直接读取body为json.RawMessage + var jsonData interface{} + if err := c.ShouldBindBodyWithJSON(&jsonData); err != nil { + c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 将绑定的数据转换为 JSON + jsonBytes, err := json.Marshal(jsonData) + if err != nil { + c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + messageJson := json.RawMessage(jsonBytes) + + if err := neDataService.NewCBCMessage.Update(id, messageJson); err != nil { + c.JSON(500, resp.ErrMsg(err.Error())) + return + } + + c.JSON(200, resp.Ok(nil)) +} + +// UpdateStatus 更新CB消息状态 +// 这里的 neId 参数是为了兼容性,实际更新状态时不需要使用它 +// 但为了保持与原有接口一致,仍然保留该参数 +// 如果需要根据 neId 进行特定的逻辑处理,可以在服务层实现 +// 但在当前实现中,neId 仅用于验证请求的有效性 +// 实际的状态更新逻辑不依赖于 neId +// 该接口用于更新 CB 消息的状态,状态值通过查询参数传递 +// 例如:PUT /:neId/message/status?status=ACTIVE +func (m *CBCController) UpdateStatus(c *gin.Context) { + language := reqctx.AcceptLanguage(c) + + neId := c.Query("neId") + status := c.Param("status") + if neId == "" || status == "" { + c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + messageId := c.Param("id") + if messageId != "" { + id, err := strconv.ParseInt(messageId, 10, 64) + if err != nil { + c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + // 如果提供了 messageId,则更新特定消息的状态 + if err := neDataService.NewCBCMessage.UpdateStatus(id, status); err != nil { + c.JSON(500, resp.ErrMsg(err.Error())) + return + } + c.JSON(200, resp.Ok(nil)) + return + } + // 如果没有提供 messageId,则更新所有消息的状态 + if err := neDataService.NewCBCMessage.UpdateStatusByNeId(neId, status); err != nil { + c.JSON(500, resp.ErrMsg(err.Error())) + return + } + c.JSON(200, resp.Ok(nil)) +} + +// Delete 删除CB消息 +func (m *CBCController) Delete(c *gin.Context) { + language := reqctx.AcceptLanguage(c) + + // 获取路径参数 + messageId := c.Param("id") + if messageId == "" { + c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + id, err := strconv.ParseInt(messageId, 10, 64) + if err != nil { + c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + if err := neDataService.NewCBCMessage.Delete(id); err != nil { + c.JSON(500, resp.ErrMsg(err.Error())) + return + } + + c.JSON(200, resp.Ok(nil)) +} + +// ListById 根据ID获取CB消息 +func (m *CBCController) ListById(c *gin.Context) { + language := reqctx.AcceptLanguage(c) + + // 获取路径参数 + idStr := c.Param("id") + if idStr == "" { + c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + id, err := strconv.ParseInt(idStr, 10, 64) + if err != nil { + c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + data, err := neDataService.NewCBCMessage.SelectById(id) + if err != nil { + c.JSON(500, resp.ErrMsg(err.Error())) + return + } + + if data == nil { + c.JSON(404, resp.CodeMsg(404, i18n.TKey(language, "app.common.err404"))) + return + } + // 转换数据格式,确保 MessageJson 正确序列化 + var messageJson interface{} + if len(data.MessageJson) > 0 { + // 尝试解析为 JSON 对象 + if err := json.Unmarshal(data.MessageJson, &messageJson); err != nil { + // 如果解析失败,就作为字符串 + messageJson = string(data.MessageJson) + } + } + + processedData := map[string]interface{}{ + "id": data.Id, + "neType": data.NeType, + "neId": data.NeId, + "messageJson": messageJson, // 这里是解析后的 JSON 对象 + "status": data.Status.Enum(), + "detail": data.Detail, + "createdAt": data.CreatedAt, + "updatedAt": data.UpdatedAt, + } + + c.JSON(200, resp.Ok(gin.H{ + "data": processedData, + })) +} + +func (m *CBCController) Export(c *gin.Context) { + language := reqctx.AcceptLanguage(c) + // 查询结果,根据查询条件结果,单页最大值限制 + var query model.CBCMessageQuery + if err := c.ShouldBindQuery(&query); err != nil { + errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err)) + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs)) + return + } + // 限制导出数据集 + if query.PageSize > 10000 { + query.PageSize = 10000 + } + // 查询数据 + rows, total, err := m.neCBCMessageService.SelectByPage(query) + if err != nil { + c.JSON(500, resp.ErrMsg(err.Error())) + return + } + if total == 0 { + // 导出数据记录为空 + c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.exportEmpty"))) + return + } + + // 导出文件名称 + fileName := fmt.Sprintf("cbc_message_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli()) + // 导出数据表格 + saveFilePath, err := m.neCBCMessageService.ExportXlsx(rows, fileName, language) + if err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + + c.FileAttachment(saveFilePath, fileName) +} diff --git a/src/modules/network_data/model/cbc_message.go b/src/modules/network_data/model/cbc_message.go new file mode 100644 index 00000000..fb7f75a3 --- /dev/null +++ b/src/modules/network_data/model/cbc_message.go @@ -0,0 +1,112 @@ +package model + +import ( + "database/sql/driver" + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" +) + +type CBCEventStatus int + +// CBCEventStatus CB事件状态枚举 +const ( + CBCEventStatusNull CBCEventStatus = iota // 未知状态 + CBCEventStatusActive + CBCEventStatusInactive +) + +func (status CBCEventStatus) Enum() string { + switch status { + case CBCEventStatusNull: + return "NULL" + case CBCEventStatusActive: + return "ACTIVE" + case CBCEventStatusInactive: + return "INACTIVE" + default: + return "UNKNOWN" + } +} + +func (status CBCEventStatus) String() string { + return fmt.Sprintf("%d", status) +} + +// ParseCBCEventStatus 将字符串转换为 枚举类型 +func ParseCBCEventStatus(s string) CBCEventStatus { + if i, err := strconv.Atoi(s); err == nil { + return CBCEventStatus(i) + } + // 如果转换失败,则按名称匹配(忽略大小写) + switch strings.ToUpper(s) { + case "NULL": + return CBCEventStatusNull + case "ACTIVE": + return CBCEventStatusActive + case "INACTIVE": + return CBCEventStatusInactive + case "": + // 如果字符串为空,则返回未知状态 + return CBCEventStatusNull + default: + // 默认返回未知状态 + return CBCEventStatusNull + } +} + +// CBCMessageQuery 查询条件结构体 +type CBCMessageQuery struct { + NeType string `form:"neType"` // 网元类型 + NeId string `form:"neId"` // 网元ID + EventName string `form:"eventName"` // 事件名称 + Status string `form:"status"` // 消息状态 + StartTime string `form:"startTime"` // 创建时间范围-起始 + EndTime string `form:"endTime"` // 创建时间范围-结束 + PageNum int `form:"pageNum" binding:"required"` + PageSize int `form:"pageSize" binding:"required"` +} + +// @Description CBCMessage CB消息 +type CBCMessage struct { + Id int64 `json:"id" gorm:"column:id"` // CB消息ID + NeType string `json:"neType" gorm:"column:ne_type"` // 网元类型 + NeId string `json:"neId" gorm:"column:ne_id"` // 网元ID + MessageJson json.RawMessage `json:"messageJson" gorm:"column:message_json"` // 消息内容JSON + Status CBCEventStatus `json:"status" gorm:"column:status"` // 消息状态 + Detail string `json:"detail" gorm:"column:detail"` // 详情 + CreatedAt int64 `json:"createdAt" gorm:"column:created_at"` // 创建时间 + UpdatedAt *int64 `json:"updatedAt" gorm:"column:updated_at;autoUpdateTime:false"` // 更新时间 +} + +// TableName 表名称 +func (*CBCMessage) TableName() string { + return "cbc_message" +} + +// Scan 实现 sql.Scanner 接口,支持从数据库字符串转为 CBCEventStatus +func (s *CBCEventStatus) Scan(value interface{}) error { + switch v := value.(type) { + case string: + *s = ParseCBCEventStatus(v) + return nil + case []byte: + *s = ParseCBCEventStatus(string(v)) + return nil + case int64: + *s = CBCEventStatus(v) + return nil + case int: + *s = CBCEventStatus(v) + return nil + default: + return errors.New("unsupported Scan type for CBCEventStatus") + } +} + +// Value 实现 driver.Valuer 接口,支持将 CBCEventStatus 存为字符串 +func (s CBCEventStatus) Value() (driver.Value, error) { + return s.Enum(), nil +} diff --git a/src/modules/network_data/network_data.go b/src/modules/network_data/network_data.go index 0cbb2fe3..e41d1683 100644 --- a/src/modules/network_data/network_data.go +++ b/src/modules/network_data/network_data.go @@ -579,6 +579,44 @@ func Setup(router *gin.Engine) { controller.NewPCF.RuleInfoImport, ) } + + // 网元CBC + cbcGroup := neDataGroup.Group("/cbc") + { + cbcGroup.GET("/message/list", + middleware.AuthorizeUser(nil), + controller.NewCBC.List, + ) + cbcGroup.GET("/message/:id", + middleware.AuthorizeUser(nil), + controller.NewCBC.ListById, + ) + cbcGroup.POST("/message", + middleware.AuthorizeUser(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.cbcMessage", collectlogs.BUSINESS_TYPE_IMPORT)), + controller.NewCBC.Insert, + ) + cbcGroup.PUT("/message/:id", + middleware.AuthorizeUser(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.cbcMessage", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewCBC.Update, + ) + cbcGroup.PUT("/message/:id/:status", + middleware.AuthorizeUser(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.cbcMessage", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewCBC.UpdateStatus, + ) + cbcGroup.DELETE("/message/:id", + middleware.AuthorizeUser(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.cbcMessage", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewCBC.Delete, + ) + cbcGroup.GET("/message/export", + middleware.AuthorizeUser(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.cbcMessage", collectlogs.BUSINESS_TYPE_EXPORT)), + controller.NewCBC.Export, + ) + } } // InitLoad 初始参数 diff --git a/src/modules/network_data/repository/cbc_message.go b/src/modules/network_data/repository/cbc_message.go new file mode 100644 index 00000000..b9d140d3 --- /dev/null +++ b/src/modules/network_data/repository/cbc_message.go @@ -0,0 +1,254 @@ +package repository + +import ( + "encoding/json" + "fmt" + "time" + + "be.ems/src/framework/database/db" + "be.ems/src/modules/network_data/model" + "gorm.io/gorm" +) + +// 实例化数据层 UEEvent 结构体 +var NewCBCMessage = &CBCMessage{} + +// UEEvent UE会话事件 数据层处理 +type CBCMessage struct{} + +// SelectCBCMessage 根据条件分页查询CB消息 +func (s *CBCMessage) SelectByPage(query model.CBCMessageQuery) ([]model.CBCMessage, int, error) { + var msg []model.CBCMessage + var total int64 + + tx := db.DB("").Table("cbc_message") + + if query.NeType != "" { + tx = tx.Where("ne_type = ?", query.NeType) + } + if query.NeId != "" { + tx = tx.Where("ne_id = ?", query.NeId) + } + if query.EventName != "" { + tx = tx.Where("JSON_EXTRACT(message_json, '$.eventName') = ?", query.EventName) + } + if query.Status != "" { + tx = tx.Where("status = ?", query.Status) + } + + var startMicro, endMicro int64 + var err error + if query.StartTime != "" { + startMicro, err = parseTimeToMicro(query.StartTime) + if err != nil { + return nil, 0, fmt.Errorf("invalid start time: %w", err) + } + } + if query.EndTime != "" { + endMicro, err = parseTimeToMicro(query.EndTime) + if err != nil { + return nil, 0, fmt.Errorf("invalid end time: %w", err) + } + } + if startMicro > 0 && endMicro > 0 { + tx = tx.Where("created_at BETWEEN ? AND ?", startMicro, endMicro) + } else if startMicro > 0 { + tx = tx.Where("created_at >= ?", startMicro) + } else if endMicro > 0 { + tx = tx.Where("created_at <= ?", endMicro) + } + + // 统计总数 + if err := tx.Count(&total).Error; err != nil { + return nil, 0, fmt.Errorf("failed to count CBC message: %w", err) + } + + // 分页查询 + offset := (query.PageNum - 1) * query.PageSize + if err := tx.Limit(query.PageSize).Offset(offset).Order("created_at desc").Find(&msg).Error; err != nil { + return nil, 0, fmt.Errorf("failed to select CBC message: %w", err) + } + + return msg, int(total), nil +} + +// SelectCBCMessageByPage 分页查询CB消息 +// @Description 分页查询CB消息 +// @param page 页码 +// @param pageSize 每页大小 +// @return []model.CBCMessage CB消息列表 +// @return int 总记录数 +// @return error 错误信息 +// @example +// SelectByPage(1, 10) +// func (s *CBCMessage) SelectByPage(pageNum int, pageSize int) ([]model.CBCMessage, int, error) { +// var tickets []model.CBCMessage +// var total int64 + +// // 统计总数 +// if err := db.DB("").Table("cbc_message").Count(&total).Error; err != nil { +// return nil, 0, fmt.Errorf("failed to count CBC message: %w", err) +// } + +// // 分页查询 +// offset := (pageNum - 1) * pageSize +// if err := db.DB("").Table("cbc_message"). +// Limit(pageSize). +// Offset(offset). +// Find(&tickets).Error; err != nil { +// return nil, 0, fmt.Errorf("failed to select CBC message: %w", err) +// } + +// return tickets, int(total), nil +// } + +// InsertCBCMessage 插入CB消息 +// @Description 插入CB消息 +// @param msg CB消息对象 +// @return error 错误信息 +// @example +// CBCMessage.InsertCBCMessage(msg) +func (s *CBCMessage) Insert(msg model.CBCMessage) error { + // 这里可以使用ORM或其他方式将ticket插入到数据库中 + if err := db.DB("").Table("cbc_message").Create(&msg).Error; err != nil { + return fmt.Errorf("failed to insert CBC message: %w", err) + } + return nil +} + +// SelectCBCMessageById 根据工单ID查询CB消息 +// @Description 根据工单ID查询CB消息 +// @param id 工单ID +// @return *model.CBCMessage CB消息对象 +// @return error 错误信息 +// @example +// CBCMessage.SelectCBCMessageById(12345) +func (s *CBCMessage) SelectById(id int64) (*model.CBCMessage, error) { + var msg model.CBCMessage + if err := db.DB("").Table("cbc_message"). + Where("id = ?", id). + First(&msg).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return nil, nil + } + return nil, fmt.Errorf("failed to select CBC message: %w", err) + } + return &msg, nil +} + +// SelectByEventName 根据事件名称查询CB消息 +func (s *CBCMessage) SelectByEventName(eventName string) (*model.CBCMessage, error) { + var msg model.CBCMessage + if err := db.DB("").Table("cbc_message"). + Where("JSON_EXTRACT(message_json, '$.eventName') = ?", eventName). + First(&msg).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return nil, nil + } + return nil, err + } + return &msg, nil +} + +// UpdateCBCMessage 更新CB消息 +// @Description 更新CB消息 +// @param msg CB消息对象 +// @return error 错误信息 +// @example +// mfCBCMessageService.UpdateCBCMessage(msg) +func (s *CBCMessage) Update(id int64, messageJson json.RawMessage) error { + now := time.Now().UnixMicro() + if err := db.DB("").Table("cbc_message"). + Where("id = ?", id). + Updates(map[string]interface{}{ + "message_json": messageJson, + "updated_at": now, + }).Error; err != nil { + return fmt.Errorf("failed to update CBC message: %w", err) + } + + return nil +} + +// UpdateCBCMessage 更新CB消息 +// @Description 更新CB消息 +// @param msg CB消息对象 +// @return error 错误信息 +// @example +// UpdateCBCMessageDetail(msg) +func (s *CBCMessage) UpdateDetail(eventName, detail string) error { + now := time.Now().UnixMicro() + if err := db.DB("").Table("cbc_message"). + Where("JSON_EXTRACT(message_json, '$.eventName') = ?", eventName). + Updates(map[string]any{ + "detail": detail, + "updated_at": now, + }).Error; err != nil { + return fmt.Errorf("failed to update CBC message: %w", err) + } + + return nil +} + +// DeleteCBCMessage 删除CB消息 +// @Description 删除CB消息 +// @param id 工单ID +// @return error 错误信息 +// @example +// DeleteCBCMessage(12345) +func (s *CBCMessage) Delete(id int64) error { + // 执行删除操作 + if err := db.DB("").Table("cbc_message"). + Where("id = ?", id). + Delete(&model.CBCMessage{}).Error; err != nil { + return fmt.Errorf("failed to delete CBC message: %w", err) + } + return nil +} + +// UpdateCBCMessageStatus 更新CB消息状态 +// @Description 更新CB消息状态,并根据状态变化发送相应的HTTP请求 +// @param status 新状态 +// @return error 错误信息 +func (s *CBCMessage) UpdateStatus(id int64, status model.CBCEventStatus) error { + // 更新数据库状态 + now := time.Now().UnixMicro() + if err := db.DB("").Table("cbc_message"). + Where("id = ?", id). + Updates(map[string]interface{}{ + "status": status, + "updated_at": now, + }).Error; err != nil { + return fmt.Errorf("failed to update CBC message status: %w", err) + } + + return nil +} + +// Select 查询所有CB消息 +func (s *CBCMessage) Select(msgs *[]model.CBCMessage) error { + if err := db.DB("").Table("cbc_message").Find(&msgs).Error; err != nil { + return fmt.Errorf("failed to query CB messages: %w", err) + } + return nil +} + +// SelectByNeId 根据网元ID查询CB消息 +func (s *CBCMessage) SelectByNeId(neId string, msgs *[]model.CBCMessage) error { + if err := db.DB("").Table("cbc_message").Where("ne_id = ?", neId).Find(&msgs).Error; err != nil { + return fmt.Errorf("failed to query CB messages: %w", err) + } + return nil +} + +// 假设 query.StartTime 和 query.EndTime 是 "2006-01-02 15:04:05" 格式字符串 +func parseTimeToMicro(ts string) (int64, error) { + if ts == "" { + return 0, nil + } + t, err := time.ParseInLocation("2006-01-02 15:04:05", ts, time.Local) + if err != nil { + return 0, err + } + return t.UnixMicro(), nil +} diff --git a/src/modules/network_data/service/cbc_message.go b/src/modules/network_data/service/cbc_message.go new file mode 100644 index 00000000..8062e256 --- /dev/null +++ b/src/modules/network_data/service/cbc_message.go @@ -0,0 +1,453 @@ +package service + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "strconv" + "time" + + "be.ems/src/framework/i18n" + "be.ems/src/framework/utils/file" + "be.ems/src/modules/network_data/model" + "be.ems/src/modules/network_data/repository" + neService "be.ems/src/modules/network_element/service" + "gorm.io/gorm" +) + +// 实例化数据层 CBCMessage 结构体 +var NewCBCMessage = &CBCMessage{ + cbcMessageRepository: repository.NewCBCMessage, +} + +// CBCMessage CB消息 服务层处理 +type CBCMessage struct { + cbcMessageRepository *repository.CBCMessage // UE会话事件数据信息 +} + +// SelectByPage 根据条件分页查询CB消息 +func (s *CBCMessage) SelectByPage(query model.CBCMessageQuery) ([]model.CBCMessage, int, error) { + return s.cbcMessageRepository.SelectByPage(query) +} + +// SelectCBCMessageById 根据工单ID查询CB消息 +// @Description 根据工单ID查询CB消息 +// @param id 工单ID +// @return *model.CBCMessage CB消息对象 +// @return error 错误信息 +// @example +// CBCMessage.SelectCBCMessageById(12345) +func (s *CBCMessage) SelectById(id int64) (*model.CBCMessage, error) { + return s.cbcMessageRepository.SelectById(id) +} + +func (s *CBCMessage) Insert(msg model.CBCMessage) error { + // 解析messageJson获取eventName + var messageData map[string]interface{} + if err := json.Unmarshal(msg.MessageJson, &messageData); err != nil { + return fmt.Errorf("failed to parse message_json: %w", err) + } + + // 提取eventName + eventName, ok := messageData["eventName"].(string) + if !ok || eventName == "" { + return fmt.Errorf("eventName is required in message_json") + } + + // 检查是否已存在相同的eventName + var err error + var existingMsg *model.CBCMessage + if existingMsg, err = s.cbcMessageRepository.SelectByEventName(eventName); err != nil { + return fmt.Errorf("failed to check existing CBC message: %w", err) + } + + if existingMsg != nil { + return fmt.Errorf("CBC message with eventName '%s' already exists for neType '%s' and neId '%s'", + eventName, existingMsg.NeType, existingMsg.NeId) + } + return s.cbcMessageRepository.Insert(msg) +} + +// UpdateCBCMessage 更新CB消息 +// @Description 更新CB消息 +// @param msg CB消息对象 +// @return error 错误信息 +// @example +// mfCBCMessageService.UpdateCBCMessage(msg) +func (s *CBCMessage) Update(id int64, messageJson json.RawMessage) error { + // 解析messageJson获取eventName + var messageData map[string]interface{} + if err := json.Unmarshal(messageJson, &messageData); err != nil { + return fmt.Errorf("failed to parse message_json: %w", err) + } + // 提取eventName + eventName, ok := messageData["eventName"].(string) + if !ok || eventName == "" { + return fmt.Errorf("eventName is required in message_json") + } + + // 检查是否已存在相同的eventName + var err error + var msg *model.CBCMessage + if msg, err = s.cbcMessageRepository.SelectByEventName(eventName); err != nil { + if err == gorm.ErrRecordNotFound { + return fmt.Errorf("no existing CBC message found with eventName: %s", eventName) + } + return fmt.Errorf("failed to query existing CBC message: %w", err) + } + + // 如果存在,更新记录 + if err := s.cbcMessageRepository.Update(id, messageJson); err != nil { + return fmt.Errorf("failed to update CBC message: %w", err) + } + + // 如果状态是ACTIVE,发送更新请求 + if msg.Status == model.CBCEventStatusActive { + if err := s.sendUpdateRequest(*msg); err != nil { + return fmt.Errorf("failed to send update request: %w", err) + } + } + + return nil +} + +// UpdateCBCMessage 更新CB消息 +// @Description 更新CB消息 +// @param msg CB消息对象 +// @return error 错误信息 +// @example +// UpdateCBCMessageDetail(msg) +func (s *CBCMessage) UpdateDetail(eventName, detail string) error { + if err := s.cbcMessageRepository.UpdateDetail(eventName, detail); err != nil { + return fmt.Errorf("failed to update CBC message detail: %w", err) + } + + return nil +} + +// DeleteCBCMessage 删除CB消息 +// @Description 删除CB消息 +// @param id 工单ID +// @return error 错误信息 +// @example +// mfCBCMessageService.DeleteCBCMessage(12345) +func (s *CBCMessage) Delete(id int64) error { + // 先查询记录状态 + var err error + var msg *model.CBCMessage + if msg, err = s.cbcMessageRepository.SelectById(id); err != nil { + if err == gorm.ErrRecordNotFound { + return fmt.Errorf("CBC message with ID %d not found", id) + } + return fmt.Errorf("failed to query CBC message: %w", err) + } + + // 检查状态是否为ACTIVE + if msg.Status == model.CBCEventStatusActive { + return fmt.Errorf("cannot delete an active CBC message (ID: %d)", id) + } + + // 执行删除操作 + if err := s.cbcMessageRepository.Delete(id); err != nil { + return fmt.Errorf("failed to delete CBC message: %w", err) + } + return nil +} + +// UpdateCBCMessageStatus 更新CB消息状态 +// @Description 更新CB消息状态,并根据状态变化发送相应的HTTP请求 +// @param status 新状态 +// @return error 错误信息 +func (s *CBCMessage) UpdateStatus(id int64, status string) error { + newStatus := model.ParseCBCEventStatus(status) + + // 查询所有需要更新的记录 + var err error + var msg *model.CBCMessage + if msg, err = s.cbcMessageRepository.SelectById(id); err != nil { + if err == gorm.ErrRecordNotFound { + return fmt.Errorf("CBC message with ID %d not found", id) + } + return fmt.Errorf("failed to query CBC message: %w", err) + } + + oldStatus := msg.Status + + // 检查状态是否发生变化 + if oldStatus == newStatus { + return fmt.Errorf("CBC message status is already %s", newStatus.Enum()) + } + + // 更新数据库状态 + if err := s.cbcMessageRepository.UpdateStatus(id, newStatus); err != nil { + return fmt.Errorf("failed to update CBC message status: %w", err) + } + + // 根据状态变化发送HTTP请求 + if err := s.handleStatusChange(*msg, oldStatus, newStatus); err != nil { + // 记录错误但不中断处理其他消息 + fmt.Printf("Failed to handle status change for message %d: %v\n", msg.Id, err) + } + + return nil +} + +// UpdateCBCMessageStatus 更新CB消息状态 +// @Description 更新CB消息状态,并根据状态变化发送相应的HTTP请求 +// @param status 新状态 +// @return error 错误信息 +func (s *CBCMessage) UpdateStatusByNeId(neId string, status string) error { + newStatus := model.ParseCBCEventStatus(status) + + // 查询所有需要更新的记录 + msgs := make([]model.CBCMessage, 0) + if err := s.cbcMessageRepository.SelectByNeId(neId, &msgs); err != nil { + return fmt.Errorf("failed to query CB messages: %w", err) + } + + for _, msg := range msgs { + oldStatus := msg.Status + + // 检查状态是否发生变化 + if oldStatus == newStatus { + continue // 状态没有变化,跳过 + } + + // 更新数据库状态 + if err := s.cbcMessageRepository.UpdateStatus(msg.Id, newStatus); err != nil { + return fmt.Errorf("failed to update CBC message status: %w", err) + } + + // 根据状态变化发送HTTP请求 + if err := s.handleStatusChange(msg, oldStatus, newStatus); err != nil { + // 记录错误但不中断处理其他消息 + fmt.Printf("Failed to handle status change for message %d: %v\n", msg.Id, err) + } + } + + return nil +} + +// UpdateCBCMessageStatus 更新CB消息状态 +// @Description 更新CB消息状态,并根据状态变化发送相应的HTTP请求 +// @param status 新状态 +// @return error 错误信息 +func (s *CBCMessage) UpdateAllStatus(status string) error { + newStatus := model.ParseCBCEventStatus(status) + + // 查询所有需要更新的记录 + msgs := make([]model.CBCMessage, 0) + if err := s.cbcMessageRepository.Select(&msgs); err != nil { + return fmt.Errorf("failed to query CB messages: %w", err) + } + + for _, msg := range msgs { + oldStatus := msg.Status + + // 检查状态是否发生变化 + if oldStatus == newStatus { + continue // 状态没有变化,跳过 + } + + // 更新数据库状态 + if err := s.cbcMessageRepository.UpdateStatus(msg.Id, newStatus); err != nil { + return fmt.Errorf("failed to update CBC message status: %w", err) + } + + // 根据状态变化发送HTTP请求 + if err := s.handleStatusChange(msg, oldStatus, newStatus); err != nil { + // 记录错误但不中断处理其他消息 + fmt.Printf("Failed to handle status change for message %d: %v\n", msg.Id, err) + } + } + + return nil +} + +// ExportXlsx 导出数据到 xlsx 文件 +func (r CBCMessage) ExportXlsx(rows []model.CBCMessage, fileName, language string) (string, error) { + // 第一行表头标题 + headerCells := map[string]string{ + "A1": i18n.TKey(language, "alarm.export.alarmType"), + "B1": i18n.TKey(language, "alarm.export.origSeverity"), + "C1": i18n.TKey(language, "alarm.export.alarmTitle"), + "D1": i18n.TKey(language, "alarm.export.eventTime"), + "E1": i18n.TKey(language, "alarm.export.alarmId"), + "F1": i18n.TKey(language, "alarm.export.alarmCode"), + "G1": i18n.TKey(language, "ne.common.neType"), + "H1": i18n.TKey(language, "ne.common.neName"), + "I1": i18n.TKey(language, "ne.common.neId"), + } + + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + + cells := map[string]any{ + "A" + idx: row.NeType, + "B" + idx: row.NeId, + "C" + idx: row.MessageJson, // 这里假设 MessageJson 已经是字符串格式 + "D" + idx: row.Status.Enum(), + "E" + idx: row.Detail, + "F" + idx: time.Unix(row.CreatedAt, 0).Format(time.RFC3339), + "G" + idx: time.Unix(*row.UpdatedAt, 0).Format(time.RFC3339), + } + + dataCells = append(dataCells, cells) + } + + // 导出数据表格 + return file.WriteSheet(headerCells, dataCells, fileName, "") +} + +// handleStatusChange 处理状态变化时的HTTP请求 +func (s *CBCMessage) handleStatusChange(msg model.CBCMessage, oldStatus, newStatus model.CBCEventStatus) error { + // 从NULL/INACTIVE状态修改为ACTIVE + if (oldStatus == model.CBCEventStatusNull || oldStatus == model.CBCEventStatusInactive) && + newStatus == model.CBCEventStatusActive { + return s.sendActivateRequest(msg) + } + + // 从ACTIVE更改为INACTIVE状态 + if oldStatus == model.CBCEventStatusActive && newStatus == model.CBCEventStatusInactive { + return s.sendDeactivateRequest(msg) + } + + return nil +} + +// getCBCNetworkElement 获取CBC网元的IP和端口信息 +// 这个方法需要根据你的实际网元管理系统来实现 +func (s *CBCMessage) getCBCNetworkElement(neId string) (string, int64, error) { + // 查询网元信息 + neInfo := neService.NewNeInfo.FindByNeTypeAndNeID("CBC", neId) + if neInfo.IP == "" { + return "", 0, fmt.Errorf("CBC network element not found for neId: %s", neId) + } + return neInfo.IP, neInfo.Port, nil +} + +// CBCHTTPClient CBC网元HTTP客户端 +type CBCHTTPClient struct { + client *http.Client + baseURL string +} + +// NewCBCHTTPClient 创建CBC HTTP客户端 +func NewCBCHTTPClient(baseURL string) *CBCHTTPClient { + return &CBCHTTPClient{ + client: &http.Client{ + Timeout: 10 * time.Second, + }, + baseURL: baseURL, + } +} + +// PostMessage 发送POST请求创建消息 +func (c *CBCHTTPClient) PostMessage(messageData []byte) error { + url := fmt.Sprintf("%s/api/v1/cbe/message", c.baseURL) + return c.sendRequest("POST", url, messageData) +} + +// PutMessage 发送PUT请求更新消息 +func (c *CBCHTTPClient) PutMessage(messageData []byte) error { + url := fmt.Sprintf("%s/api/v1/cbe/message", c.baseURL) + return c.sendRequest("PUT", url, messageData) +} + +// DeleteMessage 发送DELETE请求删除消息 +func (c *CBCHTTPClient) DeleteMessage(eventName string, deletePayload []byte) error { + url := fmt.Sprintf("%s/api/v1/cbe/message/%s", c.baseURL, eventName) + return c.sendRequest("DELETE", url, deletePayload) +} + +// sendRequest 发送HTTP请求 +func (c *CBCHTTPClient) sendRequest(method, url string, body []byte) error { + req, err := http.NewRequest(method, url, bytes.NewReader(body)) + if err != nil { + return fmt.Errorf("failed to create %s request: %w", method, err) + } + + req.Header.Set("Content-Type", "application/json") + + resp, err := c.client.Do(req) + if err != nil { + return fmt.Errorf("failed to send %s request: %w", method, err) + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + body, _ := io.ReadAll(resp.Body) + return fmt.Errorf("%s request failed with status: %d, body: %s", + method, resp.StatusCode, string(body)) + } + + return nil +} + +// 在CBCMessageService中添加方法 +func (s *CBCMessage) getCBCHTTPClient(neId string) (*CBCHTTPClient, error) { + cbcIP, cbcPort, err := s.getCBCNetworkElement(neId) + if err != nil { + return nil, fmt.Errorf("failed to get CBC network element info: %w", err) + } + + baseURL := fmt.Sprintf("http://%s:%d", cbcIP, cbcPort) + return NewCBCHTTPClient(baseURL), nil +} + +// 重构后的激活请求 +func (s *CBCMessage) sendActivateRequest(msg model.CBCMessage) error { + client, err := s.getCBCHTTPClient(msg.NeId) + if err != nil { + return err + } + + return client.PostMessage(msg.MessageJson) +} + +// 重构后的更新请求 +func (s *CBCMessage) sendUpdateRequest(msg model.CBCMessage) error { + client, err := s.getCBCHTTPClient(msg.NeId) + if err != nil { + return err + } + + return client.PutMessage(msg.MessageJson) +} + +// 重构后的停用请求 +func (s *CBCMessage) sendDeactivateRequest(msg model.CBCMessage) error { + client, err := s.getCBCHTTPClient(msg.NeId) + if err != nil { + return err + } + + // 解析和构造删除载荷的逻辑保持不变 + var messageData map[string]interface{} + if err := json.Unmarshal(msg.MessageJson, &messageData); err != nil { + return fmt.Errorf("failed to parse message_json: %w", err) + } + + eventName, ok := messageData["eventName"].(string) + if !ok || eventName == "" { + return fmt.Errorf("eventName not found in message_json") + } + + deletePayload := make(map[string]interface{}) + if messageIdProfile, exists := messageData["messageIdProfile"]; exists { + deletePayload["messageIdProfile"] = messageIdProfile + } + if warningAreaList, exists := messageData["warningAreaList"]; exists { + deletePayload["warningAreaList"] = warningAreaList + } + + payloadBytes, err := json.Marshal(deletePayload) + if err != nil { + return fmt.Errorf("failed to marshal delete payload: %w", err) + } + + return client.DeleteMessage(eventName, payloadBytes) +} diff --git a/src/modules/oam/controller/api_rest.go b/src/modules/oam/controller/api_rest.go index 955ad745..cfcd2822 100644 --- a/src/modules/oam/controller/api_rest.go +++ b/src/modules/oam/controller/api_rest.go @@ -696,3 +696,27 @@ func (s APIRestController) QuerySystemState(c *gin.Context) { func (s APIRestController) NeConfigOMC(c *gin.Context) { c.JSON(204, nil) } + +// @Description CBSManagement CB消息 +type CBSState struct { + NeName string `json:"neName"` // 网元名称 + RmUID string `json:"rmUID"` // 网元唯一标识 + EventData []oamService.CBSEventData `json:"eventData"` // 事件数据 +} + +func (s APIRestController) ResolveCBSState(c *gin.Context) { + var state CBSState + if err := c.ShouldBindBodyWithJSON(&state); err != nil { + errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err)) + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs)) + return + } + + for _, eventData := range state.EventData { + if err := oamService.NewCBS.Resolve(eventData); err != nil { + c.JSON(200, resp.ErrMsg(err.Error())) + return + } + } + c.JSON(200, resp.Ok(nil)) +} diff --git a/src/modules/oam/oam.go b/src/modules/oam/oam.go index fa4cac0b..54dd3a68 100644 --- a/src/modules/oam/oam.go +++ b/src/modules/oam/oam.go @@ -33,6 +33,7 @@ func Setup(router *gin.Engine) { aprRestGroup.POST("/cdrManagement/v1/elementType/:elementTypeValue/objectType/cdrEvent", aprRest.ResolveCDR) aprRestGroup.POST("/performanceManagement/v1/elementType/:elementTypeValue/objectType/kpiReport/:index", aprRest.ResolveKPI) aprRestGroup.POST("/ueManagement/v1/elementType/:elementTypeValue/objectType/nbState", aprRest.ResolveNBState) + aprRestGroup.POST("/ueManagement/v1/elementType/:elementTypeValue/objectType/cbsState", aprRest.ResolveCBSState) aprRestGroup.POST("/logManagement/v1/elementType/:elementTypeValue/objectType/ueEvent", aprRest.ResolveUENB) router.POST("/upload-ue/v1/:eventType", aprRest.ResolveUENBByAMF) // AMF特殊上报 aprRestGroup.GET("/systemManagement/v1/elementType/:elementTypeValue/objectType/systemState", aprRest.QuerySystemState) diff --git a/src/modules/oam/service/cbs_state.go b/src/modules/oam/service/cbs_state.go new file mode 100644 index 00000000..3b898621 --- /dev/null +++ b/src/modules/oam/service/cbs_state.go @@ -0,0 +1,29 @@ +package service + +import ( + neDataService "be.ems/src/modules/network_data/service" + neService "be.ems/src/modules/network_element/service" +) + +// 实例化服务层 CDR 结构体 +var NewCBS = &CBS{ + neInfoService: neService.NewNeInfo, + cbcMessageService: neDataService.NewCBCMessage, +} + +// CDR 消息处理 +type CBS struct { + neInfoService *neService.NeInfo + cbcMessageService *neDataService.CBCMessage // CDR会话事件服务 +} + +type CBSEventData struct { + EventName string `json:"eventName"` // 事件名称 + MessageId int64 `json:"messageId"` // 消息ID + Detail string `json:"detail"` // 详情 +} + +// Resolve 接收处理 +func (s *CBS) Resolve(c CBSEventData) error { + return s.cbcMessageService.UpdateDetail(c.EventName, c.Detail) +}