feat: 新增企业微信应用消息发送渠道
- 添加 WeChatCorpAccount 消息类型常量 - 新增企业微信应用配置结构体和渠道验证逻辑 - 实现企业微信应用消息发送服务,支持 text、markdown 和 textcard 格式 - 添加前端配置界面,支持动态接收者设置 - 集成代理支持(HTTP/HTTPS/SOCKS5)
This commit is contained in:
@@ -33,6 +33,10 @@ type InsDtalkConfig struct {
|
|||||||
type InsQyWeiXinConfig struct {
|
type InsQyWeiXinConfig struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InsWeChatCorpAccountConfig struct {
|
||||||
|
ToAccount string `json:"to_account" validate:"required,max=200" label:"接收者UserId"`
|
||||||
|
}
|
||||||
|
|
||||||
// InsFeishuConfig 实例里面的飞书config
|
// InsFeishuConfig 实例里面的飞书config
|
||||||
type InsFeishuConfig struct {
|
type InsFeishuConfig struct {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ const (
|
|||||||
MessageTypeEmail = "Email"
|
MessageTypeEmail = "Email"
|
||||||
MessageTypeDtalk = "Dtalk"
|
MessageTypeDtalk = "Dtalk"
|
||||||
MessageTypeQyWeiXin = "QyWeiXin"
|
MessageTypeQyWeiXin = "QyWeiXin"
|
||||||
|
MessageTypeWeChatCorpAccount = "WeChatCorpAccount"
|
||||||
MessageTypeFeishu = "Feishu"
|
MessageTypeFeishu = "Feishu"
|
||||||
MessageTypeCustom = "Custom"
|
MessageTypeCustom = "Custom"
|
||||||
MessageTypeWeChatOFAccount = "WeChatOFAccount"
|
MessageTypeWeChatOFAccount = "WeChatOFAccount"
|
||||||
|
|||||||
@@ -0,0 +1,279 @@
|
|||||||
|
package message
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/proxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
type wechatCorpTokenResponse struct {
|
||||||
|
ErrCode int `json:"errcode"`
|
||||||
|
ErrMsg string `json:"errmsg"`
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type wechatCorpSendResponse struct {
|
||||||
|
ErrCode int `json:"errcode"`
|
||||||
|
ErrMsg string `json:"errmsg"`
|
||||||
|
InvalidUser string `json:"invaliduser,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type wechatCorpSendRequest struct {
|
||||||
|
ToUser string `json:"touser,omitempty"`
|
||||||
|
MsgType string `json:"msgtype"`
|
||||||
|
AgentID int `json:"agentid"`
|
||||||
|
Safe int `json:"safe,omitempty"`
|
||||||
|
Text *struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
} `json:"text,omitempty"`
|
||||||
|
Markdown *struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
} `json:"markdown,omitempty"`
|
||||||
|
TextCard *struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
} `json:"textcard,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type wechatCorpTokenCacheItem struct {
|
||||||
|
token string
|
||||||
|
expiresAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
var wechatCorpTokenCache = struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
m map[string]wechatCorpTokenCacheItem
|
||||||
|
}{
|
||||||
|
m: make(map[string]wechatCorpTokenCacheItem),
|
||||||
|
}
|
||||||
|
|
||||||
|
type WeChatCorpAccount struct {
|
||||||
|
CorpID string
|
||||||
|
AgentID int
|
||||||
|
AgentSecret string
|
||||||
|
ProxyURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *WeChatCorpAccount) cacheKey() string {
|
||||||
|
return fmt.Sprintf("%s|%d|%s", c.CorpID, c.AgentID, c.AgentSecret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *WeChatCorpAccount) GetAccessToken() (string, error) {
|
||||||
|
if c.CorpID == "" || c.AgentSecret == "" {
|
||||||
|
return "", errors.New("企业微信应用参数缺失")
|
||||||
|
}
|
||||||
|
|
||||||
|
key := c.cacheKey()
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
wechatCorpTokenCache.mu.RLock()
|
||||||
|
item, ok := wechatCorpTokenCache.m[key]
|
||||||
|
wechatCorpTokenCache.mu.RUnlock()
|
||||||
|
|
||||||
|
if ok && item.token != "" && now.Before(item.expiresAt) {
|
||||||
|
return item.token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
token, expiresAt, err := c.refreshAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
wechatCorpTokenCache.mu.Lock()
|
||||||
|
wechatCorpTokenCache.m[key] = wechatCorpTokenCacheItem{token: token, expiresAt: expiresAt}
|
||||||
|
wechatCorpTokenCache.mu.Unlock()
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *WeChatCorpAccount) refreshAccessToken() (string, time.Time, error) {
|
||||||
|
client := c.getHTTPClient()
|
||||||
|
reqURL := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s",
|
||||||
|
url.QueryEscape(c.CorpID), url.QueryEscape(c.AgentSecret))
|
||||||
|
|
||||||
|
resp, err := client.Get(reqURL)
|
||||||
|
if err != nil {
|
||||||
|
return "", time.Time{}, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", time.Time{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var res wechatCorpTokenResponse
|
||||||
|
if err := json.Unmarshal(body, &res); err != nil {
|
||||||
|
return "", time.Time{}, err
|
||||||
|
}
|
||||||
|
if res.ErrCode != 0 {
|
||||||
|
return "", time.Time{}, errors.New(res.ErrMsg)
|
||||||
|
}
|
||||||
|
if res.AccessToken == "" || res.ExpiresIn <= 0 {
|
||||||
|
return "", time.Time{}, errors.New("企业微信 access_token 响应无效")
|
||||||
|
}
|
||||||
|
|
||||||
|
expiresAt := time.Now().Add(time.Duration(res.ExpiresIn) * time.Second).Add(-60 * time.Second)
|
||||||
|
return res.AccessToken, expiresAt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *WeChatCorpAccount) SendText(toUser string, content string) (string, error) {
|
||||||
|
req := wechatCorpSendRequest{
|
||||||
|
ToUser: toUser,
|
||||||
|
MsgType: "text",
|
||||||
|
AgentID: c.AgentID,
|
||||||
|
Text: &struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
}{Content: content},
|
||||||
|
}
|
||||||
|
return c.send(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *WeChatCorpAccount) SendMarkdown(toUser string, content string) (string, error) {
|
||||||
|
req := wechatCorpSendRequest{
|
||||||
|
ToUser: toUser,
|
||||||
|
MsgType: "markdown",
|
||||||
|
AgentID: c.AgentID,
|
||||||
|
Markdown: &struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
}{Content: content},
|
||||||
|
}
|
||||||
|
return c.send(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *WeChatCorpAccount) SendTextCard(toUser, title, description, linkURL string) (string, error) {
|
||||||
|
req := wechatCorpSendRequest{
|
||||||
|
ToUser: toUser,
|
||||||
|
MsgType: "textcard",
|
||||||
|
AgentID: c.AgentID,
|
||||||
|
TextCard: &struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
}{
|
||||||
|
Title: title,
|
||||||
|
Description: description,
|
||||||
|
URL: linkURL,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return c.send(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *WeChatCorpAccount) send(req wechatCorpSendRequest) (string, error) {
|
||||||
|
if req.ToUser == "" {
|
||||||
|
return "", errors.New("企业微信应用接收者不能为空")
|
||||||
|
}
|
||||||
|
if c.AgentID <= 0 {
|
||||||
|
return "", errors.New("企业微信应用 AgentID 无效")
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := c.GetAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := c.getHTTPClient()
|
||||||
|
apiURL := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=%s", url.QueryEscape(token))
|
||||||
|
resp, err := client.Post(apiURL, "application/json", bytes.NewBuffer(b))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var res wechatCorpSendResponse
|
||||||
|
if err := json.Unmarshal(body, &res); err != nil {
|
||||||
|
return string(body), err
|
||||||
|
}
|
||||||
|
if res.ErrCode != 0 {
|
||||||
|
if res.InvalidUser != "" {
|
||||||
|
return string(body), errors.New(fmt.Sprintf("%s (invaliduser=%s)", res.ErrMsg, res.InvalidUser))
|
||||||
|
}
|
||||||
|
return string(body), errors.New(res.ErrMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(body), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *WeChatCorpAccount) getHTTPClient() *http.Client {
|
||||||
|
client := &http.Client{
|
||||||
|
Timeout: 15 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.ProxyURL == "" {
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyURL, err := url.Parse(c.ProxyURL)
|
||||||
|
if err != nil {
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(strings.ToLower(c.ProxyURL), "socks5://") {
|
||||||
|
dialer, err := c.createSOCKS5Dialer(proxyURL)
|
||||||
|
if err != nil {
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
client.Transport = &http.Transport{
|
||||||
|
DialContext: dialer.DialContext,
|
||||||
|
}
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Transport = &http.Transport{
|
||||||
|
Proxy: http.ProxyURL(proxyURL),
|
||||||
|
}
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *WeChatCorpAccount) createSOCKS5Dialer(proxyURL *url.URL) (proxy.ContextDialer, error) {
|
||||||
|
host := proxyURL.Host
|
||||||
|
|
||||||
|
var auth *proxy.Auth
|
||||||
|
if proxyURL.User != nil {
|
||||||
|
password, _ := proxyURL.User.Password()
|
||||||
|
auth = &proxy.Auth{
|
||||||
|
User: proxyURL.User.Username(),
|
||||||
|
Password: password,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
baseDialer := &net.Dialer{
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
KeepAlive: 30 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
dialer, err := proxy.SOCKS5("tcp", host, auth, baseDialer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
contextDialer, ok := dialer.(proxy.ContextDialer)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("failed to convert to ContextDialer")
|
||||||
|
}
|
||||||
|
|
||||||
|
return contextDialer, nil
|
||||||
|
}
|
||||||
|
|
||||||
@@ -58,6 +58,24 @@ func (sw *SendTaskInsService) ValidateDiffIns(ins models.SendTasksIns) (string,
|
|||||||
var Config models.InsQyWeiXinConfig
|
var Config models.InsQyWeiXinConfig
|
||||||
return "", Config
|
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 {
|
if ins.WayType == constant.MessageTypeFeishu {
|
||||||
var Config models.InsFeishuConfig
|
var Config models.InsFeishuConfig
|
||||||
return "", Config
|
return "", Config
|
||||||
|
|||||||
@@ -438,6 +438,7 @@ func (sm *SendMessageService) supportsDynamicRecipient(wayType string) bool {
|
|||||||
supportedTypes := map[string]bool{
|
supportedTypes := map[string]bool{
|
||||||
constant.MessageTypeEmail: true,
|
constant.MessageTypeEmail: true,
|
||||||
constant.MessageTypeWeChatOFAccount: true,
|
constant.MessageTypeWeChatOFAccount: true,
|
||||||
|
constant.MessageTypeWeChatCorpAccount: true,
|
||||||
constant.MessageTypeAliyunSMS: true,
|
constant.MessageTypeAliyunSMS: true,
|
||||||
// 可以继续添加其他支持动态接收者的渠道
|
// 可以继续添加其他支持动态接收者的渠道
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const (
|
|||||||
MessageTypeEmail = channels.MessageTypeEmail
|
MessageTypeEmail = channels.MessageTypeEmail
|
||||||
MessageTypeDtalk = channels.MessageTypeDtalk
|
MessageTypeDtalk = channels.MessageTypeDtalk
|
||||||
MessageTypeQyWeiXin = channels.MessageTypeQyWeiXin
|
MessageTypeQyWeiXin = channels.MessageTypeQyWeiXin
|
||||||
|
MessageTypeWeChatCorpAccount = channels.MessageTypeWeChatCorpAccount
|
||||||
MessageTypeFeishu = channels.MessageTypeFeishu
|
MessageTypeFeishu = channels.MessageTypeFeishu
|
||||||
MessageTypeCustom = channels.MessageTypeCustom
|
MessageTypeCustom = channels.MessageTypeCustom
|
||||||
MessageTypeWeChatOFAccount = channels.MessageTypeWeChatOFAccount
|
MessageTypeWeChatOFAccount = channels.MessageTypeWeChatOFAccount
|
||||||
@@ -97,6 +98,7 @@ func GetGlobalChannelRegistry() *ChannelRegistry {
|
|||||||
globalChannelRegistry.Register(channels.NewEmailChannel())
|
globalChannelRegistry.Register(channels.NewEmailChannel())
|
||||||
globalChannelRegistry.Register(channels.NewDtalkChannel())
|
globalChannelRegistry.Register(channels.NewDtalkChannel())
|
||||||
globalChannelRegistry.Register(channels.NewQyWeiXinChannel())
|
globalChannelRegistry.Register(channels.NewQyWeiXinChannel())
|
||||||
|
globalChannelRegistry.Register(channels.NewWeChatCorpAccountChannel())
|
||||||
globalChannelRegistry.Register(channels.NewFeishuChannel())
|
globalChannelRegistry.Register(channels.NewFeishuChannel())
|
||||||
globalChannelRegistry.Register(channels.NewCustomChannel())
|
globalChannelRegistry.Register(channels.NewCustomChannel())
|
||||||
globalChannelRegistry.Register(channels.NewWeChatOFAccountChannel())
|
globalChannelRegistry.Register(channels.NewWeChatOFAccountChannel())
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const (
|
|||||||
MessageTypeEmail = constant.MessageTypeEmail
|
MessageTypeEmail = constant.MessageTypeEmail
|
||||||
MessageTypeDtalk = constant.MessageTypeDtalk
|
MessageTypeDtalk = constant.MessageTypeDtalk
|
||||||
MessageTypeQyWeiXin = constant.MessageTypeQyWeiXin
|
MessageTypeQyWeiXin = constant.MessageTypeQyWeiXin
|
||||||
|
MessageTypeWeChatCorpAccount = constant.MessageTypeWeChatCorpAccount
|
||||||
MessageTypeFeishu = constant.MessageTypeFeishu
|
MessageTypeFeishu = constant.MessageTypeFeishu
|
||||||
MessageTypeCustom = constant.MessageTypeCustom
|
MessageTypeCustom = constant.MessageTypeCustom
|
||||||
MessageTypeWeChatOFAccount = constant.MessageTypeWeChatOFAccount
|
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
|
||||||
|
}
|
||||||
|
|
||||||
@@ -40,6 +40,7 @@ var (
|
|||||||
constant.MessageTypeEmail: func() WayValidator { return &WayDetailEmail{} },
|
constant.MessageTypeEmail: func() WayValidator { return &WayDetailEmail{} },
|
||||||
constant.MessageTypeDtalk: func() WayValidator { return &WayDetailDTalk{} },
|
constant.MessageTypeDtalk: func() WayValidator { return &WayDetailDTalk{} },
|
||||||
constant.MessageTypeQyWeiXin: func() WayValidator { return &WayDetailQyWeiXin{} },
|
constant.MessageTypeQyWeiXin: func() WayValidator { return &WayDetailQyWeiXin{} },
|
||||||
|
constant.MessageTypeWeChatCorpAccount: func() WayValidator { return &WayDetailWeChatCorpAccount{} },
|
||||||
constant.MessageTypeFeishu: func() WayValidator { return &WayDetailFeishu{} },
|
constant.MessageTypeFeishu: func() WayValidator { return &WayDetailFeishu{} },
|
||||||
constant.MessageTypeCustom: func() WayValidator { return &WayDetailCustom{} },
|
constant.MessageTypeCustom: func() WayValidator { return &WayDetailCustom{} },
|
||||||
constant.MessageTypeWeChatOFAccount: func() WayValidator { return &WeChatOFAccount{} },
|
constant.MessageTypeWeChatOFAccount: func() WayValidator { return &WeChatOFAccount{} },
|
||||||
@@ -53,6 +54,7 @@ var (
|
|||||||
constant.MessageTypeEmail: func(m interface{}) WayTester { return m.(*WayDetailEmail) },
|
constant.MessageTypeEmail: func(m interface{}) WayTester { return m.(*WayDetailEmail) },
|
||||||
constant.MessageTypeDtalk: func(m interface{}) WayTester { return m.(*WayDetailDTalk) },
|
constant.MessageTypeDtalk: func(m interface{}) WayTester { return m.(*WayDetailDTalk) },
|
||||||
constant.MessageTypeQyWeiXin: func(m interface{}) WayTester { return m.(*WayDetailQyWeiXin) },
|
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.MessageTypeFeishu: func(m interface{}) WayTester { return m.(*WayDetailFeishu) },
|
||||||
constant.MessageTypeCustom: func(m interface{}) WayTester { return m.(*WayDetailCustom) },
|
constant.MessageTypeCustom: func(m interface{}) WayTester { return m.(*WayDetailCustom) },
|
||||||
constant.MessageTypeWeChatOFAccount: func(m interface{}) WayTester { return m.(*WeChatOFAccount) },
|
constant.MessageTypeWeChatOFAccount: func(m interface{}) WayTester { return m.(*WeChatOFAccount) },
|
||||||
@@ -147,6 +149,37 @@ func (w *WayDetailQyWeiXin) Test() (string, string) {
|
|||||||
return "", string(res)
|
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 飞书渠道明细字段
|
// WayDetailFeishu 飞书渠道明细字段
|
||||||
type WayDetailFeishu struct {
|
type WayDetailFeishu struct {
|
||||||
AccessToken string `json:"access_token" validate:"required,max=100" label:"飞书access_token"`
|
AccessToken string `json:"access_token" validate:"required,max=100" label:"飞书access_token"`
|
||||||
|
|||||||
@@ -207,6 +207,9 @@ const getFinalData = () => {
|
|||||||
if (config.type == 'Email' && input.col == 'port') {
|
if (config.type == 'Email' && input.col == 'port') {
|
||||||
authData[input.col] = parseInt(formData.value[input.col])
|
authData[input.col] = parseInt(formData.value[input.col])
|
||||||
}
|
}
|
||||||
|
if (config.type == 'WeChatCorpAccount' && input.col == 'agent_id') {
|
||||||
|
authData[input.col] = parseInt(formData.value[input.col])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -259,6 +262,7 @@ const getChannelIcon = (type: string) => {
|
|||||||
'Email': Mail,
|
'Email': Mail,
|
||||||
'Dtalk': MessageSquare,
|
'Dtalk': MessageSquare,
|
||||||
'QyWeiXin': MessageCircle,
|
'QyWeiXin': MessageCircle,
|
||||||
|
'WeChatCorpAccount': MessageCircle,
|
||||||
'Feishu': Send,
|
'Feishu': Send,
|
||||||
'Custom': Webhook,
|
'Custom': Webhook,
|
||||||
'WeChatOFAccount': MessageCircleCode,
|
'WeChatOFAccount': MessageCircleCode,
|
||||||
|
|||||||
@@ -76,6 +76,34 @@ const CONSTANT = {
|
|||||||
taskInsInputs: [
|
taskInsInputs: [
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'WeChatCorpAccount',
|
||||||
|
label: '企业微信应用',
|
||||||
|
dynamicRecipient: {
|
||||||
|
support: true,
|
||||||
|
field: 'to_account',
|
||||||
|
label: '接收者UserId',
|
||||||
|
desc: 'UserId',
|
||||||
|
},
|
||||||
|
inputs: [
|
||||||
|
{ subLabel: 'corp_id', value: '', col: 'corp_id', desc: "企业微信企业ID(CorpID)" },
|
||||||
|
{ subLabel: 'agent_id', value: '', col: 'agent_id', desc: "企业微信应用AgentId(数字)" },
|
||||||
|
{ subLabel: 'agent_secret', value: '', col: 'agent_secret', desc: "企业微信应用Secret" },
|
||||||
|
{ subLabel: 'proxy_url', value: '', col: 'proxy_url', desc: "可选:代理地址,支持 http://、https://、socks5://" },
|
||||||
|
{ subLabel: '渠道名', value: '', col: 'name', desc: "想要设置的渠道名字" },
|
||||||
|
],
|
||||||
|
tips: {
|
||||||
|
text: "企业微信应用说明",
|
||||||
|
desc: "通过企业微信应用消息接口发送消息,需要配置 CorpID / AgentId / Secret。可选 proxy_url 用于 http/https/socks5 代理。"
|
||||||
|
},
|
||||||
|
taskInsRadios: [
|
||||||
|
{ subLabel: 'text', content: 'text' },
|
||||||
|
{ subLabel: 'markdown', content: 'markdown' },
|
||||||
|
],
|
||||||
|
taskInsInputs: [
|
||||||
|
{ value: '', col: 'to_account', desc: '接收者UserId', label: '接收者UserId' },
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'Feishu',
|
type: 'Feishu',
|
||||||
label: '飞书机器人',
|
label: '飞书机器人',
|
||||||
|
|||||||
Reference in New Issue
Block a user