1275 lines
43 KiB
Go
1275 lines
43 KiB
Go
package service
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"encoding/xml"
|
|
"errors"
|
|
"fmt"
|
|
"html"
|
|
"io"
|
|
"log"
|
|
"math/rand"
|
|
"net/http"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
"xiawan/wx/api/req"
|
|
"xiawan/wx/api/vo"
|
|
"xiawan/wx/clientsdk/baseinfo"
|
|
"xiawan/wx/clientsdk/baseutils"
|
|
"xiawan/wx/db"
|
|
"xiawan/wx/srv/wxcore"
|
|
"xiawan/wx/srv/wxface"
|
|
"xiawan/wx/srv/wxtask"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/gogf/gf/container/garray"
|
|
"github.com/gorilla/websocket"
|
|
)
|
|
|
|
// AddMessageMgrService 添加要发送的消息进入消息管理器
|
|
func AddMessageMgrService(queryKey string, m req.SendMessageModel) vo.DTO {
|
|
return checkExIdPerformNoCreateConnect(queryKey, func(connect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
|
|
|
wxAccount := connect.GetWXAccount()
|
|
loginState := wxAccount.GetLoginState()
|
|
|
|
//判断在线情况
|
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
|
} else if !connect.CheckOnLineStatus() {
|
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
|
}
|
|
|
|
// 获取好友消息助手
|
|
friendMsgMgr := connect.GetWXFriendMsgMgr()
|
|
// 发送文本消息
|
|
if len(m.MsgItem) <= 0 {
|
|
return vo.NewFail("没有要加入消息管理器的消息!")
|
|
}
|
|
for _, item := range m.MsgItem {
|
|
//1 text 2 Image
|
|
if item.MsgType == 1 {
|
|
friendMsgMgr.AddNewTextMsg(item.TextContent, item.ToUserName)
|
|
} else if item.MsgType == 2 {
|
|
sImageBase := strings.Split(item.ImageContent, ",")
|
|
if len(sImageBase) > 1 {
|
|
item.ImageContent = sImageBase[1]
|
|
}
|
|
imageBytes, _ := base64.StdEncoding.DecodeString(item.ImageContent)
|
|
friendMsgMgr.AddImageMsg(imageBytes, item.ToUserName)
|
|
}
|
|
}
|
|
return vo.NewSuccess(gin.H{}, "已加入消息管理器队列")
|
|
})
|
|
}
|
|
func SendTestService(queryKey string, m req.SendMessageModel) vo.DTO {
|
|
return checkExIdPerformNoCreateConnect(queryKey, func(connect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
|
|
|
wxAccount := connect.GetWXAccount()
|
|
loginState := wxAccount.GetLoginState()
|
|
|
|
//判断在线情况
|
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
|
} else if !connect.CheckOnLineStatus() {
|
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
|
}
|
|
|
|
reqInvoker := connect.GetWXReqInvoker()
|
|
err := reqInvoker.SendAutoAuthRequest()
|
|
return vo.NewSuccessObj(err, "")
|
|
})
|
|
}
|
|
|
|
func WebSocketHandler(c *gin.Context, key string) {
|
|
connectMgr := WXServer.GetWXConnectMgr()
|
|
wxconn := connectMgr.GetWXConnectByUserInfoUUID(key)
|
|
if wxconn == nil {
|
|
c.JSON(http.StatusOK, vo.NewFail("WX 长链接不存在"))
|
|
return
|
|
}
|
|
|
|
currentTaskMgr := wxconn.GetWXTaskMgr()
|
|
taskMgr, _ := currentTaskMgr.(*wxcore.WXTaskMgr)
|
|
wsTask := taskMgr.SocketMsgTask
|
|
currentReqInvoker := wxconn.GetWXReqInvoker()
|
|
|
|
// 防止重复连接,如果已经有WebSocket连接,先清理掉
|
|
existingConn := wsTask.GetWebSocket(key)
|
|
if existingConn != nil {
|
|
fmt.Println("已存在WebSocket连接,正在清理...")
|
|
existingConn.Close()
|
|
wsTask.DeleteWebSocket(key)
|
|
}
|
|
|
|
// 启用此用户的WebSocket功能
|
|
wsTask.SetWebSocketEnabled(true)
|
|
|
|
conn, err := wsTask.Upgrader.Upgrade(c.Writer, c.Request, nil)
|
|
if err != nil {
|
|
log.Println("WebSocket升级失败:", err)
|
|
// 返回一个JSON错误响应,然后退出
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "WebSocket升级失败: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
// 设置WebSocket超时和心跳
|
|
conn.SetReadDeadline(time.Now().Add(300 * time.Second))
|
|
conn.SetPongHandler(func(appData string) error {
|
|
conn.SetReadDeadline(time.Now().Add(300 * time.Second)) // 收到Pong后刷新超时
|
|
fmt.Println("收到Pong消息")
|
|
return nil
|
|
})
|
|
|
|
// 更新WebSocket连接
|
|
wsTask.UpdateWebSocket(key, conn)
|
|
|
|
// 创建一个关闭函数,确保资源正确释放
|
|
cleanup := func() {
|
|
fmt.Println("正在关闭WebSocket连接...")
|
|
// 使用PushTypedMessageToClient来发送关闭消息,确保线程安全
|
|
_ = wxtask.PushTypedMessageToClient(websocket.CloseMessage,
|
|
websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), conn)
|
|
_ = conn.Close()
|
|
wsTask.DeleteWebSocket(key)
|
|
}
|
|
|
|
// 确保在函数退出时资源被释放
|
|
defer cleanup()
|
|
|
|
// WebSocket连接建立时,先重置初始化状态为false,跳过历史数据
|
|
wxCache := wxconn.GetWXCache()
|
|
wxCache.SetInitNewSyncFinished(false)
|
|
fmt.Println("WebSocket连接已建立,正在跳过历史数据...")
|
|
|
|
// 等待5秒,让所有历史数据被跳过
|
|
time.Sleep(5 * time.Second)
|
|
|
|
// 延迟后标记初始化完成,开始接收实时消息
|
|
wxCache.SetInitNewSyncFinished(true)
|
|
fmt.Println("WebSocket初始化完成,开始接收实时消息")
|
|
|
|
// 创建一个心跳检测goroutine
|
|
stopPing := make(chan bool)
|
|
go func() {
|
|
ticker := time.NewTicker(60 * time.Second)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
// 发送Ping消息,使用PushTypedMessageToClient来确保线程安全
|
|
if err := wxtask.PushTypedMessageToClient(websocket.PingMessage, nil, conn); err != nil {
|
|
fmt.Println("发送Ping失败:", err)
|
|
return
|
|
}
|
|
fmt.Println("已发送Ping消息")
|
|
case <-stopPing:
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
// 处理WebSocket消息
|
|
for {
|
|
messageType, message, err := conn.ReadMessage()
|
|
if err != nil {
|
|
fmt.Println("WebSocket读取错误:", err)
|
|
close(stopPing) // 通知心跳goroutine退出
|
|
break // 连接断开,跳出循环,函数将退出
|
|
}
|
|
|
|
fmt.Printf("收到WebSocket消息[%d]: %s\n", messageType, string(message))
|
|
|
|
if string(message) == "ping" {
|
|
// 使用PushMessageToClient来发送pong响应,确保线程安全
|
|
err = wxtask.PushMessageToClient("pong", conn)
|
|
if err != nil {
|
|
fmt.Println("发送pong响应失败:", err)
|
|
break
|
|
}
|
|
} else if string(message) == "sync" {
|
|
_ = currentReqInvoker.SendNewSyncRequest(baseinfo.MMSyncSceneTypeNeed)
|
|
// 使用PushMessageToClient来发送完成消息,确保线程安全
|
|
err = wxtask.PushMessageToClient("sync completed", conn)
|
|
if err != nil {
|
|
fmt.Println("发送同步完成响应失败:", err)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
fmt.Println("WebSocket连接断开")
|
|
}
|
|
|
|
// HttpSyncMsg HTTP-轮询 同步消息时 获取 Redis 内的消息
|
|
func HttpSyncMsg(queryKey string, count int) vo.DTO {
|
|
results, err := db.HttpSyncMsy(queryKey, count)
|
|
if err != nil {
|
|
return vo.NewFail("发生错误: " + err.Error())
|
|
}
|
|
return vo.NewSuccessObj(results, "")
|
|
}
|
|
|
|
// SendImageMessageService 发送图片消息
|
|
func SendImageMessageService(queryKey string, m req.SendMessageModel) vo.DTO {
|
|
return checkExIdPerformNoCreateConnect(queryKey, func(connect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
|
|
|
wxAccount := connect.GetWXAccount()
|
|
loginState := wxAccount.GetLoginState()
|
|
|
|
//判断在线情况
|
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
|
} else if !connect.CheckOnLineStatus() {
|
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
|
}
|
|
|
|
reqInvoker := connect.GetWXReqInvoker()
|
|
// 发送文本消息
|
|
if len(m.MsgItem) <= 0 {
|
|
return vo.NewFail("没有要加入消息管理器的消息!")
|
|
}
|
|
results := garray.New(true)
|
|
for _, item := range m.MsgItem {
|
|
sImageBase := strings.Split(item.ImageContent, ",")
|
|
if len(sImageBase) > 1 {
|
|
item.ImageContent = sImageBase[1]
|
|
}
|
|
imageBytes, _ := base64.StdEncoding.DecodeString(item.ImageContent)
|
|
imageId := baseutils.Md5ValueByte(imageBytes, false)
|
|
|
|
cdnUploadImageResp, err := reqInvoker.SendCdnUploadImageReuqest(imageBytes, item.ToUserName)
|
|
if err != nil {
|
|
results.Append(gin.H{
|
|
"imageId": imageId,
|
|
"toUSerName": item.ToUserName,
|
|
"isSendSuccess": cdnUploadImageResp,
|
|
"errMsg": err.Error(),
|
|
})
|
|
continue
|
|
} else {
|
|
results.Append(gin.H{
|
|
"imageId": imageId,
|
|
"toUSerName": item.ToUserName,
|
|
"isSendSuccess": cdnUploadImageResp,
|
|
})
|
|
}
|
|
}
|
|
return vo.NewSuccessObj(results, "")
|
|
})
|
|
}
|
|
|
|
// SendImageNewMessageService 发送图片New
|
|
func SendImageNewMessageService(queryKey string, m req.SendMessageModel) vo.DTO {
|
|
return checkExIdPerformNoCreateConnect(queryKey, func(connect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
|
|
|
wxAccount := connect.GetWXAccount()
|
|
loginState := wxAccount.GetLoginState()
|
|
|
|
//判断在线情况
|
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
|
} else if !connect.CheckOnLineStatus() {
|
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
|
}
|
|
|
|
reqInvoker := connect.GetWXReqInvoker()
|
|
// 发送文本消息
|
|
if len(m.MsgItem) <= 0 {
|
|
return vo.NewFail("没有要加入消息管理器的消息!")
|
|
}
|
|
results := garray.New(true)
|
|
for _, item := range m.MsgItem {
|
|
sImageBase := strings.Split(item.ImageContent, ",")
|
|
if len(sImageBase) > 1 {
|
|
item.ImageContent = sImageBase[1]
|
|
}
|
|
imageBytes, _ := base64.StdEncoding.DecodeString(item.ImageContent)
|
|
imageId := baseutils.Md5ValueByte(imageBytes, false)
|
|
resp, err := reqInvoker.SendUploadImageNewRequest(imageBytes, item.ToUserName)
|
|
if err != nil {
|
|
results.Append(gin.H{
|
|
"imageId": imageId,
|
|
"toUSerName": item.ToUserName,
|
|
"resp": resp,
|
|
"errMsg": err.Error(),
|
|
})
|
|
continue
|
|
} else {
|
|
results.Append(gin.H{
|
|
"imageId": imageId,
|
|
"toUSerName": item.ToUserName,
|
|
"resp": resp,
|
|
})
|
|
}
|
|
/*if len(m.MsgItem) > 1 {
|
|
v := rand.Intn(3)
|
|
if v == 0 || v == 1 {
|
|
time.Sleep(time.Second * 1)
|
|
} else if v == 2 {
|
|
time.Sleep(time.Second * 2)
|
|
} else {
|
|
time.Sleep(time.Second * 3)
|
|
}
|
|
}*/
|
|
}
|
|
return vo.NewSuccessObj(results, "")
|
|
})
|
|
}
|
|
|
|
// SendTextMessageService 发送文本消息
|
|
func SendTextMessageService(queryKey string, m req.SendMessageModel) vo.DTO {
|
|
return checkExIdPerformNoCreateConnect(queryKey, func(connect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
|
wxAccount := connect.GetWXAccount()
|
|
loginState := wxAccount.GetLoginState()
|
|
|
|
//判断在线情况
|
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
|
} else if !connect.CheckOnLineStatus() {
|
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
|
}
|
|
|
|
reqInvoker := connect.GetWXReqInvoker()
|
|
// 发送文本消息
|
|
if len(m.MsgItem) <= 0 {
|
|
return vo.NewFail("没有要加入消息管理器的消息!")
|
|
}
|
|
results := garray.New(true)
|
|
for _, item := range m.MsgItem {
|
|
resp, err := reqInvoker.SendTextMsgRequest(item.ToUserName, item.TextContent, item.AtWxIDList, item.MsgType)
|
|
if err != nil {
|
|
results.Append(gin.H{
|
|
"toUSerName": item.ToUserName,
|
|
"textContent": item.TextContent,
|
|
"isSendSuccess": false,
|
|
"errMsg": err.Error(),
|
|
})
|
|
continue
|
|
} else {
|
|
results.Append(gin.H{
|
|
"textContent": item.TextContent,
|
|
"toUSerName": item.ToUserName,
|
|
"resp": resp,
|
|
"isSendSuccess": true,
|
|
})
|
|
}
|
|
if len(m.MsgItem) > 1 {
|
|
v := rand.Intn(3)
|
|
if v == 0 || v == 1 {
|
|
time.Sleep(time.Second * 1)
|
|
} else if v == 2 {
|
|
time.Sleep(time.Second * 2)
|
|
} else {
|
|
time.Sleep(time.Second * 3)
|
|
}
|
|
}
|
|
}
|
|
return vo.NewSuccessObj(results, "")
|
|
})
|
|
}
|
|
|
|
// 发送不显示好友消息
|
|
func SendTextMessageNoShowService(queryKey string, m req.MessageNoShowParam) vo.DTO {
|
|
return checkExIdPerformNoCreateConnect(queryKey, func(connect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
|
wxAccount := connect.GetWXAccount()
|
|
loginState := wxAccount.GetLoginState()
|
|
|
|
//判断在线情况
|
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
|
} else if !connect.CheckOnLineStatus() {
|
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
|
}
|
|
// <msg>\n<op id='14'>\n<name>lastMessage</name>\n<arg>{\"messageSvrId\":\"2454843855128515400\",\"MsgCreateTime\":\"1726013089\"}</arg>\n</op>\n</msg>
|
|
// <msg>\n<op id='14'>\n<name>lastMessage</name>\n<arg></arg>\n</op>\n</msg>
|
|
content := fmt.Sprintf("<msg>\n<op id='14'>\n<name>lastMessage</name>\n<arg></arg>\n</op>\n</msg>")
|
|
reqInvoker := connect.GetWXReqInvoker()
|
|
// 发送文本消息
|
|
resp, err := reqInvoker.SendTextMsgRequest(m.ToUserName, content, []string{}, 10000)
|
|
var result gin.H
|
|
if err != nil {
|
|
result = gin.H{
|
|
"toUserName": m.ToUserName,
|
|
"textContent": "",
|
|
"isSendSuccess": false,
|
|
"errMsg": err.Error(),
|
|
}
|
|
} else {
|
|
result = gin.H{
|
|
"toUserName": m.ToUserName,
|
|
"textContent": "",
|
|
"resp": resp,
|
|
"isSendSuccess": true,
|
|
}
|
|
}
|
|
|
|
return vo.NewSuccessObj(result, "")
|
|
})
|
|
}
|
|
|
|
// ShareCardMessageService 分享名片消息
|
|
func ShareCardMessageService(queryKey string, m req.ShareCardParam) vo.DTO {
|
|
return checkExIdPerformNoCreateConnect(queryKey, func(connect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
|
wxAccount := connect.GetWXAccount()
|
|
loginState := wxAccount.GetLoginState()
|
|
|
|
//判断在线情况
|
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
|
} else if !connect.CheckOnLineStatus() {
|
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
|
}
|
|
|
|
content := fmt.Sprintf("<msg username=\"%v\" nickname=\"%v\" fullpy=\"\" shortpy=\"\" alias=\"%v\" imagestatus=\"3\" scene=\"17\" province=\"\" city=\"\" sign=\"\" sex=\"1\" certflag=\"0\" certinfo=\"\" brandIconUrl=\"\" brandHomeUrl=\"\" brandSubscriptConfigUrl=\"\" brandFlags=\"0\" regionCode=\"CN\" ></msg>", m.CardWxId, m.CardNickName, m.CardAlias)
|
|
reqInvoker := connect.GetWXReqInvoker()
|
|
// 发送文本消息
|
|
resp, err := reqInvoker.SendTextMsgRequest(m.ToUserName, content, []string{}, 42)
|
|
var result gin.H
|
|
if err != nil {
|
|
result = gin.H{
|
|
"toUserName": m.ToUserName,
|
|
"textContent": "",
|
|
"isSendSuccess": false,
|
|
"errMsg": err.Error(),
|
|
}
|
|
} else {
|
|
result = gin.H{
|
|
"toUserName": m.ToUserName,
|
|
"textContent": "",
|
|
"resp": resp,
|
|
"isSendSuccess": true,
|
|
}
|
|
}
|
|
|
|
return vo.NewSuccessObj(result, "")
|
|
})
|
|
}
|
|
|
|
// ForwardImageMessageService 转发图片
|
|
func ForwardImageMessageService(queryKey string, m req.ForwardMessageModel) vo.DTO {
|
|
return checkExIdPerformNoCreateConnect(queryKey, func(connect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
|
wxAccount := connect.GetWXAccount()
|
|
loginState := wxAccount.GetLoginState()
|
|
|
|
//判断在线情况
|
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
|
} else if !connect.CheckOnLineStatus() {
|
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
|
}
|
|
|
|
reqInvoker := connect.GetWXReqInvoker()
|
|
|
|
results := garray.New(true)
|
|
|
|
if len(m.ForwardImageList) <= 0 {
|
|
return vo.NewFail("没有要进行转发的数据。")
|
|
}
|
|
for i, item := range m.ForwardImageList {
|
|
var cdnItem baseinfo.ForwardImageItem
|
|
StructCopy(&cdnItem, &item)
|
|
resp, err := reqInvoker.ForwardCdnImageRequest(cdnItem)
|
|
if err != nil {
|
|
results.Append(gin.H{
|
|
"toUSerName": item.ToUserName,
|
|
"cdnMidImgUrl": item.CdnMidImgUrl,
|
|
"isSendSuccess": false,
|
|
"errMsg": err.Error(),
|
|
})
|
|
continue
|
|
} else {
|
|
isSendSuccess := false
|
|
if resp.GetBaseResponse().GetRet() == 0 {
|
|
isSendSuccess = true
|
|
}
|
|
results.Append(gin.H{
|
|
"cdnMidImgUrl": item.CdnMidImgUrl,
|
|
"toUSerName": item.ToUserName,
|
|
"isSendSuccess": isSendSuccess,
|
|
"resp": resp,
|
|
"retCode": resp.GetBaseResponse().GetRet(),
|
|
"errMsg": resp.GetBaseResponse().GetErrMsg().GetStr(),
|
|
})
|
|
}
|
|
//每两天延迟1秒
|
|
if i != 0 && i%2 == 0 {
|
|
time.Sleep(time.Second * 1)
|
|
}
|
|
}
|
|
return vo.NewSuccessObj(results.Interfaces(), "")
|
|
})
|
|
}
|
|
|
|
// ForwardVideoMessageService 转发视频
|
|
func ForwardVideoMessageService(queryKey string, m req.ForwardMessageModel) vo.DTO {
|
|
return checkExIdPerformNoCreateConnect(queryKey, func(connect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
|
wxAccount := connect.GetWXAccount()
|
|
loginState := wxAccount.GetLoginState()
|
|
|
|
//判断在线情况
|
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
|
} else if !connect.CheckOnLineStatus() {
|
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
|
}
|
|
|
|
reqInvoker := connect.GetWXReqInvoker()
|
|
|
|
results := garray.New(true)
|
|
|
|
if len(m.ForwardVideoList) <= 0 {
|
|
return vo.NewFail("没有要进行转发的数据。")
|
|
}
|
|
for i, item := range m.ForwardVideoList {
|
|
var cdnItem baseinfo.ForwardVideoItem
|
|
StructCopy(&cdnItem, &item)
|
|
resp, err := reqInvoker.ForwardCdnVideoRequest(cdnItem)
|
|
if err != nil {
|
|
results.Append(gin.H{
|
|
"toUSerName": item.ToUserName,
|
|
"CdnVideoUrl": item.CdnVideoUrl,
|
|
"isSendSuccess": false,
|
|
"errMsg": err.Error(),
|
|
})
|
|
continue
|
|
} else {
|
|
isSendSuccess := false
|
|
if resp.GetBaseResponse().GetRet() == 0 {
|
|
isSendSuccess = true
|
|
}
|
|
results.Append(gin.H{
|
|
"CdnVideoUrl": item.CdnVideoUrl,
|
|
"toUSerName": item.ToUserName,
|
|
"isSendSuccess": isSendSuccess,
|
|
"resp": resp,
|
|
"retCode": resp.GetBaseResponse().GetRet(),
|
|
"errMsg": resp.GetBaseResponse().GetErrMsg().GetStr(),
|
|
})
|
|
}
|
|
//每两天延迟1秒
|
|
if i != 0 && i%2 == 0 {
|
|
time.Sleep(time.Second * 1)
|
|
}
|
|
}
|
|
return vo.NewSuccessObj(results.Interfaces(), "")
|
|
})
|
|
}
|
|
|
|
// SendAppMessageService 发送app消息
|
|
func SendAppMessageService(queryKey string, m req.AppMessageModel) vo.DTO {
|
|
return checkExIdPerformNoCreateConnect(queryKey, func(iwxConnect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
|
wxAccount := iwxConnect.GetWXAccount()
|
|
loginState := wxAccount.GetLoginState()
|
|
|
|
//判断在线情况
|
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
|
} else if !iwxConnect.CheckOnLineStatus() {
|
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
|
}
|
|
|
|
reqInvoker := iwxConnect.GetWXReqInvoker()
|
|
results := garray.New(true)
|
|
if len(m.AppList) == 0 {
|
|
return vo.NewFail("没有要进行发送的数据。")
|
|
}
|
|
for _, item := range m.AppList {
|
|
resp, err := reqInvoker.SendAppMessage(item.ContentXML, item.ToUserName, item.ContentType)
|
|
if err != nil {
|
|
results.Append(gin.H{
|
|
"contentXML": item.ContentXML,
|
|
"toUserName": item.ToUserName,
|
|
"contentType": item.ContentType,
|
|
"isSendSuccess": false,
|
|
"errMsg": err.Error(),
|
|
})
|
|
continue
|
|
} else {
|
|
isSendSuccess, retCode := false, resp.GetBaseResponse().GetRet()
|
|
if resp.GetBaseResponse().GetRet() == 0 {
|
|
isSendSuccess = true
|
|
}
|
|
results.Append(gin.H{
|
|
"contentXML": item.ContentXML,
|
|
"toUserName": item.ToUserName,
|
|
"contentType": item.ContentType,
|
|
"isSendSuccess": isSendSuccess,
|
|
"resp": resp,
|
|
"retCode": retCode,
|
|
})
|
|
|
|
}
|
|
}
|
|
return vo.NewSuccessObj(results.Interfaces(), "")
|
|
})
|
|
}
|
|
|
|
// SendEmojiMessageService 发送表情
|
|
func SendEmojiMessageService(queryKey string, m req.SendEmojiMessageModel) vo.DTO {
|
|
return checkExIdPerformNoCreateConnect(queryKey, func(connect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
|
wxAccount := connect.GetWXAccount()
|
|
loginState := wxAccount.GetLoginState()
|
|
|
|
//判断在线情况
|
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
|
} else if !connect.CheckOnLineStatus() {
|
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
|
}
|
|
|
|
reqInvoker := connect.GetWXReqInvoker()
|
|
|
|
results := garray.New(true)
|
|
|
|
if len(m.EmojiList) <= 0 {
|
|
return vo.NewFail("没有要进行转发的数据。")
|
|
}
|
|
for i, item := range m.EmojiList {
|
|
resp, err := reqInvoker.SendEmojiRequest(item.EmojiMd5, item.ToUserName, item.EmojiSize)
|
|
if err != nil {
|
|
results.Append(gin.H{
|
|
"toUSerName": item.ToUserName,
|
|
"EmojiMd5": item.EmojiMd5,
|
|
"isSendSuccess": false,
|
|
"errMsg": err.Error(),
|
|
})
|
|
continue
|
|
} else {
|
|
isSendSuccess := false
|
|
if resp.GetBaseResponse().GetRet() == 0 {
|
|
isSendSuccess = true
|
|
}
|
|
results.Append(gin.H{
|
|
"EmojiMd5": item.EmojiMd5,
|
|
"toUSerName": item.ToUserName,
|
|
"isSendSuccess": isSendSuccess,
|
|
"retCode": resp.GetBaseResponse().GetRet(),
|
|
"errMsg": resp.GetBaseResponse().GetErrMsg().GetStr(),
|
|
})
|
|
}
|
|
//每两天延迟1秒
|
|
if i != 0 && i%2 == 0 {
|
|
time.Sleep(time.Second * 1)
|
|
}
|
|
}
|
|
return vo.NewSuccessObj(results.Interfaces(), "")
|
|
|
|
})
|
|
}
|
|
|
|
// ForwardEmojiService 发送表情&包含动图
|
|
func ForwardEmojiService(queryKey string, m req.SendEmojiMessageModel) vo.DTO {
|
|
return checkExIdPerformNoCreateConnect(queryKey, func(connect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
|
wxAccount := connect.GetWXAccount()
|
|
loginState := wxAccount.GetLoginState()
|
|
|
|
//判断在线情况
|
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
|
} else if !connect.CheckOnLineStatus() {
|
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
|
}
|
|
|
|
reqInvoker := connect.GetWXReqInvoker()
|
|
|
|
results := garray.New(true)
|
|
|
|
if len(m.EmojiList) <= 0 {
|
|
return vo.NewFail("没有要进行转发的数据。")
|
|
}
|
|
for i, item := range m.EmojiList {
|
|
resp, err := reqInvoker.ForwardEmojiRequest(item.EmojiMd5, item.ToUserName, item.EmojiSize)
|
|
if err != nil {
|
|
results.Append(gin.H{
|
|
"toUSerName": item.ToUserName,
|
|
"EmojiMd5": item.EmojiMd5,
|
|
"isSendSuccess": false,
|
|
"errMsg": err.Error(),
|
|
})
|
|
continue
|
|
} else {
|
|
isSendSuccess := false
|
|
if resp.GetBaseResponse().GetRet() == 0 {
|
|
isSendSuccess = true
|
|
}
|
|
results.Append(gin.H{
|
|
"EmojiMd5": item.EmojiMd5,
|
|
"toUSerName": item.ToUserName,
|
|
"isSendSuccess": isSendSuccess,
|
|
"retCode": resp.GetBaseResponse().GetRet(),
|
|
"errMsg": resp.GetBaseResponse().GetErrMsg().GetStr(),
|
|
})
|
|
}
|
|
//每两天延迟1秒
|
|
if i != 0 && i%2 == 0 {
|
|
time.Sleep(time.Second * 1)
|
|
}
|
|
}
|
|
return vo.NewSuccessObj(results.Interfaces(), "")
|
|
})
|
|
}
|
|
|
|
// RevokeMsgService 撤销消息
|
|
func RevokeMsgService(queryKey string, m req.RevokeMsgModel) vo.DTO {
|
|
return checkExIdPerformNoCreateConnect(queryKey, func(iwxConnect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
|
wxAccount := iwxConnect.GetWXAccount()
|
|
loginState := wxAccount.GetLoginState()
|
|
|
|
//判断在线情况
|
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
|
} else if !iwxConnect.CheckOnLineStatus() {
|
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
|
}
|
|
|
|
reqInvoker := iwxConnect.GetWXReqInvoker()
|
|
resp, err := reqInvoker.SendRevokeMsgRequest(m.NewMsgId, m.ClientMsgId, m.ToUserName)
|
|
if err != nil {
|
|
return vo.NewFail("RevokeMsgService err:" + err.Error())
|
|
}
|
|
return vo.NewSuccessObj(resp, "")
|
|
})
|
|
}
|
|
|
|
// RevokeMsgNewService 撤回消息New
|
|
func RevokeMsgNewService(queryKey string, m req.RevokeMsgModel) vo.DTO {
|
|
return checkExIdPerformNoCreateConnect(queryKey, func(iwxConnect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
|
wxAccount := iwxConnect.GetWXAccount()
|
|
loginState := wxAccount.GetLoginState()
|
|
|
|
//判断在线情况
|
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
|
} else if !iwxConnect.CheckOnLineStatus() {
|
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
|
}
|
|
|
|
reqInvoker := iwxConnect.GetWXReqInvoker()
|
|
resp, err := reqInvoker.SendRevokeMsgRequestNew(m)
|
|
if err != nil {
|
|
return vo.NewFail("RevokeMsgNewService err:" + err.Error())
|
|
}
|
|
return vo.NewSuccessObj(resp, "")
|
|
})
|
|
}
|
|
|
|
// UploadVoiceRequestService 发送语音
|
|
func UploadVoiceRequestService(queryKey string, m req.SendUploadVoiceRequestModel) vo.DTO {
|
|
return checkExIdPerformNoCreateConnect(queryKey, func(iwxConnect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
|
wxAccount := iwxConnect.GetWXAccount()
|
|
loginState := wxAccount.GetLoginState()
|
|
|
|
//判断在线情况
|
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
|
} else if !iwxConnect.CheckOnLineStatus() {
|
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
|
}
|
|
/*voiceData, err := base64.StdEncoding.DecodeString(m.VoiceData)
|
|
if err != nil {
|
|
return vo.NewFail("UploadVoiceRequestService base64.StdEncoding.DecodeString err" + err.Error())
|
|
}*/
|
|
reqInvoker := iwxConnect.GetWXReqInvoker()
|
|
resp, err := reqInvoker.SendUploadVoiceRequest(m.ToUserName, m.VoiceData, m.VoiceSecond, m.VoiceFormat)
|
|
if err != nil {
|
|
return vo.NewFail("UploadVoiceRequestService err:" + err.Error())
|
|
}
|
|
return vo.NewSuccessObj(resp, "")
|
|
})
|
|
}
|
|
|
|
// SendCdnUploadVideoRequestService UploadVoiceRequestService 发送视频
|
|
func SendCdnUploadVideoRequestService(queryKey string, m req.CdnUploadVideoRequest) vo.DTO {
|
|
return checkExIdPerformNoCreateConnect(queryKey, func(iwxConnect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
|
wxAccount := iwxConnect.GetWXAccount()
|
|
loginState := wxAccount.GetLoginState()
|
|
|
|
//判断在线情况
|
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
|
} else if !iwxConnect.CheckOnLineStatus() {
|
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
|
}
|
|
|
|
reqInvoker := iwxConnect.GetWXReqInvoker()
|
|
resp, err := reqInvoker.SendCdnUploadVideoRequest(m.ToUserName, m.ThumbData, m.VideoData)
|
|
if err != nil {
|
|
return vo.NewFail("UploadVoiceRequestService err:" + err.Error())
|
|
}
|
|
return vo.NewSuccessObj(resp, "")
|
|
})
|
|
}
|
|
|
|
// SendCdnDownloadService 下载请求
|
|
func SendCdnDownloadService(queryKey string, m req.DownMediaModel) vo.DTO {
|
|
return checkExIdPerformNoCreateConnect(queryKey, func(iwxConnect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
|
wxAccount := iwxConnect.GetWXAccount()
|
|
loginState := wxAccount.GetLoginState()
|
|
//判断在线情况
|
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
|
} else if !iwxConnect.CheckOnLineStatus() {
|
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
|
}
|
|
reqInvoker := iwxConnect.GetWXReqInvoker()
|
|
item := baseinfo.DownMediaItem{
|
|
AesKey: m.AesKey,
|
|
FileURL: m.FileURL,
|
|
FileType: m.FileType,
|
|
}
|
|
errorCdn := reqInvoker.SendGetCDNDnsRequest()
|
|
if errorCdn != nil {
|
|
return vo.NewFail("SendGetCDNDnsRequest err:" + errorCdn.Error())
|
|
}
|
|
resp, err := reqInvoker.SendCdnDownloadReuqest(&item)
|
|
if err != nil {
|
|
return vo.NewFail("UploadVoiceRequestService err:" + err.Error())
|
|
}
|
|
return vo.NewSuccessObj(resp, "")
|
|
})
|
|
}
|
|
|
|
// GetMsgBigImgService 获取图片请求
|
|
func GetMsgBigImgService(queryKey string, m req.DownloadParam) vo.DTO {
|
|
return checkExIdPerformNoCreateConnect(queryKey, func(iwxConnect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
|
wxAccount := iwxConnect.GetWXAccount()
|
|
loginState := wxAccount.GetLoginState()
|
|
//判断在线情况
|
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
|
} else if !iwxConnect.CheckOnLineStatus() {
|
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
|
}
|
|
reqInvoker := iwxConnect.GetWXReqInvoker()
|
|
resp, err := reqInvoker.GetMsgBigImg(m)
|
|
if err != nil {
|
|
return vo.NewFail("GetMsgBigImgService err:" + err.Error())
|
|
}
|
|
return vo.NewSuccessObj(resp, "")
|
|
})
|
|
}
|
|
|
|
// GetMsgVideoService 获取视频请求
|
|
func GetMsgVideoService(queryKey string, m req.DownloadParam) vo.DTO {
|
|
return checkExIdPerformNoCreateConnect(queryKey, func(iwxConnect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
|
wxAccount := iwxConnect.GetWXAccount()
|
|
loginState := wxAccount.GetLoginState()
|
|
//判断在线情况
|
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
|
} else if !iwxConnect.CheckOnLineStatus() {
|
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
|
}
|
|
reqInvoker := iwxConnect.GetWXReqInvoker()
|
|
resp, err := reqInvoker.GetMsgVideo(m)
|
|
if err != nil {
|
|
return vo.NewFail("GetMsgVideoService err:" + err.Error())
|
|
}
|
|
return vo.NewSuccessObj(resp, "")
|
|
})
|
|
}
|
|
|
|
// GroupMassMsgTextService 群发文字
|
|
func GroupMassMsgTextService(queryKey string, m req.GroupMassMsgTextModel) vo.DTO {
|
|
return checkExIdPerformNoCreateConnect(queryKey, func(iwxConnect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
|
wxAccount := iwxConnect.GetWXAccount()
|
|
loginState := wxAccount.GetLoginState()
|
|
//判断在线情况
|
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
|
} else if !iwxConnect.CheckOnLineStatus() {
|
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
|
}
|
|
reqInvoker := iwxConnect.GetWXReqInvoker()
|
|
resp, err := reqInvoker.SendGroupMassMsgTextRequest(m.ToUserName, m.Content)
|
|
if err != nil {
|
|
return vo.NewFail("GroupMassMsgTextService err:" + err.Error())
|
|
}
|
|
return vo.NewSuccessObj(resp, "")
|
|
})
|
|
}
|
|
|
|
// GroupMassMsgImageService 群发图片
|
|
func GroupMassMsgImageService(queryKey string, m req.GroupMassMsgImageModel) vo.DTO {
|
|
return checkExIdPerformNoCreateConnect(queryKey, func(iwxConnect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
|
wxAccount := iwxConnect.GetWXAccount()
|
|
loginState := wxAccount.GetLoginState()
|
|
//判断在线情况
|
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
|
} else if !iwxConnect.CheckOnLineStatus() {
|
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
|
}
|
|
reqInvoker := iwxConnect.GetWXReqInvoker()
|
|
imageBytes, _ := base64.StdEncoding.DecodeString(m.ImageBase64)
|
|
resp, err := reqInvoker.SendGroupMassMsgImageRequest(m.ToUserName, imageBytes)
|
|
if err != nil {
|
|
return vo.NewFail("GroupMassMsgImageService err:" + err.Error())
|
|
}
|
|
return vo.NewSuccessObj(resp, "")
|
|
})
|
|
}
|
|
|
|
// SendPatService 群拍一拍
|
|
func SendPatService(queryKey string, m req.SendPatModel) vo.DTO {
|
|
return checkExIdPerformNoCreateConnect(queryKey, func(iwxConnect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
|
wxAccount := iwxConnect.GetWXAccount()
|
|
loginState := wxAccount.GetLoginState()
|
|
//判断在线情况
|
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
|
} else if !iwxConnect.CheckOnLineStatus() {
|
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
|
}
|
|
reqInvoker := iwxConnect.GetWXReqInvoker()
|
|
resp, err := reqInvoker.SendSendPatRequest(m.ChatRoomName, m.ToUserName, m.Scene)
|
|
if err != nil {
|
|
return vo.NewFail("GroupMassMsgTextService err:" + err.Error())
|
|
}
|
|
return vo.NewSuccessObj(resp, "")
|
|
})
|
|
}
|
|
|
|
// GetMsgVoiceService 下载语音
|
|
func GetMsgVoiceService(queryKey string, m req.DownloadVoiceModel) vo.DTO {
|
|
return checkExIdPerformNoCreateConnect(queryKey, func(iwxConnect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
|
wxAccount := iwxConnect.GetWXAccount()
|
|
loginState := wxAccount.GetLoginState()
|
|
//判断在线情况
|
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
|
} else if !iwxConnect.CheckOnLineStatus() {
|
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
|
}
|
|
reqInvoker := iwxConnect.GetWXReqInvoker()
|
|
resp, err := reqInvoker.SendGetMsgVoiceRequest(m.ToUserName, m.NewMsgId, m.Bufid, m.Length)
|
|
if err != nil {
|
|
return vo.NewFail("GetMsgVoiceService err:" + err.Error())
|
|
}
|
|
return vo.NewSuccessObj(resp, "")
|
|
})
|
|
}
|
|
|
|
// NewSyncHistoryMessageService 同步历史消息
|
|
func NewSyncHistoryMessageService(queryKey string) vo.DTO {
|
|
return checkExIdPerformNoCreateConnect(queryKey, func(iwxConnect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
|
wxAccount := iwxConnect.GetWXAccount()
|
|
loginState := wxAccount.GetLoginState()
|
|
//判断在线情况
|
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
|
} else if !iwxConnect.CheckOnLineStatus() {
|
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
|
}
|
|
reqInvoker := iwxConnect.GetWXReqInvoker()
|
|
resp, err := reqInvoker.SendWXSyncContactRequest()
|
|
if err != nil {
|
|
return vo.NewFail("NewSyncHistoryMessageService err:" + err.Error())
|
|
}
|
|
return vo.NewSuccessObj(resp, "成功")
|
|
})
|
|
}
|
|
|
|
// EmojiXML 表情XML结构体
|
|
type EmojiXML struct {
|
|
XMLName xml.Name `xml:"msg"`
|
|
Emoji struct {
|
|
EncryptURL string `xml:"encrypturl,attr"`
|
|
AESKey string `xml:"aeskey,attr"`
|
|
MD5 string `xml:"md5,attr"`
|
|
} `xml:"emoji"`
|
|
}
|
|
|
|
// EmojiDownloadResult 表情下载结果
|
|
type EmojiDownloadResult struct {
|
|
MD5 string `json:"md5"`
|
|
EncryptURL string `json:"encrypt_url"`
|
|
AESKey string `json:"aes_key"`
|
|
DecryptData string `json:"decrypt_data"` // base64编码的解密后数据
|
|
FileExt string `json:"file_ext"`
|
|
}
|
|
|
|
// sanitizeXMLEntities 修复非法 & 为合法 XML 实体
|
|
func sanitizeXMLEntities(xmlStr string) string {
|
|
xmlStr = html.UnescapeString(xmlStr)
|
|
// 先替换所有 & 为临时标记,然后恢复合法实体
|
|
xmlStr = strings.ReplaceAll(xmlStr, "&", "__AMP__")
|
|
xmlStr = strings.ReplaceAll(xmlStr, "__AMP__amp;", "&")
|
|
xmlStr = strings.ReplaceAll(xmlStr, "__AMP__lt;", "<")
|
|
xmlStr = strings.ReplaceAll(xmlStr, "__AMP__gt;", ">")
|
|
xmlStr = strings.ReplaceAll(xmlStr, "__AMP__quot;", """)
|
|
xmlStr = strings.ReplaceAll(xmlStr, "__AMP__apos;", "'")
|
|
// 处理数字实体 &#数字;
|
|
re := regexp.MustCompile(`__AMP__(#[0-9]{1,6};)`)
|
|
xmlStr = re.ReplaceAllString(xmlStr, "&$1")
|
|
// 剩余的 __AMP__ 替换为 &
|
|
xmlStr = strings.ReplaceAll(xmlStr, "__AMP__", "&")
|
|
return xmlStr
|
|
}
|
|
|
|
// parseEmojiXML 解析表情XML
|
|
func parseEmojiXML(xmlStr string) (string, string, string, string, error) {
|
|
if strings.TrimSpace(xmlStr) == "" {
|
|
return "", "", "", "", fmt.Errorf("XML内容为空")
|
|
}
|
|
|
|
xmlStr = sanitizeXMLEntities(xmlStr)
|
|
|
|
var emojiXML EmojiXML
|
|
err := xml.Unmarshal([]byte(xmlStr), &emojiXML)
|
|
if err != nil {
|
|
return "", "", "", "", fmt.Errorf("解析XML失败: %v", err)
|
|
}
|
|
|
|
encryptURL := emojiXML.Emoji.EncryptURL
|
|
aesKey := emojiXML.Emoji.AESKey
|
|
md5 := emojiXML.Emoji.MD5
|
|
|
|
// 验证必要字段
|
|
if encryptURL == "" {
|
|
return "", "", "", "", fmt.Errorf("缺少encrypturl字段")
|
|
}
|
|
if aesKey == "" {
|
|
return "", "", "", "", fmt.Errorf("缺少aeskey字段")
|
|
}
|
|
if md5 == "" {
|
|
return "", "", "", "", fmt.Errorf("缺少md5字段")
|
|
}
|
|
|
|
// 从URL推断文件扩展名
|
|
ext := ".gif" // 默认为gif
|
|
if strings.Contains(encryptURL, ".jpg") || strings.Contains(encryptURL, ".jpeg") {
|
|
ext = ".jpg"
|
|
} else if strings.Contains(encryptURL, ".png") {
|
|
ext = ".png"
|
|
}
|
|
|
|
return encryptURL, aesKey, md5, ext, nil
|
|
}
|
|
|
|
// downloadFile 下载文件
|
|
func downloadFile(url string) ([]byte, error) {
|
|
resp, err := http.Get(url)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("下载文件失败: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("下载文件失败,状态码: %d", resp.StatusCode)
|
|
}
|
|
|
|
data, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("读取文件数据失败: %v", err)
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
// decryptFile 解密文件
|
|
func decryptFile(encryptedData []byte, aeskeyHex string) ([]byte, error) {
|
|
key, err := hex.DecodeString(aeskeyHex)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("解析AES密钥失败: %v", err)
|
|
}
|
|
|
|
// 支持16字节(AES-128)和32字节(AES-256)密钥
|
|
if len(key) != 16 && len(key) != 32 {
|
|
return nil, fmt.Errorf("AES密钥长度错误,期望16或32字节,实际%d字节, AESKey: %s", len(key), aeskeyHex)
|
|
}
|
|
|
|
block, err := aes.NewCipher(key)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("创建AES加密器失败: %v", err)
|
|
}
|
|
|
|
if len(encryptedData) < aes.BlockSize {
|
|
return nil, errors.New("加密数据长度不足")
|
|
}
|
|
|
|
// 使用密钥的前16字节作为IV(与Python代码保持一致)
|
|
var iv []byte
|
|
if len(key) >= 16 {
|
|
iv = key[:16]
|
|
} else {
|
|
iv = key
|
|
}
|
|
mode := cipher.NewCBCDecrypter(block, iv)
|
|
|
|
// 确保数据长度是块大小的倍数
|
|
if len(encryptedData)%aes.BlockSize != 0 {
|
|
return nil, errors.New("加密数据长度不是块大小的倍数")
|
|
}
|
|
|
|
decryptedData := make([]byte, len(encryptedData))
|
|
mode.CryptBlocks(decryptedData, encryptedData)
|
|
|
|
// 移除PKCS7填充(与Python代码保持一致)
|
|
if len(decryptedData) > 0 {
|
|
padLen := int(decryptedData[len(decryptedData)-1])
|
|
if padLen > 0 && padLen < 16 && padLen <= len(decryptedData) {
|
|
decryptedData = decryptedData[:len(decryptedData)-padLen]
|
|
}
|
|
}
|
|
|
|
return decryptedData, nil
|
|
}
|
|
|
|
// processEmojiDownload 处理表情下载
|
|
func processEmojiDownload(xmlContent string) (*EmojiDownloadResult, error) {
|
|
// 解析XML获取下载信息
|
|
encryptURL, aesKey, md5, ext, err := parseEmojiXML(xmlContent)
|
|
if err != nil {
|
|
log.Printf("[EmojiDownload] 解析XML失败: %v, XML内容: %s", err, xmlContent)
|
|
return nil, fmt.Errorf("解析XML失败: %v", err)
|
|
}
|
|
|
|
log.Printf("[EmojiDownload] 开始下载表情: URL=%s, MD5=%s, AESKey=%s", encryptURL, md5, aesKey)
|
|
|
|
// 下载加密文件
|
|
encryptedData, err := downloadFile(encryptURL)
|
|
if err != nil {
|
|
log.Printf("[EmojiDownload] 下载文件失败: %v, URL: %s", err, encryptURL)
|
|
return nil, fmt.Errorf("下载文件失败: %v", err)
|
|
}
|
|
|
|
log.Printf("[EmojiDownload] 文件下载成功,大小: %d bytes", len(encryptedData))
|
|
|
|
// 解密文件
|
|
decryptedData, err := decryptFile(encryptedData, aesKey)
|
|
if err != nil {
|
|
log.Printf("[EmojiDownload] 解密文件失败: %v, AESKey: %s", err, aesKey)
|
|
return nil, fmt.Errorf("解密文件失败: %v", err)
|
|
}
|
|
|
|
log.Printf("[EmojiDownload] 文件解密成功,解密后大小: %d bytes", len(decryptedData))
|
|
|
|
// 将解密后的数据编码为base64
|
|
decryptedBase64 := base64.StdEncoding.EncodeToString(decryptedData)
|
|
|
|
log.Printf("[EmojiDownload] 表情处理完成,MD5: %s", md5)
|
|
|
|
result := &EmojiDownloadResult{
|
|
MD5: md5,
|
|
EncryptURL: encryptURL,
|
|
AESKey: aesKey,
|
|
DecryptData: decryptedBase64,
|
|
FileExt: ext,
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// DownloadEmojiGifService 下载表情gif服务
|
|
func DownloadEmojiGifService(queryKey string, m req.DownloadEmojiModel) vo.DTO {
|
|
return checkExIdPerformNoCreateConnect(queryKey, func(connect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
|
wxAccount := connect.GetWXAccount()
|
|
loginState := wxAccount.GetLoginState()
|
|
|
|
//判断在线情况
|
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
|
} else if !connect.CheckOnLineStatus() {
|
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
|
}
|
|
|
|
// 调用表情下载处理函数
|
|
result, err := processEmojiDownload(m.XmlContent)
|
|
if err != nil {
|
|
return vo.NewFail("处理表情下载失败: " + err.Error())
|
|
}
|
|
|
|
return vo.NewSuccessObj(result, "表情下载成功")
|
|
})
|
|
}
|
|
|
|
// UploadImageToCDNService 纯CDN图片上传
|
|
func UploadImageToCDNService(queryKey string, m req.UploadImageToCDNModel) vo.DTO {
|
|
return checkExIdPerformNoCreateConnect(queryKey, func(connect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
|
|
|
wxAccount := connect.GetWXAccount()
|
|
loginState := wxAccount.GetLoginState()
|
|
|
|
//判断在线情况
|
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
|
} else if !connect.CheckOnLineStatus() {
|
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
|
}
|
|
|
|
reqInvoker := connect.GetWXReqInvoker()
|
|
|
|
if m.ImageContent == "" {
|
|
return vo.NewFail("没有要上传的图片!")
|
|
}
|
|
|
|
// 解析 base64 图片
|
|
imageContent := m.ImageContent
|
|
sImageBase := strings.Split(imageContent, ",")
|
|
if len(sImageBase) > 1 {
|
|
imageContent = sImageBase[1]
|
|
}
|
|
imageBytes, err := base64.StdEncoding.DecodeString(imageContent)
|
|
if err != nil {
|
|
return vo.NewFail("图片解码失败: " + err.Error())
|
|
}
|
|
|
|
imageId := baseutils.Md5ValueByte(imageBytes, false)
|
|
|
|
// 使用文件传输助手作为占位符
|
|
toUser := "filehelper"
|
|
|
|
// 使用默认 source 值 2
|
|
sourceValue := uint32(2)
|
|
|
|
var cdnResp *baseinfo.CdnImageUploadResponse
|
|
var aesKey string
|
|
|
|
cdnResp, aesKey, err = reqInvoker.SendCdnUploadImageReuqestWithSource(imageBytes, toUser, sourceValue)
|
|
if err != nil || cdnResp == nil {
|
|
errMsg := "上传失败"
|
|
if err != nil {
|
|
errMsg = err.Error()
|
|
}
|
|
return vo.NewFail(errMsg)
|
|
}
|
|
|
|
// 返回完整的CDN响应
|
|
result := gin.H{
|
|
"imageId": imageId,
|
|
"imageMD5": imageId,
|
|
"isSendSuccess": true,
|
|
"aesKey": aesKey,
|
|
"cdnResponse": gin.H{
|
|
"ver": cdnResp.Ver,
|
|
"seq": cdnResp.Seq,
|
|
"retCode": cdnResp.RetCode,
|
|
"fileKey": cdnResp.FileKey,
|
|
"recvLen": cdnResp.RecvLen,
|
|
"sKeyResp": cdnResp.SKeyResp,
|
|
"fileID": cdnResp.FileID,
|
|
"cdnBigImgUrl": cdnResp.CdnBigImgUrl,
|
|
"cdnMidImgUrl": cdnResp.CdnMidImgUrl,
|
|
"cdnThumbImgUrl": cdnResp.CdnThumbImgUrl,
|
|
"existFlag": cdnResp.ExistFlag,
|
|
"hitType": cdnResp.HitType,
|
|
"retrySec": cdnResp.RetrySec,
|
|
"isRetry": cdnResp.IsRetry,
|
|
"isOverLoad": cdnResp.IsOverLoad,
|
|
"isGetCDN": cdnResp.IsGetCDN,
|
|
"xClientIP": cdnResp.XClientIP,
|
|
},
|
|
}
|
|
|
|
return vo.NewSuccessObj(result, "")
|
|
})
|
|
}
|