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