feat: support CBC broadcast
This commit is contained in:
38
build/database/std/install/cbc_message.sql
Executable file
38
build/database/std/install/cbc_message.sql
Executable file
@@ -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;
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
37
build/database/std/upgrade/upg_cbc_message.sql
Executable file
37
build/database/std/upgrade/upg_cbc_message.sql
Executable file
@@ -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;
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
316
src/modules/network_data/controller/cbc.go
Normal file
316
src/modules/network_data/controller/cbc.go
Normal file
@@ -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)
|
||||
}
|
||||
112
src/modules/network_data/model/cbc_message.go
Normal file
112
src/modules/network_data/model/cbc_message.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -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 初始参数
|
||||
|
||||
254
src/modules/network_data/repository/cbc_message.go
Normal file
254
src/modules/network_data/repository/cbc_message.go
Normal file
@@ -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
|
||||
}
|
||||
453
src/modules/network_data/service/cbc_message.go
Normal file
453
src/modules/network_data/service/cbc_message.go
Normal file
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
29
src/modules/oam/service/cbs_state.go
Normal file
29
src/modules/oam/service/cbs_state.go
Normal file
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user