feat: 新增企业微信应用消息发送渠道
- 添加 WeChatCorpAccount 消息类型常量 - 新增企业微信应用配置结构体和渠道验证逻辑 - 实现企业微信应用消息发送服务,支持 text、markdown 和 textcard 格式 - 添加前端配置界面,支持动态接收者设置 - 集成代理支持(HTTP/HTTPS/SOCKS5)
This commit is contained in:
@@ -58,6 +58,24 @@ func (sw *SendTaskInsService) ValidateDiffIns(ins models.SendTasksIns) (string,
|
||||
var Config models.InsQyWeiXinConfig
|
||||
return "", Config
|
||||
}
|
||||
if ins.WayType == constant.MessageTypeWeChatCorpAccount {
|
||||
var Config models.InsWeChatCorpAccountConfig
|
||||
err := json.Unmarshal([]byte(ins.Config), &Config)
|
||||
if err != nil {
|
||||
return "企业微信应用发送配置反序列化失败!", empty
|
||||
}
|
||||
|
||||
var configMap map[string]interface{}
|
||||
json.Unmarshal([]byte(ins.Config), &configMap)
|
||||
allowMultiRecip, exists := configMap["allowMultiRecip"].(bool)
|
||||
|
||||
if exists && allowMultiRecip && Config.ToAccount == "" {
|
||||
return "", Config
|
||||
}
|
||||
|
||||
_, Msg := app.CommonPlaygroundValid(Config)
|
||||
return Msg, Config
|
||||
}
|
||||
if ins.WayType == constant.MessageTypeFeishu {
|
||||
var Config models.InsFeishuConfig
|
||||
return "", Config
|
||||
|
||||
@@ -438,6 +438,7 @@ func (sm *SendMessageService) supportsDynamicRecipient(wayType string) bool {
|
||||
supportedTypes := map[string]bool{
|
||||
constant.MessageTypeEmail: true,
|
||||
constant.MessageTypeWeChatOFAccount: true,
|
||||
constant.MessageTypeWeChatCorpAccount: true,
|
||||
constant.MessageTypeAliyunSMS: true,
|
||||
// 可以继续添加其他支持动态接收者的渠道
|
||||
}
|
||||
|
||||
@@ -18,17 +18,18 @@ const (
|
||||
FormatTypeHTML = channels.FormatTypeHTML
|
||||
FormatTypeMarkdown = channels.FormatTypeMarkdown
|
||||
|
||||
MessageTypeEmail = channels.MessageTypeEmail
|
||||
MessageTypeDtalk = channels.MessageTypeDtalk
|
||||
MessageTypeQyWeiXin = channels.MessageTypeQyWeiXin
|
||||
MessageTypeFeishu = channels.MessageTypeFeishu
|
||||
MessageTypeCustom = channels.MessageTypeCustom
|
||||
MessageTypeWeChatOFAccount = channels.MessageTypeWeChatOFAccount
|
||||
MessageTypeMessageNest = channels.MessageTypeMessageNest
|
||||
MessageTypeAliyunSMS = channels.MessageTypeAliyunSMS
|
||||
MessageTypeTelegram = channels.MessageTypeTelegram
|
||||
MessageTypeBark = channels.MessageTypeBark
|
||||
MessageTypePushMe = channels.MessageTypePushMe
|
||||
MessageTypeEmail = channels.MessageTypeEmail
|
||||
MessageTypeDtalk = channels.MessageTypeDtalk
|
||||
MessageTypeQyWeiXin = channels.MessageTypeQyWeiXin
|
||||
MessageTypeWeChatCorpAccount = channels.MessageTypeWeChatCorpAccount
|
||||
MessageTypeFeishu = channels.MessageTypeFeishu
|
||||
MessageTypeCustom = channels.MessageTypeCustom
|
||||
MessageTypeWeChatOFAccount = channels.MessageTypeWeChatOFAccount
|
||||
MessageTypeMessageNest = channels.MessageTypeMessageNest
|
||||
MessageTypeAliyunSMS = channels.MessageTypeAliyunSMS
|
||||
MessageTypeTelegram = channels.MessageTypeTelegram
|
||||
MessageTypeBark = channels.MessageTypeBark
|
||||
MessageTypePushMe = channels.MessageTypePushMe
|
||||
)
|
||||
|
||||
// ChannelRegistry 渠道注册表
|
||||
@@ -97,6 +98,7 @@ func GetGlobalChannelRegistry() *ChannelRegistry {
|
||||
globalChannelRegistry.Register(channels.NewEmailChannel())
|
||||
globalChannelRegistry.Register(channels.NewDtalkChannel())
|
||||
globalChannelRegistry.Register(channels.NewQyWeiXinChannel())
|
||||
globalChannelRegistry.Register(channels.NewWeChatCorpAccountChannel())
|
||||
globalChannelRegistry.Register(channels.NewFeishuChannel())
|
||||
globalChannelRegistry.Register(channels.NewCustomChannel())
|
||||
globalChannelRegistry.Register(channels.NewWeChatOFAccountChannel())
|
||||
|
||||
@@ -13,6 +13,7 @@ const (
|
||||
MessageTypeEmail = constant.MessageTypeEmail
|
||||
MessageTypeDtalk = constant.MessageTypeDtalk
|
||||
MessageTypeQyWeiXin = constant.MessageTypeQyWeiXin
|
||||
MessageTypeWeChatCorpAccount = constant.MessageTypeWeChatCorpAccount
|
||||
MessageTypeFeishu = constant.MessageTypeFeishu
|
||||
MessageTypeCustom = constant.MessageTypeCustom
|
||||
MessageTypeWeChatOFAccount = constant.MessageTypeWeChatOFAccount
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package channels
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"message-nest/models"
|
||||
"message-nest/pkg/message"
|
||||
"message-nest/service/send_ins_service"
|
||||
"message-nest/service/send_way_service"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type WeChatCorpAccountChannel struct{ *BaseChannel }
|
||||
|
||||
func NewWeChatCorpAccountChannel() *WeChatCorpAccountChannel {
|
||||
return &WeChatCorpAccountChannel{BaseChannel: NewBaseChannel(MessageTypeWeChatCorpAccount, []string{FormatTypeMarkdown, FormatTypeText})}
|
||||
}
|
||||
|
||||
func (c *WeChatCorpAccountChannel) SendUnified(msgObj interface{}, ins models.SendTasksIns, content *UnifiedMessageContent) (string, string) {
|
||||
auth, ok := msgObj.(*send_way_service.WayDetailWeChatCorpAccount)
|
||||
if !ok {
|
||||
return "", "类型转换失败"
|
||||
}
|
||||
|
||||
insService := send_ins_service.SendTaskInsService{}
|
||||
errStr, configInterface := insService.ValidateDiffIns(ins)
|
||||
if errStr != "" {
|
||||
return errStr, ""
|
||||
}
|
||||
config, ok := configInterface.(models.InsWeChatCorpAccountConfig)
|
||||
if !ok {
|
||||
return "企业微信应用config校验失败", ""
|
||||
}
|
||||
|
||||
contentType, formattedContent, err := c.FormatContent(content)
|
||||
if err != nil {
|
||||
return "", err.Error()
|
||||
}
|
||||
|
||||
toUser := config.ToAccount
|
||||
if content.IsAtAll() {
|
||||
toUser = "@all"
|
||||
} else {
|
||||
atUserIds := content.GetAtUserIds()
|
||||
if len(atUserIds) > 0 {
|
||||
toUser = strings.Join(atUserIds, "|")
|
||||
}
|
||||
}
|
||||
|
||||
cli := message.WeChatCorpAccount{
|
||||
CorpID: auth.CorpID,
|
||||
AgentID: auth.AgentID,
|
||||
AgentSecret: auth.AgentSecret,
|
||||
ProxyURL: auth.ProxyURL,
|
||||
}
|
||||
|
||||
var res string
|
||||
var sendErr error
|
||||
if contentType == FormatTypeMarkdown {
|
||||
res, sendErr = cli.SendMarkdown(toUser, formattedContent)
|
||||
} else if contentType == FormatTypeText {
|
||||
if content.Title != "" && content.URL != "" {
|
||||
res, sendErr = cli.SendTextCard(toUser, content.Title, formattedContent, content.URL)
|
||||
} else {
|
||||
res, sendErr = cli.SendText(toUser, formattedContent)
|
||||
}
|
||||
} else {
|
||||
sendErr = fmt.Errorf("未知的企业微信应用发送内容类型:%s", contentType)
|
||||
}
|
||||
|
||||
var errMsg string
|
||||
if sendErr != nil {
|
||||
errMsg = fmt.Sprintf("发送失败:%s", sendErr.Error())
|
||||
}
|
||||
return res, errMsg
|
||||
}
|
||||
|
||||
@@ -37,30 +37,32 @@ type WayTester interface {
|
||||
// 渠道注册表
|
||||
var (
|
||||
validatorRegistry = map[string]func() WayValidator{
|
||||
constant.MessageTypeEmail: func() WayValidator { return &WayDetailEmail{} },
|
||||
constant.MessageTypeDtalk: func() WayValidator { return &WayDetailDTalk{} },
|
||||
constant.MessageTypeQyWeiXin: func() WayValidator { return &WayDetailQyWeiXin{} },
|
||||
constant.MessageTypeFeishu: func() WayValidator { return &WayDetailFeishu{} },
|
||||
constant.MessageTypeCustom: func() WayValidator { return &WayDetailCustom{} },
|
||||
constant.MessageTypeWeChatOFAccount: func() WayValidator { return &WeChatOFAccount{} },
|
||||
constant.MessageTypeMessageNest: func() WayValidator { return &MessageNest{} },
|
||||
constant.MessageTypeAliyunSMS: func() WayValidator { return &WayDetailAliyunSMS{} },
|
||||
constant.MessageTypeTelegram: func() WayValidator { return &WayDetailTelegram{} },
|
||||
constant.MessageTypeBark: func() WayValidator { return &WayDetailBark{} },
|
||||
constant.MessageTypePushMe: func() WayValidator { return &WayDetailPushMe{} },
|
||||
constant.MessageTypeEmail: func() WayValidator { return &WayDetailEmail{} },
|
||||
constant.MessageTypeDtalk: func() WayValidator { return &WayDetailDTalk{} },
|
||||
constant.MessageTypeQyWeiXin: func() WayValidator { return &WayDetailQyWeiXin{} },
|
||||
constant.MessageTypeWeChatCorpAccount: func() WayValidator { return &WayDetailWeChatCorpAccount{} },
|
||||
constant.MessageTypeFeishu: func() WayValidator { return &WayDetailFeishu{} },
|
||||
constant.MessageTypeCustom: func() WayValidator { return &WayDetailCustom{} },
|
||||
constant.MessageTypeWeChatOFAccount: func() WayValidator { return &WeChatOFAccount{} },
|
||||
constant.MessageTypeMessageNest: func() WayValidator { return &MessageNest{} },
|
||||
constant.MessageTypeAliyunSMS: func() WayValidator { return &WayDetailAliyunSMS{} },
|
||||
constant.MessageTypeTelegram: func() WayValidator { return &WayDetailTelegram{} },
|
||||
constant.MessageTypeBark: func() WayValidator { return &WayDetailBark{} },
|
||||
constant.MessageTypePushMe: func() WayValidator { return &WayDetailPushMe{} },
|
||||
}
|
||||
testerRegistry = map[string]func(interface{}) WayTester{
|
||||
constant.MessageTypeEmail: func(m interface{}) WayTester { return m.(*WayDetailEmail) },
|
||||
constant.MessageTypeDtalk: func(m interface{}) WayTester { return m.(*WayDetailDTalk) },
|
||||
constant.MessageTypeQyWeiXin: func(m interface{}) WayTester { return m.(*WayDetailQyWeiXin) },
|
||||
constant.MessageTypeFeishu: func(m interface{}) WayTester { return m.(*WayDetailFeishu) },
|
||||
constant.MessageTypeCustom: func(m interface{}) WayTester { return m.(*WayDetailCustom) },
|
||||
constant.MessageTypeWeChatOFAccount: func(m interface{}) WayTester { return m.(*WeChatOFAccount) },
|
||||
constant.MessageTypeMessageNest: func(m interface{}) WayTester { return m.(*MessageNest) },
|
||||
constant.MessageTypeAliyunSMS: func(m interface{}) WayTester { return m.(*WayDetailAliyunSMS) },
|
||||
constant.MessageTypeTelegram: func(m interface{}) WayTester { return m.(*WayDetailTelegram) },
|
||||
constant.MessageTypeBark: func(m interface{}) WayTester { return m.(*WayDetailBark) },
|
||||
constant.MessageTypePushMe: func(m interface{}) WayTester { return m.(*WayDetailPushMe) },
|
||||
constant.MessageTypeEmail: func(m interface{}) WayTester { return m.(*WayDetailEmail) },
|
||||
constant.MessageTypeDtalk: func(m interface{}) WayTester { return m.(*WayDetailDTalk) },
|
||||
constant.MessageTypeQyWeiXin: func(m interface{}) WayTester { return m.(*WayDetailQyWeiXin) },
|
||||
constant.MessageTypeWeChatCorpAccount: func(m interface{}) WayTester { return m.(*WayDetailWeChatCorpAccount) },
|
||||
constant.MessageTypeFeishu: func(m interface{}) WayTester { return m.(*WayDetailFeishu) },
|
||||
constant.MessageTypeCustom: func(m interface{}) WayTester { return m.(*WayDetailCustom) },
|
||||
constant.MessageTypeWeChatOFAccount: func(m interface{}) WayTester { return m.(*WeChatOFAccount) },
|
||||
constant.MessageTypeMessageNest: func(m interface{}) WayTester { return m.(*MessageNest) },
|
||||
constant.MessageTypeAliyunSMS: func(m interface{}) WayTester { return m.(*WayDetailAliyunSMS) },
|
||||
constant.MessageTypeTelegram: func(m interface{}) WayTester { return m.(*WayDetailTelegram) },
|
||||
constant.MessageTypeBark: func(m interface{}) WayTester { return m.(*WayDetailBark) },
|
||||
constant.MessageTypePushMe: func(m interface{}) WayTester { return m.(*WayDetailPushMe) },
|
||||
}
|
||||
)
|
||||
|
||||
@@ -147,6 +149,37 @@ func (w *WayDetailQyWeiXin) Test() (string, string) {
|
||||
return "", string(res)
|
||||
}
|
||||
|
||||
type WayDetailWeChatCorpAccount struct {
|
||||
CorpID string `json:"corp_id" validate:"required,max=100" label:"企业微信CorpID"`
|
||||
AgentID int `json:"agent_id" validate:"required,min=1" label:"企业微信AgentID"`
|
||||
AgentSecret string `json:"agent_secret" validate:"required,max=200" label:"企业微信AgentSecret"`
|
||||
ProxyURL string `json:"proxy_url" validate:"max=200" label:"代理地址"`
|
||||
}
|
||||
|
||||
func (w *WayDetailWeChatCorpAccount) Validate(authJson string) (string, interface{}) {
|
||||
var empty interface{}
|
||||
err := json.Unmarshal([]byte(authJson), w)
|
||||
if err != nil {
|
||||
return "企业微信应用auth反序列化失败!", empty
|
||||
}
|
||||
_, msg := app.CommonPlaygroundValid(*w)
|
||||
return msg, w
|
||||
}
|
||||
|
||||
func (w *WayDetailWeChatCorpAccount) Test() (string, string) {
|
||||
cli := message.WeChatCorpAccount{
|
||||
CorpID: w.CorpID,
|
||||
AgentID: w.AgentID,
|
||||
AgentSecret: w.AgentSecret,
|
||||
ProxyURL: w.ProxyURL,
|
||||
}
|
||||
_, err := cli.GetAccessToken()
|
||||
if err != nil {
|
||||
return fmt.Sprintf("连接失败:%s", err.Error()), ""
|
||||
}
|
||||
return "", "access_token获取成功"
|
||||
}
|
||||
|
||||
// WayDetailFeishu 飞书渠道明细字段
|
||||
type WayDetailFeishu struct {
|
||||
AccessToken string `json:"access_token" validate:"required,max=100" label:"飞书access_token"`
|
||||
|
||||
Reference in New Issue
Block a user