Files
Message-Push-Nest/service/send_message_service/send_message.go
T
2025-12-06 00:28:18 +08:00

390 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package send_message_service
import (
"errors"
"fmt"
"message-nest/models"
"message-nest/pkg/constant"
"message-nest/service/send_message_service/unified"
"message-nest/service/send_task_service"
"message-nest/service/send_way_service"
"strings"
"github.com/sirupsen/logrus"
)
const (
SendSuccess = 1
SendFail = 0
)
// 发送模式类型
const (
SendModeTask = "task" // 传统任务模式
SendModeTemplate = "template" // 模板模式
)
func errStrIsSuccess(errStr string) int {
if errStr == "" {
return SendSuccess
}
return SendFail
}
type SendMessageService struct {
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
AtUserIds []string
AtAll bool
Status int
LogOutput []string
DefaultLogger *logrus.Entry
}
// LogsAndStatusMark 记录执行的日志和状态标记
func (sm *SendMessageService) LogsAndStatusMark(errStr string, status int) {
sm.LogOutput = append(sm.LogOutput, errStr)
if status == SendFail {
sm.Status = SendFail
}
sm.DefaultLogger.Infof("%s, 状态:%d", strings.Trim(errStr, "\n"), status)
}
// AsyncSend 异步发送一个消息任务的所有实例
func (sm *SendMessageService) AsyncSend(task models.TaskIns) {
defer func() {
if r := recover(); r != nil {
logrus.Error("AsyncSend: Recovered from panic:", r)
}
}()
// 限制并发异步发送数量
constant.MaxSendTaskSemaphoreChan <- ""
defer func() {
<-constant.MaxSendTaskSemaphoreChan
}()
go func() {
entry := logrus.WithFields(logrus.Fields{
"prefix": "[Send Goroutine]",
})
_, err := sm.Send(task)
if err != nil {
entry.Errorf("任务[%s][%s]发送错误: %s", sm.TaskID, sm.Title, err)
} else {
entry.Infof("完成任务[%s][%s]发送", sm.TaskID, sm.Title)
}
}()
}
// SendPreCheck 发送前数据准备和预检查
// 支持两种模式:
// 1. SendModeTask:传统任务模式,使用 TaskID 查询任务和实例
// 2. SendModeTemplate:模板模式,使用 TemplateID 查询模板关联的实例
func (sm *SendMessageService) SendPreCheck() (models.TaskIns, error) {
errStr := ""
entry := logrus.WithFields(logrus.Fields{
"prefix": "[Message PreChecK]",
})
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)
}
}
// Send 发送一个消息任务的所有实例
func (sm *SendMessageService) Send(task models.TaskIns) (string, error) {
sm.Status = SendSuccess
sm.LogsAndStatusMark(fmt.Sprintf("发送标题《%s》 \n", sm.Title), sm.Status)
sm.LogsAndStatusMark(fmt.Sprintf("开始任务[%s]的发送", sm.TaskID), sm.Status)
for idx, ins := range task.InsData {
way, err := models.GetWayByID(ins.WayID)
if err != nil {
errStr := fmt.Sprintf("渠道[%s]信息不存在!跳过这个实例的发送", ins.WayID)
sm.LogsAndStatusMark(errStr, SendFail)
continue
}
// 暂停了实例的发送
if ins.Enable != 1 {
//sm.LogsAndStatusMark("该实例发送已经被暂停,跳过发送!\n", sm.Status)
continue
}
wayService := send_way_service.SendWay{
ID: fmt.Sprintf("%s", way.ID),
Name: way.Name,
Auth: way.Auth,
Type: way.Type,
}
sm.LogsAndStatusMark(fmt.Sprintf(">> 实例 %d", idx+1), sm.Status)
sm.LogsAndStatusMark(fmt.Sprintf("开始发送,实例: %s", ins.WayID), sm.Status)
sm.LogsAndStatusMark(fmt.Sprintf("实例渠道名: %s", way.Name), sm.Status)
sm.LogsAndStatusMark(fmt.Sprintf("实例类型: %s + %s", ins.WayType, ins.ContentType), sm.Status)
sm.LogsAndStatusMark(fmt.Sprintf("实例配置: %s", ins.Config), sm.Status)
// 发送渠道的校验
errStr, msgObj := wayService.ValidateDiffWay()
if errStr != "" {
sm.LogsAndStatusMark(fmt.Sprintf("实例渠道认证校验失败: %s", errStr), SendFail)
continue
}
// 使用新的Channel架构发送消息
channelRegistry := unified.GetGlobalChannelRegistry()
channel, ok := channelRegistry.GetChannel(way.Type)
if !ok {
sm.LogsAndStatusMark(fmt.Sprintf("发送失败:未知渠道类型 %s 的发信实例: %s\n", way.Type, ins.ID), SendFail)
continue
}
// 根据发送模式构建消息内容
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 方法(自动格式转换和@功能支持)
res, errMsg := channel.SendUnified(msgObj, ins.SendTasksIns, unifiedContent)
if res != "" {
sm.LogsAndStatusMark(fmt.Sprintf("返回内容:%s", res), sm.Status)
} else {
sm.LogsAndStatusMark(sm.TransError(errMsg), errStrIsSuccess(errMsg))
}
}
// 追加记录发送内容
sm.AppendSendContent()
// 日志写到数据库
sm.RecordSendLog()
totalOutputLog := strings.Join(sm.LogOutput, "\n")
if sm.Status == SendSuccess {
return totalOutputLog, nil
} else {
return totalOutputLog, errors.New("发送过程中有失败,请检查详细日志")
}
}
// AppendSendContent 添加发送内容
func (sm *SendMessageService) AppendSendContent() {
sm.LogOutput = append(sm.LogOutput, fmt.Sprintf(">> 发送的内容:"))
if sm.Text != "" {
sm.LogOutput = append(sm.LogOutput, fmt.Sprintf("Text: %s \n", sm.Text))
}
if sm.HTML != "" {
sm.LogOutput = append(sm.LogOutput, fmt.Sprintf("HTML: %s \n", sm.HTML))
}
if sm.MarkDown != "" {
sm.LogOutput = append(sm.LogOutput, fmt.Sprintf("MarkDown: %s \n", sm.MarkDown))
}
}
// 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,
}
err := log.Add()
if err != nil {
sm.DefaultLogger.Errorf("添加日志失败!原因是:%s", err)
}
}
// TransError 转化错误
func (sm *SendMessageService) TransError(err string) string {
if err == "" {
return "发送成功!\n"
} else {
return fmt.Sprintf("发送失败:%s\n", err)
}
}
// 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{}
data[unified.FormatTypeText] = sm.Text
data[unified.FormatTypeHTML] = sm.HTML
data[unified.FormatTypeMarkdown] = sm.MarkDown
content, ok := data[strings.ToLower(ins.ContentType)]
if !ok || len(content) == 0 {
content, ok := data[unified.FormatTypeText]
if !ok {
logrus.Error("text节点数据为空!")
return unified.FormatTypeText, ""
} else {
logrus.Error(fmt.Sprintf("没有找到%s对应的消息,使用text消息替代!", ins.ContentType))
return unified.FormatTypeText, content
}
} else {
return strings.ToLower(ins.ContentType), content
}
}