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,
|
||||
},
|
||||
}
|
||||
|
||||
// 添加@功能
|
||||
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)
|
||||
return resp, err
|
||||
}
|
||||
@@ -74,6 +94,26 @@ func (t *Dtalk) SendMessageMarkdown(title, text string, at ...string) ([]byte, e
|
||||
"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)
|
||||
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) {
|
||||
msg := map[string]interface{}{
|
||||
"msgtype": "text",
|
||||
"text": map[string]string{
|
||||
"text": map[string]interface{}{
|
||||
"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)
|
||||
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) {
|
||||
msg := map[string]interface{}{
|
||||
"msgtype": "markdown",
|
||||
"markdown": map[string]string{
|
||||
"title": title,
|
||||
"markdown": map[string]interface{}{
|
||||
"content": text,
|
||||
},
|
||||
}
|
||||
|
||||
// 企业微信Markdown消息不支持@功能,但可以在内容中手动添加
|
||||
// 如果需要@功能,建议使用text类型
|
||||
|
||||
resp, err := t.Request(msg)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
@@ -23,6 +23,11 @@ type SendMessageReq struct {
|
||||
URL string `json:"url" label:"消息详情url地址"`
|
||||
MarkDown string `json:"markdown" label:"markdown内容"`
|
||||
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 外部调用发信接口
|
||||
@@ -56,15 +61,18 @@ func DoSendMassage(c *gin.Context) {
|
||||
}
|
||||
|
||||
msgService := send_message_service.SendMessageService{
|
||||
TaskID: taskID,
|
||||
Title: req.Title,
|
||||
Text: req.Text,
|
||||
HTML: req.HTML,
|
||||
URL: req.URL,
|
||||
MarkDown: req.MarkDown,
|
||||
CallerIp: c.ClientIP(),
|
||||
TaskID: taskID,
|
||||
Title: req.Title,
|
||||
Text: req.Text,
|
||||
HTML: req.HTML,
|
||||
URL: req.URL,
|
||||
MarkDown: req.MarkDown,
|
||||
CallerIp: c.ClientIP(),
|
||||
AtMobiles: req.AtMobiles,
|
||||
AtUserIds: req.AtUserIds,
|
||||
AtAll: req.AtAll,
|
||||
DefaultLogger: logrus.WithFields(logrus.Fields{
|
||||
//"prefix": "[Message Instance]",
|
||||
"prefix": "[Send Instance]",
|
||||
}),
|
||||
}
|
||||
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"
|
||||
"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, " ", " ")
|
||||
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 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 = [
|
||||
{ value: 'curl', label: 'cURL', icon: '🌐' },
|
||||
@@ -56,7 +64,14 @@ export default defineComponent({
|
||||
// 生成API代码示例
|
||||
const generateApiCode = (language: string) => {
|
||||
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) {
|
||||
case 'curl':
|
||||
@@ -91,6 +106,12 @@ export default defineComponent({
|
||||
return {
|
||||
handleUpdateOpen,
|
||||
activeTab,
|
||||
showHtml,
|
||||
showMarkdown,
|
||||
showUrl,
|
||||
showAtMobiles,
|
||||
showAtUserIds,
|
||||
showAtAll,
|
||||
codeLanguages,
|
||||
generateApiCode,
|
||||
copyToClipboard
|
||||
@@ -122,6 +143,41 @@ export default defineComponent({
|
||||
</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">
|
||||
<h3 class="font-semibold">代码示例</h3>
|
||||
|
||||
@@ -40,6 +40,16 @@ class ApiStrGenerate {
|
||||
if (options.url) {
|
||||
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);
|
||||
return dataStr
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user