feat: add message template
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
package message_template_service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"message-nest/models"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type MessageTemplateService struct {
|
||||
ID string
|
||||
Name string
|
||||
Description string
|
||||
TextTemplate string
|
||||
HTMLTemplate string
|
||||
MarkdownTemplate string
|
||||
Placeholders string
|
||||
AtMobiles string
|
||||
AtUserIds string
|
||||
IsAtAll bool
|
||||
Status string
|
||||
Text string
|
||||
|
||||
PageNum int
|
||||
PageSize int
|
||||
}
|
||||
|
||||
// Placeholder 占位符定义
|
||||
type Placeholder struct {
|
||||
Key string `json:"key"`
|
||||
Label string `json:"label"`
|
||||
Default string `json:"default"`
|
||||
}
|
||||
|
||||
// Add 添加消息模板
|
||||
func (s *MessageTemplateService) Add() error {
|
||||
if err := s.validatePlaceholders(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newUUID := models.GenerateTemplateUniqueID()
|
||||
model := models.MessageTemplate{
|
||||
UUIDModel: models.UUIDModel{
|
||||
ID: newUUID,
|
||||
},
|
||||
Name: s.Name,
|
||||
Description: s.Description,
|
||||
TextTemplate: s.TextTemplate,
|
||||
HTMLTemplate: s.HTMLTemplate,
|
||||
MarkdownTemplate: s.MarkdownTemplate,
|
||||
Placeholders: s.Placeholders,
|
||||
AtMobiles: s.AtMobiles,
|
||||
AtUserIds: s.AtUserIds,
|
||||
IsAtAll: s.IsAtAll,
|
||||
Status: s.Status,
|
||||
}
|
||||
|
||||
return model.Add()
|
||||
}
|
||||
|
||||
// Update 更新消息模板
|
||||
func (s *MessageTemplateService) Update() error {
|
||||
if err := s.validatePlaceholders(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
model := models.MessageTemplate{
|
||||
UUIDModel: models.UUIDModel{
|
||||
ID: s.ID,
|
||||
},
|
||||
Name: s.Name,
|
||||
Description: s.Description,
|
||||
TextTemplate: s.TextTemplate,
|
||||
HTMLTemplate: s.HTMLTemplate,
|
||||
MarkdownTemplate: s.MarkdownTemplate,
|
||||
Placeholders: s.Placeholders,
|
||||
AtMobiles: s.AtMobiles,
|
||||
AtUserIds: s.AtUserIds,
|
||||
IsAtAll: s.IsAtAll,
|
||||
Status: s.Status,
|
||||
}
|
||||
|
||||
return model.Update()
|
||||
}
|
||||
|
||||
// Delete 删除消息模板
|
||||
func (s *MessageTemplateService) Delete() error {
|
||||
model := models.MessageTemplate{
|
||||
UUIDModel: models.UUIDModel{
|
||||
ID: s.ID,
|
||||
},
|
||||
}
|
||||
return model.Delete()
|
||||
}
|
||||
|
||||
// Get 获取单个消息模板
|
||||
func (s *MessageTemplateService) Get() (*models.MessageTemplateResult, error) {
|
||||
return models.GetMessageTemplateByID(s.ID)
|
||||
}
|
||||
|
||||
// GetAll 获取消息模板列表
|
||||
func (s *MessageTemplateService) GetAll() ([]models.MessageTemplateResult, error) {
|
||||
templates, err := models.GetMessageTemplates(s.PageNum, s.PageSize, s.Text, s.getMaps())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return templates, nil
|
||||
}
|
||||
|
||||
// Count 获取消息模板总数
|
||||
func (s *MessageTemplateService) Count() (int64, error) {
|
||||
return models.GetMessageTemplatesTotal(s.Text, s.getMaps())
|
||||
}
|
||||
|
||||
// ExistByID 检查模板是否存在
|
||||
func (s *MessageTemplateService) ExistByID() (bool, error) {
|
||||
return models.ExistMessageTemplateByID(s.ID)
|
||||
}
|
||||
|
||||
// RenderTemplate 渲染模板(替换占位符)
|
||||
func (s *MessageTemplateService) RenderTemplate(templateContent string, params map[string]string) string {
|
||||
result := templateContent
|
||||
|
||||
for key, value := range params {
|
||||
placeholder := "{{" + key + "}}"
|
||||
result = strings.ReplaceAll(result, placeholder, value)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// PreviewTemplate 预览模板效果
|
||||
func (s *MessageTemplateService) PreviewTemplate(params map[string]string) (map[string]string, error) {
|
||||
template, err := s.Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(map[string]string)
|
||||
|
||||
if template.TextTemplate != "" {
|
||||
result["text"] = s.RenderTemplate(template.TextTemplate, params)
|
||||
}
|
||||
|
||||
if template.HTMLTemplate != "" {
|
||||
result["html"] = s.RenderTemplate(template.HTMLTemplate, params)
|
||||
}
|
||||
|
||||
if template.MarkdownTemplate != "" {
|
||||
result["markdown"] = s.RenderTemplate(template.MarkdownTemplate, params)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// validatePlaceholders 验证占位符格式
|
||||
func (s *MessageTemplateService) validatePlaceholders() error {
|
||||
if s.Placeholders == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var placeholders []Placeholder
|
||||
if err := json.Unmarshal([]byte(s.Placeholders), &placeholders); err != nil {
|
||||
return errors.New("占位符格式错误,必须是有效的JSON数组")
|
||||
}
|
||||
|
||||
for _, p := range placeholders {
|
||||
if p.Key == "" {
|
||||
return errors.New("占位符的key不能为空")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getMaps 获取查询条件
|
||||
func (s *MessageTemplateService) getMaps() map[string]interface{} {
|
||||
maps := make(map[string]interface{})
|
||||
|
||||
if s.Status != "" {
|
||||
maps["status"] = s.Status
|
||||
}
|
||||
|
||||
return maps
|
||||
}
|
||||
@@ -18,6 +18,12 @@ const (
|
||||
SendFail = 0
|
||||
)
|
||||
|
||||
// 发送模式类型
|
||||
const (
|
||||
SendModeTask = "task" // 传统任务模式
|
||||
SendModeTemplate = "template" // 模板模式
|
||||
)
|
||||
|
||||
func errStrIsSuccess(errStr string) int {
|
||||
if errStr == "" {
|
||||
return SendSuccess
|
||||
@@ -26,13 +32,16 @@ func errStrIsSuccess(errStr string) int {
|
||||
}
|
||||
|
||||
type SendMessageService struct {
|
||||
TaskID string
|
||||
Title string
|
||||
Text string
|
||||
HTML string
|
||||
URL string
|
||||
MarkDown string
|
||||
CallerIp string
|
||||
SendMode string // 发送模式:task(任务模式) 或 template(模板模式)
|
||||
TaskID string // 任务ID(任务模式)或模板ID(模板模式,用于日志记录)
|
||||
TemplateID string // 模板ID(仅模板模式使用)
|
||||
Name string // 任务或模板名称(用于日志记录)
|
||||
Title string
|
||||
Text string
|
||||
HTML string
|
||||
URL string
|
||||
MarkDown string
|
||||
CallerIp string
|
||||
|
||||
// @提及相关字段
|
||||
AtMobiles []string
|
||||
@@ -82,31 +91,88 @@ func (sm *SendMessageService) AsyncSend(task models.TaskIns) {
|
||||
}
|
||||
|
||||
// SendPreCheck 发送前数据准备和预检查
|
||||
// 支持两种模式:
|
||||
// 1. SendModeTask:传统任务模式,使用 TaskID 查询任务和实例
|
||||
// 2. SendModeTemplate:模板模式,使用 TemplateID 查询模板关联的实例
|
||||
func (sm *SendMessageService) SendPreCheck() (models.TaskIns, error) {
|
||||
errStr := ""
|
||||
entry := logrus.WithFields(logrus.Fields{
|
||||
"prefix": "[Message PreChecK]",
|
||||
})
|
||||
sendTaskService := send_task_service.SendTaskService{
|
||||
ID: sm.TaskID,
|
||||
}
|
||||
task, err := sendTaskService.GetTaskWithIns()
|
||||
if err != nil {
|
||||
errStr = fmt.Sprintf("任务[%s]查询失败!", sm.TaskID)
|
||||
|
||||
var task models.TaskIns
|
||||
|
||||
switch sm.SendMode {
|
||||
case SendModeTemplate:
|
||||
// 模板模式:使用模板ID获取实例
|
||||
if sm.TemplateID == "" {
|
||||
errStr = "模板模式下 TemplateID 不能为空"
|
||||
entry.Errorf(errStr)
|
||||
return task, errors.New(errStr)
|
||||
}
|
||||
|
||||
// 获取模板关联的实例列表
|
||||
insList, err := models.GetTemplateInsList(sm.TemplateID)
|
||||
if err != nil {
|
||||
errStr = fmt.Sprintf("模板[%s]实例查询失败:%s", sm.TemplateID, err)
|
||||
entry.Errorf(errStr)
|
||||
return task, errors.New(errStr)
|
||||
}
|
||||
if len(insList) == 0 {
|
||||
errStr = fmt.Sprintf("模板[%s]没有关联任何实例!", sm.TemplateID)
|
||||
entry.Errorf(errStr)
|
||||
return task, errors.New(errStr)
|
||||
}
|
||||
|
||||
// 构造虚拟任务对象(用于兼容现有发送逻辑)
|
||||
// 将模板ID作为TaskID使用,便于日志记录
|
||||
task.ID = sm.TaskID // 使用传入的TaskID(实际是模板ID)
|
||||
task.InsData = make([]models.SendTasksInsRes, 0, len(insList))
|
||||
for _, ins := range insList {
|
||||
task.InsData = append(task.InsData, ins)
|
||||
}
|
||||
entry.Infof("模板[%s]加载了 %d 个实例", sm.TemplateID, len(insList))
|
||||
return task, nil
|
||||
|
||||
case SendModeTask:
|
||||
// 传统任务模式:使用任务ID查询
|
||||
if sm.TaskID == "" {
|
||||
errStr = "任务模式下 TaskID 不能为空"
|
||||
entry.Errorf(errStr)
|
||||
return task, errors.New(errStr)
|
||||
}
|
||||
|
||||
sendTaskService := send_task_service.SendTaskService{
|
||||
ID: sm.TaskID,
|
||||
}
|
||||
task, err := sendTaskService.GetTaskWithIns()
|
||||
if err != nil {
|
||||
errStr = fmt.Sprintf("任务[%s]查询失败!", sm.TaskID)
|
||||
entry.Errorf(errStr)
|
||||
return task, errors.New(errStr)
|
||||
}
|
||||
if task.ID == "" {
|
||||
errStr = fmt.Sprintf("任务[%s]不存在!", sm.TaskID)
|
||||
entry.Errorf(errStr)
|
||||
return task, errors.New(errStr)
|
||||
}
|
||||
if len(task.InsData) == 0 {
|
||||
errStr = fmt.Sprintf("任务[%s]没有关联任何实例!!", sm.TaskID)
|
||||
entry.Errorf(errStr)
|
||||
return task, errors.New(errStr)
|
||||
}
|
||||
// 设置任务名称用于日志记录
|
||||
if sm.Name == "" {
|
||||
sm.Name = task.Name
|
||||
}
|
||||
return task, nil
|
||||
|
||||
default:
|
||||
// SendMode 未设置或无效
|
||||
errStr = fmt.Sprintf("SendMode 未设置或无效: %s,必须是 '%s' 或 '%s'", sm.SendMode, SendModeTask, SendModeTemplate)
|
||||
entry.Errorf(errStr)
|
||||
return task, errors.New(errStr)
|
||||
}
|
||||
if task.ID == "" {
|
||||
errStr = fmt.Sprintf("任务[%s]不存在!", sm.TaskID)
|
||||
entry.Errorf(errStr)
|
||||
return task, errors.New(errStr)
|
||||
}
|
||||
if len(task.InsData) == 0 {
|
||||
errStr = fmt.Sprintf("任务[%s]没有关联任何实例!!", sm.TaskID)
|
||||
entry.Errorf(errStr)
|
||||
return task, errors.New(errStr)
|
||||
}
|
||||
return task, nil
|
||||
}
|
||||
|
||||
// Send 发送一个消息任务的所有实例
|
||||
@@ -143,13 +209,6 @@ func (sm *SendMessageService) Send(task models.TaskIns) (string, error) {
|
||||
sm.LogsAndStatusMark(fmt.Sprintf("实例类型: %s + %s", ins.WayType, ins.ContentType), sm.Status)
|
||||
sm.LogsAndStatusMark(fmt.Sprintf("实例配置: %s", ins.Config), sm.Status)
|
||||
|
||||
// 发送内容校验绑定
|
||||
typeC, content := sm.GetSendMsg(ins.SendTasksIns)
|
||||
if content == "" {
|
||||
sm.LogsAndStatusMark(fmt.Sprintf("发送内容为空,设置的类型: %s,实际检测的类型: %s", ins.SendTasksIns.ContentType, typeC), SendFail)
|
||||
continue
|
||||
}
|
||||
|
||||
// 发送渠道的校验
|
||||
errStr, msgObj := wayService.ValidateDiffWay()
|
||||
if errStr != "" {
|
||||
@@ -165,16 +224,33 @@ func (sm *SendMessageService) Send(task models.TaskIns) (string, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 构建统一消息内容(支持@功能)
|
||||
unifiedContent := &unified.UnifiedMessageContent{
|
||||
Title: sm.Title,
|
||||
Text: sm.Text,
|
||||
HTML: sm.HTML,
|
||||
Markdown: sm.MarkDown,
|
||||
URL: sm.URL,
|
||||
AtMobiles: sm.AtMobiles,
|
||||
AtUserIds: sm.AtUserIds,
|
||||
AtAll: sm.AtAll,
|
||||
// 根据发送模式构建消息内容
|
||||
var unifiedContent *unified.UnifiedMessageContent
|
||||
if sm.SendMode == SendModeTemplate {
|
||||
// 模板模式:根据实例的 ContentType 精确发送对应类型的内容
|
||||
unifiedContent = sm.BuildTemplateContent(ins.SendTasksIns)
|
||||
if unifiedContent == nil {
|
||||
sm.LogsAndStatusMark(fmt.Sprintf("模板内容为空,实例类型: %s", ins.ContentType), SendFail)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// 任务模式:使用现有逻辑(支持内容类型回退)
|
||||
typeC, content := sm.GetSendMsg(ins.SendTasksIns)
|
||||
if content == "" {
|
||||
sm.LogsAndStatusMark(fmt.Sprintf("发送内容为空,设置的类型: %s,实际检测的类型: %s", ins.SendTasksIns.ContentType, typeC), SendFail)
|
||||
continue
|
||||
}
|
||||
// 构建统一消息内容(支持@功能)
|
||||
unifiedContent = &unified.UnifiedMessageContent{
|
||||
Title: sm.Title,
|
||||
Text: sm.Text,
|
||||
HTML: sm.HTML,
|
||||
Markdown: sm.MarkDown,
|
||||
URL: sm.URL,
|
||||
AtMobiles: sm.AtMobiles,
|
||||
AtUserIds: sm.AtUserIds,
|
||||
AtAll: sm.AtAll,
|
||||
}
|
||||
}
|
||||
|
||||
// 使用 SendUnified 方法(自动格式转换和@功能支持)
|
||||
@@ -216,9 +292,17 @@ func (sm *SendMessageService) AppendSendContent() {
|
||||
|
||||
// RecordSendLog 记录发送日志
|
||||
func (sm *SendMessageService) RecordSendLog() {
|
||||
// 确定日志类型
|
||||
logType := "task"
|
||||
if sm.SendMode == SendModeTemplate {
|
||||
logType = "template"
|
||||
}
|
||||
|
||||
log := models.SendTasksLogs{
|
||||
Log: strings.Join(sm.LogOutput, "\n"),
|
||||
TaskID: sm.TaskID,
|
||||
Type: logType,
|
||||
Name: sm.Name,
|
||||
Status: &sm.Status,
|
||||
CallerIp: sm.CallerIp,
|
||||
}
|
||||
@@ -237,7 +321,52 @@ func (sm *SendMessageService) TransError(err string) string {
|
||||
}
|
||||
}
|
||||
|
||||
// GetSendMsg 获取对应消息内容
|
||||
// BuildTemplateContent 构建模板模式的消息内容
|
||||
// 模板模式:根据实例的 ContentType 精确匹配对应类型的内容,只传递该类型的内容
|
||||
func (sm *SendMessageService) BuildTemplateContent(ins models.SendTasksIns) *unified.UnifiedMessageContent {
|
||||
contentType := strings.ToLower(ins.ContentType)
|
||||
|
||||
// 内容类型映射表
|
||||
contentMap := map[string]string{
|
||||
unified.FormatTypeText: sm.Text,
|
||||
unified.FormatTypeHTML: sm.HTML,
|
||||
unified.FormatTypeMarkdown: sm.MarkDown,
|
||||
}
|
||||
|
||||
// 检查内容是否存在
|
||||
contentValue, exists := contentMap[contentType]
|
||||
if !exists {
|
||||
logrus.Warnf("模板模式:未知的内容类型 %s", ins.ContentType)
|
||||
return nil
|
||||
}
|
||||
if contentValue == "" {
|
||||
logrus.Warnf("模板模式:实例要求的 %s 类型内容为空", contentType)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 构建消息内容,只填充实例要求的类型
|
||||
content := &unified.UnifiedMessageContent{
|
||||
Title: sm.Title,
|
||||
URL: sm.URL,
|
||||
AtMobiles: sm.AtMobiles,
|
||||
AtUserIds: sm.AtUserIds,
|
||||
AtAll: sm.AtAll,
|
||||
}
|
||||
|
||||
// 根据类型填充对应字段
|
||||
switch contentType {
|
||||
case unified.FormatTypeText:
|
||||
content.Text = contentValue
|
||||
case unified.FormatTypeHTML:
|
||||
content.HTML = contentValue
|
||||
case unified.FormatTypeMarkdown:
|
||||
content.Markdown = contentValue
|
||||
}
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
// GetSendMsg 获取对应消息内容(任务模式使用)
|
||||
// 先根据实例设置的类型取,取不到或者取到的是空,则使用text发送
|
||||
func (sm *SendMessageService) GetSendMsg(ins models.SendTasksIns) (string, string) {
|
||||
data := map[string]string{}
|
||||
|
||||
Reference in New Issue
Block a user