18c9354095
- 注入数据库、运行时配置、统一响应、文件存储和平台 AI 能力,补充业务读写工具与客户快捷操作契约。 - 移除模块内重复的组织、客户、工单、标签、技能、旧工作流、MCP 和迁移实现,将身份权限与业务主体交由宿主管理。 - 使用 libSQL 重构向量存储,并完善图片消息、访客身份、排队调度、企业微信和支持聊天页面。 - 统一 HTTP、DTO 与 WebSocket 的 snake_case 协议,补齐模块初始化、业务动作和公共载荷等回归测试。
154 lines
3.7 KiB
Go
154 lines
3.7 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"code.tczkiot.com/wlw/ai-agent/identity"
|
|
"code.tczkiot.com/wlw/ai-agent/internal/wxwork"
|
|
|
|
"github.com/mlogclub/simple/common/arrs"
|
|
wxmessage "github.com/silenceper/wechat/v2/work/message"
|
|
"github.com/spf13/cast"
|
|
)
|
|
|
|
var WxWorkNotifyService = newWxWorkNotifyService()
|
|
|
|
type wxWorkMessageSender interface {
|
|
SendText(request wxmessage.SendTextRequest) (*wxmessage.SendResponse, error)
|
|
}
|
|
|
|
type wxWorkNotifyService struct {
|
|
senderFactory func() (wxWorkMessageSender, error)
|
|
}
|
|
|
|
func newWxWorkNotifyService() *wxWorkNotifyService {
|
|
return &wxWorkNotifyService{
|
|
senderFactory: func() (wxWorkMessageSender, error) {
|
|
if !wxwork.Enabled() || wxwork.GetWorkCli() == nil {
|
|
return nil, fmt.Errorf("wxwork is not enabled")
|
|
}
|
|
return wxwork.GetWorkCli().GetMessage(), nil
|
|
},
|
|
}
|
|
}
|
|
|
|
func (s *wxWorkNotifyService) Enabled() bool {
|
|
if !wxwork.Enabled() {
|
|
return false
|
|
}
|
|
cfg, err := wxwork.CurrentConfig()
|
|
return err == nil && cfg.Notify.Enabled
|
|
}
|
|
|
|
func (s *wxWorkNotifyService) SendTextToAssigneeOrDefault(assigneeID int64, title, body string) error {
|
|
if !s.Enabled() {
|
|
return nil
|
|
}
|
|
toUsers := s.resolveToUsersByUserIDs([]int64{assigneeID})
|
|
if len(toUsers) == 0 {
|
|
toUsers = s.defaultToUsers()
|
|
}
|
|
if len(toUsers) == 0 {
|
|
return nil
|
|
}
|
|
return s.sendText(title, body, toUsers)
|
|
}
|
|
|
|
func (s *wxWorkNotifyService) sendText(title, body string, toUsers []string) error {
|
|
if !s.Enabled() {
|
|
return nil
|
|
}
|
|
content := s.buildTextContent(title, body)
|
|
if content == "" {
|
|
return nil
|
|
}
|
|
sender, err := s.senderFactory()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cfg, err := wxwork.CurrentConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req := wxmessage.SendTextRequest{
|
|
SendRequestCommon: &wxmessage.SendRequestCommon{
|
|
ToUser: strings.Join(toUsers, "|"),
|
|
AgentID: strings.TrimSpace(cfg.AgentID),
|
|
Safe: cast.ToInt(cfg.Notify.Safe),
|
|
EnableDuplicateCheck: cast.ToInt(cfg.Notify.EnableDuplicateCheck),
|
|
DuplicateCheckInterval: s.normalizeDuplicateCheckInterval(cfg.Notify.DuplicateCheckInterval),
|
|
},
|
|
Text: wxmessage.TextField{Content: content},
|
|
}
|
|
_, err = sender.SendText(req)
|
|
return err
|
|
}
|
|
|
|
func (s *wxWorkNotifyService) resolveToUsersByUserIDs(userIDs []int64) []string {
|
|
userIDs = arrs.Distinct(userIDs)
|
|
if len(userIDs) == 0 {
|
|
return nil
|
|
}
|
|
subjects, err := SubjectService.Query(context.Background(), identity.Query{
|
|
Types: []identity.SubjectType{identity.SubjectAdmin},
|
|
IDs: userIDs,
|
|
EnabledOnly: true,
|
|
})
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
toUsers := make([]string, 0, len(subjects))
|
|
for i := range subjects {
|
|
if receiver := strings.TrimSpace(subjects[i].Bindings["wxwork"]); receiver != "" {
|
|
toUsers = append(toUsers, receiver)
|
|
}
|
|
}
|
|
return arrs.Distinct(toUsers)
|
|
}
|
|
|
|
func (s *wxWorkNotifyService) defaultToUsers() []string {
|
|
cfg, err := wxwork.CurrentConfig()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return s.resolveToUsersByUserIDs(cfg.Notify.ToUsers)
|
|
}
|
|
|
|
func (s *wxWorkNotifyService) buildTextContent(title, body string) string {
|
|
title = strings.TrimSpace(title)
|
|
body = strings.TrimSpace(body)
|
|
switch {
|
|
case title == "" && body == "":
|
|
return ""
|
|
case title == "":
|
|
return s.truncateRunes(body, 1024)
|
|
case body == "":
|
|
return s.truncateRunes(title, 1024)
|
|
default:
|
|
return s.truncateRunes(title+"\n\n"+body, 1024)
|
|
}
|
|
}
|
|
|
|
func (s *wxWorkNotifyService) normalizeDuplicateCheckInterval(value int) int {
|
|
if value <= 0 {
|
|
return 1800
|
|
}
|
|
if value > 14400 {
|
|
return 14400
|
|
}
|
|
return value
|
|
}
|
|
|
|
func (s *wxWorkNotifyService) truncateRunes(value string, max int) string {
|
|
if max <= 0 {
|
|
return ""
|
|
}
|
|
runes := []rune(strings.TrimSpace(value))
|
|
if len(runes) <= max {
|
|
return string(runes)
|
|
}
|
|
return string(runes[:max])
|
|
}
|