package clientsdk import ( "encoding/hex" "errors" "fmt" "log" "math/rand" "strconv" "strings" "time" "xiawan/wx/api/req" "xiawan/wx/clientsdk/mmtls" "github.com/gogo/protobuf/proto" "xiawan/wx/clientsdk/baseinfo" "xiawan/wx/clientsdk/baseutils" "xiawan/wx/protobuf/wechat" ) // ---------------------创建请求--------------------------------------------------------------- // CreateCDNDownloadRequest 创建Cdn资源下载请求 func CreateCDNDownloadRequest(userInfo *baseinfo.UserInfo, dnsInfo *wechat.CDNDnsInfo, aesKeyBytes []byte, imageURL string, fileType uint32) *baseinfo.CdnImageDownloadRequest { request := &baseinfo.CdnImageDownloadRequest{} request.Ver = 1 request.WeiXinNum = dnsInfo.GetUin() request.Seq = 1 request.ClientVersion = userInfo.ClientVersion if !(int(request.ClientVersion) > 0) { request.ClientVersion = baseinfo.ClientVersion } if userInfo.DeviceInfo == nil { request.ClientOsType = baseinfo.AndroidDeviceType } else { request.ClientOsType = userInfo.DeviceInfo.OsType } // authkey authKey := dnsInfo.GetAuthKey().GetBuffer() request.AuthKey = authKey request.NetType = 1 request.AcceptDupack = 1 request.RsaVer = 1 rsaBytes, _ := baseutils.CdnRsaEncrypt(aesKeyBytes) request.RsaValue = rsaBytes request.FileType = fileType request.WxChatType = 0 request.FileID = imageURL request.LastRetCode = 0 request.IPSeq = 0 request.CliQuicFlag = 0 request.WxMsgFlag = nil request.WxAutoStart = 1 request.DownPicFormat = 1 request.Offset = 0 request.LargesVideo = 0 request.SourceFlag = 0 return request } // CreateVideoUploadRequest 创建视频上传请求 func CreateVideoUploadRequest(userInfo *baseinfo.UserInfo, videoItem *baseinfo.UploadVideoItem) (*baseinfo.CdnVideoUploadRequest, error) { request := &baseinfo.CdnVideoUploadRequest{} request.Ver = 1 request.WeiXinNum = videoItem.CDNDns.GetUin() request.Seq = videoItem.Seq request.ClientVersion = userInfo.ClientVersion if !(int(request.ClientVersion) > 0) { request.ClientVersion = baseinfo.ClientVersion } if userInfo.DeviceInfo == nil { request.ClientOSType = baseinfo.AndroidDeviceType } else { request.ClientOSType = userInfo.DeviceInfo.OsType } request.AutoKey = videoItem.CDNDns.AuthKey.GetBuffer() request.NetType = 1 request.AcceptDuPack = 1 request.SafeProto = 1 request.FileType = 4 request.WeChatType = 0 request.LastRetCode = 0 request.IpSeq = 0 request.HastHumb = 1 // ToUser retBytes := baseutils.AesEncrypt([]byte(videoItem.ToUser), []byte("wxusrname2016cdn")) request.ToUSerName = "@cdn2_" + baseutils.BytesToHexString(retBytes, false) request.CompressType = 0 request.NoCheckAesKey = 1 request.EnaBleHit = 1 request.ExistAnceCheck = 0 request.AppType = 0 // 加密视频 videoEncodeData := baseutils.AesEncryptECB(videoItem.VideoData, videoItem.AesKey) // 加密微缩图 thumbEncodeData := baseutils.AesEncryptECB(videoItem.ThumbData, videoItem.AesKey) userMd5 := baseutils.Md5ValueByte([]byte(userInfo.GetUserName()+"-"+videoItem.ToUser), false) timeStamp := fmt.Sprintf("%d", time.Now().UnixNano()/1000/1000) videoKey := timeStamp + "baed6285091" fileKey := fmt.Sprintf("aupvideo_%s_%s_%s", userMd5, timeStamp, videoKey) // guid request.FileKey = fileKey request.LocalName = baseutils.Md5ValueByte(videoItem.VideoData, false) + ".jpg" request.Offset = 0 request.LargesVideo = 1 request.SourceFlag = 0 request.DropRateFlag = 1 request.ClientRsaVer = 1 //rsaValue, _ := baseutils.RsaEncryptByVer(videoItem.AesKey, userInfo.GetLoginRsaVer()) rsaValue, _ := baseutils.CdnRsaEncrypt(videoItem.AesKey) request.ClientRsaVal = rsaValue request.AdVideoFlag = 0 request.FileMd5 = baseutils.Md5ValueByte(videoItem.VideoData, false) request.RawFileMd5 = baseutils.Md5ValueByte(videoItem.VideoData, false) request.DataCheckSum = baseutils.Adler32(0, videoEncodeData) request.EncThumbCrc = baseutils.Adler32(0, thumbEncodeData) request.ThumbData = thumbEncodeData request.RawThumbMd5 = baseutils.Md5ValueByte(videoItem.ThumbData, false) request.TotalSize = uint32(len(videoEncodeData)) request.RawTotalSize = uint32(len(videoItem.VideoData)) request.ThumbTotalSize = uint32(len(thumbEncodeData)) request.RawThumbSize = uint32(len(videoItem.ThumbData)) request.FileCrc = baseutils.Adler32(0, videoItem.VideoData) request.FileData = videoEncodeData request.Mp4identify = "a79c98ca478c707db3c80d28766f89e0" return request, nil } // CreateImageUploadRequest 创建上传高清图请求 func CreateImageUploadRequest(userInfo *baseinfo.UserInfo, imgItem *baseinfo.UploadImgItem) (*baseinfo.CdnImageUploadRequest, error) { request := &baseinfo.CdnImageUploadRequest{} request.Ver = 1 request.WeiXinNum = uint32(imgItem.CDNDns.GetUin()) request.Seq = imgItem.Seq if !strings.HasPrefix(userInfo.LoginDataInfo.LoginData, "A") && userInfo.DeviceInfo != nil { request.ClientOsType = userInfo.DeviceInfo.OsType request.ClientVersion = userInfo.ClientVersion if !(int(request.ClientVersion) > 0) { request.ClientVersion = baseinfo.ClientVersion } } else { request.ClientOsType = baseinfo.AndroidDeviceType request.ClientVersion = baseinfo.AndroidClientVersion } request.AuthKey = imgItem.CDNDns.AuthKey.GetBuffer() request.NetType = 1 request.AcceptDupack = 1 request.SafeProto = 1 request.FileType = 2 request.WxChatType = 0 request.LastRetCode = 0 request.IPSeq = 0 request.CliQuicFlag = 0 request.CompressType = 0 request.NoCheckAesKey = 1 request.EnableHit = 1 request.ExistAnceCheck = 0 request.AppType = 1 request.LocalName = imgItem.LocalName + imgItem.ExtName request.Offset = 0 request.LargesVideo = 0 request.SourceFlag = 0 request.AdVideoFlag = 0 // ToUser retBytes := baseutils.AesEncrypt([]byte(imgItem.ToUser), []byte("wxusrname2016cdn")) request.ToUser = "@cdn2_" + baseutils.BytesToHexString(retBytes, false) // ThumbData thumbItem := CreateThumbImage(imgItem.ImageData) if thumbItem == nil { return nil, errors.New("生成缩略图失败") } thumbEncodeData := baseutils.AesEncryptECB(thumbItem.Data, []byte(imgItem.AesKey)) request.HasThumb = 1 request.ThumbTotalSize = uint32(len(thumbEncodeData)) request.RawThumbSize = uint32(len(thumbItem.Data)) request.RawThumbMD5 = baseutils.Md5ValueByte(thumbItem.Data, false) request.EncThumbCRC = baseutils.Adler32(0, thumbEncodeData) request.ThumbData = thumbEncodeData // FileKey request.FileKey = "wxupload_" + imgItem.ToUser + imgItem.LocalName + "_" + strconv.Itoa(int(imgItem.CreateTime)) // FileData fileEncodeData := baseutils.AesEncryptECB(imgItem.ImageData, []byte(imgItem.AesKey)) request.TotalSize = uint32(len(fileEncodeData)) request.RawTotalSize = uint32(len(imgItem.ImageData)) request.FileMD5 = baseutils.Md5ValueByte(fileEncodeData, false) request.RawFileMD5 = baseutils.Md5ValueByte(imgItem.ImageData, false) request.FileCRC = baseutils.Adler32(0, imgItem.ImageData) request.DataCheckSum = baseutils.Adler32(0, fileEncodeData) request.FileData = fileEncodeData request.SetOfPicFormat = "011000" // prepareRequestItem var prepareRequestItem baseinfo.CDNUploadMsgImgPrepareRequestItem prepareRequestItem.ToUser = imgItem.ToUser prepareRequestItem.LocalName = imgItem.LocalName prepareRequestItem.CreateTime = imgItem.CreateTime prepareRequestItem.ThumbWidth = thumbItem.Width prepareRequestItem.ThumbHeight = thumbItem.Height prepareRequestItem.AesKey = imgItem.AesKey prepareRequestItem.Crc32 = request.FileCRC // SessionBuf request.SessionBuf = CreateCDNUploadMsgImgPrepareRequest(userInfo, &prepareRequestItem) return request, nil } // CreateSnsImageUploadRequest 创建Cdn上传朋友圈高清图请求 func CreateSnsImageUploadRequest(userInfo *baseinfo.UserInfo, snsImgItem *baseinfo.SnsUploadImgItem) (*baseinfo.CdnSnsImageUploadRequest, error) { request := &baseinfo.CdnSnsImageUploadRequest{} request.Ver = 1 request.WeiXinNum = uint32(snsImgItem.CDNDns.GetUin()) request.Seq = snsImgItem.Seq request.ClientVersion = userInfo.ClientVersion if !(int(request.ClientVersion) > 0) { request.ClientVersion = baseinfo.ClientVersion } if userInfo.DeviceInfo == nil { request.ClientOsType = baseinfo.AndroidDeviceType } else { request.ClientOsType = userInfo.DeviceInfo.OsType } request.AuthKey = snsImgItem.CDNDns.AuthKey.GetBuffer() request.NetType = 1 request.AcceptDupack = 1 request.RsaVer = 1 rsaValue, _ := baseutils.CdnRsaEncrypt(snsImgItem.AesKey) request.RsaValue = rsaValue request.FileType = 20201 request.WxChatType = 0 request.LastRetCode = 0 request.IPSeq = 0 request.CliQuicFlag = 0 request.HasThumb = 0 request.ToUser = "" request.CompressType = 1 request.NoCheckAesKey = 1 request.EnableHit = 1 request.ExistAnceCheck = 0 request.AppType = 108 request.LocalName = "[TEMP]" + strconv.Itoa(int(snsImgItem.ImageID)) + "_" + strconv.Itoa(int(snsImgItem.CreateTime)) request.FileKey = request.LocalName + "_" + strconv.Itoa(int(snsImgItem.CreateTime)+rand.Intn(1000)) request.Offset = 0 request.ThumbTotalSize = 0 request.RawThumbSize = 0 request.RawThumbMD5 = "" request.ThumbCRC = 0 request.LargesVideo = 0 request.SourceFlag = 0 request.AdVideoFlag = 0 // FileData fileEncodeData := baseutils.AesEncryptECB(snsImgItem.ImageData, []byte(snsImgItem.AesKey)) request.TotalSize = uint32(len(snsImgItem.ImageData)) request.RawTotalSize = request.TotalSize request.FileMD5 = baseutils.Md5ValueByte(snsImgItem.ImageData, false) request.RawFileMD5 = request.FileMD5 request.FileCRC = baseutils.Adler32(0, snsImgItem.ImageData) request.DataCheckSum = baseutils.Adler32(0, fileEncodeData) request.FileData = snsImgItem.ImageData return request, nil } // CreateSnsVideoDownloadRequest 创建Cdn下载朋友圈视频请求 func CreateSnsVideoDownloadRequest(userInfo *baseinfo.UserInfo, snsVideoItem *baseinfo.SnsVideoDownloadItem) (*baseinfo.CdnSnsVideoDownloadRequest, error) { request := &baseinfo.CdnSnsVideoDownloadRequest{} request.Ver = 1 request.WeiXinNum = uint32(snsVideoItem.CDNDns.GetUin()) request.Seq = snsVideoItem.Seq request.ClientVersion = userInfo.ClientVersion if !(int(request.ClientVersion) > 0) { request.ClientVersion = baseinfo.ClientVersion } if userInfo.DeviceInfo == nil { request.ClientOsType = baseinfo.AndroidDeviceType } else { request.ClientOsType = userInfo.DeviceInfo.OsType } request.AuthKey = snsVideoItem.CDNDns.AuthKey.GetBuffer() request.NetType = 1 request.AcceptDupack = 1 request.Signal = "" request.Scene = "" request.URL = snsVideoItem.URL request.RangeStart = snsVideoItem.RangeStart request.RangeEnd = snsVideoItem.RangeEnd request.LastRetCode = 0 request.IPSeq = 0 request.RedirectType = 0 request.LastVideoFormat = 0 request.VideoFormat = 2 request.XSnsVideoFlag = snsVideoItem.XSnsVideoFlag return request, nil } // CreateCdnSnsVideoUploadRequest 上传朋友圈视频 func CreateCdnSnsVideoUploadRequest(userInfo *baseinfo.UserInfo, videoUploadItem *baseinfo.SnsVideoUploadItem) (*baseinfo.CdnSnsVideoUploadRequest, error) { request := &baseinfo.CdnSnsVideoUploadRequest{} filekey := fmt.Sprintf("aupimg_%s_%d", userInfo.GetUserName(), time.Now().UnixNano()/1000/1000) request.Ver = 1 request.WeiXinNum = uint32(videoUploadItem.CDNDns.GetUin()) request.Seq = videoUploadItem.Seq request.ClientVersion = userInfo.ClientVersion if !(int(request.ClientVersion) > 0) { request.ClientVersion = baseinfo.ClientVersion } if userInfo.DeviceInfo == nil { request.ClientOsType = baseinfo.AndroidDeviceType } else { request.ClientOsType = userInfo.DeviceInfo.OsType } request.AuthKey = videoUploadItem.CDNDns.AuthKey.GetBuffer() request.NetType = 1 request.AcceptDupack = 1 request.RsaVer = 1 fileAESKey := "42807213a18c421db34bad77219f5172" bFileAESKey, _ := hex.DecodeString(fileAESKey) rsaValue, _ := baseutils.CdnRsaEncrypt(bFileAESKey) request.RsaValue = rsaValue request.FileType = 20202 //20202 request.WxChatType = 0 request.LastRetCode = 0 request.IPSeq = 0 request.CliQuicFlag = 0 request.IsStoreVideo = 0 request.NoCheckAesKey = 1 request.EnableHit = 1 request.ExistAnceCheck = 0 request.AppType = 102 totalSize := uint32(len(videoUploadItem.VideoData)) request.TotalSize = totalSize request.RawTotalSize = totalSize // tmpLocalNameNoExt := "[TEMP]" + strconv.Itoa(int(videoUploadItem.VideoID)) + "_" + strconv.Itoa(int(videoUploadItem.CreateTime)) request.LocalName = "sns_tmps_Locall_path4" request.FileKey = filekey request.Offset = 0 // 暂时不设置Thumb数据 thumbDataLen := uint32(len(videoUploadItem.ThumbData)) request.HasThumb = 1 request.ThumbTotalSize = thumbDataLen request.RawThumbSize = thumbDataLen request.RawThumbMD5 = baseutils.Md5ValueByte(videoUploadItem.ThumbData, false) request.ThumbCRC = baseutils.Adler32(0, videoUploadItem.ThumbData) request.ThumbData = videoUploadItem.ThumbData request.LargesVideo = 80 request.SourceFlag = 0 request.AdVideoFlag = 0 fileEncodeData := baseutils.AesEncryptECB(videoUploadItem.VideoData, videoUploadItem.AesKey) request.Mp4Identify = baseutils.Md5ValueByte(fileEncodeData, false) md5Value := baseutils.Md5ValueByte(videoUploadItem.VideoData, false) request.FileMD5 = md5Value request.RawFileMD5 = md5Value request.FileCRC = baseutils.Adler32(0, videoUploadItem.VideoData) request.DataCheckSum = baseutils.Adler32(0, fileEncodeData) request.FileData = videoUploadItem.VideoData //request.UserLargeFileApi=true return request, nil } // ---------------------发送请求--------------------------------------------------------------- // SendCdnDownloadReuqest 发送CDN下载请求 func SendCdnDownloadReuqest(userInfo *baseinfo.UserInfo, downItem *baseinfo.DownMediaItem) (*baseinfo.CdnDownloadResponse, error) { dnsInfo := userInfo.DNSInfo aesKeyBytes := baseutils.HexStringToBytes(downItem.AesKey) request := CreateCDNDownloadRequest(userInfo, dnsInfo, aesKeyBytes, downItem.FileURL, downItem.FileType) sendData := PackCdnImageDownloadRequest(request) // 连接Cdn服务器 serverIP := dnsInfo.FrontIplist[0].GetStr() serverPort := dnsInfo.FrontIpportList[0].PortList[0] conn, err := ConnectCdnServer(serverIP, serverPort) if err != nil { return nil, err } defer conn.Close() // 发送请求数据 conn.Write(sendData) retData := CDNRecvData(conn) // 返回解密图片数据 response, err := DecodeImageDownloadResponse(retData) if err != nil { return nil, err } response.FileData = baseutils.AesDecryptECB(response.FileData, aesKeyBytes) return response, nil } // 获取图片 func GetMsgBigImg(userInfo *baseinfo.UserInfo, m req.DownloadParam) (*baseinfo.PackHeader, error) { if m.Section.DataLen > 65535 { m.Section.DataLen = 65535 } reqData := wechat.GetMsgImgRequest{ BaseRequest: GetBaseRequest(userInfo), MsgId: proto.Uint32(m.MsgId), FromUserName: &wechat.SKBuiltinString{ Str: proto.String(m.FromUserName), }, ToUserName: &wechat.SKBuiltinString{ Str: proto.String(m.ToUserName), //Str: proto.String(userInfo.WxId), // 别人发送给 "我" 的图片数据 }, TotalLen: proto.Uint32(uint32(m.TotalLen)), StartPos: proto.Uint32(m.Section.StartPos), DataLen: proto.Uint32(m.Section.DataLen), CompressType: proto.Uint32(uint32(m.CompressType)), } // 打包发送数据 srcData, _ := proto.Marshal(&reqData) sendEncodeData := Pack(userInfo, srcData, 109, 5) resp, err := mmtls.MMHTTPPostData(userInfo.MMInfo, "/cgi-bin/micromsg-bin/getmsgimg", sendEncodeData) if err != nil { return nil, err } return DecodePackHeader(resp, nil) } // GetMsgCdnVideo 获取视频(下载视频数据) func GetMsgCdnVideo(userInfo *baseinfo.UserInfo, m req.DownloadParam) (*baseinfo.PackHeader, error) { req := wechat.DownloadVideoRequest{ BaseRequest: GetBaseRequest(userInfo), MsgId: proto.Uint32(m.MsgId), TotalLen: proto.Uint32(uint32(m.TotalLen)), StartPos: proto.Uint32(m.Section.StartPos), NetworkEnv: proto.Uint32(1), MxPackSize: proto.Uint32(m.Section.DataLen), } // 打包发送数据 srcData, _ := proto.Marshal(&req) sendEncodeData := Pack(userInfo, srcData, baseinfo.MMRequestTypeForwardCdnVideo, 5) resp, err := mmtls.MMHTTPPostData(userInfo.MMInfo, "/cgi-bin/micromsg-bin/downloadvideo", sendEncodeData) if err != nil { return nil, err } return DecodePackHeader(resp, nil) } func SendCdnUploadVideoRequest(userInfo *baseinfo.UserInfo, toUser string, imgData []byte, videoData []byte) (*baseinfo.CdnMsgVideoUploadResponse, error) { videoItem := &baseinfo.UploadVideoItem{} videoItem.ThumbData = imgData videoItem.VideoData = videoData videoItem.ToUser = toUser videoItem.CDNDns = userInfo.DNSInfo videoItem.Seq = uint32(rand.Intn(10)) videoItem.AesKey, _ = hex.DecodeString("1f22e78fd07d46a68b889aa222e93563") request, err := CreateVideoUploadRequest(userInfo, videoItem) if err != nil { return nil, err } sendData := PackCdnVideoUploadRequest(request) // 连接Cdn服务器 serverIP := videoItem.CDNDns.FrontIplist[0].GetStr() serverPort := videoItem.CDNDns.FrontIpportList[0].PortList[0] conn, err := ConnectCdnServer(serverIP, serverPort) if err != nil { return nil, err } // 发送数据 conn.Write(sendData) defer conn.Close() retryCount := uint32(0) // 接收响应信息 for { // 接收响应信息,解析 retData := CDNRecvData(conn) response, err := DecodeVideoUploadResponse(retData) if err != nil { if retryCount < 3 { retryCount++ continue } return nil, err } retryCount = 0 // 判断错误码 if response.RetCode != 0 { return nil, errors.New("上传视频失败: ErrCode = " + GetErrStringByRetCode(response.RetCode)) } // 判断 服务器是否接收完毕 if response.FileID != "" { response.FileAesKey = "1f22e78fd07d46a68b889aa222e93563" response.ThumbDataSize = uint32(len(videoItem.ThumbData)) response.VideoDataSize = uint32(len(videoItem.VideoData)) response.Mp4identify = "a79c98ca478c707db3c80d28766f89e0" response.VideoDataMD5 = request.RawFileMd5 response.ThumbWidth = 200 response.ThumbHeight = 200 return response, nil } // 设置请求数据 //response.ReqData = request } } // SendCdnUploadImageReuqest 发送CDN上传图片请求 func SendCdnUploadImageReuqest(userInfo *baseinfo.UserInfo, toUser string, imgData []byte) (bool, error) { imgItem := &baseinfo.UploadImgItem{} imgItem.ToUser = toUser imgItem.Seq = uint32(rand.Intn(10)) imgItem.LocalName = strconv.Itoa(rand.Intn(100)) imgItem.ExtName = ".pic" // 随机生成AesKey imgItem.AesKey = []byte(baseutils.RandomStringByLength(16)) imgItem.ImageData = imgData imgItem.CreateTime = uint32(time.Now().UnixNano() / 1000000000) // 设置Dns路由信息 imgItem.CDNDns = userInfo.FAKEDnsInfo // 创建上传图片请求 request, err := CreateImageUploadRequest(userInfo, imgItem) if err != nil { return false, err } // 打包请求 sendData := PackCdnImageUploadRequest(request) // 连接Cdn服务器 serverIP := imgItem.CDNDns.FrontIplist[0].GetStr() serverPort := imgItem.CDNDns.FrontIpportList[0].PortList[0] conn, err := ConnectCdnServer(serverIP, serverPort) if err != nil { return false, err } // 发送数据 conn.Write(sendData) defer conn.Close() retryCount := uint32(0) // 接收响应信息 sendImgLen := uint32(len(request.FileData)) for { // 接收响应信息,解析 retData := CDNRecvData(conn) response, err := DecodeImageUploadResponse(retData) if err != nil { if retryCount < 3 { retryCount++ continue } return false, err } retryCount = 0 // 判断错误码 if response.RetCode != 0 { return false, errors.New("发送图片失败: ErrCode = " + GetErrStringByRetCode(response.RetCode)) } // 判断 服务器是否接收完毕 if response.RecvLen < sendImgLen { continue } // SKeyResp CDNUploadMsgImgPrepareResponse的错误码 if response.SKeyResp != 0 { return false, errors.New("发送图片失败: SKeyResp = " + strconv.Itoa(int(response.SKeyResp))) } // SKeyBuf skbufLen := len(response.SKeyBuf) if skbufLen <= 0 { continue } break } return true, nil } // SendCdnUploadImageReuqestWithDetails 发送CDN上传图片请求并返回详细信息 func SendCdnUploadImageReuqestWithDetails(userInfo *baseinfo.UserInfo, toUser string, imgData []byte) (*baseinfo.CdnImageUploadResponse, string, error) { imgItem := &baseinfo.UploadImgItem{} imgItem.ToUser = toUser imgItem.Seq = uint32(rand.Intn(10)) imgItem.LocalName = strconv.Itoa(rand.Intn(100)) imgItem.ExtName = ".pic" // 随机生成AesKey imgItem.AesKey = []byte(baseutils.RandomStringByLength(16)) aesKeyHex := hex.EncodeToString(imgItem.AesKey) imgItem.ImageData = imgData imgItem.CreateTime = uint32(time.Now().UnixNano() / 1000000000) // 设置Dns路由信息 imgItem.CDNDns = userInfo.FAKEDnsInfo // 创建上传图片请求 request, err := CreateImageUploadRequest(userInfo, imgItem) if err != nil { return nil, "", err } // 打包请求 sendData := PackCdnImageUploadRequest(request) // 连接Cdn服务器 serverIP := imgItem.CDNDns.FrontIplist[0].GetStr() serverPort := imgItem.CDNDns.FrontIpportList[0].PortList[0] conn, err := ConnectCdnServer(serverIP, serverPort) if err != nil { return nil, "", err } // 发送数据 conn.Write(sendData) defer conn.Close() retryCount := uint32(0) // 接收响应信息 sendImgLen := uint32(len(request.FileData)) var finalResponse *baseinfo.CdnImageUploadResponse for { // 接收响应信息,解析 retData := CDNRecvData(conn) response, err := DecodeImageUploadResponse(retData) if err != nil { if retryCount < 3 { retryCount++ continue } return nil, "", err } retryCount = 0 // 判断错误码 if response.RetCode != 0 { return nil, "", errors.New("发送图片失败: ErrCode = " + GetErrStringByRetCode(response.RetCode)) } // 判断 服务器是否接收完毕 if response.RecvLen < sendImgLen { continue } // SKeyResp CDNUploadMsgImgPrepareResponse的错误码 if response.SKeyResp != 0 { return nil, "", errors.New("发送图片失败: SKeyResp = " + strconv.Itoa(int(response.SKeyResp))) } // SKeyBuf skbufLen := len(response.SKeyBuf) if skbufLen <= 0 { continue } finalResponse = response break } log.Printf("CDN上传图片响应: Ver=%d, Seq=%d, RetCode=%d, FileKey=%s, RecvLen=%d, FileID=%s", finalResponse.Ver, finalResponse.Seq, finalResponse.RetCode, finalResponse.FileKey, finalResponse.RecvLen, finalResponse.FileID) // 解析SKeyBuf获取FileID log.Printf("SKeyBuf 长度: %d, SKeyResp: %d", len(finalResponse.SKeyBuf), finalResponse.SKeyResp) // 如果 CDN 响应中已经有 FileID,直接使用 if finalResponse.FileID == "" && len(finalResponse.SKeyBuf) > 0 { var cdnPrepareResp wechat.CDNUploadMsgImgPrepareResponse // SKeyBuf 可能是加密的,尝试解密 decryptedSKeyBuf, err := baseutils.AesDecrypt(finalResponse.SKeyBuf, userInfo.SessionKey) if err != nil { // 尝试直接解析(可能不需要解密) err = proto.Unmarshal(finalResponse.SKeyBuf, &cdnPrepareResp) if err == nil && cdnPrepareResp.FileId != nil { finalResponse.FileID = cdnPrepareResp.GetFileId() } } else { // 尝试解析 protobuf err = proto.Unmarshal(decryptedSKeyBuf, &cdnPrepareResp) if err == nil && cdnPrepareResp.FileId != nil { finalResponse.FileID = cdnPrepareResp.GetFileId() } } } else if finalResponse.FileID != "" { log.Printf("响应中已包含 FileID: %s", finalResponse.FileID) } // 设置 CDN URL finalResponse.CdnBigImgUrl = finalResponse.FileID finalResponse.CdnMidImgUrl = finalResponse.FileID finalResponse.CdnThumbImgUrl = finalResponse.FileID return finalResponse, aesKeyHex, nil } // SendCdnSnsUploadImageReuqest 发送CDN朋友圈上传图片请求 func SendCdnSnsUploadImageReuqest(userInfo *baseinfo.UserInfo, imgData []byte) (*baseinfo.CdnSnsImageUploadResponse, error) { // 生产SnsImgItem snsImgItem := &baseinfo.SnsUploadImgItem{} snsImgItem.Seq = uint32(rand.Intn(50)) snsImgItem.AesKey = []byte(baseutils.RandomStringByLength(16)) snsImgItem.ImageData = imgData snsImgItem.ImageID = CreateID(imgData) snsImgItem.CreateTime = uint32(time.Now().UnixNano() / 1000000000) if userInfo.SNSDnsInfo == nil { return nil, errors.New("SendCdnSnsUploadImageReuqest err:userInfo.SNSDnsInfo == nil") } snsImgItem.CDNDns = userInfo.SNSDnsInfo // 创建上传图片请求 request, err := CreateSnsImageUploadRequest(userInfo, snsImgItem) if err != nil { return nil, err } // 打包请求 sendData := PackCdnSnsImageUploadRequest(request) // 连接Cdn服务器 serverIP := snsImgItem.CDNDns.FrontIplist[0].GetStr() serverPort := snsImgItem.CDNDns.FrontIpportList[0].PortList[0] conn, err := ConnectCdnServer(serverIP, serverPort) if err != nil { return nil, err } // 发送数据 conn.Write(sendData) defer conn.Close() retryCount := uint32(0) // 接收响应信息 sendImgLen := uint32(len(request.FileData)) for { // 接收响应信息,解析 retData := CDNRecvData(conn) response, err := DecodeSnsImageUploadResponse(retData) if err != nil { if retryCount < 3 { retryCount++ continue } return nil, err } retryCount = 0 // 判断错误码 if response.RetCode != 0 { return nil, errors.New("发送图片失败: ErrCode = " + GetErrStringByRetCode(response.RetCode)) } // 判断 服务器是否接收完毕 if response.RecvLen < sendImgLen { continue } response.ImageMD5 = request.FileMD5 width, height := GetImageBounds(request.FileData) response.ImageWidth = width response.ImageHeight = height return response, nil } } // SendCdnSnsVideoUploadReuqest 发送CDN朋友圈上传视频请求 func SendCdnSnsVideoUploadReuqest(userInfo *baseinfo.UserInfo, videoData []byte, thumbData []byte) (*baseinfo.CdnSnsVideoUploadResponse, error) { // 生产SnsImgItem snsVideoItem := &baseinfo.SnsVideoUploadItem{} snsVideoItem.Seq = uint32(rand.Intn(10)) snsVideoItem.AesKey = []byte(baseutils.RandomStringByLength(16)) snsVideoItem.VideoData = videoData snsVideoItem.ThumbData = thumbData snsVideoItem.VideoID = CreateID(videoData) snsVideoItem.CreateTime = uint32(time.Now().UnixNano() / 1000000000) snsVideoItem.CDNDns = userInfo.SNSDnsInfo // 创建上传朋友圈视频请求 request, err := CreateCdnSnsVideoUploadRequest(userInfo, snsVideoItem) if err != nil { return nil, err } // 打包请求 sendData := PackCdnSnsVideoUploadRequest(request) // 连接Cdn服务器 serverIP := snsVideoItem.CDNDns.FrontIplist[0].GetStr() serverPort := snsVideoItem.CDNDns.FrontIpportList[0].PortList[0] conn, err := ConnectCdnServer(serverIP, serverPort) if err != nil { return nil, err } // 发送数据 conn.Write(sendData) defer conn.Close() retryCount := uint32(0) // 接收响应信息 for { // 接收响应信息,解析 retData := CDNRecvData(conn) response, err := DecodeSnsVideoUploadResponse(retData) if err != nil { if retryCount < 3 { retryCount++ continue } return nil, err } retryCount = 0 // 判断错误码 if response.RetCode != 0 { return nil, errors.New("上传朋友圈视频失败: ErrCode = " + GetErrStringByRetCode(response.RetCode)) } // 判断 服务器是否接收完毕 if response.RecvLen < request.TotalSize { continue } // 设置请求数据 response.ReqData = request return response, nil } } // SendCdnSnsVideoDownloadReuqestPiece 分片下载 func SendCdnSnsVideoDownloadReuqestPiece(userInfo *baseinfo.UserInfo, snsVideoItem *baseinfo.SnsVideoDownloadItem) (*baseinfo.CdnSnsVideoDownloadResponse, error) { // 创建朋友圈视频下载请求 request, err := CreateSnsVideoDownloadRequest(userInfo, snsVideoItem) if err != nil { return nil, err } // 打包请求 sendData := PackCdnSnsVideoDownloadRequest(request) // 连接Cdn服务器 serverIP := snsVideoItem.CDNDns.FrontIplist[0].GetStr() serverPort := snsVideoItem.CDNDns.FrontIpportList[0].PortList[0] conn, err := ConnectCdnServer(serverIP, serverPort) if err != nil { return nil, err } // 发送数据 conn.Write(sendData) defer conn.Close() // 接收响应信息 // 接收响应信息,解析 retData := CDNRecvData(conn) response, err := DecodeSnsVideoDownloadResponse(retData) if err != nil { return nil, err } // 判断错误码 if response.RetCode != 0 { return nil, errors.New("下载朋友圈视频失败: ErrCode = " + GetErrStringByRetCode(response.RetCode)) } return response, nil } // SendCdnSnsVideoDownloadReuqest 发送CDN朋友圈视频下载请求 func SendCdnSnsVideoDownloadReuqest(userInfo *baseinfo.UserInfo, encKey uint64, tmpURL string) ([]byte, error) { retFileData := []byte{} lessLength := uint32(2000000) encLen := uint32(0) videoFlag := string("V2") retryCount := uint32(0) for { // 生产SnsImgItem var snsVideoItem baseinfo.SnsVideoDownloadItem snsVideoItem.Seq = uint32(rand.Intn(10)) snsVideoItem.URL = tmpURL snsVideoItem.RangeStart = uint32(len(retFileData)) snsVideoItem.RangeEnd = snsVideoItem.RangeStart + lessLength snsVideoItem.XSnsVideoFlag = videoFlag snsVideoItem.CDNDns = userInfo.SNSDnsInfo // 发送分片下载请求 response, err := SendCdnSnsVideoDownloadReuqestPiece(userInfo, &snsVideoItem) if err != nil { if retryCount < 3 { retryCount++ continue } return nil, err } retryCount = 0 // 判断错误码 if response.RetCode != 0 { return nil, errors.New("SendCdnSnsVideoDownloadReuqest err: response.RetCode != 0") } // 设置加密的字节数 if encLen == 0 { encLen = response.XEncLen } // 合并数据 retFileData = append(retFileData, response.FileData[0:]...) currentLen := uint32(len(retFileData)) if currentLen >= response.TotalSize { break } // 如果没有读取完 lessLength = response.TotalSize - currentLen videoFlag = response.XSnsVideoFlag } if encKey != 0 { retFileData = baseutils.DecryptSnsVideoData(retFileData, encLen, encKey) } // ioutil.WriteFile("log/1.mp4", retFileData, 0777) // 解密数据 return retFileData, nil } // ForwardCdnImageRequest 转发Cdn图片 func ForwardCdnImageRequest(userInfo *baseinfo.UserInfo, item baseinfo.ForwardImageItem) (*baseinfo.PackHeader, error) { req := &wechat.UploadMsgImgRequest{ BaseRequest: GetBaseRequest(userInfo), AesKey: proto.String(item.AesKey), CdnThumbAesKey: proto.String(item.AesKey), CdnMidImgSize: proto.Uint32(uint32(item.CdnMidImgSize)), CdnThumbImgSize: proto.Uint32(uint32(item.CdnThumbImgSize)), CdnSmallImgUrl: proto.String(item.CdnMidImgUrl), CdnThumbImgUrl: proto.String(item.CdnMidImgUrl), TotalLen: proto.Uint32(uint32(item.CdnMidImgSize)), DataLen: proto.Uint32(uint32(item.CdnMidImgSize)), ClientImgId: &wechat.SKBuiltinString{ Str: proto.String(func() string { //logger.Println(cli.GetAcctSect().GetUserName()) userMd5 := strings.ToLower(baseutils.Md5Value(userInfo.GetUserName() + "-" + item.ToUserName)[:16]) filekey := fmt.Sprintf("aupimg_%s_%d", userMd5, time.Now().UnixNano()/10000) return filekey }()), }, RecvWxid: &wechat.SKBuiltinString{ Str: proto.String(item.ToUserName), }, MsgType: proto.Uint32(3), SenderWxid: &wechat.SKBuiltinString{ Str: proto.String(userInfo.GetUserName()), }, StartPos: proto.Uint32(0), ReqTime: proto.Uint32(uint32(time.Now().Unix())), EncryVer: proto.Uint32(1), Data: &wechat.SKBuiltinString_{ Len: proto.Uint32(0), Buffer: []byte{}, }, MessageExt: proto.String("png"), } // 打包发送数据 srcData, _ := proto.Marshal(req) sendEncodeData := Pack(userInfo, srcData, baseinfo.MMRequestTypeForwardCdnImage, 5) resp, err := mmtls.MMHTTPPostData(userInfo.MMInfo, "/cgi-bin/micromsg-bin/uploadmsgimg", sendEncodeData) if err != nil { return nil, err } return DecodePackHeader(resp, nil) } // ForwardCdnVideoRequest 转发Cdn视频 func ForwardCdnVideoRequest(userInfo *baseinfo.UserInfo, item baseinfo.ForwardVideoItem) (*baseinfo.PackHeader, error) { req := &wechat.UploadVideoRequest{ BaseRequest: GetBaseRequest(userInfo), ClientMsgId: proto.String(func() string { userMd5 := strings.ToLower(baseutils.Md5Value(userInfo.GetUserName() + "-" + item.ToUserName)[:16]) videokey := strconv.FormatInt(time.Now().UnixNano()/10000, 10) + "baed6285091" filekey := fmt.Sprintf("aupvideo_%s_%d_%s", userMd5, time.Now().UnixNano()/10000, videokey) return filekey }()), FromUserName: proto.String(userInfo.GetUserName()), ToUserName: proto.String(item.ToUserName), AESKey: proto.String(item.AesKey), CDNThumbAESKey: proto.String(item.AesKey), CDNThumbImgHeight: proto.Int32(120), CDNThumbImgWidth: proto.Int32(120), CDNThumbUrl: proto.String(item.CdnVideoUrl), CDNVideoUrl: proto.String(item.CdnVideoUrl), EncryVer: proto.Int32(1), ThumbData: &wechat.BufferT{ ILen: proto.Uint32(0), Buffer: []byte{}, }, VideoData: &wechat.BufferT{ ILen: proto.Uint32(0), Buffer: []byte{}, }, MsgForwardType: proto.Uint32(1), CameraType: proto.Uint32(2), MsgSource: proto.String(""), FuncFlag: proto.Uint32(0), ThumbTotalLen: proto.Uint32(uint32(item.CdnThumbLength)), VideoTotalLen: proto.Uint32(uint32(item.Length)), VideoStartPos: proto.Uint32(0), ThumbStartPos: proto.Uint32(0), PlayLength: proto.Uint32(uint32(item.PlayLength)), VideoFrom: proto.Int32(0), NetworkEnv: proto.Uint32(1), ReqTime: proto.Uint32(uint32(time.Now().Unix())), } // 打包发送数据 srcData, _ := proto.Marshal(req) sendEncodeData := Pack(userInfo, srcData, baseinfo.MMRequestTypeForwardCdnVideo, 5) resp, err := mmtls.MMHTTPPostData(userInfo.MMInfo, "/cgi-bin/micromsg-bin/uploadvideo", sendEncodeData) if err != nil { return nil, err } return DecodePackHeader(resp, nil) } // SendCdnUploadImageReuqestWithSource 发送CDN上传图片请求 func SendCdnUploadImageReuqestWithSource(userInfo *baseinfo.UserInfo, toUser string, imgData []byte, sourceValue uint32) (*baseinfo.CdnImageUploadResponse, string, error) { imgItem := &baseinfo.UploadImgItem{} imgItem.ToUser = toUser imgItem.Seq = uint32(rand.Intn(10)) imgItem.LocalName = strconv.Itoa(rand.Intn(100)) imgItem.ExtName = ".pic" // 随机生成AesKey imgItem.AesKey = []byte(baseutils.RandomStringByLength(16)) aesKeyHex := hex.EncodeToString(imgItem.AesKey) imgItem.ImageData = imgData imgItem.CreateTime = uint32(time.Now().UnixNano() / 1000000000) // 设置Dns路由信息 imgItem.CDNDns = userInfo.FAKEDnsInfo // 创建上传图片请求 request := &baseinfo.CdnImageUploadRequest{} request.Ver = 1 request.WeiXinNum = uint32(imgItem.CDNDns.GetUin()) request.Seq = imgItem.Seq if !strings.HasPrefix(userInfo.LoginDataInfo.LoginData, "A") && userInfo.DeviceInfo != nil { request.ClientOsType = userInfo.DeviceInfo.OsType request.ClientVersion = userInfo.ClientVersion if !(int(request.ClientVersion) > 0) { request.ClientVersion = baseinfo.ClientVersion } } else { request.ClientOsType = baseinfo.AndroidDeviceType request.ClientVersion = baseinfo.AndroidClientVersion } request.AuthKey = imgItem.CDNDns.AuthKey.GetBuffer() request.NetType = 1 request.AcceptDupack = 1 request.SafeProto = 1 request.FileType = 2 request.WxChatType = 0 request.LastRetCode = 0 request.IPSeq = 0 request.CliQuicFlag = 0 request.CompressType = 0 request.NoCheckAesKey = 1 request.EnableHit = 1 request.ExistAnceCheck = 0 request.AppType = 1 request.LocalName = imgItem.LocalName + imgItem.ExtName request.Offset = 0 request.LargesVideo = 0 request.SourceFlag = 0 request.AdVideoFlag = 0 // ToUser retBytes := baseutils.AesEncrypt([]byte(imgItem.ToUser), []byte("wxusrname2016cdn")) request.ToUser = "@cdn2_" + baseutils.BytesToHexString(retBytes, false) // request.HasThumb = 0 // request.ThumbData = nil // ThumbData thumbItem := CreateThumbImage(imgItem.ImageData) if thumbItem == nil { return nil, "", errors.New("生成缩略图失败") } thumbEncodeData := baseutils.AesEncryptECB(imgItem.ImageData, []byte(imgItem.AesKey)) request.HasThumb = 1 request.ThumbTotalSize = uint32(len(thumbEncodeData)) request.RawThumbSize = uint32(len(imgItem.ImageData)) request.RawThumbMD5 = baseutils.Md5ValueByte(imgItem.ImageData, false) request.EncThumbCRC = baseutils.Adler32(0, thumbEncodeData) request.ThumbData = thumbEncodeData // FileKey request.FileKey = "wxupload_" + imgItem.ToUser + imgItem.LocalName + "_" + strconv.Itoa(int(imgItem.CreateTime)) // FileData fileEncodeData := baseutils.AesEncryptECB(imgItem.ImageData, []byte(imgItem.AesKey)) request.TotalSize = uint32(len(fileEncodeData)) request.RawTotalSize = uint32(len(imgItem.ImageData)) request.FileMD5 = baseutils.Md5ValueByte(fileEncodeData, false) request.RawFileMD5 = baseutils.Md5ValueByte(imgItem.ImageData, false) request.FileCRC = baseutils.Adler32(0, imgItem.ImageData) request.DataCheckSum = baseutils.Adler32(0, fileEncodeData) request.FileData = fileEncodeData request.SetOfPicFormat = "111000" // prepareRequestItem var prepareRequestItem baseinfo.CDNUploadMsgImgPrepareRequestItem prepareRequestItem.ToUser = imgItem.ToUser prepareRequestItem.LocalName = imgItem.LocalName prepareRequestItem.CreateTime = imgItem.CreateTime prepareRequestItem.ThumbWidth = thumbItem.Width prepareRequestItem.ThumbHeight = thumbItem.Height prepareRequestItem.AesKey = imgItem.AesKey prepareRequestItem.Crc32 = request.FileCRC // SessionBuf - 使用自定义 source 值 request.SessionBuf = CreateCDNUploadMsgImgPrepareRequestWithSource(userInfo, &prepareRequestItem, sourceValue) // 打包请求 sendData := PackCdnImageUploadRequest(request) // 连接Cdn服务器 serverIP := imgItem.CDNDns.FrontIplist[0].GetStr() serverPort := imgItem.CDNDns.FrontIpportList[0].PortList[0] conn, err := ConnectCdnServer(serverIP, serverPort) if err != nil { return nil, "", err } // 发送数据 conn.Write(sendData) defer conn.Close() retryCount := uint32(0) // 接收响应信息 sendImgLen := uint32(len(request.FileData)) var finalResponse *baseinfo.CdnImageUploadResponse for { // 接收响应信息,解析 retData := CDNRecvData(conn) response, err := DecodeImageUploadResponse(retData) if err != nil { if retryCount < 3 { retryCount++ continue } return nil, "", err } retryCount = 0 // 判断错误码 if response.RetCode != 0 { return nil, "", errors.New("发送图片失败: ErrCode = " + GetErrStringByRetCode(response.RetCode)) } // 判断 服务器是否接收完毕 if response.RecvLen < sendImgLen { continue } // SKeyResp CDNUploadMsgImgPrepareResponse的错误码 if response.SKeyResp != 0 { return nil, "", errors.New("发送图片失败: SKeyResp = " + strconv.Itoa(int(response.SKeyResp))) } // SKeyBuf skbufLen := len(response.SKeyBuf) if skbufLen <= 0 { continue } finalResponse = response break } log.Printf("CDN上传图片响应 (source=%d): Ver=%d, Seq=%d, RetCode=%d, FileKey=%s, RecvLen=%d, FileID=%s", sourceValue, finalResponse.Ver, finalResponse.Seq, finalResponse.RetCode, finalResponse.FileKey, finalResponse.RecvLen, finalResponse.FileID) // 设置 CDN URL finalResponse.CdnBigImgUrl = finalResponse.FileID finalResponse.CdnMidImgUrl = finalResponse.FileID finalResponse.CdnThumbImgUrl = finalResponse.FileID return finalResponse, aesKeyHex, nil }