iT邦幫忙

2023 iThome 鐵人賽

DAY 25
0

昨天我們已經可以成功從LineBot上收到檔案並暫存下來,今天我們透過domainDrive.SaveContent()回傳的*os.File,把他傳入adapter並操作GoogleDrive API將檔案真的上傳上去~

  1. 首先到adapter\google\drive.go,我們建立UploadFile(folderID string, fileName string, file *os.File)folderID 是指Google Drive資料夾的ID,讓使用者能傳送檔案到自己指定的資料夾,若為空而沒設定到parents就會上傳到根目錄,fileName代表上傳上去的檔案名稱,file就是我們從domain拿到的*os.File代表檔案內容。.Create建立檔案的基本資訊,設定好目標資料夾、檔案名,接著透過.Media代入檔案內容,就沒問題了~

    // internal\adapter\google\drive.go
    func (d *GoogleDrive) UploadFile(folderID string, fileName string, file *os.File) error {
    	defer file.Close()
    	// 指定目標資料夾的 ID
    	var parents []string
    	if folderID != "" {
    		parents = []string{folderID}
    	}
    
    	// 上傳文件
    	driveFile, err := d.Service.Files.Create(&drive.File{
    		Name:    fileName,
    		Parents: parents,
    	}).Media(file).Do()
    	if err != nil {
    		log.Println("Upload Error:", err)
    		return err
    	}
    
    	log.Printf("Got drive.File, err: %#v, %v", driveFile, err)
    	return nil
    }
    
  2. 下一步,folderID我們現在是寫死的,但應該是要儲存起來的。我們先到adapter\dynamodb\oauth.go,改一下GoogleOAuthToken 讓他先存到dynamodb裡面,我們在GoogleOAuthToken下面新增一個Info map[string]interface{},來儲存一些接下來可能會用到的attribute。

    // internal\adapter\dynamodb\oauth.go
    type GoogleOAuthToken struct {
    	PK           string    `dynamodbav:"PK"`
    	AccessToken  string    `dynamodbav:"access_token"`
    	TokenType    string    `dynamodbav:"token_type"`
    	RefreshToken string    `dynamodbav:"refresh_token"`
    	Expiry       time.Time `dynamodbav:"expiry"`
    
    	Info map[string]interface{} `dynamodbav:"info"`
    }
    
  3. 接著我們到login_service.go,讓建立GoogleOAuthToken的同時能在Info帶上upload_folder_id

    // internal\app\service\drive\login_service.go
    func (dr *GoogleDriveService) Login(ctx context.Context, lineID string, authCode string) error {
    	tok, err := dr.driveServiceGoogleOA.UserOAuthToken(authCode)
    	if err != nil {
    		return err
    	}
    
    	dToken := dynamodb.GoogleOAuthToken{
    		PK:           lineID,
    		AccessToken:  tok.AccessToken,
    		TokenType:    tok.TokenType,
    		RefreshToken: tok.RefreshToken,
    		Expiry:       tok.Expiry,
    		Info: map[string]interface{}{
    			"upload_folder_id": ""},
    	}
    
    	err = dr.driveServiceDynamodb.AddGoogleOAuthToken(dToken)
    	if err != nil {
    		return err
    	}
    
    	return nil
    }
    
  4. 接著回到adapter,改一下更新的function,我們補上對應的update.Set(),讓他能更新其他的欄位。

    // internal\adapter\dynamodb\oauth.go
    func (basics TableBasics) TxUpdateGoogleOAuthToken(tok GoogleOAuthToken) (*dynamodb.TransactWriteItemsOutput, error) {
    	var err error
    	var response *dynamodb.TransactWriteItemsOutput
    
    	update := expression.Set(expression.Name("refresh_token"), expression.Value(tok.RefreshToken))
    	update.Set(expression.Name("access_token"), expression.Value(tok.AccessToken))
    	// 補上更新其他欄位
    	update.Set(expression.Name("token_type"), expression.Value(tok.TokenType))
    	update.Set(expression.Name("expiry"), expression.Value(tok.Expiry))
    	update.Set(expression.Name("info.upload_folder_id"), expression.Value(tok.Info["upload_folder_id"]))
    
    	expr, err := expression.NewBuilder().WithUpdate(update).Build()
    	if err != nil {
    		log.Printf("Couldn't build expression for update. Here's why: %v\n", err)
    	} else {
    		twii := &dynamodb.TransactWriteItemsInput{
    			TransactItems: []types.TransactWriteItem{
    				{
    					Update: &types.Update{
    						Key:                       tok.GetKey(),
    						TableName:                 aws.String(basics.TableName),
    						ExpressionAttributeNames:  expr.Names(),
    						ExpressionAttributeValues: expr.Values(),
    						UpdateExpression:          expr.Update(),
    					},
    				},
    			},
    		}
    		response, err = basics.DynamoDbClient.TransactWriteItems(context.TODO(), twii)
    		if err != nil {
    			log.Printf("Couldn't trasnaciton update tok %v. Here's why: %v\n", tok.PK, err)
    		}
    	}
    
    	return response, err
    }
    
  5. 然後,我們複製之前list folder拿到的folderID,選一個來做測試。

    https://ithelp.ithome.com.tw/upload/images/20231010/20115990uxS5vtlIMu.png

  6. 回到drive_service.go,正常情況我們從dynamodb取出folderID,但我們還沒做選擇資料夾的功能,所以我們先在下面貼上剛剛複製的來覆寫測試。

    // internal\app\service\drive\drive_service.go
    func (dr *GoogleDriveService) UploadFile(ctx context.Context, lineID string, fileName string, content io.ReadCloser) error {
    	dToken, err := dr.driveServiceDynamodb.GetGoogleOAuthToken(lineID)
    
    	if err != nil {
    		log.Println(err)
    		return err
    	}
    
    	tok := oauth2.Token{
    		AccessToken:  dToken.AccessToken,
    		TokenType:    dToken.TokenType,
    		RefreshToken: dToken.RefreshToken,
    		Expiry:       dToken.Expiry,
    	}
    
    	d, err := dr.driveServiceGoogleOA.NewGoogleDrive(ctx, &tok)
    	if err != nil {
    		log.Println(err)
    		return err
    	}
    
    	file, err := domainDrive.SaveContent(content)
    	if err != nil {
    		log.Println(err)
    		return err
    	}
    
    	log.Println("START Upload File To Drive")
    
    	folderID := dToken.Info["upload_folder_id"].(string)
    	// 假設預設的儲存路徑
    	folderID = "19Dxrgx_lYM68o3w_Fi4Qy5aysTX6ZAlt"
    
    	err = d.UploadFile(folderID, fileName, file)
    	if err != nil {
    		log.Println("err:", err)
    		return err
    	}
    	return nil
    
    }
    
  7. 最後,跟昨天一樣用測試.txt來試試看,可以看到Linebot收到檔案後,能成功地上傳到Google Drive並且檔案有在我們指定的資料夾下,那我們今天就寫到這囉~
    https://ithelp.ithome.com.tw/upload/images/20231010/201159902PkppgQvck.png
    https://ithelp.ithome.com.tw/upload/images/20231010/20115990ba7ubWlPqb.png


