mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-19 08:19:15 +08:00
feat: OneDrive 增加 Token 刷新机制 (#3637)
This commit is contained in:
parent
1e5cd23bbe
commit
6499d7f462
@ -49,6 +49,17 @@ func (b *BaseApi) CreateBackup(c *gin.Context) {
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Backup Account
|
||||
// @Summary Refresh OneDrive token
|
||||
// @Description 刷新 OneDrive token
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/backup/refresh/onedrive [post]
|
||||
func (b *BaseApi) RefreshOneDriveToken(c *gin.Context) {
|
||||
backupService.Run()
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Backup Account
|
||||
// @Summary List buckets
|
||||
// @Description 获取 bucket 列表
|
||||
|
@ -6,12 +6,10 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
@ -19,6 +17,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage/client"
|
||||
fileUtils "github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
@ -55,6 +54,8 @@ type IBackupService interface {
|
||||
|
||||
AppBackup(db dto.CommonBackup) error
|
||||
AppRecover(req dto.CommonRecover) error
|
||||
|
||||
Run()
|
||||
}
|
||||
|
||||
func NewIBackupService() IBackupService {
|
||||
@ -205,6 +206,9 @@ func (u *BackupService) Create(req dto.BackupOperate) error {
|
||||
return buserr.WithMap("ErrBackupCheck", map[string]interface{}{"err": err.Error()}, err)
|
||||
}
|
||||
}
|
||||
if backup.Type == constant.OneDrive {
|
||||
StartRefreshOneDriveToken()
|
||||
}
|
||||
if err := backupRepo.Create(&backup); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -232,6 +236,13 @@ func (u *BackupService) GetBuckets(backupDto dto.ForBuckets) ([]interface{}, err
|
||||
}
|
||||
|
||||
func (u *BackupService) Delete(id uint) error {
|
||||
backup, _ := backupRepo.Get(commonRepo.WithByID(id))
|
||||
if backup.ID == 0 {
|
||||
return constant.ErrRecordNotFound
|
||||
}
|
||||
if backup.Type == constant.OneDrive {
|
||||
global.Cron.Remove(global.OneDriveCronID)
|
||||
}
|
||||
cronjobs, _ := cronjobRepo.List(cronjobRepo.WithByBackupID(id))
|
||||
if len(cronjobs) != 0 {
|
||||
return buserr.New(constant.ErrBackupInUsed)
|
||||
@ -387,6 +398,15 @@ func (u *BackupService) loadByType(accountType string, accounts []model.BackupAc
|
||||
if err := copier.Copy(&item, &account); err != nil {
|
||||
global.LOG.Errorf("copy backup account to dto backup info failed, err: %v", err)
|
||||
}
|
||||
if account.Type == constant.OneDrive {
|
||||
varMap := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(item.Vars), &varMap); err != nil {
|
||||
return dto.BackupInfo{Type: accountType}
|
||||
}
|
||||
delete(varMap, "refresh_token")
|
||||
itemVars, _ := json.Marshal(varMap)
|
||||
item.Vars = string(itemVars)
|
||||
}
|
||||
return item
|
||||
}
|
||||
}
|
||||
@ -398,44 +418,23 @@ func (u *BackupService) loadAccessToken(backup *model.BackupAccount) error {
|
||||
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
|
||||
return fmt.Errorf("unmarshal backup vars failed, err: %v", err)
|
||||
}
|
||||
|
||||
data := url.Values{}
|
||||
data.Set("client_id", global.CONF.System.OneDriveID)
|
||||
data.Set("client_secret", global.CONF.System.OneDriveSc)
|
||||
data.Set("grant_type", "authorization_code")
|
||||
data.Set("code", varMap["code"].(string))
|
||||
data.Set("redirect_uri", constant.OneDriveRedirectURI)
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("POST", "https://login.microsoftonline.com/common/oauth2/v2.0/token", strings.NewReader(data.Encode()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("new http post client for access token failed, err: %v", err)
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("request for access token failed, err: %v", err)
|
||||
}
|
||||
delete(varMap, "code")
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read data from response body failed, err: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
token := map[string]interface{}{}
|
||||
if err := json.Unmarshal(respBody, &token); err != nil {
|
||||
return fmt.Errorf("unmarshal data from response body failed, err: %v", err)
|
||||
}
|
||||
accessToken, ok := token["refresh_token"].(string)
|
||||
code, ok := varMap["code"]
|
||||
if !ok {
|
||||
return errors.New("no such access token in response")
|
||||
return errors.New("no such token in request, please retry!")
|
||||
}
|
||||
|
||||
itemVars, err := json.Marshal(varMap)
|
||||
token, refreshToken, err := client.RefreshToken("authorization_code", code.(string))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
backup.Credential = token
|
||||
varMapItem := make(map[string]interface{})
|
||||
varMapItem["refresh_status"] = constant.StatusSuccess
|
||||
varMapItem["refresh_time"] = time.Now().Format("2006-01-02 15:04:05")
|
||||
varMapItem["refresh_token"] = refreshToken
|
||||
itemVars, err := json.Marshal(varMapItem)
|
||||
if err != nil {
|
||||
return fmt.Errorf("json marshal var map failed, err: %v", err)
|
||||
}
|
||||
backup.Credential = accessToken
|
||||
backup.Vars = string(itemVars)
|
||||
return nil
|
||||
}
|
||||
@ -521,3 +520,56 @@ func (u *BackupService) checkBackupConn(backup *model.BackupAccount) (bool, erro
|
||||
targetPath := strings.TrimPrefix(path.Join(backup.BackupPath, "test/1panel"), "/")
|
||||
return client.Upload(fileItem, targetPath)
|
||||
}
|
||||
|
||||
func StartRefreshOneDriveToken() {
|
||||
service := NewIBackupService()
|
||||
oneDriveCronID, err := global.Cron.AddJob("0 * * * *", service)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("can not add OneDrive corn job: %s", err.Error())
|
||||
return
|
||||
}
|
||||
global.OneDriveCronID = oneDriveCronID
|
||||
}
|
||||
|
||||
func (u *BackupService) Run() {
|
||||
var backupItem model.BackupAccount
|
||||
_ = global.DB.Where("`type` = ?", "OneDrive").First(&backupItem)
|
||||
if backupItem.ID == 0 {
|
||||
return
|
||||
}
|
||||
if len(backupItem.Credential) == 0 {
|
||||
global.LOG.Error("OneDrive configuration lacks token information, please rebind.")
|
||||
return
|
||||
}
|
||||
global.LOG.Info("start to refresh token of OneDrive ...")
|
||||
varMap := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(backupItem.Vars), &varMap); err != nil {
|
||||
global.LOG.Errorf("Failed to refresh OneDrive token, please retry, err: %v", err)
|
||||
return
|
||||
}
|
||||
refreshItem, ok := varMap["refresh_token"]
|
||||
if !ok {
|
||||
global.LOG.Error("Failed to refresh OneDrive token, please retry, err: no such refresh token")
|
||||
return
|
||||
}
|
||||
|
||||
token, refreshToken, err := client.RefreshToken("refresh_token", refreshItem.(string))
|
||||
varMap["refresh_status"] = constant.StatusSuccess
|
||||
varMap["refresh_time"] = time.Now().Format("2006-01-02 15:04:05")
|
||||
if err != nil {
|
||||
varMap["refresh_status"] = constant.StatusFailed
|
||||
varMap["refresh_msg"] = err.Error()
|
||||
global.LOG.Errorf("Failed to refresh OneDrive token, please retry, err: %v", err)
|
||||
return
|
||||
}
|
||||
varMap["refresh_token"] = refreshToken
|
||||
|
||||
varsItem, _ := json.Marshal(varMap)
|
||||
_ = global.DB.Model(&model.BackupAccount{}).
|
||||
Where("id = ?", backupItem.ID).
|
||||
Updates(map[string]interface{}{
|
||||
"credential": token,
|
||||
"vars": varsItem,
|
||||
}).Error
|
||||
global.LOG.Info("Successfully refreshed OneDrive token.")
|
||||
}
|
||||
|
@ -211,6 +211,6 @@ func StartMonitor(removeBefore bool, interval string) error {
|
||||
go service.saveIODataToDB(ctx, float64(intervalItem))
|
||||
go service.saveNetDataToDB(ctx, float64(intervalItem))
|
||||
|
||||
global.MonitorCronID = int(monitorID)
|
||||
global.MonitorCronID = monitorID
|
||||
return nil
|
||||
}
|
||||
|
@ -44,6 +44,12 @@ func Run() {
|
||||
if _, err := global.Cron.AddJob("@daily", job.NewAppStoreJob()); err != nil {
|
||||
global.LOG.Errorf("can not add appstore corn job: %s", err.Error())
|
||||
}
|
||||
|
||||
var backup model.BackupAccount
|
||||
_ = global.DB.Where("type = ?", "OneDrive").Find(&backup).Error
|
||||
if backup.ID != 0 {
|
||||
service.StartRefreshOneDriveToken()
|
||||
}
|
||||
global.Cron.Start()
|
||||
|
||||
var cronJobs []model.Cronjob
|
||||
|
@ -20,6 +20,7 @@ var (
|
||||
CACHE *badger_db.Cache
|
||||
Viper *viper.Viper
|
||||
|
||||
Cron *cron.Cron
|
||||
MonitorCronID int
|
||||
Cron *cron.Cron
|
||||
MonitorCronID cron.EntryID
|
||||
OneDriveCronID cron.EntryID
|
||||
)
|
||||
|
@ -66,6 +66,7 @@ func Init() {
|
||||
migrations.AddTableDatabasePostgresql,
|
||||
migrations.AddPostgresqlSuperUser,
|
||||
migrations.UpdateCronjobWithWebsite,
|
||||
migrations.UpdateOneDriveToken,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
@ -1,11 +1,17 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/service"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage/client"
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@ -188,3 +194,58 @@ var UpdateCronjobWithWebsite = &gormigrate.Migration{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var UpdateOneDriveToken = &gormigrate.Migration{
|
||||
ID: "20240117-update-onedrive-token",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
var (
|
||||
backup model.BackupAccount
|
||||
clientSetting model.Setting
|
||||
secretSetting model.Setting
|
||||
)
|
||||
_ = tx.Where("type = ?", "OneDrive").First(&backup).Error
|
||||
if backup.ID == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(backup.Credential) == 0 {
|
||||
global.LOG.Error("OneDrive configuration lacks token information, please rebind.")
|
||||
return nil
|
||||
}
|
||||
|
||||
_ = tx.Where("key = ?", "OneDriveID").First(&clientSetting).Error
|
||||
if clientSetting.ID == 0 {
|
||||
global.LOG.Error("system configuration lacks clientID information, please retry.")
|
||||
return nil
|
||||
}
|
||||
_ = tx.Where("key = ?", "OneDriveSc").First(&secretSetting).Error
|
||||
if secretSetting.ID == 0 {
|
||||
global.LOG.Error("system configuration lacks clientID information, please retry.")
|
||||
return nil
|
||||
}
|
||||
idItem, _ := base64.StdEncoding.DecodeString(clientSetting.Value)
|
||||
global.CONF.System.OneDriveID = string(idItem)
|
||||
scItem, _ := base64.StdEncoding.DecodeString(secretSetting.Value)
|
||||
global.CONF.System.OneDriveSc = string(scItem)
|
||||
|
||||
varMap := make(map[string]interface{})
|
||||
token, refreshToken, err := client.RefreshToken("refresh_token", backup.Credential)
|
||||
varMap["refresh_status"] = constant.StatusSuccess
|
||||
varMap["refresh_time"] = time.Now().Format("2006-01-02 15:04:05")
|
||||
if err != nil {
|
||||
varMap["refresh_msg"] = err.Error()
|
||||
varMap["refresh_status"] = constant.StatusFailed
|
||||
}
|
||||
varMap["refresh_token"] = refreshToken
|
||||
itemVars, _ := json.Marshal(varMap)
|
||||
if err := tx.Model(&model.BackupAccount{}).
|
||||
Where("id = ?", backup.ID).
|
||||
Updates(map[string]interface{}{
|
||||
"credential": token,
|
||||
"vars": string(itemVars),
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
settingRouter.GET("/backup/search", baseApi.ListBackup)
|
||||
settingRouter.GET("/backup/onedrive", baseApi.LoadOneDriveInfo)
|
||||
settingRouter.POST("/backup/backup", baseApi.Backup)
|
||||
settingRouter.POST("/backup/refresh/onedrive", baseApi.RefreshOneDriveToken)
|
||||
settingRouter.POST("/backup/recover", baseApi.Recover)
|
||||
settingRouter.POST("/backup/recover/byupload", baseApi.RecoverByUpload)
|
||||
settingRouter.POST("/backup/search/files", baseApi.LoadFilesFromBackup)
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@ -15,11 +14,10 @@ import (
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
odsdk "github.com/goh-chunlin/go-onedrive/onedrive"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
@ -31,15 +29,8 @@ type oneDriveClient struct {
|
||||
func NewOneDriveClient(vars map[string]interface{}) (*oneDriveClient, error) {
|
||||
token := loadParamFromVars("accessToken", true, vars)
|
||||
ctx := context.Background()
|
||||
|
||||
newToken, err := refreshToken(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = global.DB.Model(&model.Group{}).Where("type = ?", "OneDrive").Updates(map[string]interface{}{"credential": newToken}).Error
|
||||
|
||||
ts := oauth2.StaticTokenSource(
|
||||
&oauth2.Token{AccessToken: newToken},
|
||||
&oauth2.Token{AccessToken: token},
|
||||
)
|
||||
tc := oauth2.NewClient(ctx, ts)
|
||||
|
||||
@ -110,87 +101,24 @@ func (o oneDriveClient) Upload(src, target string) (bool, error) {
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
file, err := os.Open(src)
|
||||
folderID, err := o.loadIDByPath(path.Dir(target))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer file.Close()
|
||||
fileInfo, err := file.Stat()
|
||||
fileInfo, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if fileInfo.IsDir() {
|
||||
return false, errors.New("Only file is allowed to be uploaded here.")
|
||||
}
|
||||
fileName := fileInfo.Name()
|
||||
fileSize := fileInfo.Size()
|
||||
|
||||
folderID, err := o.loadIDByPath(path.Dir(target))
|
||||
if err != nil {
|
||||
return false, err
|
||||
var isOk bool
|
||||
if fileInfo.Size() > 4*1024*1024 {
|
||||
isOk, err = o.upSmall(ctx, src, folderID, fileInfo.Size())
|
||||
} else {
|
||||
isOk, err = o.upBig(ctx, src, folderID, fileInfo.Size())
|
||||
}
|
||||
apiURL := fmt.Sprintf("me/drive/items/%s:/%s:/createUploadSession", url.PathEscape(folderID), fileName)
|
||||
sessionCreationRequestInside := NewUploadSessionCreationRequest{
|
||||
ConflictBehavior: "rename",
|
||||
}
|
||||
|
||||
sessionCreationRequest := struct {
|
||||
Item NewUploadSessionCreationRequest `json:"item"`
|
||||
DeferCommit bool `json:"deferCommit"`
|
||||
}{sessionCreationRequestInside, false}
|
||||
|
||||
sessionCreationReq, err := o.client.NewRequest("POST", apiURL, sessionCreationRequest)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var sessionCreationResp *NewUploadSessionCreationResponse
|
||||
err = o.client.Do(ctx, sessionCreationReq, false, &sessionCreationResp)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("session creation failed %w", err)
|
||||
}
|
||||
|
||||
fileSessionUploadUrl := sessionCreationResp.UploadURL
|
||||
|
||||
sizePerSplit := int64(3200 * 1024)
|
||||
buffer := make([]byte, 3200*1024)
|
||||
splitCount := fileSize / sizePerSplit
|
||||
if fileSize%sizePerSplit != 0 {
|
||||
splitCount += 1
|
||||
}
|
||||
bfReader := bufio.NewReader(file)
|
||||
httpClient := http.Client{
|
||||
Timeout: time.Minute * 10,
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
for splitNow := int64(0); splitNow < splitCount; splitNow++ {
|
||||
length, err := bfReader.Read(buffer)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if int64(length) < sizePerSplit {
|
||||
bufferLast := buffer[:length]
|
||||
buffer = bufferLast
|
||||
}
|
||||
sessionFileUploadReq, err := o.NewSessionFileUploadRequest(fileSessionUploadUrl, splitNow*sizePerSplit, fileSize, bytes.NewReader(buffer))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
res, err := httpClient.Do(sessionFileUploadReq)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if res.StatusCode != 201 && res.StatusCode != 202 && res.StatusCode != 200 {
|
||||
data, _ := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
return false, errors.New(string(data))
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
return isOk, err
|
||||
}
|
||||
|
||||
func (o oneDriveClient) Download(src, target string) (bool, error) {
|
||||
@ -264,38 +192,48 @@ func (o *oneDriveClient) loadIDByPath(path string) (string, error) {
|
||||
return driveItem.Id, nil
|
||||
}
|
||||
|
||||
func refreshToken(oldToken string) (string, error) {
|
||||
func RefreshToken(grantType, grantCode string) (string, string, error) {
|
||||
data := url.Values{}
|
||||
data.Set("client_id", global.CONF.System.OneDriveID)
|
||||
data.Set("client_secret", global.CONF.System.OneDriveSc)
|
||||
data.Set("grant_type", "refresh_token")
|
||||
data.Set("refresh_token", oldToken)
|
||||
if grantType == "refresh_token" {
|
||||
data.Set("grant_type", "refresh_token")
|
||||
data.Set("refresh_token", grantCode)
|
||||
} else {
|
||||
data.Set("grant_type", "authorization_code")
|
||||
data.Set("code", grantCode)
|
||||
}
|
||||
data.Set("redirect_uri", constant.OneDriveRedirectURI)
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("POST", "https://login.microsoftonline.com/common/oauth2/v2.0/token", strings.NewReader(data.Encode()))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("new http post client for access token failed, err: %v", err)
|
||||
return "", "", fmt.Errorf("new http post client for access token failed, err: %v", err)
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("request for access token failed, err: %v", err)
|
||||
return "", "", fmt.Errorf("request for access token failed, err: %v", err)
|
||||
}
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read data from response body failed, err: %v", err)
|
||||
return "", "", fmt.Errorf("read data from response body failed, err: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
tokenMap := map[string]interface{}{}
|
||||
if err := json.Unmarshal(respBody, &tokenMap); err != nil {
|
||||
return "", fmt.Errorf("unmarshal data from response body failed, err: %v", err)
|
||||
return "", "", fmt.Errorf("unmarshal data from response body failed, err: %v", err)
|
||||
}
|
||||
accessToken, ok := tokenMap["access_token"].(string)
|
||||
if !ok {
|
||||
return "", errors.New("no such access token in response")
|
||||
return "", "", errors.New("no such access token in response")
|
||||
}
|
||||
return accessToken, nil
|
||||
refreshToken, ok := tokenMap["refresh_token"].(string)
|
||||
if !ok {
|
||||
return "", "", errors.New("no such access token in response")
|
||||
}
|
||||
return accessToken, refreshToken, nil
|
||||
}
|
||||
|
||||
func (o *oneDriveClient) createFolder(parent string) error {
|
||||
@ -355,3 +293,89 @@ func (o *oneDriveClient) NewSessionFileUploadRequest(absoluteUrl string, grandOf
|
||||
|
||||
return req, err
|
||||
}
|
||||
|
||||
func (o *oneDriveClient) upSmall(ctx context.Context, srcPath, folderID string, fileSize int64) (bool, error) {
|
||||
file, err := os.Open(srcPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
buffer := make([]byte, fileSize)
|
||||
_, _ = file.Read(buffer)
|
||||
fileReader := bytes.NewReader(buffer)
|
||||
apiURL := fmt.Sprintf("me/drive/items/%s:/%s:/content?@microsoft.graph.conflictBehavior=rename", url.PathEscape(folderID), path.Base(srcPath))
|
||||
|
||||
mimeType := files.GetMimeType(srcPath)
|
||||
req, err := o.client.NewFileUploadRequest(apiURL, mimeType, fileReader)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
var response *DriveItem
|
||||
if err := o.client.Do(context.Background(), req, false, &response); err != nil {
|
||||
return false, fmt.Errorf("do request for list failed, err: %v", err)
|
||||
}
|
||||
fmt.Println(response)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (o *oneDriveClient) upBig(ctx context.Context, srcPath, folderID string, fileSize int64) (bool, error) {
|
||||
file, err := os.Open(srcPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
apiURL := fmt.Sprintf("me/drive/items/%s:/%s:/createUploadSession", url.PathEscape(folderID), path.Base(srcPath))
|
||||
sessionCreationRequestInside := NewUploadSessionCreationRequest{
|
||||
ConflictBehavior: "rename",
|
||||
}
|
||||
|
||||
sessionCreationRequest := struct {
|
||||
Item NewUploadSessionCreationRequest `json:"item"`
|
||||
DeferCommit bool `json:"deferCommit"`
|
||||
}{sessionCreationRequestInside, false}
|
||||
|
||||
sessionCreationReq, err := o.client.NewRequest("POST", apiURL, sessionCreationRequest)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var sessionCreationResp *NewUploadSessionCreationResponse
|
||||
err = o.client.Do(ctx, sessionCreationReq, false, &sessionCreationResp)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("session creation failed %w", err)
|
||||
}
|
||||
|
||||
fileSessionUploadUrl := sessionCreationResp.UploadURL
|
||||
|
||||
sizePerSplit := int64(3200 * 1024)
|
||||
buffer := make([]byte, 3200*1024)
|
||||
splitCount := fileSize / sizePerSplit
|
||||
if fileSize%sizePerSplit != 0 {
|
||||
splitCount += 1
|
||||
}
|
||||
bfReader := bufio.NewReader(file)
|
||||
var fileUploadResp *UploadSessionUploadResponse
|
||||
for splitNow := int64(0); splitNow < splitCount; splitNow++ {
|
||||
length, err := bfReader.Read(buffer)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if int64(length) < sizePerSplit {
|
||||
bufferLast := buffer[:length]
|
||||
buffer = bufferLast
|
||||
}
|
||||
sessionFileUploadReq, err := o.NewSessionFileUploadRequest(fileSessionUploadUrl, splitNow*sizePerSplit, fileSize, bytes.NewReader(buffer))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := o.client.Do(ctx, sessionFileUploadReq, false, &fileUploadResp); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
if fileUploadResp.Id == "" {
|
||||
return false, errors.New("something went wrong. file upload incomplete. consider upload the file in a step-by-step manner")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
@ -97,6 +97,10 @@ func (s s3Client) Delete(path string) (bool, error) {
|
||||
}
|
||||
|
||||
func (s s3Client) Upload(src, target string) (bool, error) {
|
||||
fileInfo, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
file, err := os.Open(src)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -104,6 +108,9 @@ func (s s3Client) Upload(src, target string) (bool, error) {
|
||||
defer file.Close()
|
||||
|
||||
uploader := s3manager.NewUploader(&s.Sess)
|
||||
if fileInfo.Size() > s3manager.MaxUploadParts*s3manager.DefaultUploadPartSize {
|
||||
uploader.PartSize = fileInfo.Size() / (s3manager.MaxUploadParts - 1)
|
||||
}
|
||||
if _, err := uploader.Upload(&s3manager.UploadInput{
|
||||
Bucket: aws.String(s.bucket),
|
||||
Key: aws.String(target),
|
||||
|
@ -1,8 +1,8 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
@ -56,33 +56,14 @@ func (s sftpClient) Upload(src, target string) (bool, error) {
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
targetFilePath := s.bucket + "/" + target
|
||||
targetDir, _ := path.Split(targetFilePath)
|
||||
if _, err = client.Stat(targetDir); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if err = client.MkdirAll(targetDir); err != nil {
|
||||
return false, err
|
||||
}
|
||||
} else {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
dstFile, err := client.Create(targetFilePath)
|
||||
dstFile, err := client.Create(path.Join(s.bucket, target))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer dstFile.Close()
|
||||
|
||||
reader := bufio.NewReaderSize(srcFile, 128*1024*1024)
|
||||
for {
|
||||
chunk, err := reader.Peek(8 * 1024 * 1024)
|
||||
if len(chunk) != 0 {
|
||||
_, _ = dstFile.Write(chunk)
|
||||
_, _ = reader.Discard(len(chunk))
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if _, err := io.Copy(dstFile, srcFile); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
@ -103,6 +84,7 @@ func (s sftpClient) Download(src, target string) (bool, error) {
|
||||
}
|
||||
defer client.Close()
|
||||
defer sshClient.Close()
|
||||
|
||||
srcFile, err := client.Open(s.bucket + "/" + src)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -121,7 +103,7 @@ func (s sftpClient) Download(src, target string) (bool, error) {
|
||||
return true, err
|
||||
}
|
||||
|
||||
func (s sftpClient) Exist(path string) (bool, error) {
|
||||
func (s sftpClient) Exist(filePath string) (bool, error) {
|
||||
sshClient, err := ssh.Dial("tcp", s.connInfo, s.config)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -133,7 +115,7 @@ func (s sftpClient) Exist(path string) (bool, error) {
|
||||
defer client.Close()
|
||||
defer sshClient.Close()
|
||||
|
||||
srcFile, err := client.Open(s.bucket + "/" + path)
|
||||
srcFile, err := client.Open(path.Join(s.bucket, filePath))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
@ -145,7 +127,7 @@ func (s sftpClient) Exist(path string) (bool, error) {
|
||||
return true, err
|
||||
}
|
||||
|
||||
func (s sftpClient) Size(path string) (int64, error) {
|
||||
func (s sftpClient) Size(filePath string) (int64, error) {
|
||||
sshClient, err := ssh.Dial("tcp", s.connInfo, s.config)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@ -157,7 +139,7 @@ func (s sftpClient) Size(path string) (int64, error) {
|
||||
defer client.Close()
|
||||
defer sshClient.Close()
|
||||
|
||||
files, err := client.Stat(s.bucket + "/" + path)
|
||||
files, err := client.Stat(path.Join(s.bucket, filePath))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@ -176,8 +158,7 @@ func (s sftpClient) Delete(filePath string) (bool, error) {
|
||||
defer client.Close()
|
||||
defer sshClient.Close()
|
||||
|
||||
targetFilePath := s.bucket + "/" + filePath
|
||||
if err := client.Remove(targetFilePath); err != nil {
|
||||
if err := client.Remove(path.Join(s.bucket, filePath)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
@ -195,7 +176,7 @@ func (s sftpClient) ListObjects(prefix string) ([]string, error) {
|
||||
defer client.Close()
|
||||
defer sshClient.Close()
|
||||
|
||||
files, err := client.ReadDir(s.bucket + "/" + prefix)
|
||||
files, err := client.ReadDir(path.Join(s.bucket, prefix))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -84,6 +84,9 @@ export const handleRecover = (params: Backup.Recover) => {
|
||||
export const handleRecoverByUpload = (params: Backup.Recover) => {
|
||||
return http.post(`/settings/backup/recover/byupload`, params, TimeoutEnum.T_1D);
|
||||
};
|
||||
export const refreshOneDrive = () => {
|
||||
return http.post(`/settings/backup/refresh/onedrive`, {});
|
||||
};
|
||||
export const downloadBackupRecord = (params: Backup.RecordDownload) => {
|
||||
return http.post<string>(`/settings/backup/record/download`, params, TimeoutEnum.T_10M);
|
||||
};
|
||||
|
@ -1241,6 +1241,8 @@ const message = {
|
||||
SFTP: 'SFTP',
|
||||
WebDAV: 'WebDAV',
|
||||
OneDrive: 'Microsoft OneDrive',
|
||||
refreshTime: 'Token Refresh Time',
|
||||
refreshStatus: 'Token Refresh Status',
|
||||
backupDir: 'Backup dir',
|
||||
codeWarning: 'The current authorization code format is incorrect, please confirm again!',
|
||||
isCN: 'Domestic version (not supported at the moment)',
|
||||
|
@ -1165,6 +1165,8 @@ const message = {
|
||||
SFTP: 'SFTP',
|
||||
WebDAV: 'WebDAV',
|
||||
OneDrive: '微軟 OneDrive',
|
||||
refreshTime: '令牌刷新時間',
|
||||
refreshStatus: '令牌刷新狀態',
|
||||
codeWarning: '當前授權碼格式錯誤,請重新確認!',
|
||||
backupDir: '備份路徑',
|
||||
isCN: '國內版 (暫不支持)',
|
||||
|
@ -1166,6 +1166,8 @@ const message = {
|
||||
SFTP: 'SFTP',
|
||||
WebDAV: 'WebDAV',
|
||||
OneDrive: '微软 OneDrive',
|
||||
refreshTime: '令牌刷新时间',
|
||||
refreshStatus: '令牌刷新状态',
|
||||
codeWarning: '当前授权码格式错误,请重新确认!',
|
||||
backupDir: '备份路径',
|
||||
isCN: '国内版 (暂不支持)',
|
||||
|
@ -212,7 +212,7 @@
|
||||
round
|
||||
plain
|
||||
:disabled="oneDriveData.id === 0"
|
||||
@click="onOpenDialog('edit', 'SFTP', oneDriveData)"
|
||||
@click="onOpenDialog('edit', 'OneDrive', oneDriveData)"
|
||||
>
|
||||
{{ $t('commons.button.edit') }}
|
||||
</el-button>
|
||||
@ -227,6 +227,26 @@
|
||||
<span v-if="oneDriveData.backupPath">{{ oneDriveData.backupPath }}</span>
|
||||
<span v-else>{{ $t('setting.unSetting') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('setting.refreshTime')">
|
||||
<span>{{ oneDriveData.varsJson['refresh_time'] }}</span>
|
||||
<el-button @click="refreshToken" link type="primary" class="ml-2">
|
||||
{{ $t('commons.button.refresh') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('setting.refreshStatus')">
|
||||
<el-tag v-if="oneDriveData.varsJson['refresh_status'] === 'Success'" type="success">
|
||||
{{ $t('commons.status.success') }}
|
||||
</el-tag>
|
||||
<el-tooltip
|
||||
v-if="oneDriveData.varsJson['refresh_status'] === 'Failed'"
|
||||
:content="oneDriveData.varsJson['refresh_msg']"
|
||||
placement="top"
|
||||
>
|
||||
<el-tag type="danger">
|
||||
{{ $t('commons.status.failed') }}
|
||||
</el-tag>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.createdAt')">
|
||||
{{ dateFormat(0, 0, oneDriveData.createdAt) }}
|
||||
</el-form-item>
|
||||
@ -430,7 +450,7 @@
|
||||
import { dateFormat } from '@/utils/util';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import OpDialog from '@/components/del-dialog/index.vue';
|
||||
import { getBackupList, deleteBackup } from '@/api/modules/setting';
|
||||
import { getBackupList, deleteBackup, refreshOneDrive } from '@/api/modules/setting';
|
||||
import localDialog from '@/views/setting/backup-account/local/index.vue';
|
||||
import s3Dialog from '@/views/setting/backup-account/s3/index.vue';
|
||||
import ossDialog from '@/views/setting/backup-account/oss/index.vue';
|
||||
@ -536,7 +556,9 @@ const oneDriveData = ref<Backup.BackupInfo>({
|
||||
backupPath: '',
|
||||
vars: '',
|
||||
varsJson: {
|
||||
redirectURI: '',
|
||||
refresh_msg: '',
|
||||
refresh_time: '',
|
||||
refresh_status: '',
|
||||
},
|
||||
createdAt: new Date(),
|
||||
});
|
||||
@ -680,6 +702,11 @@ const onOpenDialog = async (
|
||||
}
|
||||
};
|
||||
|
||||
const refreshToken = async () => {
|
||||
await refreshOneDrive();
|
||||
search();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user