feat: send recode adjust
This commit is contained in:
@@ -62,6 +62,26 @@ func (t *Dtalk) SendMessageText(text string, at ...string) ([]byte, error) {
|
|||||||
"content": text,
|
"content": text,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加@功能
|
||||||
|
if len(at) > 0 {
|
||||||
|
atMobiles := []string{}
|
||||||
|
isAtAll := false
|
||||||
|
|
||||||
|
for _, mobile := range at {
|
||||||
|
if mobile == "all" || mobile == "@all" {
|
||||||
|
isAtAll = true
|
||||||
|
} else {
|
||||||
|
atMobiles = append(atMobiles, mobile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msg["at"] = map[string]interface{}{
|
||||||
|
"atMobiles": atMobiles,
|
||||||
|
"isAtAll": isAtAll,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := t.Request(msg)
|
resp, err := t.Request(msg)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
@@ -74,6 +94,26 @@ func (t *Dtalk) SendMessageMarkdown(title, text string, at ...string) ([]byte, e
|
|||||||
"text": text,
|
"text": text,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加@功能
|
||||||
|
if len(at) > 0 {
|
||||||
|
atMobiles := []string{}
|
||||||
|
isAtAll := false
|
||||||
|
|
||||||
|
for _, mobile := range at {
|
||||||
|
if mobile == "all" || mobile == "@all" {
|
||||||
|
isAtAll = true
|
||||||
|
} else {
|
||||||
|
atMobiles = append(atMobiles, mobile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msg["at"] = map[string]interface{}{
|
||||||
|
"atMobiles": atMobiles,
|
||||||
|
"isAtAll": isAtAll,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := t.Request(msg)
|
resp, err := t.Request(msg)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-3
@@ -53,10 +53,40 @@ func (t *QyWeiXin) Request(msg interface{}) ([]byte, error) {
|
|||||||
func (t *QyWeiXin) SendMessageText(text string, at ...string) ([]byte, error) {
|
func (t *QyWeiXin) SendMessageText(text string, at ...string) ([]byte, error) {
|
||||||
msg := map[string]interface{}{
|
msg := map[string]interface{}{
|
||||||
"msgtype": "text",
|
"msgtype": "text",
|
||||||
"text": map[string]string{
|
"text": map[string]interface{}{
|
||||||
"content": text,
|
"content": text,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加@功能
|
||||||
|
// 企业微信支持两种@方式:
|
||||||
|
// 1. mentioned_list: userid列表或"@all"
|
||||||
|
// 2. mentioned_mobile_list: 手机号列表
|
||||||
|
if len(at) > 0 {
|
||||||
|
mentionedList := []string{}
|
||||||
|
mentionedMobileList := []string{}
|
||||||
|
|
||||||
|
for _, item := range at {
|
||||||
|
if item == "@all" || item == "all" {
|
||||||
|
mentionedList = append(mentionedList, "@all")
|
||||||
|
} else if len(item) == 11 && item[0] == '1' {
|
||||||
|
// 判断是否为手机号(简单判断:11位且以1开头)
|
||||||
|
mentionedMobileList = append(mentionedMobileList, item)
|
||||||
|
} else {
|
||||||
|
// 否则当作userid处理
|
||||||
|
mentionedList = append(mentionedList, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
textContent := msg["text"].(map[string]interface{})
|
||||||
|
if len(mentionedList) > 0 {
|
||||||
|
textContent["mentioned_list"] = mentionedList
|
||||||
|
}
|
||||||
|
if len(mentionedMobileList) > 0 {
|
||||||
|
textContent["mentioned_mobile_list"] = mentionedMobileList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := t.Request(msg)
|
resp, err := t.Request(msg)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
@@ -64,11 +94,14 @@ func (t *QyWeiXin) SendMessageText(text string, at ...string) ([]byte, error) {
|
|||||||
func (t *QyWeiXin) SendMessageMarkdown(title, text string, at ...string) ([]byte, error) {
|
func (t *QyWeiXin) SendMessageMarkdown(title, text string, at ...string) ([]byte, error) {
|
||||||
msg := map[string]interface{}{
|
msg := map[string]interface{}{
|
||||||
"msgtype": "markdown",
|
"msgtype": "markdown",
|
||||||
"markdown": map[string]string{
|
"markdown": map[string]interface{}{
|
||||||
"title": title,
|
|
||||||
"content": text,
|
"content": text,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 企业微信Markdown消息不支持@功能,但可以在内容中手动添加
|
||||||
|
// 如果需要@功能,建议使用text类型
|
||||||
|
|
||||||
resp, err := t.Request(msg)
|
resp, err := t.Request(msg)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ type SendMessageReq struct {
|
|||||||
URL string `json:"url" label:"消息详情url地址"`
|
URL string `json:"url" label:"消息详情url地址"`
|
||||||
MarkDown string `json:"markdown" label:"markdown内容"`
|
MarkDown string `json:"markdown" label:"markdown内容"`
|
||||||
Mode string `json:"mode" label:"是否异步发送"`
|
Mode string `json:"mode" label:"是否异步发送"`
|
||||||
|
|
||||||
|
// @提及相关参数
|
||||||
|
AtMobiles []string `json:"at_mobiles" label:"@的手机号列表"`
|
||||||
|
AtUserIds []string `json:"at_user_ids" label:"@的用户ID列表"`
|
||||||
|
AtAll bool `json:"at_all" label:"是否@所有人"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DoSendMassage 外部调用发信接口
|
// DoSendMassage 外部调用发信接口
|
||||||
@@ -56,15 +61,18 @@ func DoSendMassage(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
msgService := send_message_service.SendMessageService{
|
msgService := send_message_service.SendMessageService{
|
||||||
TaskID: taskID,
|
TaskID: taskID,
|
||||||
Title: req.Title,
|
Title: req.Title,
|
||||||
Text: req.Text,
|
Text: req.Text,
|
||||||
HTML: req.HTML,
|
HTML: req.HTML,
|
||||||
URL: req.URL,
|
URL: req.URL,
|
||||||
MarkDown: req.MarkDown,
|
MarkDown: req.MarkDown,
|
||||||
CallerIp: c.ClientIP(),
|
CallerIp: c.ClientIP(),
|
||||||
|
AtMobiles: req.AtMobiles,
|
||||||
|
AtUserIds: req.AtUserIds,
|
||||||
|
AtAll: req.AtAll,
|
||||||
DefaultLogger: logrus.WithFields(logrus.Fields{
|
DefaultLogger: logrus.WithFields(logrus.Fields{
|
||||||
//"prefix": "[Message Instance]",
|
"prefix": "[Send Instance]",
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
task, err := msgService.SendPreCheck()
|
task, err := msgService.SendPreCheck()
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"message-nest/models"
|
"message-nest/models"
|
||||||
"message-nest/pkg/constant"
|
"message-nest/pkg/constant"
|
||||||
|
"message-nest/service/send_message_service/unified"
|
||||||
"message-nest/service/send_task_service"
|
"message-nest/service/send_task_service"
|
||||||
"message-nest/service/send_way_service"
|
"message-nest/service/send_way_service"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -33,6 +34,11 @@ type SendMessageService struct {
|
|||||||
MarkDown string
|
MarkDown string
|
||||||
CallerIp string
|
CallerIp string
|
||||||
|
|
||||||
|
// @提及相关字段
|
||||||
|
AtMobiles []string
|
||||||
|
AtUserIds []string
|
||||||
|
AtAll bool
|
||||||
|
|
||||||
Status int
|
Status int
|
||||||
LogOutput []string
|
LogOutput []string
|
||||||
|
|
||||||
@@ -151,19 +157,33 @@ func (sm *SendMessageService) Send(task models.TaskIns) (string, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用注册的处理器发送消息
|
// 使用新的Channel架构发送消息
|
||||||
registry := GetGlobalRegistry()
|
channelRegistry := unified.GetGlobalChannelRegistry()
|
||||||
handler, ok := registry.GetHandler(way.Type)
|
channel, ok := channelRegistry.GetChannel(way.Type)
|
||||||
if !ok {
|
if !ok {
|
||||||
sm.LogsAndStatusMark(fmt.Sprintf("发送失败:未知渠道类型 %s 的发信实例: %s\n", way.Type, ins.ID), SendFail)
|
sm.LogsAndStatusMark(fmt.Sprintf("发送失败:未知渠道类型 %s 的发信实例: %s\n", way.Type, ins.ID), SendFail)
|
||||||
continue
|
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 != "" {
|
if res != "" {
|
||||||
sm.LogsAndStatusMark(fmt.Sprintf("返回内容:%s", res), sm.Status)
|
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发送
|
// 先根据实例设置的类型取,取不到或者取到的是空,则使用text发送
|
||||||
func (sm *SendMessageService) GetSendMsg(ins models.SendTasksIns) (string, string) {
|
func (sm *SendMessageService) GetSendMsg(ins models.SendTasksIns) (string, string) {
|
||||||
data := map[string]string{}
|
data := map[string]string{}
|
||||||
data["text"] = sm.Text
|
data[unified.FormatTypeText] = sm.Text
|
||||||
data["html"] = sm.HTML
|
data[unified.FormatTypeHTML] = sm.HTML
|
||||||
data["markdown"] = sm.MarkDown
|
data[unified.FormatTypeMarkdown] = sm.MarkDown
|
||||||
content, ok := data[strings.ToLower(ins.ContentType)]
|
content, ok := data[strings.ToLower(ins.ContentType)]
|
||||||
if !ok || len(content) == 0 {
|
if !ok || len(content) == 0 {
|
||||||
content, ok := data["text"]
|
content, ok := data[unified.FormatTypeText]
|
||||||
if !ok {
|
if !ok {
|
||||||
logrus.Error("text节点数据为空!")
|
logrus.Error("text节点数据为空!")
|
||||||
return "text", ""
|
return unified.FormatTypeText, ""
|
||||||
} else {
|
} else {
|
||||||
logrus.Error(fmt.Sprintf("没有找到%s对应的消息,使用text消息替代!", ins.ContentType))
|
logrus.Error(fmt.Sprintf("没有找到%s对应的消息,使用text消息替代!", ins.ContentType))
|
||||||
return "text", content
|
return unified.FormatTypeText, content
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return strings.ToLower(ins.ContentType), content
|
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, " ", " ")
|
||||||
|
t = strings.ReplaceAll(t, "<", "<")
|
||||||
|
t = strings.ReplaceAll(t, ">", ">")
|
||||||
|
t = strings.ReplaceAll(t, "&", "&")
|
||||||
|
t = strings.ReplaceAll(t, """, "\"")
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -42,6 +42,14 @@ export default defineComponent({
|
|||||||
// 当前选中的标签
|
// 当前选中的标签
|
||||||
const activeTab = ref('curl')
|
const activeTab = ref('curl')
|
||||||
|
|
||||||
|
// 可选参数选项
|
||||||
|
const showHtml = ref(false)
|
||||||
|
const showMarkdown = ref(false)
|
||||||
|
const showUrl = ref(false)
|
||||||
|
const showAtMobiles = ref(false)
|
||||||
|
const showAtUserIds = ref(false)
|
||||||
|
const showAtAll = ref(false)
|
||||||
|
|
||||||
// 代码语言选项
|
// 代码语言选项
|
||||||
const codeLanguages = [
|
const codeLanguages = [
|
||||||
{ value: 'curl', label: 'cURL', icon: '🌐' },
|
{ value: 'curl', label: 'cURL', icon: '🌐' },
|
||||||
@@ -56,7 +64,14 @@ export default defineComponent({
|
|||||||
// 生成API代码示例
|
// 生成API代码示例
|
||||||
const generateApiCode = (language: string) => {
|
const generateApiCode = (language: string) => {
|
||||||
const taskId = props.taskData?.id || 'TASK_ID'
|
const taskId = props.taskData?.id || 'TASK_ID'
|
||||||
const options = { html: false, markdown: false, url: false }
|
const options = {
|
||||||
|
html: showHtml.value,
|
||||||
|
markdown: showMarkdown.value,
|
||||||
|
url: showUrl.value,
|
||||||
|
at_mobiles: showAtMobiles.value,
|
||||||
|
at_user_ids: showAtUserIds.value,
|
||||||
|
at_all: showAtAll.value
|
||||||
|
}
|
||||||
|
|
||||||
switch (language) {
|
switch (language) {
|
||||||
case 'curl':
|
case 'curl':
|
||||||
@@ -91,6 +106,12 @@ export default defineComponent({
|
|||||||
return {
|
return {
|
||||||
handleUpdateOpen,
|
handleUpdateOpen,
|
||||||
activeTab,
|
activeTab,
|
||||||
|
showHtml,
|
||||||
|
showMarkdown,
|
||||||
|
showUrl,
|
||||||
|
showAtMobiles,
|
||||||
|
showAtUserIds,
|
||||||
|
showAtAll,
|
||||||
codeLanguages,
|
codeLanguages,
|
||||||
generateApiCode,
|
generateApiCode,
|
||||||
copyToClipboard
|
copyToClipboard
|
||||||
@@ -122,6 +143,41 @@ export default defineComponent({
|
|||||||
</div>
|
</div>
|
||||||
</div> -->
|
</div> -->
|
||||||
|
|
||||||
|
<!-- 可选参数 -->
|
||||||
|
<div class="border rounded-lg p-4 space-y-3">
|
||||||
|
<h3 class="font-semibold text-sm">可选参数</h3>
|
||||||
|
<div class="grid grid-cols-2 md:grid-cols-3 gap-3">
|
||||||
|
<label class="flex items-center gap-2 cursor-pointer">
|
||||||
|
<input type="checkbox" v-model="showHtml" class="rounded">
|
||||||
|
<span class="text-sm">HTML</span>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center gap-2 cursor-pointer">
|
||||||
|
<input type="checkbox" v-model="showMarkdown" class="rounded">
|
||||||
|
<span class="text-sm">Markdown</span>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center gap-2 cursor-pointer">
|
||||||
|
<input type="checkbox" v-model="showUrl" class="rounded">
|
||||||
|
<span class="text-sm">URL</span>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center gap-2 cursor-pointer">
|
||||||
|
<input type="checkbox" v-model="showAtMobiles" class="rounded">
|
||||||
|
<span class="text-sm">@手机号</span>
|
||||||
|
<Badge variant="secondary" class="text-xs">新</Badge>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center gap-2 cursor-pointer">
|
||||||
|
<input type="checkbox" v-model="showAtUserIds" class="rounded">
|
||||||
|
<span class="text-sm">@用户ID</span>
|
||||||
|
<Badge variant="secondary" class="text-xs">新</Badge>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center gap-2 cursor-pointer">
|
||||||
|
<input type="checkbox" v-model="showAtAll" class="rounded">
|
||||||
|
<span class="text-sm">@所有人</span>
|
||||||
|
<Badge variant="secondary" class="text-xs">新</Badge>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-gray-500">💡 提示:@功能仅钉钉和企业微信支持</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 代码示例 -->
|
<!-- 代码示例 -->
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<h3 class="font-semibold">代码示例</h3>
|
<h3 class="font-semibold">代码示例</h3>
|
||||||
|
|||||||
@@ -40,6 +40,16 @@ class ApiStrGenerate {
|
|||||||
if (options.url) {
|
if (options.url) {
|
||||||
data.url = 'https://github.com';
|
data.url = 'https://github.com';
|
||||||
}
|
}
|
||||||
|
// @提及功能参数(可选)
|
||||||
|
if (options.at_mobiles) {
|
||||||
|
data.at_mobiles = ['13800138000', '13900139000'];
|
||||||
|
}
|
||||||
|
if (options.at_user_ids) {
|
||||||
|
data.at_user_ids = ['zhangsan', 'lisi'];
|
||||||
|
}
|
||||||
|
if (options.at_all) {
|
||||||
|
data.at_all = true;
|
||||||
|
}
|
||||||
let dataStr = JSON.stringify(data, null, 4);
|
let dataStr = JSON.stringify(data, null, 4);
|
||||||
return dataStr
|
return dataStr
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user