feat: send recode adjust

This commit is contained in:
engigu
2025-12-04 21:16:32 +08:00
parent 0639f5ec6d
commit ff194daef2
22 changed files with 743 additions and 424 deletions
@@ -1,164 +0,0 @@
package send_message_service
import (
"message-nest/models"
"message-nest/service/send_way_service"
)
// 消息类型常量定义
const (
MessageTypeEmail = "Email"
MessageTypeDtalk = "Dtalk"
MessageTypeQyWeiXin = "QyWeiXin"
MessageTypeCustom = "Custom"
MessageTypeWeChatOFAccount = "WeChatOFAccount"
MessageTypeMessageNest = "MessageNest"
)
// MessageHandler 消息处理器接口
type MessageHandler interface {
// Send 发送消息
// 返回:响应内容,错误信息
Send(msgObj interface{}, ins models.SendTasksIns, typeC string, title string, content string, url string) (string, string)
// GetType 返回该处理器支持的消息类型字符串
GetType() string
}
// MessageHandlerRegistry 消息处理器注册表
type MessageHandlerRegistry struct {
handlers map[string]MessageHandler
}
// NewMessageHandlerRegistry 创建新的消息处理器注册表
func NewMessageHandlerRegistry() *MessageHandlerRegistry {
return &MessageHandlerRegistry{
handlers: make(map[string]MessageHandler),
}
}
// Register 注册消息处理器
func (r *MessageHandlerRegistry) Register(handler MessageHandler) {
r.handlers[handler.GetType()] = handler
}
// GetHandler 根据类型字符串获取对应的处理器
func (r *MessageHandlerRegistry) GetHandler(wayType string) (MessageHandler, bool) {
handler, ok := r.handlers[wayType]
return handler, ok
}
// 全局消息处理器注册表
var globalRegistry = NewMessageHandlerRegistry()
// init 初始化时注册所有处理器
func init() {
globalRegistry.Register(&EmailHandler{})
globalRegistry.Register(&DtalkHandler{})
globalRegistry.Register(&QyWeiXinHandler{})
globalRegistry.Register(&CustomHandler{})
globalRegistry.Register(&WeChatOfAccountHandler{})
globalRegistry.Register(&HostMessageHandler{})
}
// GetGlobalRegistry 获取全局注册表
func GetGlobalRegistry() *MessageHandlerRegistry {
return globalRegistry
}
// EmailHandler 邮箱消息处理器
type EmailHandler struct{}
func (h *EmailHandler) GetType() string {
return MessageTypeEmail
}
func (h *EmailHandler) Send(msgObj interface{}, ins models.SendTasksIns, typeC string, title string, content string, url string) (string, string) {
auth, ok := msgObj.(send_way_service.WayDetailEmail)
if !ok {
return "", "类型转换失败"
}
es := EmailService{}
errMsg := es.SendTaskEmail(auth, ins, typeC, title, content)
return "", errMsg
}
// DtalkHandler 钉钉消息处理器
type DtalkHandler struct{}
func (h *DtalkHandler) GetType() string {
return MessageTypeDtalk
}
func (h *DtalkHandler) Send(msgObj interface{}, ins models.SendTasksIns, typeC string, title string, content string, url string) (string, string) {
auth, ok := msgObj.(send_way_service.WayDetailDTalk)
if !ok {
return "", "类型转换失败"
}
es := DtalkService{}
return es.SendDtalkMessage(auth, ins, typeC, title, content)
}
// QyWeiXinHandler 企业微信消息处理器
type QyWeiXinHandler struct{}
func (h *QyWeiXinHandler) GetType() string {
return MessageTypeQyWeiXin
}
func (h *QyWeiXinHandler) Send(msgObj interface{}, ins models.SendTasksIns, typeC string, title string, content string, url string) (string, string) {
auth, ok := msgObj.(send_way_service.WayDetailQyWeiXin)
if !ok {
return "", "类型转换失败"
}
es := QyWeiXinService{}
return es.SendQyWeiXinMessage(auth, ins, typeC, title, content)
}
// CustomHandler 自定义webhook消息处理器
type CustomHandler struct{}
func (h *CustomHandler) GetType() string {
return MessageTypeCustom
}
func (h *CustomHandler) Send(msgObj interface{}, ins models.SendTasksIns, typeC string, title string, content string, url string) (string, string) {
auth, ok := msgObj.(send_way_service.WayDetailCustom)
if !ok {
return "", "类型转换失败"
}
cs := CustomService{}
return cs.SendCustomMessage(auth, ins, typeC, title, content)
}
// WeChatOfAccountHandler 微信公众号消息处理器
type WeChatOfAccountHandler struct{}
func (h *WeChatOfAccountHandler) GetType() string {
return MessageTypeWeChatOFAccount
}
func (h *WeChatOfAccountHandler) Send(msgObj interface{}, ins models.SendTasksIns, typeC string, title string, content string, url string) (string, string) {
auth, ok := msgObj.(send_way_service.WeChatOFAccount)
if !ok {
return "", "类型转换失败"
}
cs := WeChatOfAccountService{}
return cs.SendWeChatOfAccountMessage(auth, ins, typeC, title, content, url)
}
// HostMessageHandler 托管消息处理器
type HostMessageHandler struct{}
func (h *HostMessageHandler) GetType() string {
return MessageTypeMessageNest
}
func (h *HostMessageHandler) Send(msgObj interface{}, ins models.SendTasksIns, typeC string, title string, content string, url string) (string, string) {
auth, ok := msgObj.(send_way_service.MessageNest)
if !ok {
return "", "类型转换失败"
}
cs := HostMessageService{}
return cs.SendHostMessage(auth, ins, typeC, title, content)
}
@@ -1,29 +0,0 @@
package send_message_service
import (
"encoding/json"
"fmt"
"message-nest/models"
"message-nest/pkg/message"
"message-nest/service/send_way_service"
"strings"
)
type CustomService struct {
}
// SendCustomMessage 执行发送钉钉
func (s *CustomService) SendCustomMessage(auth send_way_service.WayDetailCustom, ins models.SendTasksIns, typeC string, title string, content string) (string, string) {
errMsg := ""
cli := message.CustomWebhook{}
data, _ := json.Marshal(content)
dataStr := string(data)
dataStr = strings.Trim(dataStr, "\"")
bodyStr := strings.Replace(auth.Body, "TEXT", dataStr, -1)
res, err := cli.Request(auth.Webhook, bodyStr)
if err != nil {
errMsg = fmt.Sprintf("发送失败:%s", err)
}
return string(res), errMsg
}
@@ -1,47 +0,0 @@
package send_message_service
import (
"fmt"
"message-nest/models"
"message-nest/pkg/message"
"message-nest/service/send_ins_service"
"message-nest/service/send_way_service"
)
type DtalkService struct {
}
// SendDtalkMessage 执行发送钉钉
func (s *DtalkService) SendDtalkMessage(auth send_way_service.WayDetailDTalk, ins models.SendTasksIns, typeC string, title string, content string) (string, string) {
insService := send_ins_service.SendTaskInsService{}
errStr, c := insService.ValidateDiffIns(ins)
if errStr != "" {
return errStr, ""
}
_, ok := c.(models.InsDtalkConfig)
if !ok {
return "钉钉config校验失败", ""
}
errMsg := ""
var res []byte
var err error
cli := message.Dtalk{
AccessToken: auth.AccessToken,
Secret: auth.Secret,
}
if typeC == "text" {
res, err = cli.SendMessageText(content)
if err != nil {
errMsg = fmt.Sprintf("发送失败:%s", ins.ContentType)
}
} else if typeC == "markdown" {
res, err = cli.SendMessageMarkdown(title, content)
if err != nil {
errMsg = fmt.Sprintf("发送失败:%s", ins.ContentType)
}
} else {
errMsg = fmt.Sprintf("未知的钉钉发送内容类型:%s", ins.ContentType)
}
return string(res), errMsg
}
@@ -1,37 +0,0 @@
package send_message_service
import (
"fmt"
"message-nest/models"
"message-nest/pkg/message"
"message-nest/service/send_ins_service"
"message-nest/service/send_way_service"
)
type EmailService struct {
}
// SendTaskEmail 执行发送邮件
func (s *EmailService) SendTaskEmail(auth send_way_service.WayDetailEmail, ins models.SendTasksIns, typeC string, title string, content string) string {
insService := send_ins_service.SendTaskInsService{}
errStr, c := insService.ValidateDiffIns(ins)
if errStr != "" {
return errStr
}
config, ok := c.(models.InsEmailConfig)
if !ok {
return "邮箱config校验失败"
}
var emailer message.EmailMessage
errMsg := ""
emailer.Init(auth.Server, auth.Port, auth.Account, auth.Passwd)
if typeC == "text" {
errMsg = emailer.SendTextMessage(config.ToAccount, title, content)
} else if typeC == "html" {
errMsg = emailer.SendHtmlMessage(config.ToAccount, title, content)
} else {
errMsg = fmt.Sprintf("未知的邮件发送内容类型:%s", ins.ContentType)
}
return errMsg
}
@@ -1,36 +0,0 @@
package send_message_service
import (
"message-nest/models"
"message-nest/service/hosted_message_service"
"message-nest/service/send_way_service"
)
type HostMessageService struct {
}
// SendHostMessage 执行托管消息记录
func (s *HostMessageService) SendHostMessage(
auth send_way_service.MessageNest,
ins models.SendTasksIns,
typeC string,
title string,
content string) (string, string) {
errMsg := ""
var res string
var err error
messageService := hosted_message_service.HostMessageService{
Title: title,
Content: content,
Type: typeC,
}
err = messageService.Add()
if err != nil {
errMsg = err.Error()
res = "托管消息创建失败!"
} else {
res = "托管消息创建成功!"
}
return string(res), errMsg
}
+31 -11
View File
@@ -5,6 +5,7 @@ import (
"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"
@@ -33,6 +34,11 @@ type SendMessageService struct {
MarkDown string
CallerIp string
// @提及相关字段
AtMobiles []string
AtUserIds []string
AtAll bool
Status int
LogOutput []string
@@ -151,19 +157,33 @@ func (sm *SendMessageService) Send(task models.TaskIns) (string, error) {
continue
}
// 使用注册的处理器发送消息
registry := GetGlobalRegistry()
handler, ok := registry.GetHandler(way.Type)
// 使用新的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
}
res, errMsg := handler.Send(msgObj, ins.SendTasksIns, typeC, sm.Title, content, sm.URL)
// 构建统一消息内容(支持@功能)
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.LogsAndStatusMark(sm.TransError(errMsg), errStrIsSuccess(errMsg))
}
@@ -221,18 +241,18 @@ func (sm *SendMessageService) TransError(err string) string {
// 先根据实例设置的类型取,取不到或者取到的是空,则使用text发送
func (sm *SendMessageService) GetSendMsg(ins models.SendTasksIns) (string, string) {
data := map[string]string{}
data["text"] = sm.Text
data["html"] = sm.HTML
data["markdown"] = sm.MarkDown
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["text"]
content, ok := data[unified.FormatTypeText]
if !ok {
logrus.Error("text节点数据为空!")
return "text", ""
return unified.FormatTypeText, ""
} else {
logrus.Error(fmt.Sprintf("没有找到%s对应的消息,使用text消息替代!", ins.ContentType))
return "text", content
return unified.FormatTypeText, content
}
} else {
return strings.ToLower(ins.ContentType), content
@@ -1,46 +0,0 @@
package send_message_service
import (
"fmt"
"message-nest/models"
"message-nest/pkg/message"
"message-nest/service/send_ins_service"
"message-nest/service/send_way_service"
)
type QyWeiXinService struct {
}
// SendDtalkMessage 执行发送钉钉
func (s *QyWeiXinService) SendQyWeiXinMessage(auth send_way_service.WayDetailQyWeiXin, ins models.SendTasksIns, typeC string, title string, content string) (string, string) {
insService := send_ins_service.SendTaskInsService{}
errStr, c := insService.ValidateDiffIns(ins)
if errStr != "" {
return errStr, ""
}
_, ok := c.(models.InsQyWeiXinConfig)
if !ok {
return "企业微信config校验失败", ""
}
errMsg := ""
var res []byte
var err error
cli := message.QyWeiXin{
AccessToken: auth.AccessToken,
}
if typeC == "text" {
res, err = cli.SendMessageText(content)
if err != nil {
errMsg = fmt.Sprintf("发送失败:%s", ins.ContentType)
}
} else if typeC == "markdown" {
res, err = cli.SendMessageMarkdown(title, content)
if err != nil {
errMsg = fmt.Sprintf("发送失败:%s", ins.ContentType)
}
} else {
errMsg = fmt.Sprintf("未知的企业微信发送内容类型:%s", ins.ContentType)
}
return string(res), errMsg
}
@@ -1,42 +0,0 @@
package send_message_service
import (
"fmt"
"message-nest/models"
"message-nest/pkg/message"
"message-nest/service/send_ins_service"
"message-nest/service/send_way_service"
)
type WeChatOfAccountService struct {
}
// SendWeChatOfAccountMessage 执行发送微信公众号模板消息
func (s *WeChatOfAccountService) SendWeChatOfAccountMessage(auth send_way_service.WeChatOFAccount, ins models.SendTasksIns, typeC string, title string, content string, url string) (string, string) {
insService := send_ins_service.SendTaskInsService{}
errStr, c := insService.ValidateDiffIns(ins)
if errStr != "" {
return errStr, ""
}
config, ok := c.(models.InsWeChatAccountConfig)
if !ok {
return "微信公众号模板消息config校验失败", ""
}
var (
err error
res string
errMsg string
)
cli := message.WeChatOFAccount{
AppID: auth.AppID,
AppSecret: auth.APPSecret,
TemplateID: auth.TempID,
ToUser: config.ToAccount,
URL: url,
}
res, err = cli.Send(title, content)
if err != nil {
errMsg = fmt.Sprintf("发送失败:%s", ins.ContentType)
}
return res, errMsg
}
@@ -0,0 +1,126 @@
package unified
import (
"fmt"
"message-nest/service/send_message_service/unified/channels"
"sync"
)
// 重导出channels包的类型,方便外部使用
type (
Channel = channels.Channel
UnifiedMessageContent = channels.UnifiedMessageContent
)
// 重导出常量
const (
FormatTypeText = channels.FormatTypeText
FormatTypeHTML = channels.FormatTypeHTML
FormatTypeMarkdown = channels.FormatTypeMarkdown
MessageTypeEmail = channels.MessageTypeEmail
MessageTypeDtalk = channels.MessageTypeDtalk
MessageTypeQyWeiXin = channels.MessageTypeQyWeiXin
MessageTypeCustom = channels.MessageTypeCustom
MessageTypeWeChatOFAccount = channels.MessageTypeWeChatOFAccount
MessageTypeMessageNest = channels.MessageTypeMessageNest
)
// ChannelRegistry 渠道注册表
type ChannelRegistry struct {
channels map[string]Channel
mu sync.RWMutex
}
// NewChannelRegistry 创建渠道注册表
func NewChannelRegistry() *ChannelRegistry {
return &ChannelRegistry{
channels: make(map[string]Channel),
}
}
// Register 注册渠道
func (r *ChannelRegistry) Register(channel Channel) {
r.mu.Lock()
defer r.mu.Unlock()
r.channels[channel.GetType()] = channel
}
// GetChannel 获取渠道
func (r *ChannelRegistry) GetChannel(channelType string) (Channel, bool) {
r.mu.RLock()
defer r.mu.RUnlock()
channel, ok := r.channels[channelType]
return channel, ok
}
// GetAllChannels 获取所有渠道
func (r *ChannelRegistry) GetAllChannels() map[string]Channel {
r.mu.RLock()
defer r.mu.RUnlock()
result := make(map[string]Channel, len(r.channels))
for k, v := range r.channels {
result[k] = v
}
return result
}
// ListChannels 列出所有渠道
func (r *ChannelRegistry) ListChannels() []string {
r.mu.RLock()
defer r.mu.RUnlock()
types := make([]string, 0, len(r.channels))
for t := range r.channels {
types = append(types, t)
}
return types
}
var (
globalChannelRegistry *ChannelRegistry
channelRegistryOnce sync.Once
)
// GetGlobalChannelRegistry 获取全局渠道注册表(单例)
func GetGlobalChannelRegistry() *ChannelRegistry {
channelRegistryOnce.Do(func() {
globalChannelRegistry = NewChannelRegistry()
// 注册所有渠道
globalChannelRegistry.Register(channels.NewEmailChannel())
globalChannelRegistry.Register(channels.NewDtalkChannel())
globalChannelRegistry.Register(channels.NewQyWeiXinChannel())
globalChannelRegistry.Register(channels.NewCustomChannel())
globalChannelRegistry.Register(channels.NewWeChatOFAccountChannel())
globalChannelRegistry.Register(channels.NewMessageNestChannel())
})
return globalChannelRegistry
}
// GetChannelInfo 获取渠道信息(用于调试和文档)
func GetChannelInfo(channelType string) (string, error) {
registry := GetGlobalChannelRegistry()
channel, ok := registry.GetChannel(channelType)
if !ok {
return "", fmt.Errorf("未知的渠道类型: %s", channelType)
}
info := fmt.Sprintf("渠道: %s\n支持格式: %v\n",
channel.GetType(),
channel.GetSupportedFormats())
return info, nil
}
// ListAllChannels 列出所有渠道信息
func ListAllChannels() string {
registry := GetGlobalChannelRegistry()
allChannels := registry.GetAllChannels()
result := "已注册的渠道:\n"
for channelType, channel := range allChannels {
result += fmt.Sprintf(" - %s: %v\n", channelType, channel.GetSupportedFormats())
}
return result
}
@@ -0,0 +1,45 @@
package channels
import "message-nest/models"
type Channel interface {
GetType() string
GetSupportedFormats() []string
FormatContent(content *UnifiedMessageContent) (formatType string, formattedContent string, err error)
SendUnified(msgObj interface{}, ins models.SendTasksIns, content *UnifiedMessageContent) (string, string)
}
type BaseChannel struct {
channelType string
supportedFormats []string
}
func NewBaseChannel(channelType string, supportedFormats []string) *BaseChannel {
return &BaseChannel{channelType: channelType, supportedFormats: supportedFormats}
}
func (c *BaseChannel) GetType() string { return c.channelType }
func (c *BaseChannel) GetSupportedFormats() []string { return c.supportedFormats }
func (c *BaseChannel) FormatContent(content *UnifiedMessageContent) (string, string, error) {
for _, formatType := range c.supportedFormats {
switch formatType {
case FormatTypeMarkdown:
if content.HasMarkdown() {
return FormatTypeMarkdown, content.Markdown, nil
}
case FormatTypeHTML:
if content.HasHTML() {
return FormatTypeHTML, content.HTML, nil
}
case FormatTypeText:
if content.HasText() {
return FormatTypeText, content.Text, nil
}
}
}
if content.HasText() {
return FormatTypeText, content.Text, nil
}
return FormatTypeText, "", nil
}
@@ -0,0 +1,83 @@
package channels
import (
"encoding/json"
"fmt"
"message-nest/models"
"message-nest/pkg/message"
"message-nest/service/send_way_service"
"regexp"
"strings"
)
type CustomChannel struct{ *BaseChannel }
func NewCustomChannel() *CustomChannel {
return &CustomChannel{BaseChannel: NewBaseChannel(MessageTypeCustom, []string{FormatTypeText})}
}
func (c *CustomChannel) FormatContent(content *UnifiedMessageContent) (string, string, error) {
if content.HasText() {
return FormatTypeText, content.Text, nil
}
if content.HasMarkdown() {
return FormatTypeText, markdownToText(content.Markdown), nil
}
if content.HasHTML() {
return FormatTypeText, htmlToText(content.HTML), nil
}
return FormatTypeText, "", nil
}
func (c *CustomChannel) SendUnified(msgObj interface{}, ins models.SendTasksIns, content *UnifiedMessageContent) (string, string) {
auth, ok := msgObj.(send_way_service.WayDetailCustom)
if !ok {
return "", "类型转换失败"
}
_, formattedContent, err := c.FormatContent(content)
if err != nil {
return "", err.Error()
}
cli := message.CustomWebhook{}
data, _ := json.Marshal(formattedContent)
dataStr := strings.Trim(string(data), "\"")
bodyStr := strings.Replace(auth.Body, "TEXT", dataStr, -1)
res, err := cli.Request(auth.Webhook, bodyStr)
var errMsg string
if err != nil {
errMsg = fmt.Sprintf("发送失败:%s", err.Error())
}
return string(res), errMsg
}
func markdownToText(md string) string {
t := md
t = regexp.MustCompile(`#{1,6}\s+`).ReplaceAllString(t, "")
t = regexp.MustCompile(`\*\*([^*]+)\*\*`).ReplaceAllString(t, "$1")
t = regexp.MustCompile(`\*([^*]+)\*`).ReplaceAllString(t, "$1")
t = regexp.MustCompile(`__([^_]+)__`).ReplaceAllString(t, "$1")
t = regexp.MustCompile(`_([^_]+)_`).ReplaceAllString(t, "$1")
t = regexp.MustCompile(`\[([^\]]+)\]\([^)]+\)`).ReplaceAllString(t, "$1")
t = regexp.MustCompile("```[^`]*```").ReplaceAllString(t, "")
t = regexp.MustCompile("`([^`]+)`").ReplaceAllString(t, "$1")
t = regexp.MustCompile(`(?m)^>\s+`).ReplaceAllString(t, "")
t = regexp.MustCompile(`(?m)^[\*\-\+]\s+`).ReplaceAllString(t, "")
t = regexp.MustCompile(`(?m)^\d+\.\s+`).ReplaceAllString(t, "")
return strings.TrimSpace(t)
}
func htmlToText(html string) string {
t := html
t = regexp.MustCompile(`(?i)<script[^>]*>.*?</script>`).ReplaceAllString(t, "")
t = regexp.MustCompile(`(?i)<style[^>]*>.*?</style>`).ReplaceAllString(t, "")
t = regexp.MustCompile(`(?i)<br\s*/?>`).ReplaceAllString(t, "\n")
t = regexp.MustCompile(`(?i)</p>`).ReplaceAllString(t, "\n")
t = regexp.MustCompile(`<[^>]+>`).ReplaceAllString(t, "")
t = strings.ReplaceAll(t, "&nbsp;", " ")
t = strings.ReplaceAll(t, "&lt;", "<")
t = strings.ReplaceAll(t, "&gt;", ">")
t = strings.ReplaceAll(t, "&amp;", "&")
t = strings.ReplaceAll(t, "&quot;", "\"")
t = regexp.MustCompile(`\n{3,}`).ReplaceAllString(t, "\n\n")
return strings.TrimSpace(t)
}
@@ -0,0 +1,56 @@
package channels
import (
"fmt"
"message-nest/models"
"message-nest/pkg/message"
"message-nest/service/send_ins_service"
"message-nest/service/send_way_service"
)
type DtalkChannel struct{ *BaseChannel }
func NewDtalkChannel() *DtalkChannel {
return &DtalkChannel{BaseChannel: NewBaseChannel(MessageTypeDtalk, []string{FormatTypeMarkdown, FormatTypeText})}
}
func (c *DtalkChannel) SendUnified(msgObj interface{}, ins models.SendTasksIns, content *UnifiedMessageContent) (string, string) {
auth, ok := msgObj.(send_way_service.WayDetailDTalk)
if !ok {
return "", "类型转换失败"
}
insService := send_ins_service.SendTaskInsService{}
errStr, configInterface := insService.ValidateDiffIns(ins)
if errStr != "" {
return errStr, ""
}
_, ok = configInterface.(models.InsDtalkConfig)
if !ok {
return "钉钉config校验失败", ""
}
contentType, formattedContent, err := c.FormatContent(content)
if err != nil {
return "", err.Error()
}
atMobiles := content.GetAtMobiles()
if content.IsAtAll() {
atMobiles = append(atMobiles, "all")
}
cli := message.Dtalk{AccessToken: auth.AccessToken, Secret: auth.Secret}
var res []byte
var errMsg string
if contentType == FormatTypeText {
res, err = cli.SendMessageText(formattedContent, atMobiles...)
if err != nil {
errMsg = fmt.Sprintf("发送失败:%s", err.Error())
}
} else if contentType == FormatTypeMarkdown {
res, err = cli.SendMessageMarkdown(content.Title, formattedContent, atMobiles...)
if err != nil {
errMsg = fmt.Sprintf("发送失败:%s", err.Error())
}
} else {
errMsg = fmt.Sprintf("未知的钉钉发送内容类型:%s", contentType)
}
return string(res), errMsg
}
@@ -0,0 +1,46 @@
package channels
import (
"fmt"
"message-nest/models"
"message-nest/pkg/message"
"message-nest/service/send_ins_service"
"message-nest/service/send_way_service"
)
type EmailChannel struct{ *BaseChannel }
func NewEmailChannel() *EmailChannel {
return &EmailChannel{BaseChannel: NewBaseChannel(MessageTypeEmail, []string{FormatTypeHTML, FormatTypeText})}
}
func (c *EmailChannel) SendUnified(msgObj interface{}, ins models.SendTasksIns, content *UnifiedMessageContent) (string, string) {
auth, ok := msgObj.(send_way_service.WayDetailEmail)
if !ok {
return "", "类型转换失败"
}
insService := send_ins_service.SendTaskInsService{}
errStr, configInterface := insService.ValidateDiffIns(ins)
if errStr != "" {
return "", errStr
}
config, ok := configInterface.(models.InsEmailConfig)
if !ok {
return "", "邮箱config校验失败"
}
contentType, formattedContent, err := c.FormatContent(content)
if err != nil {
return "", err.Error()
}
var emailer message.EmailMessage
emailer.Init(auth.Server, auth.Port, auth.Account, auth.Passwd)
var errMsg string
if contentType == FormatTypeText {
errMsg = emailer.SendTextMessage(config.ToAccount, content.Title, formattedContent)
} else if contentType == FormatTypeHTML {
errMsg = emailer.SendHtmlMessage(config.ToAccount, content.Title, formattedContent)
} else {
errMsg = fmt.Sprintf("未知的邮件发送内容类型:%s", contentType)
}
return "", errMsg
}
@@ -0,0 +1,38 @@
package channels
import (
"message-nest/models"
"message-nest/service/hosted_message_service"
"message-nest/service/send_way_service"
)
type MessageNestChannel struct{ *BaseChannel }
func NewMessageNestChannel() *MessageNestChannel {
return &MessageNestChannel{BaseChannel: NewBaseChannel(MessageTypeMessageNest, []string{FormatTypeMarkdown, FormatTypeHTML, FormatTypeText})}
}
func (c *MessageNestChannel) SendUnified(msgObj interface{}, ins models.SendTasksIns, content *UnifiedMessageContent) (string, string) {
_, ok := msgObj.(send_way_service.MessageNest)
if !ok {
return "", "类型转换失败"
}
contentType, formattedContent, err := c.FormatContent(content)
if err != nil {
return "", err.Error()
}
messageService := hosted_message_service.HostMessageService{
Title: content.Title,
Content: formattedContent,
Type: contentType,
}
err = messageService.Add()
var res, errMsg string
if err != nil {
errMsg = err.Error()
res = "托管消息创建失败!"
} else {
res = "托管消息创建成功!"
}
return res, errMsg
}
@@ -0,0 +1,58 @@
package channels
import (
"fmt"
"message-nest/models"
"message-nest/pkg/message"
"message-nest/service/send_ins_service"
"message-nest/service/send_way_service"
)
type QyWeiXinChannel struct{ *BaseChannel }
func NewQyWeiXinChannel() *QyWeiXinChannel {
return &QyWeiXinChannel{BaseChannel: NewBaseChannel(MessageTypeQyWeiXin, []string{FormatTypeMarkdown, FormatTypeText})}
}
func (c *QyWeiXinChannel) SendUnified(msgObj interface{}, ins models.SendTasksIns, content *UnifiedMessageContent) (string, string) {
auth, ok := msgObj.(send_way_service.WayDetailQyWeiXin)
if !ok {
return "", "类型转换失败"
}
insService := send_ins_service.SendTaskInsService{}
errStr, configInterface := insService.ValidateDiffIns(ins)
if errStr != "" {
return errStr, ""
}
_, ok = configInterface.(models.InsQyWeiXinConfig)
if !ok {
return "企业微信config校验失败", ""
}
contentType, formattedContent, err := c.FormatContent(content)
if err != nil {
return "", err.Error()
}
atList := []string{}
atList = append(atList, content.GetAtUserIds()...)
atList = append(atList, content.GetAtMobiles()...)
if content.IsAtAll() {
atList = append(atList, "@all")
}
cli := message.QyWeiXin{AccessToken: auth.AccessToken}
var res []byte
var errMsg string
if contentType == FormatTypeText {
res, err = cli.SendMessageText(formattedContent, atList...)
if err != nil {
errMsg = fmt.Sprintf("发送失败:%s", err.Error())
}
} else if contentType == FormatTypeMarkdown {
res, err = cli.SendMessageMarkdown(content.Title, formattedContent, atList...)
if err != nil {
errMsg = fmt.Sprintf("发送失败:%s", err.Error())
}
} else {
errMsg = fmt.Sprintf("未知的企业微信发送内容类型:%s", contentType)
}
return string(res), errMsg
}
@@ -0,0 +1,53 @@
package channels
// 消息格式类型常量
const (
FormatTypeText = "text"
FormatTypeHTML = "html"
FormatTypeMarkdown = "markdown"
)
// 消息类型常量
const (
MessageTypeEmail = "Email"
MessageTypeDtalk = "Dtalk"
MessageTypeQyWeiXin = "QyWeiXin"
MessageTypeCustom = "Custom"
MessageTypeWeChatOFAccount = "WeChatOFAccount"
MessageTypeMessageNest = "MessageNest"
)
// UnifiedMessageContent 统一的消息内容结构
type UnifiedMessageContent struct {
Title string
URL string
Text string
HTML string
Markdown string
AtMobiles []string
AtUserIds []string
AtAll bool
Summary string
ImageURL string
Extra map[string]interface{}
}
func (m *UnifiedMessageContent) HasText() bool { return m.Text != "" }
func (m *UnifiedMessageContent) HasHTML() bool { return m.HTML != "" }
func (m *UnifiedMessageContent) HasMarkdown() bool { return m.Markdown != "" }
func (m *UnifiedMessageContent) GetAtMobiles() []string {
if m.AtMobiles == nil {
return []string{}
}
return m.AtMobiles
}
func (m *UnifiedMessageContent) GetAtUserIds() []string {
if m.AtUserIds == nil {
return []string{}
}
return m.AtUserIds
}
func (m *UnifiedMessageContent) IsAtAll() bool { return m.AtAll }
@@ -0,0 +1,48 @@
package channels
import (
"fmt"
"message-nest/models"
"message-nest/pkg/message"
"message-nest/service/send_ins_service"
"message-nest/service/send_way_service"
)
type WeChatOFAccountChannel struct{ *BaseChannel }
func NewWeChatOFAccountChannel() *WeChatOFAccountChannel {
return &WeChatOFAccountChannel{BaseChannel: NewBaseChannel(MessageTypeWeChatOFAccount, []string{FormatTypeText})}
}
func (c *WeChatOFAccountChannel) SendUnified(msgObj interface{}, ins models.SendTasksIns, content *UnifiedMessageContent) (string, string) {
auth, ok := msgObj.(send_way_service.WeChatOFAccount)
if !ok {
return "", "类型转换失败"
}
insService := send_ins_service.SendTaskInsService{}
errStr, configInterface := insService.ValidateDiffIns(ins)
if errStr != "" {
return errStr, ""
}
config, ok := configInterface.(models.InsWeChatAccountConfig)
if !ok {
return "微信公众号模板消息config校验失败", ""
}
_, formattedContent, err := c.FormatContent(content)
if err != nil {
return "", err.Error()
}
cli := message.WeChatOFAccount{
AppID: auth.AppID,
AppSecret: auth.APPSecret,
TemplateID: auth.TempID,
ToUser: config.ToAccount,
URL: content.URL,
}
res, err := cli.Send(content.Title, formattedContent)
var errMsg string
if err != nil {
errMsg = fmt.Sprintf("发送失败:%s", err.Error())
}
return res, errMsg
}