mirror of
https://github.com/eatmoreapple/openwechat.git
synced 2025-12-24 11:31:02 +08:00
feat: 添加大文件上传 (#555)
This commit is contained in:
70
caller.go
70
caller.go
@@ -3,6 +3,8 @@ package openwechat
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
@@ -325,12 +327,63 @@ type CallerUploadMediaOptions struct {
|
||||
|
||||
func (c *Caller) UploadMedia(ctx context.Context, file *os.File, opt *CallerUploadMediaOptions) (*UploadResponse, error) {
|
||||
// 首先尝试上传图片
|
||||
clientWebWxUploadMediaByChunkOpt := &ClientWebWxUploadMediaByChunkOptions{
|
||||
FromUserName: opt.FromUserName,
|
||||
ToUserName: opt.ToUserName,
|
||||
BaseRequest: opt.BaseRequest,
|
||||
LoginInfo: opt.LoginInfo,
|
||||
h := md5.New()
|
||||
if _, err := io.Copy(h, file); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fileMd5 := hex.EncodeToString(h.Sum(nil))
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filename := file.Name()
|
||||
filesize := stat.Size()
|
||||
|
||||
clientWebWxUploadMediaByChunkOpt := &ClientWebWxUploadMediaByChunkOptions{
|
||||
FromUserName: opt.FromUserName,
|
||||
ToUserName: opt.ToUserName,
|
||||
BaseRequest: opt.BaseRequest,
|
||||
LoginInfo: opt.LoginInfo,
|
||||
Filename: filename,
|
||||
FileMD5: fileMd5,
|
||||
FileSize: filesize,
|
||||
LastModifiedDate: stat.ModTime(),
|
||||
}
|
||||
|
||||
if filesize > needCheckSize {
|
||||
checkUploadRequest := webWxCheckUploadRequest{
|
||||
BaseRequest: opt.BaseRequest,
|
||||
FileMd5: fileMd5,
|
||||
FileName: filename,
|
||||
FileSize: filesize,
|
||||
FileType: 7,
|
||||
FromUserName: opt.FromUserName,
|
||||
ToUserName: opt.ToUserName,
|
||||
}
|
||||
resp, err := c.Client.webWxCheckUploadRequest(ctx, checkUploadRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
var checkUploadResponse webWxCheckUploadResponse
|
||||
if err = json.NewDecoder(resp.Body).Decode(&checkUploadResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = checkUploadResponse.BaseResponse.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 如果已经上传过了,直接返回
|
||||
if checkUploadResponse.MediaId != "" {
|
||||
var item UploadResponse
|
||||
item.MediaId = checkUploadResponse.MediaId
|
||||
item.Signature = checkUploadResponse.Signature
|
||||
item.BaseResponse = checkUploadResponse.BaseResponse
|
||||
return &item, nil
|
||||
}
|
||||
clientWebWxUploadMediaByChunkOpt.AESKey = checkUploadResponse.AESKey
|
||||
clientWebWxUploadMediaByChunkOpt.Signature = checkUploadResponse.Signature
|
||||
}
|
||||
|
||||
resp, err := c.Client.WebWxUploadMediaByChunk(ctx, file, clientWebWxUploadMediaByChunkOpt)
|
||||
// 无错误上传成功之后获取请求结果,判断结果是否正常
|
||||
if err != nil {
|
||||
@@ -347,6 +400,7 @@ func (c *Caller) UploadMedia(ctx context.Context, file *os.File, opt *CallerUplo
|
||||
if len(item.MediaId) == 0 {
|
||||
return &item, errors.New("upload failed")
|
||||
}
|
||||
item.Signature = clientWebWxUploadMediaByChunkOpt.Signature
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
@@ -418,13 +472,17 @@ func (c *Caller) WebWxSendFile(ctx context.Context, reader io.Reader, opt *Calle
|
||||
return nil, err
|
||||
}
|
||||
// 构造新的文件类型的信息
|
||||
stat, _ := file.Stat()
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appMsg := newFileAppMessage(stat, resp.MediaId)
|
||||
content, err := appMsg.XmlByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg := NewSendMessage(AppMessage, string(content), opt.FromUserName, opt.ToUserName, "")
|
||||
msg.Signature = resp.Signature
|
||||
return c.WebWxSendAppMsg(ctx, msg, opt.BaseRequest)
|
||||
}
|
||||
|
||||
|
||||
129
client.go
129
client.go
@@ -3,8 +3,6 @@ package openwechat
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -479,37 +477,87 @@ func (c *Client) WebWxGetHeadImg(ctx context.Context, user *User) (*http.Respons
|
||||
return c.Do(req)
|
||||
}
|
||||
|
||||
type webWxCheckUploadRequest struct {
|
||||
BaseRequest *BaseRequest `json:"BaseRequest"`
|
||||
FileMd5 string `json:"FileMd5"`
|
||||
FileName string `json:"FileName"`
|
||||
FileSize int64 `json:"FileSize"`
|
||||
FileType uint8 `json:"FileType"`
|
||||
FromUserName string `json:"FromUserName"`
|
||||
ToUserName string `json:"ToUserName"`
|
||||
}
|
||||
|
||||
type webWxCheckUploadResponse struct {
|
||||
BaseResponse BaseResponse `json:"BaseResponse"`
|
||||
MediaId string `json:"MediaId"`
|
||||
AESKey string `json:"AESKey"`
|
||||
Signature string `json:"Signature"`
|
||||
EntryFileName string `json:"EncryFileName"`
|
||||
}
|
||||
|
||||
func (c *Client) webWxCheckUploadRequest(ctx context.Context, req webWxCheckUploadRequest) (*http.Response, error) {
|
||||
path, err := url.Parse(c.Domain.BaseHost() + webwxcheckupload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := jsonEncode(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqs, err := http.NewRequestWithContext(ctx, http.MethodPost, path.String(), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqs.Header.Add("Content-Type", jsonContentType)
|
||||
return c.Do(reqs)
|
||||
}
|
||||
|
||||
type uploadMediaRequest struct {
|
||||
UploadType uint8 `json:"UploadType"`
|
||||
BaseRequest *BaseRequest `json:"BaseRequest"`
|
||||
ClientMediaId int64 `json:"ClientMediaId"`
|
||||
TotalLen int64 `json:"TotalLen"`
|
||||
StartPos int `json:"StartPos"`
|
||||
DataLen int64 `json:"DataLen"`
|
||||
MediaType uint8 `json:"MediaType"`
|
||||
FromUserName string `json:"FromUserName"`
|
||||
ToUserName string `json:"ToUserName"`
|
||||
FileMd5 string `json:"FileMd5"`
|
||||
AESKey string `json:"AESKey,omitempty"`
|
||||
Signature string `json:"Signature,omitempty"`
|
||||
}
|
||||
|
||||
type ClientWebWxUploadMediaByChunkOptions struct {
|
||||
FromUserName string
|
||||
ToUserName string
|
||||
BaseRequest *BaseRequest
|
||||
LoginInfo *LoginInfo
|
||||
FromUserName string
|
||||
ToUserName string
|
||||
BaseRequest *BaseRequest
|
||||
LoginInfo *LoginInfo
|
||||
Filename string
|
||||
FileMD5 string
|
||||
FileSize int64
|
||||
LastModifiedDate time.Time
|
||||
AESKey string
|
||||
Signature string
|
||||
}
|
||||
|
||||
type UploadFile interface {
|
||||
io.ReaderAt
|
||||
io.ReadSeeker
|
||||
}
|
||||
|
||||
// WebWxUploadMediaByChunk 分块上传文件
|
||||
// TODO 优化掉这个函数
|
||||
func (c *Client) WebWxUploadMediaByChunk(ctx context.Context, file *os.File, opt *ClientWebWxUploadMediaByChunkOptions) (*http.Response, error) {
|
||||
func (c *Client) WebWxUploadMediaByChunk(ctx context.Context, file UploadFile, opt *ClientWebWxUploadMediaByChunkOptions) (*http.Response, error) {
|
||||
// 获取文件上传的类型
|
||||
if _, err := file.Seek(0, io.SeekStart); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contentType, err := GetFileContentType(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err = file.Seek(0, io.SeekStart); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取文件的md5
|
||||
h := md5.New()
|
||||
if _, err = io.Copy(h, file); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fileMd5 := hex.EncodeToString(h.Sum(nil))
|
||||
|
||||
sate, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filename := sate.Name()
|
||||
filename := opt.Filename
|
||||
|
||||
if ext := filepath.Ext(filename); ext == "" {
|
||||
names := strings.Split(contentType, "/")
|
||||
@@ -530,22 +578,24 @@ func (c *Client) WebWxUploadMediaByChunk(ctx context.Context, file *os.File, opt
|
||||
|
||||
cookies := c.Jar().Cookies(path)
|
||||
|
||||
webWxDataTicket, err := getWebWxDataTicket(cookies)
|
||||
webWxDataTicket, err := wxDataTicket(cookies)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uploadMediaRequest := map[string]interface{}{
|
||||
"UploadType": 2,
|
||||
"BaseRequest": opt.BaseRequest,
|
||||
"ClientMediaId": time.Now().Unix() * 1e4,
|
||||
"TotalLen": sate.Size(),
|
||||
"StartPos": 0,
|
||||
"DataLen": sate.Size(),
|
||||
"MediaType": 4,
|
||||
"FromUserName": opt.FromUserName,
|
||||
"ToUserName": opt.ToUserName,
|
||||
"FileMd5": fileMd5,
|
||||
uploadMediaRequest := &uploadMediaRequest{
|
||||
UploadType: 2,
|
||||
BaseRequest: opt.BaseRequest,
|
||||
ClientMediaId: time.Now().Unix() * 1e4,
|
||||
TotalLen: opt.FileSize,
|
||||
StartPos: 0,
|
||||
DataLen: opt.FileSize,
|
||||
MediaType: 4,
|
||||
FromUserName: opt.FromUserName,
|
||||
ToUserName: opt.ToUserName,
|
||||
FileMd5: opt.FileMD5,
|
||||
AESKey: opt.AESKey,
|
||||
Signature: opt.Signature,
|
||||
}
|
||||
|
||||
uploadMediaRequestByte, err := json.Marshal(uploadMediaRequest)
|
||||
@@ -554,14 +604,14 @@ func (c *Client) WebWxUploadMediaByChunk(ctx context.Context, file *os.File, opt
|
||||
}
|
||||
|
||||
// 计算上传文件的次数
|
||||
chunks := int((sate.Size() + chunkSize - 1) / chunkSize)
|
||||
chunks := int((opt.FileSize + chunkSize - 1) / chunkSize)
|
||||
|
||||
content := map[string]string{
|
||||
"id": "WU_FILE_0",
|
||||
"name": filename,
|
||||
"type": contentType,
|
||||
"lastModifiedDate": sate.ModTime().Format(TimeFormat),
|
||||
"size": strconv.FormatInt(sate.Size(), 10),
|
||||
"lastModifiedDate": opt.LastModifiedDate.Format(TimeFormat),
|
||||
"size": strconv.FormatInt(opt.FileSize, 10),
|
||||
"mediatype": mediaType,
|
||||
"webwx_data_ticket": webWxDataTicket,
|
||||
"pass_ticket": opt.LoginInfo.PassTicket,
|
||||
@@ -596,7 +646,7 @@ func (c *Client) WebWxUploadMediaByChunk(ctx context.Context, file *os.File, opt
|
||||
}
|
||||
|
||||
// create form file
|
||||
fileWriter, err := writer.CreateFormFile("filename", file.Name())
|
||||
fileWriter, err := writer.CreateFormFile("filename", filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -671,6 +721,7 @@ func (c *Client) WebWxSendAppMsg(ctx context.Context, msg *SendMessage, request
|
||||
params := url.Values{}
|
||||
params.Add("fun", "async")
|
||||
params.Add("f", "json")
|
||||
params.Add("lang", "zh_CN")
|
||||
path.RawQuery = params.Encode()
|
||||
return c.sendMessage(ctx, request, path.String(), msg)
|
||||
}
|
||||
@@ -815,7 +866,7 @@ func (c *Client) WebWxGetMedia(ctx context.Context, msg *Message, info *LoginInf
|
||||
return nil, err
|
||||
}
|
||||
cookies := c.Jar().Cookies(path)
|
||||
webWxDataTicket, err := getWebWxDataTicket(cookies)
|
||||
webWxDataTicket, err := wxDataTicket(cookies)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ func (c CookieGroup) GetByName(cookieName string) (cookie *http.Cookie, exist bo
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func getWebWxDataTicket(cookies []*http.Cookie) (string, error) {
|
||||
func wxDataTicket(cookies []*http.Cookie) (string, error) {
|
||||
cookieGroup := CookieGroup(cookies)
|
||||
cookie, exist := cookieGroup.GetByName("webwx_data_ticket")
|
||||
if !exist {
|
||||
|
||||
@@ -173,8 +173,9 @@ type MessageResponse struct {
|
||||
}
|
||||
|
||||
type UploadResponse struct {
|
||||
BaseResponse BaseResponse
|
||||
MediaId string
|
||||
BaseResponse BaseResponse `json:"BaseResponse"`
|
||||
MediaId string `json:"MediaId"`
|
||||
Signature string `json:"Signature"`
|
||||
}
|
||||
|
||||
type PushLoginResponse struct {
|
||||
|
||||
@@ -506,6 +506,7 @@ type SendMessage struct {
|
||||
MediaId string `json:"MediaId,omitempty"`
|
||||
EmojiFlag int `json:"EmojiFlag,omitempty"`
|
||||
EMoticonMd5 string `json:"EMoticonMd5,omitempty"`
|
||||
Signature string `json:"Signature,omitempty"`
|
||||
}
|
||||
|
||||
// NewSendMessage SendMessage的构造方法
|
||||
|
||||
Reference in New Issue
Block a user