From 8d76d68b96e531144e7e1eb40a566c7989ad1ac6 Mon Sep 17 00:00:00 2001 From: zhangsz Date: Tue, 8 Jul 2025 14:45:46 +0800 Subject: [PATCH] feat: support ticket settings in OMC --- config/etc/default/psap.yaml | 29 ++-- config/param/omc_param_config.yaml | 105 ++++++++++++- features/cdr/cdrevent.go | 4 +- features/cm/omc/implement.go | 27 ++++ features/ue/mf_callback_ticket/service.go | 9 +- lib/config/config.go | 14 +- lib/config/psap.go | 143 +++++++++++++++--- lib/email/email.go | 24 +-- restagent/etc/psap.yaml | 17 +-- restagent/etc/restconf.yaml | 2 +- .../psap_ticket_monitor.go | 4 +- 11 files changed, 306 insertions(+), 72 deletions(-) diff --git a/config/etc/default/psap.yaml b/config/etc/default/psap.yaml index c4a462ec..64b46e8d 100644 --- a/config/etc/default/psap.yaml +++ b/config/etc/default/psap.yaml @@ -1,20 +1,17 @@ ticket: - notifcation: - smtp: - enabled: true - host: mail.smtp.com - port: 25 - username: smtpext@smtp.com - # 注意:密码中如果包含特殊字符(如@、#、$等), - # 需要使用双引号括起来,避免解析错误 - # 例如:password: "1000smtp@omc!" - password: 123456 - tlsSkipVerify: true - from: omc@psap - to: # 可以是多个收件人 - - admin@psap.com - - user1@psap.com - timeout: # 超时设置 + ticketNotification: + enabled: true + host: mail.smtp.com + port: 25 + username: smtpext@smtp.com + # 注意:密码中如果包含特殊字符(如@、#、$等), + # 需要使用双引号括起来,避免解析错误 + # 例如:password: "1000smtp@omc!" + password: 123456 + tlsSkipVerify: true + from: omc@psap + to: "admin@psap.com,user1@psap.com" # 注意:可以是多个收件人,使用逗号分隔 + ticketTimeout: # 超时设置 # 这些时间单位是分钟 # 注意:这些时间是相对于工单创建时间的 # 例如:new: 60分钟,inProgress: 60分钟 diff --git a/config/param/omc_param_config.yaml b/config/param/omc_param_config.yaml index cddb3c1b..02f94ce8 100644 --- a/config/param/omc_param_config.yaml +++ b/config/param/omc_param_config.yaml @@ -56,12 +56,12 @@ omc: display: "Alarm SMS Forward Interface" sort: 4 list: - - name: "enable" + - name: "enabled" type: "bool" value: "true" access: "rw" filter: "true;false" - display: "Enable" + display: "Enabled" comment: "Is it enabled forward alarm with SMS interface" - name: "mobileList" type: "string" @@ -111,4 +111,103 @@ omc: access: "rw" filter: "3~20" display: "Service Number" - comment: "It is the source address, the length is between 3 and 20" \ No newline at end of file + comment: "It is the source address, the length is between 3 and 20" + ticketNotification: + display: "Ticket Notification Settings" + sort: 5 + list: + - name: "enabled" + type: "bool" + value: "true" + access: "rw" + filter: "true;false" + display: "Enabled" + comment: "Is it enabled notifcation ticket with Email interface" + - name: "host" + type: "string" + value: "" + access: "rw" + filter: "" + display: "SMTP Host Server" + comment: "Email SMTP server" + - name: "port" + type: "int" + value: "" + access: "rw" + filter: "0~65535" + display: "Port" + comment: "" + - name: "username" + type: "string" + value: "" + access: "rw" + filter: "" + display: "Username" + comment: "" + - name: "password" + type: "string" + value: "" + access: "rw" + filter: "" + display: "Password" + comment: "" + - name: "tlsSkipVerify" + type: "bool" + value: "true" + access: "rw" + filter: "true;false" + display: "TLS Skip Verify" + comment: "If skip TLS verify (true/false)" + - name: "from" + type: "string" + value: "omc@psap" + access: "rw" + filter: "" + display: "From" + comment: "The sender email address, default is omc@psap" + - name: "to" + type: "string" + value: "" + access: "rw" + filter: "" + display: "To" + comment: "The addition receiver email address, multiple addresses separated by commas" + ticketTimeout: + display: "Ticket Timeout Settings" + sort: 7 + list: + - name: "new" + type: int + value: 60 + access: "rw" + filter: "0~1440" + display: "New Ticket Timeout (min)" + comment: "New ticket timeout in minutes, default is 60 minutes" + - name: "inProgress" + type: int + value: 60 + access: "rw" + filter: "0~1440" + display: "In Progress Timeout (min)" + comment: "In progress ticket timeout in minutes, default is 60 minutes" + - name: "noAnswer1" + type: int + value: 240 + access: "rw" + filter: "0~1440" + display: "No Answer 1 Timeout (min)" + comment: "No answer 1 ticket timeout in minutes, default is 240 minutes" + - name: "noAnswer2" + type: int + value: 480 + access: "rw" + filter: "0~1440" + display: "No Answer 2 Timeout (min)" + comment: "No answer 2 ticket timeout in minutes, default is 480 minutes" + - name: "nearlyTimeout" + type: int + value: 20 + access: "rw" + filter: "0~1440" + display: "Nearly Timeout (min)" + comment: "Nearly timeout in minutes, default is 20 minutes" \ No newline at end of file diff --git a/features/cdr/cdrevent.go b/features/cdr/cdrevent.go index 78e8cca5..f35c02a1 100644 --- a/features/cdr/cdrevent.go +++ b/features/cdr/cdrevent.go @@ -150,14 +150,14 @@ func PostCDREventFrom(w http.ResponseWriter, r *http.Request) { // 添加配置中的原始收件人(如管理员、监控人员等) if len(emailConfig.To) > 0 { - recipients = append(recipients, emailConfig.To...) + recipients = append(recipients, strings.Split(emailConfig.To, ",")...) } // 添加当前工单的座席邮箱 recipients = append(recipients, ticket.AgentEmail) // 去重处理(避免重复邮箱) - emailCopy.To = email.RemoveDuplicateEmails(recipients) + emailCopy.To = strings.Join(email.RemoveDuplicateEmails(recipients), ",") // 设置邮件主题和内容 emailCopy.Subject = "新工单分配通知" diff --git a/features/cm/omc/implement.go b/features/cm/omc/implement.go index 9d7a4300..872be75f 100644 --- a/features/cm/omc/implement.go +++ b/features/cm/omc/implement.go @@ -22,6 +22,13 @@ func (o *ConfigOMC) Query(paramName string) (any, error) { result := config.GetYamlConfig().Alarm.SMSCForward result.Password = PASSWORD_MASK results = append(results, result) + case "ticketNotification": + result := config.GetPsapConfig().Ticket.TicketNotification + result.Password = PASSWORD_MASK + results = append(results, result) + case "ticketTimeout": + result := config.GetPsapConfig().Ticket.TicketTimeout + results = append(results, result) default: return nil, fmt.Errorf("invalid source parameter") } @@ -57,6 +64,26 @@ func (o *ConfigOMC) Modify(paramName string, paramData map[string]any) (any, err fmt.Println("failed to write config yaml file:", err) return results, err } + case "ticketNotification": + param := &(config.GetPsapConfig().Ticket.TicketNotification) + config.UpdatePsapStructFromMap(param, paramData) + result := *param + results = append(results, result) + err := config.WritePsapOriginalConfig(config.PsapYamlConfigInfo.FilePath, paramName, paramData) + if err != nil { + fmt.Println("failed to write config yaml file:", err) + return results, err + } + case "ticketTimeout": + param := &(config.GetPsapConfig().Ticket.TicketTimeout) + config.UpdatePsapStructFromMap(param, paramData) + result := *param + results = append(results, result) + err := config.WritePsapOriginalConfig(config.PsapYamlConfigInfo.FilePath, paramName, paramData) + if err != nil { + fmt.Println("failed to write config yaml file:", err) + return results, err + } default: return nil, fmt.Errorf("invalid source parameter") } diff --git a/features/ue/mf_callback_ticket/service.go b/features/ue/mf_callback_ticket/service.go index e2e3712c..0a21845d 100644 --- a/features/ue/mf_callback_ticket/service.go +++ b/features/ue/mf_callback_ticket/service.go @@ -2,6 +2,7 @@ package mf_callback_ticket import ( "fmt" + "strings" "time" "be.ems/lib/config" @@ -391,14 +392,14 @@ func (s *CallbackTicketService) UpdateTicketToTimeout(ticket *CallbackTicket, or // 添加配置中的原始收件人(如管理员、监控人员等) if len(emailConfig.To) > 0 { - recipients = append(recipients, emailConfig.To...) + recipients = append(recipients, strings.Split(emailConfig.To, ",")...) } // 添加当前工单的座席邮箱 recipients = append(recipients, ticket.AgentEmail) // 去重处理(避免重复邮箱) - emailCopy.To = email.RemoveDuplicateEmails(recipients) + emailCopy.To = strings.Join(email.RemoveDuplicateEmails(recipients), ",") // 设置邮件主题和内容 emailCopy.Subject = "新工单自动重建通知" @@ -484,14 +485,14 @@ func (s *CallbackTicketService) BatchUpdateTimeoutTickets(tickets []CallbackTick // 添加配置中的原始收件人(如管理员、监控人员等) if len(emailConfig.To) > 0 { - recipients = append(recipients, emailConfig.To...) + recipients = append(recipients, strings.Split(emailConfig.To, ",")...) } // 添加当前工单的座席邮箱 recipients = append(recipients, ticket.AgentEmail) // 去重处理(避免重复邮箱) - emailCopy.To = email.RemoveDuplicateEmails(recipients) + emailCopy.To = strings.Join(email.RemoveDuplicateEmails(recipients), ",") // 设置邮件主题和内容 emailCopy.Subject = "新工单自动重建通知" diff --git a/lib/config/config.go b/lib/config/config.go index 66358ba6..464254ee 100644 --- a/lib/config/config.go +++ b/lib/config/config.go @@ -264,7 +264,13 @@ func InitPsapConfig() error { return nil } - _, err := ReadPsapConfig(yamlConfig.PsapConfig.File) + // 确保使用正确的配置文件路径 + configFile := yamlConfig.PsapConfig.File + if configFile == "" { + configFile = "./etc/psap.yaml" // 默认路径 + } + + _, err := ReadPsapConfig(configFile) return err } @@ -320,6 +326,12 @@ func WriteOrignalConfig(configFile string, paramName string, paramData map[strin for k, v := range paramData { // find the first line nearby the paramName for j := i + 1; j < len(lines); j++ { + // ignore comment lines + trimmedLine := strings.TrimSpace(lines[j]) + if strings.HasPrefix(trimmedLine, "#") { + continue + } + if strings.Contains(lines[j], k+":") { index := strings.Index(lines[j], k) // Determine the type of v diff --git a/lib/config/psap.go b/lib/config/psap.go index 837b00a2..f63a4eac 100644 --- a/lib/config/psap.go +++ b/lib/config/psap.go @@ -1,8 +1,11 @@ package config import ( + "bufio" "fmt" "os" + "reflect" + "strings" "be.ems/lib/email" "gopkg.in/yaml.v3" @@ -11,24 +14,27 @@ import ( // PsapConfig PSAP配置结构体 type PsapConfig struct { Ticket struct { - Notification struct { - SMTP email.EmailConfig `yaml:"smtp"` - } `yaml:"notifcation"` // 注意:配置文件中是 "notifcation",保持一致 - Timeout struct { - // 时间单位为分钟 - New int `yaml:"new"` // NEW状态超时时间(分钟) - InProgress int `yaml:"inProgress"` // IN_PROGRESS状态超时时间(分钟) - NoAnswer1 int `yaml:"noAnswer1"` // NO_ANSWER_1状态超时时间(分钟) - NoAnswer2 int `yaml:"noAnswer2"` // NO_ANSWER_2状态超时时间(分钟) - NearlyTimeout int `yaml:"nearlyTimeout"` // 提前提醒时间(分钟) - } `yaml:"timeout"` - } `yaml:"ticket"` + TicketNotification email.EmailConfig `yaml:"ticketNotification" json:"ticketNotification"` // 注意:配置文件中是 "ticketNotification",保持一致 + TicketTimeout TicketTimeout `yaml:"ticketTimeout" json:"ticketTimeout"` // 注意:配置文件中是 "ticketTimeout",保持一致{ + } `yaml:"ticket" json:"ticket"` // PSAP工单相关配置 +} + +type TicketTimeout struct { + New int `yaml:"new" json:"new"` // NEW状态超时时间(分钟) + InProgress int `yaml:"inProgress" json:"inProgress"` // IN_PROGRESS状态超时时间(分钟) + NoAnswer1 int `yaml:"noAnswer1" json:"noAnswer1"` // NO_ANSWER_1状态超时时间(分钟) + NoAnswer2 int `yaml:"noAnswer2" json:"noAnswer2"` // NO_ANSWER_2状态超时时间(分钟) + NearlyTimeout int `yaml:"nearlyTimeout" json:"nearlyTimeout"` // 提前提醒时间(分钟) } var psapConfig *PsapConfig +var PsapYamlConfigInfo YamlConfigFile = YamlConfigFile{} + // ReadPsapConfig 读取PSAP配置文件 func ReadPsapConfig(configFile string) (*PsapConfig, error) { + PsapYamlConfigInfo.FilePath = configFile + yamlFile, err := os.ReadFile(configFile) if err != nil { return nil, fmt.Errorf("read psap config file error: %w", err) @@ -41,9 +47,110 @@ func ReadPsapConfig(configFile string) (*PsapConfig, error) { } psapConfig = &config + // PsapYamlConfigInfo.ConfigLines = config + + // 读取原始文件行 + err = ReadPsapOriginalConfig(configFile) + if err != nil { + return nil, fmt.Errorf("read psap original config error: %w", err) + } + return &config, nil } +// ReadPsapOriginalConfig 读取PSAP原始配置文件行 +func ReadPsapOriginalConfig(configFile string) error { + inputFile, err := os.Open(configFile) + if err != nil { + return fmt.Errorf("failed to open psap config file: %w", err) + } + defer inputFile.Close() + + // 清空之前的内容 + PsapYamlConfigInfo.OrignalLines = nil + + scanner := bufio.NewScanner(inputFile) + for scanner.Scan() { + PsapYamlConfigInfo.OrignalLines = append(PsapYamlConfigInfo.OrignalLines, scanner.Text()) + } + + if err := scanner.Err(); err != nil { + return fmt.Errorf("failed to scan psap config file: %w", err) + } + + return nil +} + +// WritePsapOriginalConfig 写回PSAP原始配置文件 +func WritePsapOriginalConfig(configFile string, paramName string, paramData map[string]any) error { + lines := PsapYamlConfigInfo.OrignalLines + for i, line := range lines { + if strings.Contains(line, paramName) { + for k, v := range paramData { + // 在paramName附近查找对应的字段 + for j := i + 1; j < len(lines); j++ { + // 跳过注释行 + trimmedLine := strings.TrimSpace(lines[j]) + if strings.HasPrefix(trimmedLine, "#") { + continue + } + + if strings.Contains(lines[j], k+":") { + index := strings.Index(lines[j], k) + // 根据v的类型确定格式 + switch v := v.(type) { + case string: + lines[j] = lines[j][:index] + fmt.Sprintf("%s: \"%s\"", k, v) + case bool: + lines[j] = lines[j][:index] + fmt.Sprintf("%s: %t", k, v) + default: + lines[j] = lines[j][:index] + fmt.Sprintf("%s: %v", k, v) + } + break + } + } + } + break + } + } + + // 写回yaml文件 + outputFile, err := os.Create(configFile) + if err != nil { + return fmt.Errorf("failed to create psap config file: %w", err) + } + defer outputFile.Close() + + writer := bufio.NewWriter(outputFile) + for _, line := range PsapYamlConfigInfo.OrignalLines { + writer.WriteString(line + "\n") + } + return writer.Flush() +} + +// UpdatePsapStructFromMap 更新PSAP结构体字段 +func UpdatePsapStructFromMap(s any, updates map[string]any) { + v := reflect.ValueOf(s).Elem() + t := v.Type() + + for key, value := range updates { + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + if field.Tag.Get("json") == key { + structField := v.FieldByName(field.Name) + if structField.IsValid() && structField.CanSet() { + // 转换值为适当的类型 + convertedValue := reflect.ValueOf(value).Convert(structField.Type()) + if structField.Type() == convertedValue.Type() { + structField.Set(convertedValue) + } + } + break + } + } + } +} + // GetPsapConfig 获取PSAP配置 func GetPsapConfig() *PsapConfig { if psapConfig == nil { @@ -64,22 +171,16 @@ func GetSMTPConfig() *email.EmailConfig { if config == nil { return nil } - return &config.Ticket.Notification.SMTP + return &config.Ticket.TicketNotification } // GetTimeoutConfig 获取超时配置 -func GetTimeoutConfig() *struct { - New int `yaml:"new"` - InProgress int `yaml:"inProgress"` - NoAnswer1 int `yaml:"noAnswer1"` - NoAnswer2 int `yaml:"noAnswer2"` - NearlyTimeout int `yaml:"nearlyTimeout"` -} { +func GetTimeoutConfig() *TicketTimeout { config := GetPsapConfig() if config == nil { return nil } - return &config.Ticket.Timeout + return &config.Ticket.TicketTimeout } // 以下是具体的配置获取方法 diff --git a/lib/email/email.go b/lib/email/email.go index 78a23d9b..665cddfa 100644 --- a/lib/email/email.go +++ b/lib/email/email.go @@ -11,16 +11,16 @@ import ( ) type EmailConfig struct { - Enabled bool `yaml:"enabled"` // 是否启用邮件发送 - Host string `yaml:"host"` - Port int `yaml:"port"` - Username string `yaml:"username"` - Password string `yaml:"password"` - TLSSkipVerify bool `yaml:"tlsSkipVerify"` - From string `yaml:"from"` - To []string `yaml:"to"` - Subject string `yaml:"subject"` - Body string `yaml:"body"` + Enabled bool `yaml:"enabled" json:"enabled"` // 是否启用邮件发送 + Host string `yaml:"host" json:"host"` // SMTP服务器地址 + Port int `yaml:"port" json:"port"` // SMTP服务器端口 + Username string `yaml:"username" json:"username"` // SMTP用户名 + Password string `yaml:"password" json:"password"` // SMTP密码 + TLSSkipVerify bool `yaml:"tlsSkipVerify" json:"tlsSkipVerify"` // 是否跳过TLS证书验证 + From string `yaml:"from" json:"from"` // 发件人邮箱地址 + To string `yaml:"to" json:"to"` // 收件人邮箱地址列表 + Subject string `yaml:"subject" json:"subject"` // 邮件主题 + Body string `yaml:"body" json:"body"` // 邮件正文内容 } // 简单邮件发送函数 @@ -31,7 +31,7 @@ func SendEmail(email EmailConfig) error { username := email.Username from := email.From password := email.Password - to := strings.Join(email.To, ",") // 将多个收件人用逗号连接 + to := email.To subject := email.Subject body := email.Body smtpHost := email.Host @@ -48,7 +48,7 @@ func SendEmail(email EmailConfig) error { func SendEmailWithGomail(email EmailConfig) error { m := gomail.NewMessage() m.SetHeader("From", email.From) - m.SetHeader("To", email.To...) + m.SetHeader("To", strings.Split(email.To, ",")...) // 支持多个收件人 m.SetHeader("Subject", email.Subject) m.SetBody("text/plain", email.Body) diff --git a/restagent/etc/psap.yaml b/restagent/etc/psap.yaml index 87a61a54..40157666 100644 --- a/restagent/etc/psap.yaml +++ b/restagent/etc/psap.yaml @@ -1,24 +1,21 @@ ticket: - notifcation: - smtp: - enabled: true + ticketNotification: + enabled: false host: mail.agrandtech.com port: 25 username: smtpext@agrandtech.com # 注意:密码中如果包含特殊字符(如@、#、$等), # 需要使用双引号括起来,避免解析错误 - # 例如:password: "1000smtp@omc!" + # 例如:password: "123456" password: Smtp123@agt tlsSkipVerify: true - from: restagent@localhost - to: - - simonzhangsz@outlook.com # 可以是多个收件人 - - shuzone@126.com - timeout: # 超时设置 + from: omc@psap + to: "simonzhangsz@outlook.com,shuzone@126.com,a@b.com" + ticketTimeout: # 超时设置 # 这些时间单位是分钟 # 注意:这些时间是相对于工单创建时间的 # 例如:new: 60分钟,inProgress: 60分钟 - new: 1 + new: 60 inProgress: 60 noAnswer1: 240 noAnswer2: 480 diff --git a/restagent/etc/restconf.yaml b/restagent/etc/restconf.yaml index d8afb8db..de51dbf1 100644 --- a/restagent/etc/restconf.yaml +++ b/restagent/etc/restconf.yaml @@ -207,4 +207,4 @@ staticFile: # PSAP RESTCONF配置 psapConfig: enabled: true - file: ./etc/psap.yaml \ No newline at end of file + file: ./etc/psap.yaml diff --git a/src/modules/crontask/processor/psap_ticket_monitor/psap_ticket_monitor.go b/src/modules/crontask/processor/psap_ticket_monitor/psap_ticket_monitor.go index 410c0b06..16c993fd 100644 --- a/src/modules/crontask/processor/psap_ticket_monitor/psap_ticket_monitor.go +++ b/src/modules/crontask/processor/psap_ticket_monitor/psap_ticket_monitor.go @@ -209,14 +209,14 @@ func (s *PsapTicketMonitor) handleNearlyTimeoutTickets(status string, timeoutMic // 添加配置中的原始收件人(如管理员、监控人员等) if len(emailConfig.To) > 0 { - recipients = append(recipients, emailConfig.To...) + recipients = append(recipients, strings.Split(emailConfig.To, ",")...) } // 添加当前工单的座席邮箱 recipients = append(recipients, ticket.AgentEmail) // 去重处理(避免重复邮箱) - emailCopy.To = email.RemoveDuplicateEmails(recipients) + emailCopy.To = strings.Join(email.RemoveDuplicateEmails(recipients), ",") // 设置邮件主题和内容 emailCopy.Subject = "工单即将超时提醒"