chore: 1. callbackService 结构优化 2. dingtalkClient 目录结构优化 3. tools_bot 结构优化
This commit is contained in:
parent
eaa2d4ca7e
commit
8e74c434ae
|
|
@ -1,4 +1,4 @@
|
|||
package dingtalk_contact
|
||||
package dingtalk
|
||||
|
||||
import (
|
||||
"ai_scheduler/internal/config"
|
||||
|
|
@ -10,12 +10,12 @@ import (
|
|||
"github.com/alibabacloud-go/tea/tea"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
type ContactClient struct {
|
||||
config *config.Config
|
||||
cli *contact.Client
|
||||
}
|
||||
|
||||
func NewContactClient(config *config.Config) (*Client, error) {
|
||||
func NewContactClient(config *config.Config) (*ContactClient, error) {
|
||||
cfg := &openapi.Config{
|
||||
AccessKeyId: tea.String(config.Tools.DingTalkBot.APIKey),
|
||||
AccessKeySecret: tea.String(config.Tools.DingTalkBot.APISecret),
|
||||
|
|
@ -26,7 +26,7 @@ func NewContactClient(config *config.Config) (*Client, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Client{config: config, cli: c}, nil
|
||||
return &ContactClient{config: config, cli: c}, nil
|
||||
}
|
||||
|
||||
type SearchUserReq struct {
|
||||
|
|
@ -40,7 +40,7 @@ type SearchUserResp struct {
|
|||
Body interface{}
|
||||
}
|
||||
|
||||
func (c *Client) SearchUserOne(accessToken string, name string) (string, error) {
|
||||
func (c *ContactClient) SearchUserOne(accessToken string, name string) (string, error) {
|
||||
headers := &contact.SearchUserHeaders{}
|
||||
headers.XAcsDingtalkAccessToken = tea.String(accessToken)
|
||||
resp, err := c.cli.SearchUserWithOptions(&contact.SearchUserRequest{
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dingtalk_notable
|
||||
package dingtalk
|
||||
|
||||
import (
|
||||
"ai_scheduler/internal/config"
|
||||
|
|
@ -10,12 +10,12 @@ import (
|
|||
"github.com/alibabacloud-go/tea/tea"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
type NotableClient struct {
|
||||
config *config.Config
|
||||
cli *notable.Client
|
||||
}
|
||||
|
||||
func NewNotableClient(config *config.Config) (*Client, error) {
|
||||
func NewNotableClient(config *config.Config) (*NotableClient, error) {
|
||||
cfg := &openapi.Config{
|
||||
AccessKeyId: tea.String(config.Tools.DingTalkBot.APIKey),
|
||||
AccessKeySecret: tea.String(config.Tools.DingTalkBot.APISecret),
|
||||
|
|
@ -26,7 +26,7 @@ func NewNotableClient(config *config.Config) (*Client, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Client{config: config, cli: c}, nil
|
||||
return &NotableClient{config: config, cli: c}, nil
|
||||
}
|
||||
|
||||
type UpdateRecordReq struct {
|
||||
|
|
@ -41,7 +41,7 @@ type UpdateRecordsserResp struct {
|
|||
Body interface{}
|
||||
}
|
||||
|
||||
func (c *Client) UpdateRecord(accessToken string, req *UpdateRecordReq) (bool, error) {
|
||||
func (c *NotableClient) UpdateRecord(accessToken string, req *UpdateRecordReq) (bool, error) {
|
||||
headers := ¬able.UpdateRecordsHeaders{}
|
||||
headers.XAcsDingtalkAccessToken = tea.String(accessToken)
|
||||
resp, err := c.cli.UpdateRecordsWithOptions(
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
package dingtalk
|
||||
|
||||
// 旧版sdk客户端 - 在用,勿删!
|
||||
|
||||
import (
|
||||
"ai_scheduler/internal/config"
|
||||
"bytes"
|
||||
|
|
@ -15,26 +17,26 @@ import (
|
|||
"github.com/fastwego/dingding"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
cfg *config.Config
|
||||
sdkClient *dingding.Client
|
||||
atm *dingding.DefaultAccessTokenManager
|
||||
type OldClient struct {
|
||||
config *config.Config
|
||||
cli *dingding.Client
|
||||
atm *dingding.DefaultAccessTokenManager
|
||||
}
|
||||
|
||||
func NewDingTalkClient(cfg *config.Config) *Client {
|
||||
atm := &dingding.DefaultAccessTokenManager{
|
||||
Id: cfg.Tools.DingTalkBot.APIKey,
|
||||
func NewOldClient(config *config.Config) *OldClient {
|
||||
atm := &dingding.DefaultAccessTokenManager{
|
||||
Id: config.Tools.DingTalkBot.APIKey,
|
||||
Name: "access_token",
|
||||
GetRefreshRequestFunc: func() *http.Request {
|
||||
params := url.Values{}
|
||||
params.Add("appkey", cfg.Tools.DingTalkBot.APIKey)
|
||||
params.Add("appsecret", cfg.Tools.DingTalkBot.APISecret)
|
||||
params.Add("appkey", config.Tools.DingTalkBot.APIKey)
|
||||
params.Add("appsecret", config.Tools.DingTalkBot.APISecret)
|
||||
req, _ := http.NewRequest(http.MethodGet, dingding.ServerUrl+"/gettoken?"+params.Encode(), nil)
|
||||
return req
|
||||
},
|
||||
Cache: file.New(os.TempDir()),
|
||||
}
|
||||
return &Client{cfg: cfg, sdkClient: dingding.NewClient(atm), atm: atm}
|
||||
}
|
||||
return &OldClient{config: config, cli: dingding.NewClient(atm), atm: atm}
|
||||
}
|
||||
|
||||
type UserDetail struct {
|
||||
|
|
@ -43,7 +45,7 @@ type UserDetail struct {
|
|||
UnionID string `json:"unionid"`
|
||||
}
|
||||
|
||||
func (c *Client) do(ctx context.Context, method, path string, body []byte) ([]byte, error) {
|
||||
func (c *OldClient) do(ctx context.Context, method, path string, body []byte) ([]byte, error) {
|
||||
var r io.Reader
|
||||
if body != nil {
|
||||
r = bytes.NewReader(body)
|
||||
|
|
@ -55,10 +57,10 @@ func (c *Client) do(ctx context.Context, method, path string, body []byte) ([]by
|
|||
if body != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
return c.sdkClient.Do(req)
|
||||
return c.cli.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) QueryUserDetails(ctx context.Context, userId string) (*UserDetail, error) {
|
||||
func (c *OldClient) QueryUserDetails(ctx context.Context, userId string) (*UserDetail, error) {
|
||||
body := struct {
|
||||
UserId string `json:"userid"`
|
||||
Language string `json:"language,omitempty"`
|
||||
|
|
@ -82,7 +84,7 @@ func (c *Client) QueryUserDetails(ctx context.Context, userId string) (*UserDeta
|
|||
return &resp.Result, nil
|
||||
}
|
||||
|
||||
func (c *Client) QueryUserDetailsByMobile(ctx context.Context, mobile string) (*UserDetail, error) {
|
||||
func (c *OldClient) QueryUserDetailsByMobile(ctx context.Context, mobile string) (*UserDetail, error) {
|
||||
body := struct {
|
||||
Mobile string `json:"mobile"`
|
||||
}{Mobile: mobile}
|
||||
|
|
@ -104,8 +106,8 @@ func (c *Client) QueryUserDetailsByMobile(ctx context.Context, mobile string) (*
|
|||
}
|
||||
return &resp.Result, nil
|
||||
}
|
||||
// GetAccessToken 通过 fastwego 的 AccessTokenManager 获取当前可用 access_token
|
||||
func (c *Client) GetAccessToken() (string, error) {
|
||||
return c.atm.GetAccessToken()
|
||||
}
|
||||
|
||||
// GetAccessToken 通过 fastwego 的 AccessTokenManager 获取当前可用 access_token
|
||||
func (c *OldClient) GetAccessToken() (string, error) {
|
||||
return c.atm.GetAccessToken()
|
||||
}
|
||||
|
|
@ -2,8 +2,6 @@ package pkg
|
|||
|
||||
import (
|
||||
"ai_scheduler/internal/pkg/dingtalk"
|
||||
"ai_scheduler/internal/pkg/dingtalk_contact"
|
||||
"ai_scheduler/internal/pkg/dingtalk_notable"
|
||||
"ai_scheduler/internal/pkg/utils_langchain"
|
||||
"ai_scheduler/internal/pkg/utils_ollama"
|
||||
|
||||
|
|
@ -16,7 +14,7 @@ var ProviderSetClient = wire.NewSet(
|
|||
utils_langchain.NewUtilLangChain,
|
||||
utils_ollama.NewClient,
|
||||
NewSafeChannelPool,
|
||||
dingtalk.NewDingTalkClient,
|
||||
dingtalk_contact.NewContactClient,
|
||||
dingtalk_notable.NewNotableClient,
|
||||
dingtalk.NewOldClient,
|
||||
dingtalk.NewContactClient,
|
||||
dingtalk.NewNotableClient,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@ package services
|
|||
|
||||
import (
|
||||
"ai_scheduler/internal/config"
|
||||
"ai_scheduler/internal/data/constants"
|
||||
errorcode "ai_scheduler/internal/data/error"
|
||||
"ai_scheduler/internal/entitys"
|
||||
"ai_scheduler/internal/gateway"
|
||||
"ai_scheduler/internal/pkg"
|
||||
"ai_scheduler/internal/pkg/dingtalk"
|
||||
"ai_scheduler/internal/pkg/dingtalk_contact"
|
||||
"ai_scheduler/internal/pkg/dingtalk_notable"
|
||||
"ai_scheduler/internal/pkg/util"
|
||||
"ai_scheduler/internal/tools_bot"
|
||||
"context"
|
||||
|
|
@ -21,17 +22,17 @@ import (
|
|||
type CallbackService struct {
|
||||
cfg *config.Config
|
||||
gateway *gateway.Gateway
|
||||
dingtalkClient *dingtalk.Client
|
||||
dingtalkContactClient *dingtalk_contact.Client
|
||||
dingtalkNotableClient *dingtalk_notable.Client
|
||||
dingtalkOldClient *dingtalk.OldClient
|
||||
dingtalkContactClient *dingtalk.ContactClient
|
||||
dingtalkNotableClient *dingtalk.NotableClient
|
||||
botTool *tools_bot.BotTool
|
||||
}
|
||||
|
||||
func NewCallbackService(cfg *config.Config, gateway *gateway.Gateway, dingtalkClient *dingtalk.Client, dingtalkContactClient *dingtalk_contact.Client, dingtalkNotableClient *dingtalk_notable.Client, botTool *tools_bot.BotTool) *CallbackService {
|
||||
func NewCallbackService(cfg *config.Config, gateway *gateway.Gateway, dingtalkOldClient *dingtalk.OldClient, dingtalkContactClient *dingtalk.ContactClient, dingtalkNotableClient *dingtalk.NotableClient, botTool *tools_bot.BotTool) *CallbackService {
|
||||
return &CallbackService{
|
||||
cfg: cfg,
|
||||
gateway: gateway,
|
||||
dingtalkClient: dingtalkClient,
|
||||
dingtalkOldClient: dingtalkOldClient,
|
||||
dingtalkContactClient: dingtalkContactClient,
|
||||
dingtalkNotableClient: dingtalkNotableClient,
|
||||
botTool: botTool,
|
||||
|
|
@ -76,7 +77,7 @@ func (s *CallbackService) Callback(c *fiber.Ctx) error {
|
|||
|
||||
// 时间窗口(如果提供了 ts 则校验,否则跳过),窗口 5 分钟
|
||||
if ts != "" && !validateTimestamp(ts, 5*time.Minute) {
|
||||
// return errorcode.AuthNotFound
|
||||
return errorcode.AuthNotFound
|
||||
}
|
||||
|
||||
// 解析 Envelope
|
||||
|
|
@ -135,91 +136,37 @@ func parseInt64(s string) (int64, bool) {
|
|||
}
|
||||
|
||||
func (s *CallbackService) handleDingTalkCallback(c *fiber.Ctx, env Envelope) error {
|
||||
// 校验taskId
|
||||
sessionID, ok := s.botTool.GetSessionByTaskID(env.TaskID)
|
||||
if !ok {
|
||||
return errorcode.ParamErr("missing session_id for task_id: %s", env.TaskID)
|
||||
}
|
||||
ctx := c.Context()
|
||||
|
||||
switch env.Action {
|
||||
// bug/优化完成回调
|
||||
case ActionBugOptimizationSubmitDone:
|
||||
// 获取 session_id
|
||||
sessionID, ok := s.botTool.GetSessionByTaskID(env.TaskID)
|
||||
if !ok {
|
||||
return errorcode.ParamErr("missing session_id for task_id: %s", env.TaskID)
|
||||
// 业务处理
|
||||
msg, businessErr := s.handleBugOptimizationSubmitDone(ctx, env.Data)
|
||||
if businessErr != nil {
|
||||
return businessErr
|
||||
}
|
||||
|
||||
var data BugOptimizationSubmitDoneData
|
||||
if err := json.Unmarshal(env.Data, &data); err != nil {
|
||||
return errorcode.ParamErr("invalid data type: %v", err)
|
||||
}
|
||||
|
||||
if len(data.Receivers) == 0 {
|
||||
return errorcode.ParamErr("empty receivers")
|
||||
}
|
||||
// 构建接收者
|
||||
receivers := s.getDingtalkReceivers(c.Context(), data.Receivers)
|
||||
|
||||
// 构建跳转链接
|
||||
var detailPage string
|
||||
if data.DetailPage != "" {
|
||||
detailPage = util.BuildJumpLink(data.DetailPage, "去查看")
|
||||
}
|
||||
|
||||
msg := data.Msg
|
||||
msg = util.ReplacePlaceholder(msg, "receivers", receivers)
|
||||
msg = util.ReplacePlaceholder(msg, "detail_page", util.EscapeJSONString(detailPage))
|
||||
|
||||
s.gateway.SendToUid(sessionID, []byte(msg))
|
||||
// 发送日志
|
||||
s.sendStreamLog(sessionID, msg)
|
||||
|
||||
// 删除映射
|
||||
s.botTool.DelTaskMapping(env.TaskID)
|
||||
|
||||
return c.JSON(fiber.Map{"code": 0, "message": "ok"})
|
||||
case ActionBugOptimizationSubmitUpdate:
|
||||
// 获取 session_id
|
||||
// sessionID, ok := s.botTool.GetSessionByTaskID(env.TaskID)
|
||||
// if !ok {
|
||||
// return errorcode.ParamErr("missing session_id for task_id: %s", env.TaskID)
|
||||
// }
|
||||
|
||||
var data BugOptimizationSubmitUpdateData
|
||||
if err := json.Unmarshal(env.Data, &data); err != nil {
|
||||
return errorcode.ParamErr("invalid data type: %v", err)
|
||||
// 业务处理
|
||||
msg, businessErr := s.handleBugOptimizationSubmitUpdate(ctx, env.Data)
|
||||
if businessErr != nil {
|
||||
return businessErr
|
||||
}
|
||||
|
||||
if data.Creator == "" {
|
||||
return errorcode.ParamErr("empty creator")
|
||||
}
|
||||
|
||||
// 获取创建者uid
|
||||
accessToken, _ := s.dingtalkClient.GetAccessToken()
|
||||
creatorId, err := s.dingtalkContactClient.SearchUserOne(accessToken, data.Creator)
|
||||
if err != nil {
|
||||
return errorcode.ParamErr("invalid data type: %v", err)
|
||||
}
|
||||
|
||||
// 获取用户详情
|
||||
userDetails, err := s.dingtalkClient.QueryUserDetails(c.Context(), creatorId)
|
||||
if err != nil {
|
||||
return errorcode.ParamErr("invalid data type: %v", err)
|
||||
}
|
||||
if userDetails == nil {
|
||||
return errorcode.ParamErr("user details not found")
|
||||
}
|
||||
unionId := userDetails.UnionID
|
||||
|
||||
// 更新记录
|
||||
ok, err := s.dingtalkNotableClient.UpdateRecord(accessToken, &dingtalk_notable.UpdateRecordReq{
|
||||
BaseId: data.BaseId,
|
||||
SheetId: data.SheetId,
|
||||
RecordId: data.RecordId,
|
||||
OperatorId: tools_bot.BotBugOptimizationSubmitAdminUnionId,
|
||||
CreatorUnionId: unionId,
|
||||
})
|
||||
if err != nil {
|
||||
return errorcode.ParamErr("invalid data type: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
return errorcode.ParamErr("update record failed")
|
||||
}
|
||||
|
||||
// s.gateway.SendToUid(sessionID, []byte("问题记录即将完成"))
|
||||
s.sendStreamLog(sessionID, msg)
|
||||
|
||||
return c.JSON(fiber.Map{"code": 0, "message": "ok"})
|
||||
default:
|
||||
|
|
@ -227,11 +174,40 @@ func (s *CallbackService) handleDingTalkCallback(c *fiber.Ctx, env Envelope) err
|
|||
}
|
||||
}
|
||||
|
||||
// handleBugOptimizationSubmitDone 处理 bug 优化提交完成回调
|
||||
func (s *CallbackService) handleBugOptimizationSubmitDone(ctx context.Context, taskData json.RawMessage) (string, *errorcode.BusinessErr) {
|
||||
var data BugOptimizationSubmitDoneData
|
||||
if err := json.Unmarshal(taskData, &data); err != nil {
|
||||
return "", errorcode.ParamErr("invalid data type: %v", err)
|
||||
}
|
||||
|
||||
if len(data.Receivers) == 0 {
|
||||
return "", errorcode.ParamErr("empty receivers")
|
||||
}
|
||||
// 构建接收者
|
||||
receivers := s.getDingtalkReceivers(ctx, data.Receivers)
|
||||
if receivers == "" {
|
||||
return "", errorcode.ParamErr("invalid receivers")
|
||||
}
|
||||
|
||||
// 构建跳转链接
|
||||
var detailPage string
|
||||
if data.DetailPage != "" {
|
||||
detailPage = util.BuildJumpLink(data.DetailPage, "去查看")
|
||||
}
|
||||
|
||||
msg := data.Msg
|
||||
msg = util.ReplacePlaceholder(msg, "receivers", receivers)
|
||||
msg = util.ReplacePlaceholder(msg, "detail_page", util.EscapeJSONString(detailPage))
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
userDetails, err := s.dingtalkOldClient.QueryUserDetails(ctx, receiverId)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
|
@ -245,3 +221,60 @@ func (s *CallbackService) getDingtalkReceivers(ctx context.Context, receiverIds
|
|||
|
||||
return receivers
|
||||
}
|
||||
|
||||
// sendStreamLog 发送流式日志
|
||||
func (s *CallbackService) sendStreamLog(sessionID string, content string) {
|
||||
streamLog := entitys.Response{
|
||||
Index: constants.BotToolsBugOptimizationSubmit,
|
||||
Content: content,
|
||||
Type: entitys.ResponseLog,
|
||||
}
|
||||
streamLogBytes := pkg.JsonByteIgonErr(streamLog)
|
||||
s.gateway.SendToUid(sessionID, streamLogBytes)
|
||||
}
|
||||
|
||||
// handleBugOptimizationSubmitUpdate 处理 bug 优化提交更新回调
|
||||
func (s *CallbackService) handleBugOptimizationSubmitUpdate(ctx context.Context, taskData json.RawMessage) (string, *errorcode.BusinessErr) {
|
||||
var data BugOptimizationSubmitUpdateData
|
||||
if err := json.Unmarshal(taskData, &data); err != nil {
|
||||
return "", errorcode.ParamErr("invalid data type: %v", err)
|
||||
}
|
||||
|
||||
if data.Creator == "" {
|
||||
return "", errorcode.ParamErr("empty creator")
|
||||
}
|
||||
|
||||
// 获取创建者uid
|
||||
accessToken, _ := s.dingtalkOldClient.GetAccessToken()
|
||||
creatorId, err := s.dingtalkContactClient.SearchUserOne(accessToken, data.Creator)
|
||||
if err != nil {
|
||||
return "", errorcode.ParamErr("invalid data type: %v", err)
|
||||
}
|
||||
|
||||
// 获取用户详情
|
||||
userDetails, err := s.dingtalkOldClient.QueryUserDetails(ctx, creatorId)
|
||||
if err != nil {
|
||||
return "", errorcode.ParamErr("invalid data type: %v", err)
|
||||
}
|
||||
if userDetails == nil {
|
||||
return "", errorcode.ParamErr("user details not found")
|
||||
}
|
||||
unionId := userDetails.UnionID
|
||||
|
||||
// 更新记录
|
||||
ok, err := s.dingtalkNotableClient.UpdateRecord(accessToken, &dingtalk.UpdateRecordReq{
|
||||
BaseId: data.BaseId,
|
||||
SheetId: data.SheetId,
|
||||
RecordId: data.RecordId,
|
||||
OperatorId: tools_bot.BotBugOptimizationSubmitAdminUnionId,
|
||||
CreatorUnionId: unionId,
|
||||
})
|
||||
if err != nil {
|
||||
return "", errorcode.ParamErr("invalid data type: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
return "", errorcode.ParamErr("update record failed")
|
||||
}
|
||||
|
||||
return "问题记录即将完成", nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,115 @@
|
|||
package tools_bot
|
||||
|
||||
import (
|
||||
errors "ai_scheduler/internal/data/error"
|
||||
"ai_scheduler/internal/entitys"
|
||||
"ai_scheduler/internal/pkg"
|
||||
"ai_scheduler/internal/pkg/l_request"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2/log"
|
||||
"github.com/google/uuid"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// BugOptimizationSubmitForm 工单提交表单参数
|
||||
type BugOptimizationSubmitForm struct {
|
||||
Mark string `json:"mark"` // 工单标识
|
||||
Text string `json:"text"` // 工单描述
|
||||
Img string `json:"img"` // 工单截图
|
||||
Creator string `json:"creator"` // 工单创建人
|
||||
TaskId string `json:"task_id"` // 当初任务ID
|
||||
}
|
||||
|
||||
const (
|
||||
// 工单QA
|
||||
BotBugOptimizationSubmitQA = "温子新"
|
||||
BotBugOptimizationSubmitPM = "贺泽琨"
|
||||
|
||||
// 管理员unionId - fzy
|
||||
BotBugOptimizationSubmitAdminUnionId = "uoCiPKNdFmuiSFmAiiXmmiSKpQiEiE"
|
||||
)
|
||||
|
||||
// BugOptimizationSubmit 工单提交
|
||||
func (w *BotTool) BugOptimizationSubmit(ctx context.Context, requireData *entitys.RequireData) (err error) {
|
||||
// 获取用户信息
|
||||
cond := builder.NewCond()
|
||||
cond = cond.And(builder.Eq{"session_id": requireData.Session})
|
||||
sessionInfo, err := w.sessionImpl.GetOneBySearch(&cond)
|
||||
if err != nil {
|
||||
err = errors.SysErr("获取会话信息失败:%v", err.Error())
|
||||
return
|
||||
}
|
||||
userName := sessionInfo["user_name"].(string)
|
||||
|
||||
// 构建工单表单参数
|
||||
body := BugOptimizationSubmitForm{
|
||||
Mark: requireData.Match.Index,
|
||||
Text: requireData.Req.Text,
|
||||
Img: requireData.Req.Img,
|
||||
Creator: userName,
|
||||
TaskId: uuid.New().String(),
|
||||
}
|
||||
|
||||
request := l_request.Request{
|
||||
Url: "https://connector.dingtalk.com/webhook/flow/10352c521dd02104cee9000c",
|
||||
Method: "POST",
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
JsonByte: pkg.JsonByteIgonErr(body),
|
||||
}
|
||||
res, err := request.Send()
|
||||
if err != nil {
|
||||
log.Errorf("发送请求失败: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
data := make(map[string]bool)
|
||||
if err = json.Unmarshal(res.Content, &data); err != nil {
|
||||
return fmt.Errorf("解析工单响应失败:%w", err)
|
||||
}
|
||||
|
||||
if data["success"] {
|
||||
// 记录 task_id 到 session_id 的映射
|
||||
w.SetTaskMapping(body.TaskId, requireData.Session)
|
||||
|
||||
// 等待异步回调完成再结束
|
||||
for {
|
||||
sessionID, ok := w.GetSessionByTaskID(body.TaskId)
|
||||
if !ok || sessionID != requireData.Session {
|
||||
break
|
||||
}
|
||||
entitys.ResLoading(requireData.Ch, requireData.Match.Index, "问题记录中...")
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
entitys.ResJson(requireData.Ch, requireData.Match.Index, fmt.Sprintf("bug问题请咨询 @%s ,优化建议请咨询 @%s 。", BotBugOptimizationSubmitQA, BotBugOptimizationSubmitPM))
|
||||
return
|
||||
}
|
||||
|
||||
// SetTaskMapping 设置 task_id 到 session_id 的映射(内存版)。
|
||||
// 后续考虑使用 Redis,确保幂等与过期清理。
|
||||
func (w *BotTool) SetTaskMapping(taskID, sessionID string) {
|
||||
if taskID == "" || sessionID == "" {
|
||||
return
|
||||
}
|
||||
w.taskMap[taskID] = sessionID
|
||||
}
|
||||
|
||||
// GetSessionByTaskID 读取映射
|
||||
func (w *BotTool) GetSessionByTaskID(taskID string) (string, bool) {
|
||||
v, ok := w.taskMap[taskID]
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// DelTaskMapping 删除 task_id 到 session_id 的映射(内存版)。
|
||||
func (w *BotTool) DelTaskMapping(taskID string) {
|
||||
delete(w.taskMap, taskID)
|
||||
}
|
||||
|
|
@ -6,17 +6,10 @@ import (
|
|||
errors "ai_scheduler/internal/data/error"
|
||||
"ai_scheduler/internal/data/impl"
|
||||
"ai_scheduler/internal/entitys"
|
||||
"ai_scheduler/internal/pkg"
|
||||
"ai_scheduler/internal/pkg/l_request"
|
||||
"ai_scheduler/internal/pkg/utils_ollama"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2/log"
|
||||
"github.com/google/uuid"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
type BotTool struct {
|
||||
|
|
@ -31,15 +24,6 @@ func NewBotTool(config *config.Config, llm *utils_ollama.Client, sessionImpl *im
|
|||
return &BotTool{config: config, llm: llm, sessionImpl: sessionImpl, taskMap: make(map[string]string)}
|
||||
}
|
||||
|
||||
// BugOptimizationSubmitForm 工单提交表单参数
|
||||
type BugOptimizationSubmitForm struct {
|
||||
Mark string `json:"mark"` // 工单标识
|
||||
Text string `json:"text"` // 工单描述
|
||||
Img string `json:"img"` // 工单截图
|
||||
Creator string `json:"creator"` // 工单创建人
|
||||
TaskId string `json:"task_id"` // 当初任务ID
|
||||
}
|
||||
|
||||
// Execute 执行直连天下订单详情查询
|
||||
func (w *BotTool) Execute(ctx context.Context, toolName string, requireData *entitys.RequireData) (err error) {
|
||||
switch toolName {
|
||||
|
|
@ -51,97 +35,3 @@ func (w *BotTool) Execute(ctx context.Context, toolName string, requireData *ent
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
// 工单QA
|
||||
BotBugOptimizationSubmitQA = "温子新"
|
||||
BotBugOptimizationSubmitPM = "贺泽琨"
|
||||
|
||||
// 管理员unionId - fzy
|
||||
BotBugOptimizationSubmitAdminUnionId = "uoCiPKNdFmuiSFmAiiXmmiSKpQiEiE"
|
||||
)
|
||||
|
||||
// 现存问题:
|
||||
// 1. 回调时 session 直接传入不安全 todo
|
||||
// 2. 创建人无法指定[钉钉用户],影响后续状态变化时通知
|
||||
// 3. 回调接口,[接收人]、[文档地址不能]动态配置
|
||||
// 4. 测试环境与线上环境,使用的不是同一个钉钉主体
|
||||
func (w *BotTool) BugOptimizationSubmit(ctx context.Context, requireData *entitys.RequireData) (err error) {
|
||||
// 获取用户信息
|
||||
cond := builder.NewCond()
|
||||
cond = cond.And(builder.Eq{"session_id": requireData.Session})
|
||||
sessionInfo, err := w.sessionImpl.GetOneBySearch(&cond)
|
||||
if err != nil {
|
||||
err = errors.SysErr("获取会话信息失败:%v", err.Error())
|
||||
return
|
||||
}
|
||||
userName := sessionInfo["user_name"].(string)
|
||||
|
||||
// 构建工单表单参数
|
||||
body := BugOptimizationSubmitForm{
|
||||
Mark: requireData.Match.Index,
|
||||
Text: requireData.Req.Text,
|
||||
Img: requireData.Req.Img,
|
||||
Creator: userName,
|
||||
TaskId: uuid.New().String(),
|
||||
}
|
||||
|
||||
request := l_request.Request{
|
||||
Url: "https://connector.dingtalk.com/webhook/flow/10352c521dd02104cee9000c",
|
||||
Method: "POST",
|
||||
Headers: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
JsonByte: pkg.JsonByteIgonErr(body),
|
||||
}
|
||||
res, err := request.Send()
|
||||
if err != nil {
|
||||
log.Errorf("发送请求失败: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
data := make(map[string]bool)
|
||||
if err = json.Unmarshal(res.Content, &data); err != nil {
|
||||
return fmt.Errorf("解析工单响应失败:%w", err)
|
||||
}
|
||||
|
||||
if data["success"] {
|
||||
// 记录 task_id 到 session_id 的映射
|
||||
w.SetTaskMapping(body.TaskId, requireData.Session)
|
||||
|
||||
// 等待异步回调完成再结束
|
||||
for {
|
||||
sessionID, ok := w.GetSessionByTaskID(body.TaskId)
|
||||
if !ok || sessionID != requireData.Session {
|
||||
break
|
||||
}
|
||||
entitys.ResLoading(requireData.Ch, requireData.Match.Index, "问题内容记录中...")
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
entitys.ResJson(requireData.Ch, requireData.Match.Index, fmt.Sprintf("bug问题请咨询 @%s ,优化建议请咨询 @%s 。", BotBugOptimizationSubmitQA, BotBugOptimizationSubmitPM))
|
||||
return
|
||||
}
|
||||
|
||||
// SetTaskMapping 设置 task_id 到 session_id 的映射(内存版)。
|
||||
// 后续考虑使用 Redis,确保幂等与过期清理。
|
||||
func (w *BotTool) SetTaskMapping(taskID, sessionID string) {
|
||||
if taskID == "" || sessionID == "" {
|
||||
return
|
||||
}
|
||||
w.taskMap[taskID] = sessionID
|
||||
}
|
||||
|
||||
// GetSessionByTaskID 读取映射
|
||||
func (w *BotTool) GetSessionByTaskID(taskID string) (string, bool) {
|
||||
v, ok := w.taskMap[taskID]
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// DelTaskMapping 删除 task_id 到 session_id 的映射(内存版)。
|
||||
func (w *BotTool) DelTaskMapping(taskID string) {
|
||||
delete(w.taskMap, taskID)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue