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", }) }