Skip to content

Commit ef7fd2c

Browse files
authored
feat: 添加大文件上传 (#555)
1 parent a2c90f5 commit ef7fd2c

File tree

5 files changed

+156
-45
lines changed

5 files changed

+156
-45
lines changed

caller.go

+63-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package openwechat
33
import (
44
"bytes"
55
"context"
6+
"crypto/md5"
7+
"encoding/hex"
68
"encoding/json"
79
"encoding/xml"
810
"errors"
@@ -325,12 +327,63 @@ type CallerUploadMediaOptions struct {
325327

326328
func (c *Caller) UploadMedia(ctx context.Context, file *os.File, opt *CallerUploadMediaOptions) (*UploadResponse, error) {
327329
// 首先尝试上传图片
330+
h := md5.New()
331+
if _, err := io.Copy(h, file); err != nil {
332+
return nil, err
333+
}
334+
fileMd5 := hex.EncodeToString(h.Sum(nil))
335+
stat, err := file.Stat()
336+
if err != nil {
337+
return nil, err
338+
}
339+
filename := file.Name()
340+
filesize := stat.Size()
341+
328342
clientWebWxUploadMediaByChunkOpt := &ClientWebWxUploadMediaByChunkOptions{
329-
FromUserName: opt.FromUserName,
330-
ToUserName: opt.ToUserName,
331-
BaseRequest: opt.BaseRequest,
332-
LoginInfo: opt.LoginInfo,
343+
FromUserName: opt.FromUserName,
344+
ToUserName: opt.ToUserName,
345+
BaseRequest: opt.BaseRequest,
346+
LoginInfo: opt.LoginInfo,
347+
Filename: filename,
348+
FileMD5: fileMd5,
349+
FileSize: filesize,
350+
LastModifiedDate: stat.ModTime(),
351+
}
352+
353+
if filesize > needCheckSize {
354+
checkUploadRequest := webWxCheckUploadRequest{
355+
BaseRequest: opt.BaseRequest,
356+
FileMd5: fileMd5,
357+
FileName: filename,
358+
FileSize: filesize,
359+
FileType: 7,
360+
FromUserName: opt.FromUserName,
361+
ToUserName: opt.ToUserName,
362+
}
363+
resp, err := c.Client.webWxCheckUploadRequest(ctx, checkUploadRequest)
364+
if err != nil {
365+
return nil, err
366+
}
367+
defer func() { _ = resp.Body.Close() }()
368+
var checkUploadResponse webWxCheckUploadResponse
369+
if err = json.NewDecoder(resp.Body).Decode(&checkUploadResponse); err != nil {
370+
return nil, err
371+
}
372+
if err = checkUploadResponse.BaseResponse.Err(); err != nil {
373+
return nil, err
374+
}
375+
// 如果已经上传过了,直接返回
376+
if checkUploadResponse.MediaId != "" {
377+
var item UploadResponse
378+
item.MediaId = checkUploadResponse.MediaId
379+
item.Signature = checkUploadResponse.Signature
380+
item.BaseResponse = checkUploadResponse.BaseResponse
381+
return &item, nil
382+
}
383+
clientWebWxUploadMediaByChunkOpt.AESKey = checkUploadResponse.AESKey
384+
clientWebWxUploadMediaByChunkOpt.Signature = checkUploadResponse.Signature
333385
}
386+
334387
resp, err := c.Client.WebWxUploadMediaByChunk(ctx, file, clientWebWxUploadMediaByChunkOpt)
335388
// 无错误上传成功之后获取请求结果,判断结果是否正常
336389
if err != nil {
@@ -347,6 +400,7 @@ func (c *Caller) UploadMedia(ctx context.Context, file *os.File, opt *CallerUplo
347400
if len(item.MediaId) == 0 {
348401
return &item, errors.New("upload failed")
349402
}
403+
item.Signature = clientWebWxUploadMediaByChunkOpt.Signature
350404
return &item, nil
351405
}
352406

@@ -418,13 +472,17 @@ func (c *Caller) WebWxSendFile(ctx context.Context, reader io.Reader, opt *Calle
418472
return nil, err
419473
}
420474
// 构造新的文件类型的信息
421-
stat, _ := file.Stat()
475+
stat, err := file.Stat()
476+
if err != nil {
477+
return nil, err
478+
}
422479
appMsg := newFileAppMessage(stat, resp.MediaId)
423480
content, err := appMsg.XmlByte()
424481
if err != nil {
425482
return nil, err
426483
}
427484
msg := NewSendMessage(AppMessage, string(content), opt.FromUserName, opt.ToUserName, "")
485+
msg.Signature = resp.Signature
428486
return c.WebWxSendAppMsg(ctx, msg, opt.BaseRequest)
429487
}
430488

client.go

+88-37
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ package openwechat
33
import (
44
"bytes"
55
"context"
6-
"crypto/md5"
7-
"encoding/hex"
86
"encoding/json"
97
"errors"
108
"fmt"
@@ -479,37 +477,87 @@ func (c *Client) WebWxGetHeadImg(ctx context.Context, user *User) (*http.Respons
479477
return c.Do(req)
480478
}
481479

482-
type ClientWebWxUploadMediaByChunkOptions struct {
483-
FromUserName string
484-
ToUserName string
485-
BaseRequest *BaseRequest
486-
LoginInfo *LoginInfo
480+
type webWxCheckUploadRequest struct {
481+
BaseRequest *BaseRequest `json:"BaseRequest"`
482+
FileMd5 string `json:"FileMd5"`
483+
FileName string `json:"FileName"`
484+
FileSize int64 `json:"FileSize"`
485+
FileType uint8 `json:"FileType"`
486+
FromUserName string `json:"FromUserName"`
487+
ToUserName string `json:"ToUserName"`
487488
}
488489

489-
// WebWxUploadMediaByChunk 分块上传文件
490-
// TODO 优化掉这个函数
491-
func (c *Client) WebWxUploadMediaByChunk(ctx context.Context, file *os.File, opt *ClientWebWxUploadMediaByChunkOptions) (*http.Response, error) {
492-
// 获取文件上传的类型
493-
contentType, err := GetFileContentType(file)
490+
type webWxCheckUploadResponse struct {
491+
BaseResponse BaseResponse `json:"BaseResponse"`
492+
MediaId string `json:"MediaId"`
493+
AESKey string `json:"AESKey"`
494+
Signature string `json:"Signature"`
495+
EntryFileName string `json:"EncryFileName"`
496+
}
497+
498+
func (c *Client) webWxCheckUploadRequest(ctx context.Context, req webWxCheckUploadRequest) (*http.Response, error) {
499+
path, err := url.Parse(c.Domain.BaseHost() + webwxcheckupload)
494500
if err != nil {
495501
return nil, err
496502
}
497-
if _, err = file.Seek(0, io.SeekStart); err != nil {
503+
body, err := jsonEncode(req)
504+
if err != nil {
498505
return nil, err
499506
}
500-
501-
// 获取文件的md5
502-
h := md5.New()
503-
if _, err = io.Copy(h, file); err != nil {
507+
reqs, err := http.NewRequestWithContext(ctx, http.MethodPost, path.String(), body)
508+
if err != nil {
504509
return nil, err
505510
}
506-
fileMd5 := hex.EncodeToString(h.Sum(nil))
511+
reqs.Header.Add("Content-Type", jsonContentType)
512+
return c.Do(reqs)
513+
}
514+
515+
type uploadMediaRequest struct {
516+
UploadType uint8 `json:"UploadType"`
517+
BaseRequest *BaseRequest `json:"BaseRequest"`
518+
ClientMediaId int64 `json:"ClientMediaId"`
519+
TotalLen int64 `json:"TotalLen"`
520+
StartPos int `json:"StartPos"`
521+
DataLen int64 `json:"DataLen"`
522+
MediaType uint8 `json:"MediaType"`
523+
FromUserName string `json:"FromUserName"`
524+
ToUserName string `json:"ToUserName"`
525+
FileMd5 string `json:"FileMd5"`
526+
AESKey string `json:"AESKey,omitempty"`
527+
Signature string `json:"Signature,omitempty"`
528+
}
529+
530+
type ClientWebWxUploadMediaByChunkOptions struct {
531+
FromUserName string
532+
ToUserName string
533+
BaseRequest *BaseRequest
534+
LoginInfo *LoginInfo
535+
Filename string
536+
FileMD5 string
537+
FileSize int64
538+
LastModifiedDate time.Time
539+
AESKey string
540+
Signature string
541+
}
507542

508-
sate, err := file.Stat()
543+
type UploadFile interface {
544+
io.ReaderAt
545+
io.ReadSeeker
546+
}
547+
548+
// WebWxUploadMediaByChunk 分块上传文件
549+
// TODO 优化掉这个函数
550+
func (c *Client) WebWxUploadMediaByChunk(ctx context.Context, file UploadFile, opt *ClientWebWxUploadMediaByChunkOptions) (*http.Response, error) {
551+
// 获取文件上传的类型
552+
if _, err := file.Seek(0, io.SeekStart); err != nil {
553+
return nil, err
554+
}
555+
contentType, err := GetFileContentType(file)
509556
if err != nil {
510557
return nil, err
511558
}
512-
filename := sate.Name()
559+
560+
filename := opt.Filename
513561

514562
if ext := filepath.Ext(filename); ext == "" {
515563
names := strings.Split(contentType, "/")
@@ -530,22 +578,24 @@ func (c *Client) WebWxUploadMediaByChunk(ctx context.Context, file *os.File, opt
530578

531579
cookies := c.Jar().Cookies(path)
532580

533-
webWxDataTicket, err := getWebWxDataTicket(cookies)
581+
webWxDataTicket, err := wxDataTicket(cookies)
534582
if err != nil {
535583
return nil, err
536584
}
537585

538-
uploadMediaRequest := map[string]interface{}{
539-
"UploadType": 2,
540-
"BaseRequest": opt.BaseRequest,
541-
"ClientMediaId": time.Now().Unix() * 1e4,
542-
"TotalLen": sate.Size(),
543-
"StartPos": 0,
544-
"DataLen": sate.Size(),
545-
"MediaType": 4,
546-
"FromUserName": opt.FromUserName,
547-
"ToUserName": opt.ToUserName,
548-
"FileMd5": fileMd5,
586+
uploadMediaRequest := &uploadMediaRequest{
587+
UploadType: 2,
588+
BaseRequest: opt.BaseRequest,
589+
ClientMediaId: time.Now().Unix() * 1e4,
590+
TotalLen: opt.FileSize,
591+
StartPos: 0,
592+
DataLen: opt.FileSize,
593+
MediaType: 4,
594+
FromUserName: opt.FromUserName,
595+
ToUserName: opt.ToUserName,
596+
FileMd5: opt.FileMD5,
597+
AESKey: opt.AESKey,
598+
Signature: opt.Signature,
549599
}
550600

551601
uploadMediaRequestByte, err := json.Marshal(uploadMediaRequest)
@@ -554,14 +604,14 @@ func (c *Client) WebWxUploadMediaByChunk(ctx context.Context, file *os.File, opt
554604
}
555605

556606
// 计算上传文件的次数
557-
chunks := int((sate.Size() + chunkSize - 1) / chunkSize)
607+
chunks := int((opt.FileSize + chunkSize - 1) / chunkSize)
558608

559609
content := map[string]string{
560610
"id": "WU_FILE_0",
561611
"name": filename,
562612
"type": contentType,
563-
"lastModifiedDate": sate.ModTime().Format(TimeFormat),
564-
"size": strconv.FormatInt(sate.Size(), 10),
613+
"lastModifiedDate": opt.LastModifiedDate.Format(TimeFormat),
614+
"size": strconv.FormatInt(opt.FileSize, 10),
565615
"mediatype": mediaType,
566616
"webwx_data_ticket": webWxDataTicket,
567617
"pass_ticket": opt.LoginInfo.PassTicket,
@@ -596,7 +646,7 @@ func (c *Client) WebWxUploadMediaByChunk(ctx context.Context, file *os.File, opt
596646
}
597647

598648
// create form file
599-
fileWriter, err := writer.CreateFormFile("filename", file.Name())
649+
fileWriter, err := writer.CreateFormFile("filename", filename)
600650
if err != nil {
601651
return err
602652
}
@@ -671,6 +721,7 @@ func (c *Client) WebWxSendAppMsg(ctx context.Context, msg *SendMessage, request
671721
params := url.Values{}
672722
params.Add("fun", "async")
673723
params.Add("f", "json")
724+
params.Add("lang", "zh_CN")
674725
path.RawQuery = params.Encode()
675726
return c.sendMessage(ctx, request, path.String(), msg)
676727
}
@@ -815,7 +866,7 @@ func (c *Client) WebWxGetMedia(ctx context.Context, msg *Message, info *LoginInf
815866
return nil, err
816867
}
817868
cookies := c.Jar().Cookies(path)
818-
webWxDataTicket, err := getWebWxDataTicket(cookies)
869+
webWxDataTicket, err := wxDataTicket(cookies)
819870
if err != nil {
820871
return nil, err
821872
}

cookiejar.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func (c CookieGroup) GetByName(cookieName string) (cookie *http.Cookie, exist bo
7777
return nil, false
7878
}
7979

80-
func getWebWxDataTicket(cookies []*http.Cookie) (string, error) {
80+
func wxDataTicket(cookies []*http.Cookie) (string, error) {
8181
cookieGroup := CookieGroup(cookies)
8282
cookie, exist := cookieGroup.GetByName("webwx_data_ticket")
8383
if !exist {

entity.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,9 @@ type MessageResponse struct {
173173
}
174174

175175
type UploadResponse struct {
176-
BaseResponse BaseResponse
177-
MediaId string
176+
BaseResponse BaseResponse `json:"BaseResponse"`
177+
MediaId string `json:"MediaId"`
178+
Signature string `json:"Signature"`
178179
}
179180

180181
type PushLoginResponse struct {

message.go

+1
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,7 @@ type SendMessage struct {
506506
MediaId string `json:"MediaId,omitempty"`
507507
EmojiFlag int `json:"EmojiFlag,omitempty"`
508508
EMoticonMd5 string `json:"EMoticonMd5,omitempty"`
509+
Signature string `json:"Signature,omitempty"`
509510
}
510511

511512
// NewSendMessage SendMessage的构造方法

0 commit comments

Comments
 (0)