ai_scheduler/internal/services/callback.go

173 lines
4.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package services
import (
"ai_scheduler/internal/config"
errorcode "ai_scheduler/internal/data/error"
"ai_scheduler/internal/gateway"
"ai_scheduler/internal/pkg/dingtalk"
"ai_scheduler/internal/pkg/util"
"ai_scheduler/internal/tools_bot"
"context"
"encoding/json"
"strings"
"time"
"github.com/gofiber/fiber/v2"
)
// CallbackService 统一回调入口
type CallbackService struct {
cfg *config.Config
gateway *gateway.Gateway
dingtalkClient *dingtalk.Client
botTool *tools_bot.BotTool
}
func NewCallbackService(cfg *config.Config, gateway *gateway.Gateway, dingtalkClient *dingtalk.Client, botTool *tools_bot.BotTool) *CallbackService {
return &CallbackService{
cfg: cfg,
gateway: gateway,
dingtalkClient: dingtalkClient,
botTool: botTool,
}
}
// Envelope 回调统一请求体
type Envelope struct {
Action string `json:"action"`
TaskID string `json:"task_id"`
Data map[string]string `json:"data"`
}
// Callback 统一回调处理
// 头部X-Source-Key / X-Timestamp
func (s *CallbackService) Callback(c *fiber.Ctx) error {
// 读取头
sourceKey := strings.TrimSpace(c.Get("X-Source-Key"))
ts := strings.TrimSpace(c.Get("X-Timestamp"))
// 时间窗口(如果提供了 ts 则校验,否则跳过),窗口 5 分钟
if ts != "" && !validateTimestamp(ts, 300*time.Minute) {
return errorcode.AuthNotFound
}
// 解析 Envelope
var env Envelope
if err := json.Unmarshal(c.Body(), &env); err != nil {
return errorcode.ParamErr("invalid json: %v", err)
}
if env.Action == "" || env.TaskID == "" {
return errorcode.ParamErr("missing action/task_id")
}
if len(env.Data) == 0 {
return errorcode.ParamErr("missing data")
}
switch sourceKey {
case "dingtalk":
return s.handleDingTalkCallback(c, env)
default:
return errorcode.AuthNotFound
}
}
func validateTimestamp(ts string, window time.Duration) bool {
// 期望毫秒时间戳或秒级,简单容错
// 尝试解析为整数
var n int64
for _, base := range []int64{1, 1000} { // 秒或毫秒
if v, ok := parseInt64(ts); ok {
n = v
// 归一为毫秒
if base == 1 && len(ts) <= 10 {
n = n * 1000
}
now := time.Now().UnixMilli()
diff := now - n
if diff < 0 {
diff = -diff
}
if diff <= window.Milliseconds() {
return true
}
}
}
return false
}
func parseInt64(s string) (int64, bool) {
var n int64
for _, ch := range s {
if ch < '0' || ch > '9' {
return 0, false
}
n = n*10 + int64(ch-'0')
}
return n, true
}
func (s *CallbackService) handleDingTalkCallback(c *fiber.Ctx, env Envelope) error {
switch env.Action {
// bug/优化完成回调
case "bug_optimization_submit_done":
// 获取 session_id
sessionID, ok := s.botTool.GetSessionByTaskID(env.TaskID)
if !ok {
return errorcode.ParamErr("missing session_id for task_id: %s", env.TaskID)
}
// 获取接收者
receiverJson := env.Data["receivers"]
if receiverJson == "" {
return errorcode.ParamErr("missing receivers")
}
var receiverIds []string
if err := json.Unmarshal([]byte(receiverJson), &receiverIds); err != nil {
return errorcode.ParamErr("invalid receivers: %v", err)
}
if len(receiverIds) == 0 {
return errorcode.ParamErr("empty receivers")
}
// 构建接收者
receivers := s.getDingtalkReceivers(c.Context(), receiverIds)
// 构建跳转链接
detailPage := env.Data["detail_page"]
if detailPage != "" {
detailPage = util.BuildJumpLink(detailPage, "去查看")
}
msg := env.Data["msg"]
msg = util.ReplacePlaceholder(msg, "receivers", receivers)
msg = util.ReplacePlaceholder(msg, "detail_page", detailPage)
s.gateway.SendToUid(sessionID, []byte(msg))
// 删除映射
s.botTool.DelTaskMapping(env.TaskID)
return c.JSON(fiber.Map{"code": 0, "message": "ok"})
default:
return errorcode.ParamErr("unknown action: %s", env.Action)
}
}
// getDingtalkReceivers 解析接收者字符串为 DingTalk 用户 ID 列表
func (s *CallbackService) getDingtalkReceivers(ctx context.Context, receiverIds []string) string {
var receiverNames []string
for _, receiverId := range receiverIds {
userDetails, err := s.dingtalkClient.QueryUserDetails(ctx, receiverId)
if err != nil {
return ""
}
if userDetails == nil {
return ""
}
receiverNames = append(receiverNames, "@"+userDetails.Name)
}
receivers := strings.Join(receiverNames, " ")
return receivers
}