補充一下遇到的坑

今天踩了一個坑,正常情形下,我們在使用d.Service.Files.Create().Media(file).Do()上傳檔案的時候,因為.Media背後其實是會帶上uploadType=media的參數,但是當要上傳的檔案大於5MB的時候,背後會變成調用"https://www.googleapis.com/upload/drive/v3/files?alt=json&prettyPrint=false&uploadType=resumable"uploadType=resumable會導致背後會走支援斷點續傳的模式,而這個自動切換也就讓我們最初NewGoogleDrive()時代入的ctx在oa.Config.Client(ctx, tok)裡面被context canceled掉。為了避免這個問題,我們把ctx改成context.Background()讓兩者不要有依賴關係,這樣就能同時讓大小檔案都能上傳了。

// internal\adapter\google\oauth.go
func (oa *GoogleOAuth) NewGoogleDrive(ctx context.Context, tok *oauth2.Token) (*GoogleDrive, error) {
	client := oa.Config.Client(context.Background(), tok)
	srv, err := drive.NewService(ctx, option.WithHTTPClient(client))
	if err != nil {
		log.Printf("Unable to retrieve Drive client: %v", err)
		return nil, err
	}
	return &GoogleDrive{
		Service: srv,
	}, nil
}

https://ithelp.ithome.com.tw/upload/images/20231010/20115990k6UFOkdw0K.png
https://ithelp.ithome.com.tw/upload/images/20231010/20115990YjVE94FNpd.png


上一篇
Day24 Drive Service - 上傳檔案01
下一篇
Day26 加上Flex Message Carousel
系列文
Golang LineBot X GoogleDrive:LINE有各種限制!? 那就丟上Drive吧!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言