ai_scheduler/internal/services/advice/wx_proxy.go

284 lines
11 KiB
Go
Raw Permalink 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 advice
import (
"ai_scheduler/internal/biz"
"ai_scheduler/internal/pkg"
"ai_scheduler/internal/pkg/wx"
"context"
"encoding/json"
"fmt"
"log"
"github.com/gofiber/fiber/v2"
)
// WxProxyService 微信 API 包装转发服务(非透传代理)
// 每个接口使用强类型请求/响应结构体,符合项目本身的输入输出规范
type WxProxyService struct {
projectBiz *biz.AdviceProjectBiz
advicerBiz *biz.AdviceAdvicerBiz
}
// NewWxProxyService 创建微信代理服务
func NewWxProxyService(projectBiz *biz.AdviceProjectBiz, advicerBiz *biz.AdviceAdvicerBiz) *WxProxyService {
return &WxProxyService{projectBiz: projectBiz, advicerBiz: advicerBiz}
}
// wxDispatcher 微信接口调度函数类型
type wxDispatcher func(ctx context.Context, token string, body []byte) (interface{}, error)
// callWx 泛型调度器:将 JSON body 解析为强类型请求,调用上游 wx API,返回强类型响应
func callWx[Req any, Res any](api wx.Api[Req, Res]) wxDispatcher {
return func(ctx context.Context, token string, body []byte) (interface{}, error) {
var req Req
if err := json.Unmarshal(body, &req); err != nil {
return nil, fmt.Errorf("请求参数解析失败: %w", err)
}
var res Res
if err := wx.Request(ctx, token, api, req, &res); err != nil {
return nil, err
}
return res, nil
}
}
// wxDispatchMap 接口路径 → 强类型调度函数映射表
var wxDispatchMap = map[string]wxDispatcher{
// ==================== 登录接口 ====================
"login/getLoginQrCode": callWx(wx.GetLoginQrCode),
"login/checkLogin": callWx(wx.CheckLogin),
"login/dialogLogin": callWx(wx.DialogLogin),
"login/setCallback": callWx(wx.SetCallback),
"login/checkOnline": callWx(wx.CheckOnline),
"login/logout": callWx(wx.Logout),
"login/reconnection": callWx(wx.Reconnection),
// ==================== 联系人接口 ====================
"contacts/fetchContactsList": callWx(wx.FetchContactsList),
"contacts/fetchContactsListCache": callWx(wx.FetchContactsListCache),
"contacts/getBriefInfo": callWx(wx.GetBriefInfo),
"contacts/getDetailInfo": callWx(wx.GetDetailInfo),
"contacts/search": callWx(wx.ContactsSearch),
"contacts/addContacts": callWx(wx.AddContacts),
"contacts/deleteFriend": callWx(wx.DeleteFriend),
"contacts/setFriendPermissions": callWx(wx.SetFriendPermissions),
"contacts/setFriendRemark": callWx(wx.SetFriendRemark),
"contacts/getPhoneAddressList": callWx(wx.GetPhoneAddressList),
"contacts/uploadPhoneAddressList": callWx(wx.UploadPhoneAddressList),
"contacts/imSearch": callWx(wx.ImSearch),
"contacts/imAdd": callWx(wx.ImAdd),
"contacts/imSync": callWx(wx.ImSync),
"contacts/imDetail": callWx(wx.ImDetail),
// ==================== 消息接口-下载 ====================
"message/downloadCdn": callWx(wx.DownloadCdn),
"message/downloadImage": callWx(wx.DownloadImage),
"message/downloadVoice": callWx(wx.DownloadVoice),
"message/downloadVideo": callWx(wx.DownloadVideo),
"message/downloadEmojiMd5": callWx(wx.DownloadEmojiMd5),
"message/downloadFile": callWx(wx.DownloadFile),
// ==================== 消息接口 ====================
"message/postText": callWx(wx.PostText),
"message/postFile": callWx(wx.PostFile),
"message/postImage": callWx(wx.PostImage),
"message/postVoice": callWx(wx.PostVoice),
"message/postVideo": callWx(wx.PostVideo),
"message/postLink": callWx(wx.PostLink),
"message/postNameCard": callWx(wx.PostNameCard),
"message/postEmoji": callWx(wx.PostEmoji),
"message/postAppMsg": callWx(wx.PostAppMsg),
"message/postMiniApp": callWx(wx.PostMiniApp),
"message/forwardFile": callWx(wx.ForwardFile),
"message/forwardImage": callWx(wx.ForwardImage),
"message/forwardVideo": callWx(wx.ForwardVideo),
"message/forwardUrl": callWx(wx.ForwardUrl),
"message/forwardMiniApp": callWx(wx.ForwardMiniApp),
"message/revokeMsg": callWx(wx.RevokeMsg),
// ==================== 个人接口 ====================
"personal/getProfile": callWx(wx.PersonalGetProfile),
"personal/getQrCode": callWx(wx.PersonalGetQrCode),
"personal/getSafetyInfo": callWx(wx.GetSafetyInfo),
"personal/privacySettings": callWx(wx.PrivacySettings),
"personal/updateProfile": callWx(wx.PersonalUpdateProfile),
"personal/updateHeadImg": callWx(wx.UpdateHeadImg),
// ==================== 标签接口 ====================
"label/add": callWx(wx.LabelAdd),
"label/delete": callWx(wx.LabelDelete),
"label/list": callWx(wx.LabelList),
"label/modifyMemberList": callWx(wx.ModifyMemberList),
// ==================== 群聊接口 ====================
"group/createChatroom": callWx(wx.CreateChatroom),
"group/modifyChatroomName": callWx(wx.ModifyChatroomName),
"group/modifyChatroomRemark": callWx(wx.ModifyChatroomRemark),
"group/modifyChatroomNickNameForSelf": callWx(wx.ModifyChatroomNickNameForSelf),
"group/inviteMember": callWx(wx.InviteMember),
"group/removeMember": callWx(wx.RemoveMember),
"group/quitChatroom": callWx(wx.QuitChatroom),
"group/disbandChatroom": callWx(wx.DisbandChatroom),
"group/getChatroomInfo": callWx(wx.GetChatroomInfo),
"group/getChatroomMemberList": callWx(wx.GetChatroomMemberList),
"group/getChatroomMemberDetail": callWx(wx.GetChatroomMemberDetail),
"group/getChatroomAnnouncement": callWx(wx.GetChatroomAnnouncement),
"group/setChatroomAnnouncement": callWx(wx.SetChatroomAnnouncement),
"group/agreeJoinRoom": callWx(wx.AgreeJoinRoom),
"group/addGroupMemberAsFriend": callWx(wx.AddGroupMemberAsFriend),
"group/getChatroomQrCode": callWx(wx.GetChatroomQrCode),
"group/saveContractList": callWx(wx.SaveContractList),
"group/adminOperate": callWx(wx.AdminOperate),
"group/pinChat": callWx(wx.PinChat),
"group/setMsgSilence": callWx(wx.SetMsgSilence),
"group/joinRoomUsingQRCode": callWx(wx.JoinRoomUsingQRCode),
"group/roomAccessApplyCheckApprove": callWx(wx.RoomAccessApplyCheckApprove),
// ==================== 朋友圈接口 ====================
"sns/likeSns": callWx(wx.LikeSns),
"sns/delSns": callWx(wx.DelSns),
"sns/snsVisibleScope": callWx(wx.SnsVisibleScope),
"sns/strangerVisibilityEnabled": callWx(wx.StrangerVisibilityEnabled),
"sns/snsSetPrivacy": callWx(wx.SnsSetPrivacy),
"sns/downloadSnsVideo": callWx(wx.DownloadSnsVideo),
"sns/sendTextSns": callWx(wx.SendTextSns),
"sns/sendImgSns": callWx(wx.SendImgSns),
"sns/sendVideoSns": callWx(wx.SendVideoSns),
"sns/sendUrlSns": callWx(wx.SendUrlSns),
"sns/uploadSnsImage": callWx(wx.UploadSnsImage),
"sns/uploadSnsVideo": callWx(wx.UploadSnsVideo),
"sns/forwardSns": callWx(wx.ForwardSns),
"sns/snsList": callWx(wx.SnsList),
"sns/contactsSnsList": callWx(wx.ContactsSnsList),
"sns/snsDetails": callWx(wx.SnsDetails),
"sns/commentSns": callWx(wx.CommentSns),
}
// Proxy 处理微信 API 请求:
// 1. 从请求体中提取 projectId,查询项目级 wx_token
// 2. 根据接口路径分发到对应的强类型处理函数
// 3. 以标准 {code, message, data} 格式返回
func (s *WxProxyService) Proxy(c *fiber.Ctx) error {
sub := c.Params("*")
// 查找对应的调度函数
dispatcher, ok := wxDispatchMap[sub]
if !ok {
return pkg.HandleResponse(c, nil, fiber.NewError(fiber.StatusBadRequest, "不支持的微信接口: "+sub))
}
// 解析请求体
var reqBody map[string]interface{}
if len(c.Body()) > 0 {
if err := json.Unmarshal(c.Body(), &reqBody); err != nil {
return pkg.HandleResponse(c, nil, fiber.NewError(fiber.StatusBadRequest, "请求体解析失败: "+err.Error()))
}
} else {
reqBody = map[string]interface{}{}
}
// 从请求体提取 projectId
var projectId int32
if v, ok := reqBody["projectId"]; ok {
switch pv := v.(type) {
case float64:
projectId = int32(pv)
case int:
projectId = int32(pv)
}
}
if projectId == 0 {
return pkg.HandleResponse(c, nil, fiber.NewError(fiber.StatusBadRequest, "缺少 projectId 参数"))
}
// 查询项目的 wx_token
project, err := s.projectBiz.BaseInfo(projectId)
if err != nil {
return pkg.HandleResponse(c, nil, err)
}
if project.WxToken == "" {
return pkg.HandleResponse(c, nil, fiber.NewError(fiber.StatusBadRequest, "项目未配置微信接入 Token,请先在项目设置中配置"))
}
// 调用对应的强类型处理函数
data, err := dispatcher(c.UserContext(), project.WxToken, c.Body())
if err != nil {
return pkg.HandleResponse(c, nil, fiber.NewError(fiber.StatusBadGateway, "微信接口调用失败: "+err.Error()))
}
// 登录成功时自动回写销售的 wx_id
if sub == "login/checkLogin" {
if resData, ok := data.(wx.CheckLoginResData); ok && resData.Status == 2 && resData.LoginInfo != nil && resData.LoginInfo.Wxid != "" {
var appId string
if v, ok := reqBody["appId"].(string); ok {
appId = v
}
if appId != "" {
advicer, err := s.advicerBiz.FindByWxDeviceId(c.UserContext(), appId)
if err == nil && advicer.AdvicerID != 0 {
_ = s.advicerBiz.UpdateWxId(c.UserContext(), advicer.AdvicerID, resData.LoginInfo.Wxid)
}
}
}
}
// 标记跳过 registerCommon 的二次包装,避免响应被双层嵌套
// Proxy 已自行返回标准 {code, data, message} 格式,无需中间件再包一层
c.Locals("skip_response_wrap", true)
return c.JSON(fiber.Map{
"code": 0,
"data": data,
"message": "ok",
})
}
// BatchChatroomInfoReq 批量获取群信息请求
type BatchChatroomInfoReq struct {
ProjectId int32 `json:"projectId"`
AppId string `json:"appId"`
ChatroomIds []string `json:"chatroomIds"`
}
// BatchChatroomInfo 批量获取多个群的信息(内部循环调用 wx API,一次返回)
func (s *WxProxyService) BatchChatroomInfo(c *fiber.Ctx) error {
var req BatchChatroomInfoReq
if err := c.BodyParser(&req); err != nil {
return pkg.HandleResponse(c, nil, err)
}
if len(req.ChatroomIds) == 0 {
return pkg.HandleResponse(c, nil, nil)
}
// 查询项目的 wx_token
project, err := s.projectBiz.BaseInfo(req.ProjectId)
if err != nil {
return pkg.HandleResponse(c, nil, err)
}
if project.WxToken == "" {
return pkg.HandleResponse(c, nil, fiber.NewError(fiber.StatusBadRequest, "项目未配置微信接入 Token"))
}
// 循环调用 wx API,收集所有结果
results := make([]wx.GetChatroomInfoResData, 0, len(req.ChatroomIds))
for _, chatroomId := range req.ChatroomIds {
wxReq := wx.GetChatroomInfoReq{
AppId: req.AppId,
ChatroomId: chatroomId,
}
var wxRes wx.GetChatroomInfoResData
if err := wx.Request(c.UserContext(), project.WxToken, wx.GetChatroomInfo, wxReq, &wxRes); err != nil {
log.Printf("[WARN] batch_chatroom_info: getChatroomInfo(%s) failed: %v", chatroomId, err)
continue // 单个失败不影响其他
}
results = append(results, wxRes)
}
c.Locals("skip_response_wrap", true)
return c.JSON(fiber.Map{
"code": 0,
"data": results,
"message": "ok",
})
}