Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
hexbee
Cloudreve
提交
dd5f6e3d
C
Cloudreve
项目概览
hexbee
/
Cloudreve
通知
2
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
C
Cloudreve
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
dd5f6e3d
编写于
1月 20, 2020
作者:
H
HFO4
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Feat: onedrive client upload / callback
上级
807aa5ac
变更
10
隐藏空白更改
内联
并排
Showing
10 changed file
with
359 addition
and
35 deletion
+359
-35
middleware/auth.go
middleware/auth.go
+16
-0
pkg/filesystem/driver/onedrive/api.go
pkg/filesystem/driver/onedrive/api.go
+192
-0
pkg/filesystem/driver/onedrive/handller.go
pkg/filesystem/driver/onedrive/handller.go
+17
-9
pkg/filesystem/driver/onedrive/options.go
pkg/filesystem/driver/onedrive/options.go
+25
-4
pkg/filesystem/filesystem.go
pkg/filesystem/filesystem.go
+28
-1
pkg/filesystem/upload.go
pkg/filesystem/upload.go
+5
-1
pkg/serializer/upload.go
pkg/serializer/upload.go
+2
-0
routers/controllers/callback.go
routers/controllers/callback.go
+11
-0
routers/router.go
routers/router.go
+9
-0
service/callback/upload.go
service/callback/upload.go
+54
-20
未找到文件。
middleware/auth.go
浏览文件 @
dd5f6e3d
...
...
@@ -273,3 +273,19 @@ func UpyunCallbackAuth() gin.HandlerFunc {
c
.
Next
()
}
}
// OneDriveCallbackAuth OneDrive回调签名验证
// TODO 解耦
func
OneDriveCallbackAuth
()
gin
.
HandlerFunc
{
return
func
(
c
*
gin
.
Context
)
{
// 验证key并查找用户
resp
,
_
:=
uploadCallbackCheck
(
c
)
if
resp
.
Code
!=
0
{
c
.
JSON
(
401
,
serializer
.
QiniuCallbackFailed
{
Error
:
resp
.
Msg
})
c
.
Abort
()
return
}
c
.
Next
()
}
}
pkg/filesystem/driver/onedrive/api.go
0 → 100644
浏览文件 @
dd5f6e3d
package
onedrive
import
(
"context"
"encoding/json"
"github.com/HFO4/cloudreve/pkg/request"
"io/ioutil"
"net/http"
"net/url"
"path"
"strings"
)
// RespError 接口返回错误
type
RespError
struct
{
APIError
APIError
`json:"error"`
}
// APIError 接口返回的错误内容
type
APIError
struct
{
Code
string
`json:"code"`
Message
string
`json:"message"`
}
// UploadSessionResponse 分片上传会话
type
UploadSessionResponse
struct
{
DataContext
string
`json:"@odata.context"`
ExpirationDateTime
string
`json:"expirationDateTime"`
NextExpectedRanges
[]
string
`json:"nextExpectedRanges"`
UploadURL
string
`json:"uploadUrl"`
}
// FileInfo 文件元信息
type
FileInfo
struct
{
Name
string
`json:"name"`
Size
uint64
`json:"size"`
Image
imageInfo
`json:"image"`
ParentReference
parentReference
`json:"parentReference"`
}
type
imageInfo
struct
{
Height
int
`json:"height"`
Width
int
`json:"width"`
}
type
parentReference
struct
{
Path
string
`json:"path"`
Name
string
`json:"name"`
ID
string
`json:"id"`
}
// GetSourcePath 获取文件的绝对路径
func
(
info
*
FileInfo
)
GetSourcePath
()
string
{
res
,
err
:=
url
.
PathUnescape
(
strings
.
TrimPrefix
(
path
.
Join
(
strings
.
TrimPrefix
(
info
.
ParentReference
.
Path
,
"/drive/root:"
),
info
.
Name
,
),
"/"
,
),
)
if
err
!=
nil
{
return
""
}
return
res
}
// Error 实现error接口
func
(
err
RespError
)
Error
()
string
{
return
err
.
APIError
.
Message
}
func
(
client
*
Client
)
getRequestURL
(
api
string
)
string
{
base
,
_
:=
url
.
Parse
(
client
.
Endpoints
.
EndpointURL
)
if
base
==
nil
{
return
""
}
base
.
Path
=
path
.
Join
(
base
.
Path
,
api
)
return
base
.
String
()
}
// Meta 根据资源ID获取文件元信息
func
(
client
*
Client
)
Meta
(
ctx
context
.
Context
,
id
string
)
(
*
FileInfo
,
error
)
{
requestURL
:=
client
.
getRequestURL
(
"/me/drive/items/"
+
id
)
res
,
err
:=
client
.
request
(
ctx
,
"GET"
,
requestURL
+
"?expand=thumbnails"
,
""
)
if
err
!=
nil
{
return
nil
,
err
}
var
(
decodeErr
error
fileInfo
FileInfo
)
decodeErr
=
json
.
Unmarshal
([]
byte
(
res
),
&
fileInfo
)
if
decodeErr
!=
nil
{
return
nil
,
decodeErr
}
return
&
fileInfo
,
nil
}
// CreateUploadSession 创建分片上传会话
func
(
client
*
Client
)
CreateUploadSession
(
ctx
context
.
Context
,
dst
string
,
opts
...
Option
)
(
string
,
error
)
{
options
:=
newDefaultOption
()
for
_
,
o
:=
range
opts
{
o
.
apply
(
options
)
}
dst
=
strings
.
TrimPrefix
(
dst
,
"/"
)
requestURL
:=
client
.
getRequestURL
(
"me/drive/root:/"
+
dst
+
":/createUploadSession"
)
body
:=
map
[
string
]
map
[
string
]
interface
{}{
"item"
:
{
"@microsoft.graph.conflictBehavior"
:
options
.
conflictBehavior
,
},
}
bodyBytes
,
_
:=
json
.
Marshal
(
body
)
res
,
err
:=
client
.
request
(
ctx
,
"POST"
,
requestURL
,
string
(
bodyBytes
))
if
err
!=
nil
{
return
""
,
err
}
var
(
decodeErr
error
uploadSession
UploadSessionResponse
)
decodeErr
=
json
.
Unmarshal
([]
byte
(
res
),
&
uploadSession
)
if
decodeErr
!=
nil
{
return
""
,
decodeErr
}
return
uploadSession
.
UploadURL
,
nil
}
func
sysError
(
err
error
)
*
RespError
{
return
&
RespError
{
APIError
:
APIError
{
Code
:
"system"
,
Message
:
err
.
Error
(),
}}
}
func
(
client
*
Client
)
request
(
ctx
context
.
Context
,
method
string
,
url
string
,
body
string
)
(
string
,
*
RespError
)
{
// 获取凭证
err
:=
client
.
UpdateCredential
(
ctx
)
if
err
!=
nil
{
return
""
,
sysError
(
err
)
}
// 发送请求
bodyReader
:=
ioutil
.
NopCloser
(
strings
.
NewReader
(
body
))
res
:=
client
.
Request
.
Request
(
method
,
url
,
bodyReader
,
request
.
WithContentLength
(
int64
(
len
(
body
))),
request
.
WithHeader
(
http
.
Header
{
"Authorization"
:
{
"Bearer "
+
client
.
Credential
.
AccessToken
},
"Content-Type"
:
{
"application/json"
},
}),
request
.
WithContext
(
ctx
),
)
if
res
.
Err
!=
nil
{
return
""
,
sysError
(
res
.
Err
)
}
respBody
,
err
:=
res
.
GetResponse
()
if
err
!=
nil
{
return
""
,
sysError
(
err
)
}
// 解析请求响应
var
(
errResp
RespError
decodeErr
error
)
// 如果有错误
if
res
.
Response
.
StatusCode
!=
200
{
decodeErr
=
json
.
Unmarshal
([]
byte
(
respBody
),
&
errResp
)
if
decodeErr
!=
nil
{
return
""
,
sysError
(
err
)
}
return
""
,
&
errResp
}
return
respBody
,
nil
}
pkg/filesystem/driver/onedrive/handller.go
浏览文件 @
dd5f6e3d
...
...
@@ -4,6 +4,7 @@ import (
"context"
"errors"
model
"github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
"github.com/HFO4/cloudreve/pkg/filesystem/response"
"github.com/HFO4/cloudreve/pkg/serializer"
"io"
...
...
@@ -51,18 +52,25 @@ func (handler Driver) Source(
// Token 获取上传策略和认证Token
func
(
handler
Driver
)
Token
(
ctx
context
.
Context
,
TTL
int64
,
key
string
)
(
serializer
.
UploadCredential
,
error
)
{
err
:=
handler
.
Client
.
UpdateCredential
(
ctx
)
// 读取上下文中生成的存储路径
savePath
,
ok
:=
ctx
.
Value
(
fsctx
.
SavePathCtx
)
.
(
string
)
if
!
ok
{
return
serializer
.
UploadCredential
{},
errors
.
New
(
"无法获取存储路径"
)
}
// 生成回调地址
siteURL
:=
model
.
GetSiteURL
()
apiBaseURI
,
_
:=
url
.
Parse
(
"/api/v3/callback/onedrive/finish/"
+
key
)
apiURL
:=
siteURL
.
ResolveReference
(
apiBaseURI
)
uploadURL
,
err
:=
handler
.
Client
.
CreateUploadSession
(
ctx
,
savePath
,
WithConflictBehavior
(
"fail"
))
if
err
!=
nil
{
return
serializer
.
UploadCredential
{},
err
}
return
serializer
.
UploadCredential
{
Policy
:
handler
.
Client
.
Credential
.
AccessToken
,
Policy
:
uploadURL
,
Token
:
apiURL
.
String
(),
},
nil
//res,err := handler.Client.ObtainToken(ctx,WithCode("M2e92c4a9-de12-cdda-9cf4-e01f67272831"))
//if err != nil{
// return serializer.UploadCredential{},err
//}
//return serializer.UploadCredential{
// Policy:res.RefreshToken,
//}, nil
}
pkg/filesystem/driver/onedrive/options.go
浏览文件 @
dd5f6e3d
package
onedrive
import
"time"
// Option 发送请求的额外设置
type
Option
interface
{
apply
(
*
options
)
}
type
options
struct
{
redirect
string
code
string
refreshToken
string
redirect
string
code
string
refreshToken
string
conflictBehavior
string
expires
time
.
Time
}
type
optionFunc
func
(
*
options
)
...
...
@@ -27,10 +31,27 @@ func WithRefreshToken(t string) Option {
})
}
// WithConflictBehavior 设置文件重名后的处理方式
func
WithConflictBehavior
(
t
string
)
Option
{
return
optionFunc
(
func
(
o
*
options
)
{
o
.
conflictBehavior
=
t
})
}
// WithExpires 设置过期时间
func
WithExpires
(
t
time
.
Time
)
Option
{
return
optionFunc
(
func
(
o
*
options
)
{
o
.
expires
=
t
})
}
func
(
f
optionFunc
)
apply
(
o
*
options
)
{
f
(
o
)
}
func
newDefaultOption
()
*
options
{
return
&
options
{}
return
&
options
{
conflictBehavior
:
"fail"
,
expires
:
time
.
Now
()
.
UTC
()
.
Add
(
time
.
Duration
(
1
)
*
time
.
Hour
),
}
}
pkg/filesystem/filesystem.go
浏览文件 @
dd5f6e3d
...
...
@@ -2,6 +2,7 @@ package filesystem
import
(
"context"
"errors"
"github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/auth"
"github.com/HFO4/cloudreve/pkg/conf"
...
...
@@ -195,7 +196,6 @@ func (fs *FileSystem) DispatchHandler() error {
}
// NewFileSystemFromContext 从gin.Context创建文件系统
// TODO 用户不存在时使用匿名文件系统
func
NewFileSystemFromContext
(
c
*
gin
.
Context
)
(
*
FileSystem
,
error
)
{
user
,
exist
:=
c
.
Get
(
"user"
)
if
!
exist
{
...
...
@@ -205,6 +205,33 @@ func NewFileSystemFromContext(c *gin.Context) (*FileSystem, error) {
return
fs
,
err
}
// NewFileSystemFromCallback 从gin.Context创建回调用文件系统
// TODO 测试
func
NewFileSystemFromCallback
(
c
*
gin
.
Context
)
(
*
FileSystem
,
error
)
{
fs
,
err
:=
NewFileSystemFromContext
(
c
)
if
err
!=
nil
{
return
nil
,
err
}
// 获取回调会话
callbackSessionRaw
,
ok
:=
c
.
Get
(
"callbackSession"
)
if
!
ok
{
return
nil
,
errors
.
New
(
"找不到回调会话"
)
}
callbackSession
:=
callbackSessionRaw
.
(
*
serializer
.
UploadSession
)
// 重新指向上传策略
policy
,
err
:=
model
.
GetPolicyByID
(
callbackSession
.
PolicyID
)
if
err
!=
nil
{
return
nil
,
err
}
fs
.
Policy
=
&
policy
fs
.
User
.
Policy
=
policy
err
=
fs
.
DispatchHandler
()
return
fs
,
err
}
// SetTargetFile 设置当前处理的目标文件
func
(
fs
*
FileSystem
)
SetTargetFile
(
files
*
[]
model
.
File
)
{
if
len
(
fs
.
FileTarget
)
==
0
{
...
...
pkg/filesystem/upload.go
浏览文件 @
dd5f6e3d
...
...
@@ -148,8 +148,10 @@ func (fs *FileSystem) GetUploadToken(ctx context.Context, path string, size uint
var
err
error
// 是否需要预先生成存储路径
var
savePath
string
if
fs
.
User
.
Policy
.
IsPathGenerateNeeded
()
{
ctx
=
context
.
WithValue
(
ctx
,
fsctx
.
SavePathCtx
,
fs
.
GenerateSavePath
(
ctx
,
local
.
FileStream
{}))
savePath
=
fs
.
GenerateSavePath
(
ctx
,
local
.
FileStream
{
Name
:
name
})
ctx
=
context
.
WithValue
(
ctx
,
fsctx
.
SavePathCtx
,
savePath
)
}
ctx
=
context
.
WithValue
(
ctx
,
fsctx
.
FileSizeCtx
,
size
)
...
...
@@ -168,6 +170,8 @@ func (fs *FileSystem) GetUploadToken(ctx context.Context, path string, size uint
PolicyID
:
fs
.
User
.
GetPolicyID
(),
VirtualPath
:
path
,
Name
:
name
,
Size
:
size
,
SavePath
:
savePath
,
},
callBackSessionTTL
,
)
...
...
pkg/serializer/upload.go
浏览文件 @
dd5f6e3d
...
...
@@ -30,6 +30,8 @@ type UploadSession struct {
PolicyID
uint
VirtualPath
string
Name
string
Size
uint64
SavePath
string
}
// UploadCallback 上传回调正文
...
...
routers/controllers/callback.go
浏览文件 @
dd5f6e3d
...
...
@@ -65,3 +65,14 @@ func UpyunCallback(c *gin.Context) {
c
.
JSON
(
200
,
ErrorResponse
(
err
))
}
}
// OneDriveCallback OneDrive上传完成客户端回调
func
OneDriveCallback
(
c
*
gin
.
Context
)
{
var
callbackBody
callback
.
OneDriveCallback
if
err
:=
c
.
ShouldBindJSON
(
&
callbackBody
);
err
==
nil
{
res
:=
callbackBody
.
PreProcess
(
c
)
c
.
JSON
(
200
,
res
)
}
else
{
c
.
JSON
(
200
,
ErrorResponse
(
err
))
}
}
routers/router.go
浏览文件 @
dd5f6e3d
...
...
@@ -150,6 +150,15 @@ func InitMasterRouter() *gin.Engine {
middleware
.
UpyunCallbackAuth
(),
controllers
.
UpyunCallback
,
)
onedrive
:=
callback
.
Group
(
"onedrive"
)
{
// 文件上传完成
onedrive
.
POST
(
"finish/:key"
,
middleware
.
OneDriveCallbackAuth
(),
controllers
.
OneDriveCallback
,
)
}
}
// 需要登录保护的
...
...
service/callback/upload.go
浏览文件 @
dd5f6e3d
...
...
@@ -2,13 +2,15 @@ package callback
import
(
"context"
model
"github.com/HFO4/cloudreve/models
"
"fmt
"
"github.com/HFO4/cloudreve/pkg/filesystem"
"github.com/HFO4/cloudreve/pkg/filesystem/driver/local"
"github.com/HFO4/cloudreve/pkg/filesystem/driver/onedrive"
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/gin-gonic/gin"
"strings"
)
// CallbackProcessService 上传请求回调正文接口
...
...
@@ -44,6 +46,12 @@ type UpyunCallbackService struct {
Size
uint64
`form:"file_size"`
}
// OneDriveCallback OneDrive 客户端回调正文
type
OneDriveCallback
struct
{
ID
string
`json:"id" binding:"required"`
Meta
*
onedrive
.
FileInfo
}
// GetBody 返回回调正文
func
(
service
UpyunCallbackService
)
GetBody
(
session
*
serializer
.
UploadSession
)
serializer
.
UploadCallback
{
res
:=
serializer
.
UploadCallback
{
...
...
@@ -68,37 +76,34 @@ func (service UploadCallbackService) GetBody(session *serializer.UploadSession)
}
}
// GetBody 返回回调正文
func
(
service
OneDriveCallback
)
GetBody
(
session
*
serializer
.
UploadSession
)
serializer
.
UploadCallback
{
var
picInfo
=
"0,0"
if
service
.
Meta
.
Image
.
Width
!=
0
{
picInfo
=
fmt
.
Sprintf
(
"%d,%d"
,
service
.
Meta
.
Image
.
Width
,
service
.
Meta
.
Image
.
Height
)
}
return
serializer
.
UploadCallback
{
Name
:
session
.
Name
,
SourceName
:
session
.
SavePath
,
PicInfo
:
picInfo
,
Size
:
session
.
Size
,
}
}
// ProcessCallback 处理上传结果回调
func
ProcessCallback
(
service
CallbackProcessService
,
c
*
gin
.
Context
)
serializer
.
Response
{
// 创建文件系统
fs
,
err
:=
filesystem
.
NewFileSystemFromC
ontext
(
c
)
fs
,
err
:=
filesystem
.
NewFileSystemFromC
allback
(
c
)
if
err
!=
nil
{
return
serializer
.
Err
(
serializer
.
CodePolicyNotAllowed
,
err
.
Error
(),
err
)
}
defer
fs
.
Recycle
()
// 获取回调会话
callbackSessionRaw
,
ok
:=
c
.
Get
(
"callbackSession"
)
if
!
ok
{
return
serializer
.
Err
(
serializer
.
CodeInternalSetting
,
"找不到回调会话"
,
nil
)
}
callbackSessionRaw
,
_
:=
c
.
Get
(
"callbackSession"
)
callbackSession
:=
callbackSessionRaw
.
(
*
serializer
.
UploadSession
)
// 获取回调正文
callbackBody
:=
service
.
GetBody
(
callbackSession
)
// 重新指向上传策略
policy
,
err
:=
model
.
GetPolicyByID
(
callbackSession
.
PolicyID
)
if
err
!=
nil
{
return
serializer
.
Err
(
serializer
.
CodePolicyNotAllowed
,
err
.
Error
(),
err
)
}
fs
.
Policy
=
&
policy
fs
.
User
.
Policy
=
policy
err
=
fs
.
DispatchHandler
()
if
err
!=
nil
{
return
serializer
.
Err
(
serializer
.
CodePolicyNotAllowed
,
err
.
Error
(),
err
)
}
// 获取父目录
exist
,
parentFolder
:=
fs
.
IsPathExist
(
callbackSession
.
VirtualPath
)
if
!
exist
{
...
...
@@ -140,3 +145,32 @@ func ProcessCallback(service CallbackProcessService, c *gin.Context) serializer.
Code
:
0
,
}
}
// PreProcess 对OneDrive客户端回调进行预处理验证
func
(
service
*
OneDriveCallback
)
PreProcess
(
c
*
gin
.
Context
)
serializer
.
Response
{
// 创建文件系统
fs
,
err
:=
filesystem
.
NewFileSystemFromCallback
(
c
)
if
err
!=
nil
{
return
serializer
.
Err
(
serializer
.
CodePolicyNotAllowed
,
err
.
Error
(),
err
)
}
defer
fs
.
Recycle
()
// 获取回调会话
callbackSessionRaw
,
_
:=
c
.
Get
(
"callbackSession"
)
callbackSession
:=
callbackSessionRaw
.
(
*
serializer
.
UploadSession
)
// 获取文件信息
info
,
err
:=
fs
.
Handler
.
(
onedrive
.
Driver
)
.
Client
.
Meta
(
context
.
Background
(),
service
.
ID
)
if
err
!=
nil
{
return
serializer
.
Err
(
serializer
.
CodeUploadFailed
,
"文件元信息查询失败"
,
err
)
}
// 验证与回调会话中是否一致
actualPath
:=
strings
.
TrimPrefix
(
callbackSession
.
SavePath
,
"/"
)
if
callbackSession
.
Size
!=
info
.
Size
||
info
.
GetSourcePath
()
!=
actualPath
{
// TODO 删除文件信息
return
serializer
.
Err
(
serializer
.
CodeUploadFailed
,
"文件信息不一致"
,
err
)
}
service
.
Meta
=
info
return
ProcessCallback
(
service
,
c
)
}
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录