Skip to content

Commit

Permalink
feat: 添加大文件上传 (#555)
Browse files Browse the repository at this point in the history
  • Loading branch information
eatmoreapple authored Dec 21, 2024
1 parent a2c90f5 commit ef7fd2c
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 45 deletions.
68 changes: 63 additions & 5 deletions caller.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package openwechat
import (
"bytes"
"context"
"crypto/md5"
"encoding/hex"
"encoding/json"
"encoding/xml"
"errors"
Expand Down Expand Up @@ -325,12 +327,63 @@ type CallerUploadMediaOptions struct {

func (c *Caller) UploadMedia(ctx context.Context, file *os.File, opt *CallerUploadMediaOptions) (*UploadResponse, error) {
// 首先尝试上传图片
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,
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 {
Expand All @@ -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
}

Expand Down Expand Up @@ -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)
}

Expand Down
125 changes: 88 additions & 37 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package openwechat
import (
"bytes"
"context"
"crypto/md5"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
Expand Down Expand Up @@ -479,37 +477,87 @@ func (c *Client) WebWxGetHeadImg(ctx context.Context, user *User) (*http.Respons
return c.Do(req)
}

type ClientWebWxUploadMediaByChunkOptions struct {
FromUserName string
ToUserName string
BaseRequest *BaseRequest
LoginInfo *LoginInfo
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"`
}

// WebWxUploadMediaByChunk 分块上传文件
// TODO 优化掉这个函数
func (c *Client) WebWxUploadMediaByChunk(ctx context.Context, file *os.File, opt *ClientWebWxUploadMediaByChunkOptions) (*http.Response, error) {
// 获取文件上传的类型
contentType, err := GetFileContentType(file)
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
}
if _, err = file.Seek(0, io.SeekStart); err != nil {
body, err := jsonEncode(req)
if err != nil {
return nil, err
}

// 获取文件的md5
h := md5.New()
if _, err = io.Copy(h, file); err != nil {
reqs, err := http.NewRequestWithContext(ctx, http.MethodPost, path.String(), body)
if err != nil {
return nil, err
}
fileMd5 := hex.EncodeToString(h.Sum(nil))
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
Filename string
FileMD5 string
FileSize int64
LastModifiedDate time.Time
AESKey string
Signature string
}

sate, err := file.Stat()
type UploadFile interface {
io.ReaderAt
io.ReadSeeker
}

// WebWxUploadMediaByChunk 分块上传文件
// TODO 优化掉这个函数
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
}
filename := sate.Name()

filename := opt.Filename

if ext := filepath.Ext(filename); ext == "" {
names := strings.Split(contentType, "/")
Expand All @@ -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)
Expand All @@ -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,
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion cookiejar.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 3 additions & 2 deletions entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions message.go
Original file line number Diff line number Diff line change
Expand Up @@ -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的构造方法
Expand Down

0 comments on commit ef7fd2c

Please sign in to comment.