Compare commits

...
53 Commits
Author SHA1 Message Date
chaunceyandGitHub 959e1f2576 fixed: webdav 上传接受图片时不生成缩略图的问题 (#779)
* fix:修复webdav上传图片时无缩略图问题

* 修改 Trim 为 TrimRight

* 1.增加图片生成缩略图时自动识别EXIF信息并存储到数据库

2.修改迁移配置、manage 接口返回文件增加 exif 信息字段

3.计划任务增加自动识别将经纬度转换为文本地址任务

* 变更assets

* fix:修复前端页面合并遗留bug

* 1. 将获取更新EXIF地址的文件列表函数更名为 GetEmptyLocationFilesByPage

2.页面API展示列表接口增加过滤默认空时间

* update assets
2021-03-10 22:44:29 -08:00
AaronLiuandGitHub e44ec0e6bf Update issue templates 2021-03-05 15:44:33 +08:00
HFO4 a93b964d8b Modify: OneDrive file URL cache will refreshed after file is updated 2021-03-03 17:07:26 +08:00
HFO4 d9cff24c75 Modify: disable association_autoupdate in model.File.UpdateSourceName 2021-03-03 14:10:08 +08:00
HFO4 e2488841b4 Test: #765 2021-03-02 12:45:54 +08:00
日下部 詩andGitHub a276be4098 注册帐号时,如果尚未验证,再发一次验证信 (#765)
* 注册帐号时,如果尚未验证,再发一次验证信

* 修正2个bug。 1:未验证显示密码错误 2:未验证无法重发email

* 小修正,如果已存在user,拿已有user资讯取代掉新user资讯来寄送激活码

* 激活码改成激活邮件

* 忘记密码以后,重设二步验证设定

* Revert "忘记密码以后,重设二步验证设定"

This reverts commit c5ac10b11c.

* 實作 https://github.com/cloudreve/Cloudreve/pull/765#discussion_r584313520
2021-03-02 12:43:14 +08:00
HFO4 4cf6c81534 Fix: failed unit test 2021-03-02 12:32:34 +08:00
HFO4 5a66af3105 Fix: failed unit test 2021-03-02 12:21:43 +08:00
HFO4 fc5c67cc20 Feat: disable overwrite for OneDrive policy 2021-03-01 13:27:18 +08:00
HFO4 5e226efea1 Feat: disable overwrite for non-updating put request, only works under local,slave,OneDrive,OSS policy. (#764) 2021-03-01 13:03:49 +08:00
HFO4 c949d47161 Update submodule version 2021-02-28 16:51:27 +08:00
HFO4 e699287ffd Modify: mark as success when deleting a file that does not exist;
Fix: minio is not usable in S3 policy
Modify: use batch request to delete S3 files
2021-02-28 16:48:51 +08:00
Cinhi YoungandGitHub 9c78515c72 Fix: email address should be lowercase for requesting Gravatar (#758) 2021-02-08 19:33:09 +08:00
HFO4 3b22b4fd25 Update version number 2021-01-06 18:18:24 +08:00
HFO4 08d998b41e Update submodule 2021-01-06 17:38:20 +08:00
Breeze ChenandGitHub 488e62f762 Fix qiniu last modify time. (#691) 2021-01-06 17:01:24 +08:00
HFO4 f35ad3fe0a Fix: #663 2021-01-06 16:35:31 +08:00
HFO4 61e6d9b591 Update version number 2020-12-10 17:26:39 +08:00
HFO4 feb1134a7c Update submodule 2020-12-10 17:05:06 +08:00
HFO4 9f2f14cacf Fix: https://github.com/cloudreve/Cloudreve/issues/504 2020-12-10 16:59:45 +08:00
HFO4 055ed0e075 Fix: standardize the use of error codes related to login credentials 2020-12-08 20:13:42 +08:00
HFO4 c87109c8b1 Fix: incorrect attr column type in download table 2020-12-08 19:55:23 +08:00
HFO4 8057c4b8bc Update: submodule 2020-12-08 18:53:20 +08:00
HFO4 5ab93a6e0d Test: frontend middleware 2020-12-08 18:15:02 +08:00
HFO4 5d406f1c6a Feat: use history router mode 2020-12-08 17:36:19 +08:00
HFO4 5b44606276 Test: replace cdn proxy url for OneDrive policy 2020-12-08 17:31:37 +08:00
HFO4 bd2bdf253b Feat: using custom reverse proxying in OneDrive file downloading 2020-12-08 17:30:22 +08:00
HFO4 0cfa61e264 Test: user storage calibration script 2020-12-06 16:50:08 +08:00
HFO4 f7c8039116 Feat: execute database script to calibrate user storage 2020-12-06 16:49:49 +08:00
HFO4 6486e8799b Fix: user storage might be returned twice when canceling uploading request (#645) 2020-12-03 18:10:10 +08:00
HFO4 7279be2924 Fix: user storage might be returned twice when OneDrive uploading canceled in WebDAV requests 2020-12-01 19:22:52 +08:00
HFO4 33f8419999 Merge remote-tracking branch 'origin/master' 2020-11-29 19:16:24 +08:00
HFO4 a5805b022a Feat: enable using LAN endpoint in serverside request of OSS policy (#399) 2020-11-29 19:15:35 +08:00
ae89b402f6 fix: statik data needs to be initialized (#640)
Co-authored-by: xuc2 <xuc2@knownsec.com>
2020-11-25 21:23:26 +08:00
HFO4 0d210e87b3 Fix: aria2 task failed due to limited size of attr filed in DB 2020-11-24 18:47:44 +08:00
HFO4 f0a68236a8 Feat: delete aria2 record in client side (#335) 2020-11-23 19:24:56 +08:00
HFO4 c6110e9e75 Feat: keep folder structure in aria2 transferring 2020-11-23 18:44:13 +08:00
HFO4 d97bc26042 Fix: add recycleLock preventing recycle FileSystem used by another goroutine 2020-11-21 19:32:25 +08:00
HFO4 11c218eb94 Merge branch 'master' of https://github.com/cloudreve/Cloudreve 2020-11-21 18:19:24 +08:00
LoyalsoldierandGitHub 79b8784934 Comply with Golang semantic import versioning (#630)
* Code: compatible with semantic import versioning

* Tools & Docs: compatible with semantic import versioning

* Clean go.mod & go.sum
2020-11-21 17:34:55 +08:00
HFO4 59d50b1b98 Modify: change Unix to UnixSocket in config section 2020-10-26 15:42:18 +08:00
HFO4 746aa3e8ef Test: s3 policy 2020-10-26 15:33:28 +08:00
HFO4 95f318e069 Feat: adapt minio for S3 policy and fix listing files 2020-10-26 15:06:02 +08:00
HFO4 77394313aa Fix: S3 adaption for minio 2020-10-11 13:05:14 +08:00
HFO4 41eb84a221 Update submodule version 2020-10-10 13:38:39 +08:00
mritdandGitHub 40414fe6ae chore(dockerfile): update node image to lts-buster (#557)
* chore(dockerfile): update node image to lts-buster

update node image to lts-buster, because the alpine image cannot be obtained on arm/arm64,
it will support `docker buildx` build after the upgrade.

Signed-off-by: mritd <mritd@linux.com>

* chore(docker): update golang build image

update golang build image

Signed-off-by: mritd <mritd@linux.com>
2020-09-09 14:56:14 +08:00
mritdandGitHub 7df09537e0 fix(db_driver): fix the panic when sqlite3 is used in the conf (#551)
* fix(db_driver): fix the panic when sqlite3 is used in the conf

fix the panic when sqlite3 is used in the conf

ref cloudreve/Cloudreve#550

Signed-off-by: mritd <mritd@linux.com>

* fix(nullpointer): fix possible null pointer error

fix possible null pointer error

Signed-off-by: mritd <mritd@linux.com>
2020-09-03 12:07:38 +08:00
mritdandGitHub f478c38307 chore(docker): add dockerfile (#549)
* chore(docker): add dockerfile

add dockerfile

Signed-off-by: mritd <mritd@linux.com>

* chore(docker): fix docker file

fix docker file

Signed-off-by: mritd <mritd@linux.com>

* chore(docker): mv bin file to /cloudreve

mv bin file to /cloudreve

Signed-off-by: mritd <mritd@linux.com>

* chore(docker): remove GOPROXY

remove GOPROXY

Signed-off-by: mritd <mritd@linux.com>
2020-09-03 12:05:00 +08:00
GuerraMorganandGitHub bfd2340732 Add: Unix Socket support (#466)
* Update conf.go

* Update driver.go

* Update session.go

* Update defaults.go

* Update main.go

* Update conf.go

* Update defaults.go
2020-08-12 20:31:28 +08:00
ZZFandGitHub dd50ef1c25 添加S3策略的支持 (#425)
* 添加亚马逊S3策略的支持

* 添加CDN支持,公有目录删除无用参数

* 增加Region
2020-06-05 14:45:24 +08:00
HFO4 27bf8ca9b2 Fix: node js version 2020-06-01 20:27:06 +08:00
HFO4 c71a2c5b64 Modify: version number to 3.1.1 2020-06-01 20:22:14 +08:00
HFO4 a7ba357cb8 Fix: hash mark in file name lead to 404 error (#405) 2020-05-30 19:35:28 +08:00
195 changed files with 2764 additions and 1029 deletions
+38
View File
@@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
+20
View File
@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
+1 -2
View File
@@ -1,8 +1,7 @@
language: go
go:
- 1.13.x
node_js:
- 12.16.3
node_js: "12.16.3"
git:
depth: 1
install:
+65
View File
@@ -0,0 +1,65 @@
# build frontend
FROM node:lts-buster AS fe-builder
COPY ./assets /assets
WORKDIR /assets
# yarn repo connection is unstable, adjust the network timeout to 10 min.
RUN set -ex \
&& yarn install --network-timeout 600000 \
&& yarn run build
# build backend
FROM golang:1.15.1-alpine3.12 AS be-builder
ENV GO111MODULE on
COPY . /go/src/github.com/cloudreve/Cloudreve/v3
COPY --from=fe-builder /assets/build/ /go/src/github.com/cloudreve/Cloudreve/v3/assets/build/
WORKDIR /go/src/github.com/cloudreve/Cloudreve/v3
RUN set -ex \
&& apk upgrade \
&& apk add gcc libc-dev git \
&& export COMMIT_SHA=$(git rev-parse --short HEAD) \
&& export VERSION=$(git describe --tags) \
&& (cd && go get github.com/rakyll/statik) \
&& statik -src=assets/build/ -include=*.html,*.js,*.json,*.css,*.png,*.svg,*.ico -f \
&& go install -ldflags "-X 'github.com/cloudreve/Cloudreve/v3/pkg/conf.BackendVersion=${VERSION}' \
-X 'github.com/cloudreve/Cloudreve/v3/pkg/conf.LastCommit=${COMMIT_SHA}'\
-w -s"
# build final image
FROM alpine:3.12 AS dist
LABEL maintainer="mritd <mritd@linux.com>"
# we use the Asia/Shanghai timezone by default, you can be modified
# by `docker build --build-arg=TZ=Other_Timezone ...`
ARG TZ="Asia/Shanghai"
ENV TZ ${TZ}
COPY --from=be-builder /go/bin/cloudreve /cloudreve/cloudreve
RUN apk upgrade \
&& apk add bash tzdata \
&& ln -s /cloudreve/cloudreve /usr/bin/cloudreve \
&& ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime \
&& echo ${TZ} > /etc/timezone \
&& rm -rf /var/cache/apk/*
# cloudreve use tcp 5212 port by default
EXPOSE 5212/tcp
# cloudreve stores all files(including executable file) in the `/cloudreve`
# directory by default; users should mount the configfile to the `/etc/cloudreve`
# directory by themselves for persistence considerations, and the data storage
# directory recommends using `/data` directory.
VOLUME /etc/cloudreve
VOLUME /data
ENTRYPOINT ["cloudreve"]
+1 -1
View File
@@ -108,7 +108,7 @@ export COMMIT_SHA=$(git rev-parse --short HEAD)
export VERSION=$(git describe --tags)
# 开始编译
go build -a -o cloudreve -ldflags " -X 'github.com/HFO4/cloudreve/pkg/conf.BackendVersion=$VERSION' -X 'github.com/HFO4/cloudreve/pkg/conf.LastCommit=$COMMIT_SHA'"
go build -a -o cloudreve -ldflags " -X 'github.com/cloudreve/Cloudreve/v3/pkg/conf.BackendVersion=$VERSION' -X 'github.com/cloudreve/Cloudreve/v3/pkg/conf.LastCommit=$COMMIT_SHA'"
```
你也可以使用项目根目录下的`build.sh`快速开始构建:
+1 -1
Submodule assets updated: 54f4e608bd...1c827ee20a
+4 -3
View File
@@ -3,9 +3,10 @@ package bootstrap
import (
"encoding/json"
"fmt"
"github.com/HFO4/cloudreve/pkg/conf"
"github.com/HFO4/cloudreve/pkg/request"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/hashicorp/go-version"
)
+8 -8
View File
@@ -1,14 +1,14 @@
package bootstrap
import (
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/aria2"
"github.com/HFO4/cloudreve/pkg/auth"
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/HFO4/cloudreve/pkg/conf"
"github.com/HFO4/cloudreve/pkg/crontab"
"github.com/HFO4/cloudreve/pkg/email"
"github.com/HFO4/cloudreve/pkg/task"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/aria2"
"github.com/cloudreve/Cloudreve/v3/pkg/auth"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
"github.com/cloudreve/Cloudreve/v3/pkg/crontab"
"github.com/cloudreve/Cloudreve/v3/pkg/email"
"github.com/cloudreve/Cloudreve/v3/pkg/task"
"github.com/gin-gonic/gin"
)
+18
View File
@@ -0,0 +1,18 @@
package bootstrap
import (
"context"
"github.com/cloudreve/Cloudreve/v3/models/scripts"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
)
func RunScript(name string) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
if err := scripts.RunDBScript(name, ctx); err != nil {
util.Log().Error("数据库脚本执行失败: %s", err)
return
}
util.Log().Info("数据库脚本 [%s] 执行完毕", name)
}
+6 -5
View File
@@ -2,15 +2,16 @@ package bootstrap
import (
"encoding/json"
"github.com/HFO4/cloudreve/pkg/conf"
"github.com/HFO4/cloudreve/pkg/util"
_ "github.com/HFO4/cloudreve/statik"
"github.com/gin-contrib/static"
"github.com/rakyll/statik/fs"
"io"
"io/ioutil"
"net/http"
"path"
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
_ "github.com/cloudreve/Cloudreve/v3/statik"
"github.com/gin-contrib/static"
"github.com/rakyll/statik/fs"
)
const StaticFolder = "statics"
+3 -3
View File
@@ -39,7 +39,7 @@ buildAssets () {
buildBinary () {
cd $REPO
go build -a -o cloudreve -ldflags " -X 'github.com/HFO4/cloudreve/pkg/conf.BackendVersion=$VERSION' -X 'github.com/HFO4/cloudreve/pkg/conf.LastCommit=$COMMIT_SHA'"
go build -a -o cloudreve -ldflags " -X 'github.com/cloudreve/Cloudreve/v3/pkg/conf.BackendVersion=$VERSION' -X 'github.com/cloudreve/Cloudreve/v3/pkg/conf.LastCommit=$COMMIT_SHA'"
}
_build() {
@@ -61,7 +61,7 @@ _build() {
out="release/cloudreve_${COMMIT_SHA}_${os}_${arch}"
fi
go build -a -o "${out}" -ldflags " -X 'github.com/HFO4/cloudreve/pkg/conf.BackendVersion=$VERSION' -X 'github.com/HFO4/cloudreve/pkg/conf.LastCommit=$COMMIT_SHA'"
go build -a -o "${out}" -ldflags " -X 'github.com/cloudreve/Cloudreve/v3/pkg/conf.BackendVersion=$VERSION' -X 'github.com/cloudreve/Cloudreve/v3/pkg/conf.LastCommit=$COMMIT_SHA'"
if [ "$os" = "windows" ]; then
mv $out release/cloudreve.exe
@@ -130,4 +130,4 @@ fi
if [ "$RELEASE" = "true" ]; then
release
fi
fi
+5 -6
View File
@@ -1,10 +1,11 @@
module github.com/HFO4/cloudreve
module github.com/cloudreve/Cloudreve/v3
go 1.13
require (
github.com/DATA-DOG/go-sqlmock v1.3.3
github.com/aliyun/aliyun-oss-go-sdk v2.0.5+incompatible
github.com/aws/aws-sdk-go v1.31.5
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
github.com/duo-labs/webauthn v0.0.0-20191119193225-4bf9a0f776d4
github.com/fatih/color v1.7.0
@@ -15,7 +16,6 @@ require (
github.com/gin-gonic/gin v1.5.0
github.com/go-ini/ini v1.50.0
github.com/go-mail/mail v2.3.1+incompatible
github.com/gofrs/uuid v3.2.0+incompatible
github.com/gomodule/redigo v2.0.0+incompatible
github.com/google/go-querystring v1.0.0
github.com/gorilla/websocket v1.4.1
@@ -25,17 +25,16 @@ require (
github.com/mattn/go-colorable v0.1.4 // indirect
github.com/mojocn/base64Captcha v0.0.0-20190801020520-752b1cd608b2
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
github.com/pkg/errors v0.8.0
github.com/pkg/errors v0.9.1
github.com/pquerna/otp v1.2.0
github.com/qingwg/payjs v0.0.0-20190928033402-c53dbe16b371
github.com/qiniu/api.v7/v7 v7.4.0
github.com/rafaeljusto/redigomock v0.0.0-20191117212112-00b2509252a1
github.com/rakyll/statik v0.1.7
github.com/robfig/cron/v3 v3.0.1
github.com/smartwalle/alipay/v3 v3.0.13
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/speps/go-hashids v2.0.0+incompatible
github.com/stretchr/testify v1.4.0
github.com/stretchr/testify v1.5.1
github.com/tencentcloud/tencentcloud-sdk-go v3.0.125+incompatible
github.com/tencentyun/cos-go-sdk-v5 v0.0.0-20200120023323-87ff3bc489ac
github.com/upyun/go-sdk v2.1.0+incompatible
+14 -20
View File
@@ -15,6 +15,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
github.com/aliyun/aliyun-oss-go-sdk v2.0.5+incompatible h1:A3oZlWPD/Poa19FvNbw+Zu4yKAurDBTjlRDilYGBiS4=
github.com/aliyun/aliyun-oss-go-sdk v2.0.5+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/aws/aws-sdk-go v1.31.5 h1:DFA7BzTydO4etqsTja+x7UfkOKQUv1xzEluLvNk81L0=
github.com/aws/aws-sdk-go v1.31.5/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -52,13 +54,11 @@ github.com/gin-contrib/gzip v0.0.2-0.20200226035851-25bef2ef21e8 h1:/DnKeA2+K83h
github.com/gin-contrib/gzip v0.0.2-0.20200226035851-25bef2ef21e8/go.mod h1:M+xPw/lXk+uAU4iYVnwPZs0iIpR/KwSQSXcJabN+gPs=
github.com/gin-contrib/sessions v0.0.1 h1:xr9V/u3ERQnkugKSY/u36cNnC4US4bHJpdxcB6eIZLk=
github.com/gin-contrib/sessions v0.0.1/go.mod h1:iziXm/6pvTtf7og1uxT499sel4h3S9DfwsrhNZ+REXM=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-contrib/static v0.0.0-20191128031702-f81c604d8ac2 h1:xLG16iua01X7Gzms9045s2Y2niNpvSY/Zb1oBwgNYZY=
github.com/gin-contrib/static v0.0.0-20191128031702-f81c604d8ac2/go.mod h1:VhW/Ch/3FhimwZb8Oj+qJmdMmoB8r7lmJ5auRjm50oQ=
github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ=
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
github.com/gin-gonic/gin v1.5.0 h1:fi+bqFAx/oLK54somfCtEZs9HeH1LHVoEPUgARpTqyc=
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
@@ -73,11 +73,10 @@ github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotf
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM=
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
@@ -86,7 +85,6 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -128,6 +126,8 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -156,7 +156,6 @@ github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
@@ -184,8 +183,9 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok=
@@ -197,8 +197,6 @@ github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/qingwg/payjs v0.0.0-20190928033402-c53dbe16b371 h1:8VWtyY2IwjEQZSNT4Kyyct9zv9hoegD5GQhFr+TMdCI=
github.com/qingwg/payjs v0.0.0-20190928033402-c53dbe16b371/go.mod h1:9UFrQveqNm3ELF6HSvMtDR3KYpJ7Ib9s0WVmYhaUBlU=
github.com/qiniu/api.v7/v7 v7.4.0 h1:9dZMVQifh31QGFLVaHls6akCaS2rlj3du8MnEFd7XjQ=
github.com/qiniu/api.v7/v7 v7.4.0/go.mod h1:VE5oC5rkE1xul0u1S2N0b2Uxq9/6hZzhyqjgK25XDcM=
github.com/quasoft/memstore v0.0.0-20180925164028-84a050167438 h1:jnz/4VenymvySjE+Ez511s0pqVzkUOmr1fwCVytNNWk=
@@ -210,13 +208,11 @@ github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Ung
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartwalle/alipay/v3 v3.0.13 h1:f1Cdnxh6TfbaziLw0i/4h+f8tw9RJwG8y4xye7vTTgY=
github.com/smartwalle/alipay/v3 v3.0.13/go.mod h1:cZUMCCnsux9YAxA0/f3PWUR+7wckWtE1BqxbVRtGij0=
github.com/smartwalle/crypto4go v1.0.2 h1:9DUEOOsPhmp00438L4oBdcL8EZG1zumecft5bWj5phI=
github.com/smartwalle/crypto4go v1.0.2/go.mod h1:LQ7vCZIb7BE5+MuMtJBuO8ORkkQ01m4DXDBWPzLbkMY=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
@@ -229,8 +225,9 @@ github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/tencentcloud/tencentcloud-sdk-go v3.0.125+incompatible h1:dqpmYaez7VBT7PCRBcBxkzlDOiTk7Td8ATiia1b1GuE=
github.com/tencentcloud/tencentcloud-sdk-go v3.0.125+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4=
github.com/tencentyun/cos-go-sdk-v5 v0.0.0-20200120023323-87ff3bc489ac h1:PSBhZblOjdwH7SIVgcue+7OlnLHkM45KuScLZ+PiVbQ=
@@ -246,7 +243,6 @@ go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -266,8 +262,9 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -284,7 +281,6 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e h1:D5TXcfTk7xF7hvieo4QErS3qqCB4teTffacDWr7CI+0=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -303,7 +299,6 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -319,7 +314,6 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc=
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
+25 -6
View File
@@ -2,20 +2,23 @@ package main
import (
"flag"
"github.com/HFO4/cloudreve/bootstrap"
"github.com/HFO4/cloudreve/pkg/conf"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/HFO4/cloudreve/routers"
"github.com/cloudreve/Cloudreve/v3/bootstrap"
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/cloudreve/Cloudreve/v3/routers"
)
var (
isEject bool
confPath string
isEject bool
confPath string
scriptName string
)
func init() {
flag.StringVar(&confPath, "c", util.RelativePath("conf.ini"), "配置文件路径")
flag.BoolVar(&isEject, "eject", false, "导出内置静态资源")
flag.StringVar(&scriptName, "database-script", "", "运行内置数据库助手脚本")
flag.Parse()
bootstrap.Init(confPath)
}
@@ -27,6 +30,12 @@ func main() {
return
}
if scriptName != "" {
// 开始运行助手数据库脚本
bootstrap.RunScript(scriptName)
return
}
api := routers.InitRouter()
// 如果启用了SSL
@@ -40,6 +49,16 @@ func main() {
}()
}
// 如果启用了Unix
if conf.UnixConfig.Listen != "" {
go func() {
util.Log().Info("开始监听 %s", conf.UnixConfig.Listen)
if err := api.RunUnix(conf.UnixConfig.Listen); err != nil {
util.Log().Error("无法监听[%s]%s", conf.UnixConfig.Listen, err)
}
}()
}
util.Log().Info("开始监听 %s", conf.SystemConfig.Listen)
if err := api.Run(conf.SystemConfig.Listen); err != nil {
util.Log().Error("无法监听[%s]%s", conf.SystemConfig.Listen, err)
+39 -23
View File
@@ -5,19 +5,20 @@ import (
"context"
"crypto/md5"
"fmt"
"github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/auth"
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/HFO4/cloudreve/pkg/filesystem/driver/onedrive"
"github.com/HFO4/cloudreve/pkg/filesystem/driver/oss"
"github.com/HFO4/cloudreve/pkg/filesystem/driver/upyun"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/HFO4/cloudreve/pkg/util"
"io/ioutil"
"net/http"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/auth"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver/onedrive"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver/oss"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver/upyun"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"github.com/qiniu/api.v7/v7/auth/qbox"
"io/ioutil"
"net/http"
)
// SignRequired 验证请求签名
@@ -34,7 +35,7 @@ func SignRequired() gin.HandlerFunc {
}
if err != nil {
c.JSON(200, serializer.Err(serializer.CodeCheckLogin, err.Error(), err))
c.JSON(200, serializer.Err(serializer.CodeCredentialInvalid, err.Error(), err))
c.Abort()
return
}
@@ -89,7 +90,7 @@ func WebDAVAuth() gin.HandlerFunc {
return
}
expectedUser, err := model.GetUserByEmail(username)
expectedUser, err := model.GetActiveUserByEmail(username)
if err != nil {
c.Status(http.StatusUnauthorized)
c.Abort()
@@ -174,7 +175,7 @@ func QiniuCallbackAuth() gin.HandlerFunc {
// 验证key并查找用户
resp, user := uploadCallbackCheck(c)
if resp.Code != 0 {
c.JSON(401, serializer.QiniuCallbackFailed{Error: resp.Msg})
c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: resp.Msg})
c.Abort()
return
}
@@ -184,12 +185,12 @@ func QiniuCallbackAuth() gin.HandlerFunc {
ok, err := mac.VerifyCallback(c.Request)
if err != nil {
util.Log().Debug("无法验证回调请求,%s", err)
c.JSON(401, serializer.QiniuCallbackFailed{Error: "无法验证回调请求"})
c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: "无法验证回调请求"})
c.Abort()
return
}
if !ok {
c.JSON(401, serializer.QiniuCallbackFailed{Error: "回调签名无效"})
c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: "回调签名无效"})
c.Abort()
return
}
@@ -204,7 +205,7 @@ func OSSCallbackAuth() gin.HandlerFunc {
// 验证key并查找用户
resp, _ := uploadCallbackCheck(c)
if resp.Code != 0 {
c.JSON(401, serializer.QiniuCallbackFailed{Error: resp.Msg})
c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: resp.Msg})
c.Abort()
return
}
@@ -212,7 +213,7 @@ func OSSCallbackAuth() gin.HandlerFunc {
err := oss.VerifyCallbackSignature(c.Request)
if err != nil {
util.Log().Debug("回调签名验证失败,%s", err)
c.JSON(401, serializer.QiniuCallbackFailed{Error: "回调签名验证失败"})
c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: "回调签名验证失败"})
c.Abort()
return
}
@@ -227,7 +228,7 @@ func UpyunCallbackAuth() gin.HandlerFunc {
// 验证key并查找用户
resp, user := uploadCallbackCheck(c)
if resp.Code != 0 {
c.JSON(401, serializer.QiniuCallbackFailed{Error: resp.Msg})
c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: resp.Msg})
c.Abort()
return
}
@@ -236,7 +237,7 @@ func UpyunCallbackAuth() gin.HandlerFunc {
body, err := ioutil.ReadAll(c.Request.Body)
c.Request.Body.Close()
if err != nil {
c.JSON(401, serializer.QiniuCallbackFailed{Error: err.Error()})
c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: err.Error()})
c.Abort()
return
}
@@ -252,7 +253,7 @@ func UpyunCallbackAuth() gin.HandlerFunc {
// 计算正文MD5
actualContentMD5 := fmt.Sprintf("%x", md5.Sum(body))
if actualContentMD5 != contentMD5 {
c.JSON(401, serializer.QiniuCallbackFailed{Error: "MD5不一致"})
c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: "MD5不一致"})
c.Abort()
return
}
@@ -267,7 +268,7 @@ func UpyunCallbackAuth() gin.HandlerFunc {
// 对比签名
if signature != actualSignature {
c.JSON(401, serializer.QiniuCallbackFailed{Error: "鉴权失败"})
c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: "鉴权失败"})
c.Abort()
return
}
@@ -283,7 +284,7 @@ func OneDriveCallbackAuth() gin.HandlerFunc {
// 验证key并查找用户
resp, _ := uploadCallbackCheck(c)
if resp.Code != 0 {
c.JSON(401, serializer.QiniuCallbackFailed{Error: resp.Msg})
c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: resp.Msg})
c.Abort()
return
}
@@ -302,7 +303,22 @@ func COSCallbackAuth() gin.HandlerFunc {
// 验证key并查找用户
resp, _ := uploadCallbackCheck(c)
if resp.Code != 0 {
c.JSON(401, serializer.QiniuCallbackFailed{Error: resp.Msg})
c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: resp.Msg})
c.Abort()
return
}
c.Next()
}
}
// S3CallbackAuth Amazon S3回调签名验证
func S3CallbackAuth() gin.HandlerFunc {
return func(c *gin.Context) {
// 验证key并查找用户
resp, _ := uploadCallbackCheck(c)
if resp.Code != 0 {
c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: resp.Msg})
c.Abort()
return
}
+55 -10
View File
@@ -3,21 +3,22 @@ package middleware
import (
"database/sql"
"errors"
"github.com/DATA-DOG/go-sqlmock"
"github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/auth"
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
"github.com/qiniu/api.v7/v7/auth/qbox"
"github.com/stretchr/testify/assert"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/DATA-DOG/go-sqlmock"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/auth"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
"github.com/qiniu/api.v7/v7/auth/qbox"
"github.com/stretchr/testify/assert"
)
var mock sqlmock.Sqlmock
@@ -747,3 +748,47 @@ func TestIsAdmin(t *testing.T) {
asserts.False(c.IsAborted())
}
}
func TestS3CallbackAuth(t *testing.T) {
asserts := assert.New(t)
rec := httptest.NewRecorder()
AuthFunc := S3CallbackAuth()
// Callback Key 相关验证失败
{
c, _ := gin.CreateTestContext(rec)
c.Params = []gin.Param{
{"key", "testUpyunBackRemote"},
}
c.Request, _ = http.NewRequest("POST", "/api/v3/callback/upyun/testUpyunBackRemote", nil)
AuthFunc(c)
asserts.True(c.IsAborted())
}
// 成功
{
cache.Set(
"callback_testCallBackUpyun",
serializer.UploadSession{
UID: 1,
PolicyID: 512,
VirtualPath: "/",
},
0,
)
cache.Deletes([]string{"1"}, "policy_")
mock.ExpectQuery("SELECT(.+)users(.+)").
WillReturnRows(sqlmock.NewRows([]string{"id", "group_id"}).AddRow(1, 1))
mock.ExpectQuery("SELECT(.+)groups(.+)").
WillReturnRows(sqlmock.NewRows([]string{"id", "policies"}).AddRow(1, "[702]"))
mock.ExpectQuery("SELECT(.+)policies(.+)").
WillReturnRows(sqlmock.NewRows([]string{"id", "access_key", "secret_key"}).AddRow(2, "123", "123"))
c, _ := gin.CreateTestContext(rec)
c.Params = []gin.Param{
{"key", "testCallBackUpyun"},
}
c.Request, _ = http.NewRequest("POST", "/api/v3/callback/upyun/testCallBackUpyun", ioutil.NopCloser(strings.NewReader("1")))
AuthFunc(c)
asserts.False(c.IsAborted())
}
}
+69
View File
@@ -0,0 +1,69 @@
package middleware
import (
"github.com/cloudreve/Cloudreve/v3/bootstrap"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gin-gonic/gin"
"io/ioutil"
"net/http"
"strings"
)
// FrontendFileHandler 前端静态文件处理
func FrontendFileHandler() gin.HandlerFunc {
ignoreFunc := func(c *gin.Context) {
c.Next()
}
if bootstrap.StaticFS == nil {
return ignoreFunc
}
// 读取index.html
file, err := bootstrap.StaticFS.Open("/index.html")
if err != nil {
util.Log().Warning("静态文件[index.html]不存在,可能会影响首页展示")
return ignoreFunc
}
fileContentBytes, err := ioutil.ReadAll(file)
if err != nil {
util.Log().Warning("静态文件[index.html]读取失败,可能会影响首页展示")
return ignoreFunc
}
fileContent := string(fileContentBytes)
fileServer := http.FileServer(bootstrap.StaticFS)
return func(c *gin.Context) {
path := c.Request.URL.Path
// API 跳过
if strings.HasPrefix(path, "/api") || strings.HasPrefix(path, "/custom") || strings.HasPrefix(path, "/dav") || path == "/manifest.json" {
c.Next()
return
}
// 不存在的路径和index.html均返回index.html
if (path == "/index.html") || (path == "/") || !bootstrap.StaticFS.Exists("/", path) {
// 读取、替换站点设置
options := model.GetSettingByNames("siteName", "siteKeywords", "siteScript",
"pwa_small_icon")
finalHTML := util.Replace(map[string]string{
"{siteName}": options["siteName"],
"{siteDes}": options["siteDes"],
"{siteScript}": options["siteScript"],
"{pwa_small_icon}": options["pwa_small_icon"],
}, fileContent)
c.Header("Content-Type", "text/html")
c.String(200, finalHTML)
c.Abort()
return
}
// 存在的静态文件
fileServer.ServeHTTP(c.Writer, c.Request)
c.Abort()
}
}
+144
View File
@@ -0,0 +1,144 @@
package middleware
import (
"errors"
"github.com/cloudreve/Cloudreve/v3/bootstrap"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
testMock "github.com/stretchr/testify/mock"
"net/http"
"net/http/httptest"
"os"
"testing"
)
type StaticMock struct {
testMock.Mock
}
func (m StaticMock) Open(name string) (http.File, error) {
args := m.Called(name)
return args.Get(0).(http.File), args.Error(1)
}
func (m StaticMock) Exists(prefix string, filepath string) bool {
args := m.Called(prefix, filepath)
return args.Bool(0)
}
func TestFrontendFileHandler(t *testing.T) {
asserts := assert.New(t)
rec := httptest.NewRecorder()
// 静态资源未加载
{
TestFunc := FrontendFileHandler()
c, _ := gin.CreateTestContext(rec)
c.Params = []gin.Param{}
c.Request, _ = http.NewRequest("GET", "/", nil)
TestFunc(c)
asserts.False(c.IsAborted())
}
// index.html 不存在
{
testStatic := &StaticMock{}
bootstrap.StaticFS = testStatic
testStatic.On("Open", "/index.html").
Return(&os.File{}, errors.New("error"))
TestFunc := FrontendFileHandler()
c, _ := gin.CreateTestContext(rec)
c.Params = []gin.Param{}
c.Request, _ = http.NewRequest("GET", "/", nil)
TestFunc(c)
asserts.False(c.IsAborted())
}
// index.html 读取失败
{
file, _ := util.CreatNestedFile("tests/index.html")
file.Close()
testStatic := &StaticMock{}
bootstrap.StaticFS = testStatic
testStatic.On("Open", "/index.html").
Return(file, nil)
TestFunc := FrontendFileHandler()
c, _ := gin.CreateTestContext(rec)
c.Params = []gin.Param{}
c.Request, _ = http.NewRequest("GET", "/", nil)
TestFunc(c)
asserts.False(c.IsAborted())
}
// 成功且命中
{
file, _ := util.CreatNestedFile("tests/index.html")
defer file.Close()
testStatic := &StaticMock{}
bootstrap.StaticFS = testStatic
testStatic.On("Open", "/index.html").
Return(file, nil)
TestFunc := FrontendFileHandler()
c, _ := gin.CreateTestContext(rec)
c.Params = []gin.Param{}
c.Request, _ = http.NewRequest("GET", "/", nil)
cache.Set("setting_siteName", "cloudreve", 0)
cache.Set("setting_siteKeywords", "cloudreve", 0)
cache.Set("setting_siteScript", "cloudreve", 0)
cache.Set("setting_pwa_small_icon", "cloudreve", 0)
TestFunc(c)
asserts.True(c.IsAborted())
}
// 成功且命中静态文件
{
file, _ := util.CreatNestedFile("tests/index.html")
defer file.Close()
testStatic := &StaticMock{}
bootstrap.StaticFS = testStatic
testStatic.On("Open", "/index.html").
Return(file, nil)
testStatic.On("Exists", "/", "/2").
Return(true)
testStatic.On("Open", "/2").
Return(file, nil)
TestFunc := FrontendFileHandler()
c, _ := gin.CreateTestContext(rec)
c.Params = []gin.Param{}
c.Request, _ = http.NewRequest("GET", "/2", nil)
TestFunc(c)
asserts.True(c.IsAborted())
testStatic.AssertExpectations(t)
}
// API 相关跳过
{
for _, reqPath := range []string{"/api/user", "/manifest.json", "/dav/path"} {
file, _ := util.CreatNestedFile("tests/index.html")
defer file.Close()
testStatic := &StaticMock{}
bootstrap.StaticFS = testStatic
testStatic.On("Open", "/index.html").
Return(file, nil)
TestFunc := FrontendFileHandler()
c, _ := gin.CreateTestContext(rec)
c.Params = []gin.Param{}
c.Request, _ = http.NewRequest("GET", reqPath, nil)
TestFunc(c)
asserts.False(c.IsAborted())
}
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
package middleware
import (
"github.com/HFO4/cloudreve/pkg/util"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gin-gonic/gin"
)
+4 -3
View File
@@ -1,12 +1,13 @@
package middleware
import (
"github.com/HFO4/cloudreve/pkg/util"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"testing"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)
func TestMockHelper(t *testing.T) {
+3 -50
View File
@@ -1,13 +1,10 @@
package middleware
import (
"github.com/HFO4/cloudreve/bootstrap"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/hashid"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/HFO4/cloudreve/pkg/util"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/hashid"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/gin-gonic/gin"
"io/ioutil"
)
// HashID 将给定对象的HashID转换为真实ID
@@ -41,47 +38,3 @@ func IsFunctionEnabled(key string) gin.HandlerFunc {
c.Next()
}
}
// InjectSiteInfo 向首页html中插入站点信息
func InjectSiteInfo() gin.HandlerFunc {
ignoreFunc := func(c *gin.Context) {
c.Next()
}
if bootstrap.StaticFS == nil {
return ignoreFunc
}
// 读取index.html
file, err := bootstrap.StaticFS.Open("/index.html")
if err != nil {
util.Log().Warning("静态文件[index.html]不存在,可能会影响首页展示")
return ignoreFunc
}
fileContentBytes, err := ioutil.ReadAll(file)
if err != nil {
util.Log().Warning("静态文件[index.html]读取失败,可能会影响首页展示")
return ignoreFunc
}
fileContent := string(fileContentBytes)
return func(c *gin.Context) {
if c.Request.URL.Path == "/" || c.Request.URL.Path == "/index.html" {
// 读取、替换站点设置
options := model.GetSettingByNames("siteName", "siteKeywords", "siteScript",
"pwa_small_icon")
finalHTML := util.Replace(map[string]string{
"{siteName}": options["siteName"],
"{siteDes}": options["siteDes"],
"{siteScript}": options["siteScript"],
"{pwa_small_icon}": options["pwa_small_icon"],
}, fileContent)
c.Header("Content-Type", "text/html")
c.String(200, finalHTML)
c.Abort()
return
}
c.Next()
}
}
+5 -113
View File
@@ -1,18 +1,14 @@
package middleware
import (
"errors"
"github.com/HFO4/cloudreve/bootstrap"
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/HFO4/cloudreve/pkg/hashid"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
testMock "github.com/stretchr/testify/mock"
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/hashid"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)
func TestHashID(t *testing.T) {
@@ -80,107 +76,3 @@ func TestIsFunctionEnabled(t *testing.T) {
}
}
type StaticMock struct {
testMock.Mock
}
func (m StaticMock) Open(name string) (http.File, error) {
args := m.Called(name)
return args.Get(0).(http.File), args.Error(1)
}
func (m StaticMock) Exists(prefix string, filepath string) bool {
args := m.Called(prefix, filepath)
return args.Bool(0)
}
func TestInjectSiteInfo(t *testing.T) {
asserts := assert.New(t)
rec := httptest.NewRecorder()
// 静态资源未加载
{
TestFunc := InjectSiteInfo()
c, _ := gin.CreateTestContext(rec)
c.Params = []gin.Param{}
c.Request, _ = http.NewRequest("GET", "/", nil)
TestFunc(c)
asserts.False(c.IsAborted())
}
// index.html 不存在
{
testStatic := &StaticMock{}
bootstrap.StaticFS = testStatic
testStatic.On("Open", "/index.html").
Return(&os.File{}, errors.New("error"))
TestFunc := InjectSiteInfo()
c, _ := gin.CreateTestContext(rec)
c.Params = []gin.Param{}
c.Request, _ = http.NewRequest("GET", "/", nil)
TestFunc(c)
asserts.False(c.IsAborted())
}
// index.html 读取失败
{
file, _ := util.CreatNestedFile("tests/index.html")
file.Close()
testStatic := &StaticMock{}
bootstrap.StaticFS = testStatic
testStatic.On("Open", "/index.html").
Return(file, nil)
TestFunc := InjectSiteInfo()
c, _ := gin.CreateTestContext(rec)
c.Params = []gin.Param{}
c.Request, _ = http.NewRequest("GET", "/", nil)
TestFunc(c)
asserts.False(c.IsAborted())
}
// 成功且命中
{
file, _ := util.CreatNestedFile("tests/index.html")
defer file.Close()
testStatic := &StaticMock{}
bootstrap.StaticFS = testStatic
testStatic.On("Open", "/index.html").
Return(file, nil)
TestFunc := InjectSiteInfo()
c, _ := gin.CreateTestContext(rec)
c.Params = []gin.Param{}
c.Request, _ = http.NewRequest("GET", "/", nil)
cache.Set("setting_siteName", "cloudreve", 0)
cache.Set("setting_siteKeywords", "cloudreve", 0)
cache.Set("setting_siteScript", "cloudreve", 0)
cache.Set("setting_pwa_small_icon", "cloudreve", 0)
TestFunc(c)
asserts.True(c.IsAborted())
}
// 成功且未命中
{
file, _ := util.CreatNestedFile("tests/index.html")
defer file.Close()
testStatic := &StaticMock{}
bootstrap.StaticFS = testStatic
testStatic.On("Open", "/index.html").
Return(file, nil)
TestFunc := InjectSiteInfo()
c, _ := gin.CreateTestContext(rec)
c.Params = []gin.Param{}
c.Request, _ = http.NewRequest("GET", "/2", nil)
TestFunc(c)
asserts.False(c.IsAborted())
}
}
+4 -4
View File
@@ -1,9 +1,9 @@
package middleware
import (
"github.com/HFO4/cloudreve/pkg/conf"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/memstore"
"github.com/gin-contrib/sessions/redis"
@@ -18,7 +18,7 @@ func Session(secret string) gin.HandlerFunc {
// Redis设置不为空,且非测试模式时使用Redis
if conf.RedisConfig.Server != "" && gin.Mode() != gin.TestMode {
var err error
Store, err = redis.NewStoreWithDB(10, "tcp", conf.RedisConfig.Server, conf.RedisConfig.Password, conf.RedisConfig.DB, []byte(secret))
Store, err = redis.NewStoreWithDB(10, conf.RedisConfig.Network, conf.RedisConfig.Server, conf.RedisConfig.Password, conf.RedisConfig.DB, []byte(secret))
if err != nil {
util.Log().Panic("无法连接到 Redis%s", err)
}
+5 -4
View File
@@ -1,13 +1,14 @@
package middleware
import (
"github.com/HFO4/cloudreve/pkg/conf"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"testing"
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)
func TestSession(t *testing.T) {
+4 -3
View File
@@ -2,9 +2,10 @@ package middleware
import (
"fmt"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/HFO4/cloudreve/pkg/util"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gin-gonic/gin"
)
+5 -4
View File
@@ -1,14 +1,15 @@
package middleware
import (
"net/http/httptest"
"testing"
"github.com/DATA-DOG/go-sqlmock"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/conf"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
"net/http/httptest"
"testing"
)
func TestShareAvailable(t *testing.T) {
+12 -6
View File
@@ -2,8 +2,9 @@ package model
import (
"encoding/json"
"github.com/HFO4/cloudreve/pkg/aria2/rpc"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/cloudreve/Cloudreve/v3/pkg/aria2/rpc"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/jinzhu/gorm"
)
@@ -17,10 +18,10 @@ type Download struct {
DownloadedSize uint64 // 文件大小
GID string `gorm:"size:32,index:gid"` // 任务ID
Speed int // 下载速度
Parent string `gorm:"type:text"` // 存储目录
Attrs string `gorm:"type:text"` // 任务状态属性
Error string `gorm:"type:text"` // 错误描述
Dst string `gorm:"type:text"` // 用户文件系统存储父目录路径
Parent string `gorm:"type:text"` // 存储目录
Attrs string `gorm:"size:65535"` // 任务状态属性
Error string `gorm:"type:text"` // 错误描述
Dst string `gorm:"type:text"` // 用户文件系统存储父目录路径
UserID uint // 发起者UID
TaskID uint // 对应的转存任务ID
@@ -108,3 +109,8 @@ func (task *Download) GetOwner() *User {
}
return task.User
}
// Delete 删除离线下载记录
func (download *Download) Delete() error {
return DB.Model(download).Delete(download).Error
}
+16
View File
@@ -161,3 +161,19 @@ func TestGetDownloadsByStatusAndUser(t *testing.T) {
asserts.Len(res, 2)
}
}
func TestDownload_Delete(t *testing.T) {
asserts := assert.New(t)
share := Download{}
{
mock.ExpectBegin()
mock.ExpectExec("UPDATE(.+)").
WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
err := share.Delete()
asserts.NoError(mock.ExpectationsWereMet())
asserts.NoError(err)
}
}
+37 -3
View File
@@ -2,10 +2,11 @@ package model
import (
"encoding/gob"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/jinzhu/gorm"
"path"
"time"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/jinzhu/gorm"
)
// File 文件
@@ -25,6 +26,12 @@ type File struct {
// 数据库忽略字段
Position string `gorm:"-"`
// exif 信息
ExifModel string
ExifDateTime time.Time
ExifLatLong string
ExifAddress string
}
func init() {
@@ -78,6 +85,16 @@ func GetFilesByIDs(ids []uint, uid uint) ([]File, error) {
return files, result.Error
}
// GetEmptyLocationFilesByPage 分页获取所有经纬度未更新文件
func GetEmptyLocationFilesByPage(page uint, pageSize uint) ([]File, error) {
var files []File
var result *gorm.DB
var offset = int(page) * int(pageSize) - int(pageSize)
result = DB.Where("exif_lat_long != '' AND (exif_address is null or exif_address = '')").Limit(pageSize).
Offset(offset).Find(&files)
return files, result.Error
}
// GetFilesByKeywords 根据关键字搜索文件,
// UID为0表示忽略用户,只根据文件ID检索
func GetFilesByKeywords(uid uint, keywords ...interface{}) ([]File, error) {
@@ -195,7 +212,24 @@ func (file *File) UpdateSize(value uint64) error {
// UpdateSourceName 更新文件的源文件名
func (file *File) UpdateSourceName(value string) error {
return DB.Model(&file).Update("source_name", value).Error
return DB.Model(&file).Set("gorm:association_autoupdate", false).Update("source_name", value).Error
}
// UpdatePicExifModel 更新文件的设备信息
func (file *File) UpdatePicExifModel(value string) error {
return DB.Model(&file).Update("exif_model", value).Error
}
// UpdatePicExifDateTime 更新图片EXIF时间
func (file *File) UpdatePicExifDateTime(value time.Time) error {
return DB.Model(&file).Update("exif_date_time", value).Error
}
// UpdatePicExifLatLong 更新图片EXIF坐标
func (file *File) UpdatePicExifLatLong(value string) error {
return DB.Model(&file).Update("exif_lat_long", value).Error
}
// UpdatePicExifAddress 更新图片EXIF位置信息
func (file *File) UpdatePicExifAddress(value string) error {
return DB.Model(&file).Update("exif_address", value).Error
}
/*
+3 -2
View File
@@ -2,10 +2,11 @@ package model
import (
"errors"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/jinzhu/gorm"
"path"
"time"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/jinzhu/gorm"
)
// Folder 目录
+5 -4
View File
@@ -2,12 +2,13 @@ package model
import (
"errors"
"github.com/DATA-DOG/go-sqlmock"
"github.com/HFO4/cloudreve/pkg/conf"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
"testing"
"time"
"github.com/DATA-DOG/go-sqlmock"
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
)
func TestFolder_Create(t *testing.T) {
+19 -12
View File
@@ -2,11 +2,12 @@ package model
import (
"fmt"
"github.com/HFO4/cloudreve/pkg/conf"
"github.com/HFO4/cloudreve/pkg/util"
"time"
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
"time"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/sqlite"
@@ -28,19 +29,30 @@ func Init() {
// 测试模式下,使用内存数据库
db, err = gorm.Open("sqlite3", ":memory:")
} else {
if conf.DatabaseConfig.Type == "UNSET" {
// 未指定数据库时,使用SQLite
switch conf.DatabaseConfig.Type {
case "UNSET", "sqlite", "sqlite3":
// 未指定数据库或者明确指定为 sqlite 时,使用 SQLite3 数据库
db, err = gorm.Open("sqlite3", util.RelativePath(conf.DatabaseConfig.DBFile))
} else {
db, err = gorm.Open(conf.DatabaseConfig.Type, fmt.Sprintf("%s:%s@(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local",
case "mysql":
// 当前只支持 sqlite3 与 mysql 数据库
// TODO: import 其他 gorm 支持的主流数据库?否则直接 Open 没有任何意义。
// TODO: 数据库连接其他参数允许用户自定义?譬如编码更换为 utf8mb4 以支持表情。
db, err = gorm.Open("mysql", fmt.Sprintf("%s:%s@(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local",
conf.DatabaseConfig.User,
conf.DatabaseConfig.Password,
conf.DatabaseConfig.Host,
conf.DatabaseConfig.Port,
conf.DatabaseConfig.Name))
default:
util.Log().Panic("不支持数据库类型: %s", conf.DatabaseConfig.Type)
}
}
//db.SetLogger(util.Log())
if err != nil {
util.Log().Panic("连接数据库不成功, %s", err)
}
// 处理表前缀
gorm.DefaultTableNameHandler = func(db *gorm.DB, defaultTableName string) string {
return conf.DatabaseConfig.TablePrefix + defaultTableName
@@ -53,11 +65,6 @@ func Init() {
db.LogMode(false)
}
//db.SetLogger(util.Log())
if err != nil {
util.Log().Panic("连接数据库不成功, %s", err)
}
//设置连接池
//空闲
db.DB().SetMaxIdleConns(50)
+4 -3
View File
@@ -1,9 +1,9 @@
package model
import (
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/HFO4/cloudreve/pkg/conf"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/fatih/color"
"github.com/jinzhu/gorm"
)
@@ -147,6 +147,7 @@ Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; verti
{Name: "home_view_method", Value: "icon", Type: "view"},
{Name: "share_view_method", Value: "list", Type: "view"},
{Name: "cron_garbage_collect", Value: "@hourly", Type: "cron"},
{Name: "cron_sync_photo_lat_long_to_address", Value: "@hourly", Type: "cron"},
{Name: "authn_enabled", Value: "0", Type: "authn"},
{Name: "captcha_height", Value: "60", Type: "captcha"},
{Name: "captcha_width", Value: "240", Type: "captcha"},
+3 -2
View File
@@ -1,10 +1,11 @@
package model
import (
"github.com/HFO4/cloudreve/pkg/conf"
"testing"
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
"testing"
)
func TestMigration(t *testing.T) {
+24 -8
View File
@@ -3,15 +3,17 @@ package model
import (
"encoding/gob"
"encoding/json"
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/jinzhu/gorm"
"fmt"
"net/url"
"path"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/jinzhu/gorm"
)
// Policy 存储策略
@@ -45,9 +47,14 @@ type PolicyOption struct {
FileType []string `json:"file_type"`
// MimeType
MimeType string `json:"mimetype"`
// OdRedirect Onedrive重定向地址
// OdRedirect Onedrive 重定向地址
OdRedirect string `json:"od_redirect,omitempty"`
// OdProxy Onedrive 反代地址
OdProxy string `json:"od_proxy,omitempty"`
// Region 区域代码
Region string `json:"region,omitempty"`
// ServerSideEndpoint 服务端请求使用的 Endpoint,为空时使用 Policy.Server 字段
ServerSideEndpoint string `json:"server_side_endpoint,omitempty"`
}
var thumbSuffix = map[string][]string{
@@ -56,6 +63,7 @@ var thumbSuffix = map[string][]string{
"oss": {".jpg", ".jpeg", ".png", ".gif", ".webp", ".tiff", ".bmp"},
"cos": {".jpg", ".jpeg", ".png", ".gif", ".webp", ".tiff", ".bmp"},
"upyun": {".svg", ".jpg", ".jpeg", ".png", ".gif", ".webp", ".tiff", ".bmp"},
"s3": {},
"remote": {},
"onedrive": {"*"},
}
@@ -234,7 +242,7 @@ func (policy *Policy) GetUploadURL() string {
return policy.Server
}
var controller *url.URL
controller, _ := url.Parse("")
switch policy.Type {
case "local", "onedrive":
return "/api/v3/file/upload"
@@ -246,9 +254,17 @@ func (policy *Policy) GetUploadURL() string {
return policy.Server
case "upyun":
return "https://v0.api.upyun.com/" + policy.BucketName
default:
controller, _ = url.Parse("")
case "s3":
if policy.Server == "" {
return fmt.Sprintf("https://%s.s3.%s.amazonaws.com/", policy.BucketName,
policy.OptionsSerialized.Region)
}
if !strings.Contains(policy.Server, policy.BucketName) {
controller, _ = url.Parse("/" + policy.BucketName)
}
}
return server.ResolveReference(controller).String()
}
+27 -4
View File
@@ -2,13 +2,14 @@ package model
import (
"encoding/json"
"github.com/DATA-DOG/go-sqlmock"
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
"strconv"
"testing"
"time"
"github.com/DATA-DOG/go-sqlmock"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
)
func TestGetPolicyByID(t *testing.T) {
@@ -209,6 +210,28 @@ func TestPolicy_GetUploadURL(t *testing.T) {
asserts.Equal("http://127.0.0.1", policy.GetUploadURL())
}
// S3 未填写自动生成
{
policy := Policy{
Type: "s3",
Server: "",
BucketName: "bucket",
OptionsSerialized: PolicyOption{Region: "us-east"},
}
asserts.Equal("https://bucket.s3.us-east.amazonaws.com/", policy.GetUploadURL())
}
// s3 自己指定
{
policy := Policy{
Type: "s3",
Server: "https://s3.us-east.amazonaws.com/",
BucketName: "bucket",
OptionsSerialized: PolicyOption{Region: "us-east"},
}
asserts.Equal("https://s3.us-east.amazonaws.com/bucket", policy.GetUploadURL())
}
}
func TestPolicy_IsPathGenerateNeeded(t *testing.T) {
+25
View File
@@ -0,0 +1,25 @@
package scripts
import (
"context"
"fmt"
)
type DBScript interface {
Run(ctx context.Context)
}
var availableScripts = make(map[string]DBScript)
func RunDBScript(name string, ctx context.Context) error {
if script, ok := availableScripts[name]; ok {
script.Run(ctx)
return nil
}
return fmt.Errorf("数据库脚本 [%s] 不存在", name)
}
func register(name string, script DBScript) {
availableScripts[name] = script
}
+49
View File
@@ -0,0 +1,49 @@
package scripts
import (
"context"
"database/sql"
"github.com/DATA-DOG/go-sqlmock"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
"testing"
)
var mock sqlmock.Sqlmock
var mockDB *gorm.DB
type TestScript int
func (script TestScript) Run(ctx context.Context) {
}
// TestMain 初始化数据库Mock
func TestMain(m *testing.M) {
var db *sql.DB
var err error
db, mock, err = sqlmock.New()
if err != nil {
panic("An error was not expected when opening a stub database connection")
}
model.DB, _ = gorm.Open("mysql", db)
mockDB = model.DB
defer db.Close()
m.Run()
}
func TestRunDBScript(t *testing.T) {
asserts := assert.New(t)
register("test", TestScript(0))
// 不存在
{
asserts.Error(RunDBScript("else", context.Background()))
}
// 存在
{
asserts.NoError(RunDBScript("test", context.Background()))
}
}
+37
View File
@@ -0,0 +1,37 @@
package scripts
import (
"context"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
)
type UserStorageCalibration int
func init() {
register("CalibrateUserStorage", UserStorageCalibration(0))
}
type storageResult struct {
Total uint64
}
// Run 运行脚本校准所有用户容量
func (script UserStorageCalibration) Run(ctx context.Context) {
// 列出所有用户
var res []model.User
model.DB.Model(&model.User{}).Find(&res)
// 逐个检查容量
for _, user := range res {
// 计算正确的容量
var total storageResult
model.DB.Model(&model.File{}).Where("user_id = ?", user.ID).Select("sum(size) as total").Scan(&total)
// 更新用户的容量
if user.Storage != total.Total {
util.Log().Info("将用户 [%s] 的容量由 %d 校准为 %d", user.Email,
user.Storage, total.Total)
model.DB.Model(&user).Update("storage", total.Total)
}
}
}
+38
View File
@@ -0,0 +1,38 @@
package scripts
import (
"context"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
"testing"
)
func TestUserStorageCalibration_Run(t *testing.T) {
asserts := assert.New(t)
script := UserStorageCalibration(0)
// 容量异常
{
mock.ExpectQuery("SELECT(.+)users(.+)").
WillReturnRows(sqlmock.NewRows([]string{"id", "email", "storage"}).AddRow(1, "a@a.com", 10))
mock.ExpectQuery("SELECT(.+)files(.+)").
WithArgs(1).
WillReturnRows(sqlmock.NewRows([]string{"total"}).AddRow(11))
mock.ExpectBegin()
mock.ExpectExec("UPDATE(.+)").WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
script.Run(context.Background())
asserts.NoError(mock.ExpectationsWereMet())
}
// 容量正常
{
mock.ExpectQuery("SELECT(.+)users(.+)").
WillReturnRows(sqlmock.NewRows([]string{"id", "email", "storage"}).AddRow(1, "a@a.com", 10))
mock.ExpectQuery("SELECT(.+)files(.+)").
WithArgs(1).
WillReturnRows(sqlmock.NewRows([]string{"total"}).AddRow(10))
script.Run(context.Background())
asserts.NoError(mock.ExpectationsWereMet())
}
}
+3 -2
View File
@@ -1,10 +1,11 @@
package model
import (
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/jinzhu/gorm"
"net/url"
"strconv"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/jinzhu/gorm"
)
// Setting 系统设置模型
+3 -2
View File
@@ -2,11 +2,12 @@ package model
import (
"database/sql"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
"testing"
)
var mock sqlmock.Sqlmock
+6 -5
View File
@@ -3,13 +3,14 @@ package model
import (
"errors"
"fmt"
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/HFO4/cloudreve/pkg/hashid"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
"strings"
"time"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/hashid"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
)
// Share 分享模型
+7 -6
View File
@@ -2,15 +2,16 @@ package model
import (
"errors"
"github.com/DATA-DOG/go-sqlmock"
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/HFO4/cloudreve/pkg/conf"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
"net/http/httptest"
"testing"
"time"
"github.com/DATA-DOG/go-sqlmock"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
)
func TestShare_Create(t *testing.T) {
+1 -1
View File
@@ -1,7 +1,7 @@
package model
import (
"github.com/HFO4/cloudreve/pkg/util"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/jinzhu/gorm"
)
+1 -1
View File
@@ -1,7 +1,7 @@
package model
import (
"github.com/HFO4/cloudreve/pkg/util"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/jinzhu/gorm"
)
+10 -2
View File
@@ -5,10 +5,11 @@ import (
"crypto/sha1"
"encoding/hex"
"encoding/json"
"github.com/HFO4/cloudreve/pkg/util"
"strings"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/jinzhu/gorm"
"github.com/pkg/errors"
"strings"
)
const (
@@ -138,6 +139,13 @@ func GetActiveUserByOpenID(openid string) (User, error) {
// GetUserByEmail 用Email获取用户
func GetUserByEmail(email string) (User, error) {
var user User
result := DB.Set("gorm:auto_preload", true).Where("email = ?", email).First(&user)
return user, result.Error
}
// GetActiveUserByEmail 用Email获取可登录用户
func GetActiveUserByEmail(email string) (User, error) {
var user User
result := DB.Set("gorm:auto_preload", true).Where("status = ? and email = ?", Active, email).First(&user)
return user, result.Error
+3 -2
View File
@@ -5,9 +5,10 @@ import (
"encoding/binary"
"encoding/json"
"fmt"
"github.com/HFO4/cloudreve/pkg/hashid"
"github.com/duo-labs/webauthn/webauthn"
"net/url"
"github.com/cloudreve/Cloudreve/v3/pkg/hashid"
"github.com/duo-labs/webauthn/webauthn"
)
/*
+14 -3
View File
@@ -2,12 +2,13 @@ package model
import (
"encoding/json"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/jinzhu/gorm"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"testing"
)
func TestGetUserByID(t *testing.T) {
@@ -351,10 +352,20 @@ func TestUser_IncreaseStorageWithoutCheck(t *testing.T) {
}
}
func TestGetUserByEmail(t *testing.T) {
func TestGetActiveUserByEmail(t *testing.T) {
asserts := assert.New(t)
mock.ExpectQuery("SELECT(.+)").WithArgs(Active, "abslant@foxmail.com").WillReturnRows(sqlmock.NewRows([]string{"id", "email"}))
_, err := GetActiveUserByEmail("abslant@foxmail.com")
asserts.Error(err)
asserts.NoError(mock.ExpectationsWereMet())
}
func TestGetUserByEmail(t *testing.T) {
asserts := assert.New(t)
mock.ExpectQuery("SELECT(.+)").WithArgs("abslant@foxmail.com").WillReturnRows(sqlmock.NewRows([]string{"id", "email"}))
_, err := GetUserByEmail("abslant@foxmail.com")
asserts.Error(err)
+5 -4
View File
@@ -2,12 +2,13 @@ package aria2
import (
"encoding/json"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/aria2/rpc"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/HFO4/cloudreve/pkg/util"
"net/url"
"sync"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/aria2/rpc"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
)
// Instance 默认使用的Aria2处理实例
+4 -3
View File
@@ -2,12 +2,13 @@ package aria2
import (
"database/sql"
"testing"
"github.com/DATA-DOG/go-sqlmock"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/cache"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
"testing"
)
var mock sqlmock.Sqlmock
+4 -3
View File
@@ -2,13 +2,14 @@ package aria2
import (
"context"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/aria2/rpc"
"github.com/HFO4/cloudreve/pkg/util"
"path/filepath"
"strconv"
"strings"
"time"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/aria2/rpc"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
)
// RPCService 通过RPC服务的Aria2任务管理器
+4 -3
View File
@@ -1,10 +1,11 @@
package aria2
import (
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/stretchr/testify/assert"
"testing"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/stretchr/testify/assert"
)
func TestRPCService_Init(t *testing.T) {
+9 -7
View File
@@ -4,17 +4,18 @@ import (
"context"
"encoding/json"
"errors"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/aria2/rpc"
"github.com/HFO4/cloudreve/pkg/filesystem"
"github.com/HFO4/cloudreve/pkg/filesystem/driver/local"
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
"github.com/HFO4/cloudreve/pkg/task"
"github.com/HFO4/cloudreve/pkg/util"
"os"
"path/filepath"
"strconv"
"time"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/aria2/rpc"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver/local"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
"github.com/cloudreve/Cloudreve/v3/pkg/task"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
)
// Monitor 离线下载状态监控
@@ -250,6 +251,7 @@ func (monitor *Monitor) Complete(status rpc.StatusInfo) bool {
file,
monitor.Task.Dst,
monitor.Task.Parent,
true,
)
if err != nil {
monitor.setErrorStatus(err)
+9 -8
View File
@@ -2,18 +2,19 @@ package aria2
import (
"errors"
"testing"
"time"
"github.com/DATA-DOG/go-sqlmock"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/aria2/rpc"
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/HFO4/cloudreve/pkg/filesystem"
"github.com/HFO4/cloudreve/pkg/task"
"github.com/HFO4/cloudreve/pkg/util"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/aria2/rpc"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem"
"github.com/cloudreve/Cloudreve/v3/pkg/task"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
testMock "github.com/stretchr/testify/mock"
"testing"
"time"
)
type InstanceMock struct {
+2 -1
View File
@@ -1,8 +1,9 @@
package aria2
import (
"github.com/HFO4/cloudreve/pkg/aria2/rpc"
"sync"
"github.com/cloudreve/Cloudreve/v3/pkg/aria2/rpc"
)
// Notifier aria2实践通知处理
+3 -2
View File
@@ -1,9 +1,10 @@
package aria2
import (
"github.com/HFO4/cloudreve/pkg/aria2/rpc"
"github.com/stretchr/testify/assert"
"testing"
"github.com/cloudreve/Cloudreve/v3/pkg/aria2/rpc"
"github.com/stretchr/testify/assert"
)
func TestNotifier_Notify(t *testing.T) {
+5 -4
View File
@@ -2,15 +2,16 @@ package auth
import (
"bytes"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/conf"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/HFO4/cloudreve/pkg/util"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
)
var (
+3 -2
View File
@@ -1,12 +1,13 @@
package auth
import (
"github.com/HFO4/cloudreve/pkg/util"
"github.com/stretchr/testify/assert"
"io/ioutil"
"net/http"
"strings"
"testing"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/stretchr/testify/assert"
)
func TestSignURI(t *testing.T) {
+6 -5
View File
@@ -3,15 +3,16 @@ package auth
import (
"database/sql"
"fmt"
"testing"
"time"
"github.com/DATA-DOG/go-sqlmock"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/conf"
"github.com/HFO4/cloudreve/pkg/util"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
"testing"
"time"
)
var mock sqlmock.Sqlmock
+1 -1
View File
@@ -1,7 +1,7 @@
package authn
import (
model "github.com/HFO4/cloudreve/models"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/duo-labs/webauthn/webauthn"
)
+3 -2
View File
@@ -1,9 +1,10 @@
package authn
import (
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/stretchr/testify/assert"
"testing"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/stretchr/testify/assert"
)
func TestInit(t *testing.T) {
+2 -2
View File
@@ -1,7 +1,7 @@
package cache
import (
"github.com/HFO4/cloudreve/pkg/conf"
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
"github.com/gin-gonic/gin"
)
@@ -15,7 +15,7 @@ func Init() {
if conf.RedisConfig.Server != "" && gin.Mode() != gin.TestMode {
Store = NewRedisStore(
10,
"tcp",
conf.RedisConfig.Network,
conf.RedisConfig.Server,
conf.RedisConfig.Password,
conf.RedisConfig.DB,
+2 -1
View File
@@ -1,9 +1,10 @@
package cache
import (
"github.com/HFO4/cloudreve/pkg/util"
"sync"
"time"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
)
// MemoStore 内存存储驱动
+3 -2
View File
@@ -3,10 +3,11 @@ package cache
import (
"bytes"
"encoding/gob"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/gomodule/redigo/redis"
"strconv"
"time"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gomodule/redigo/redis"
)
// RedisStore redis存储驱动
+15 -9
View File
@@ -1,7 +1,7 @@
package conf
import (
"github.com/HFO4/cloudreve/pkg/util"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/go-ini/ini"
"gopkg.in/go-playground/validator.v9"
)
@@ -33,6 +33,10 @@ type ssl struct {
Listen string `validate:"required"`
}
type unix struct {
Listen string
}
// slave 作为slave存储端配置
type slave struct {
Secret string `validate:"omitempty,gte=64"`
@@ -57,6 +61,7 @@ type captcha struct {
// redis 配置
type redis struct {
Network string
Server string
Password string
DB string
@@ -117,14 +122,15 @@ func Init(path string) {
}
sections := map[string]interface{}{
"Database": DatabaseConfig,
"System": SystemConfig,
"SSL": SSLConfig,
"Captcha": CaptchaConfig,
"Redis": RedisConfig,
"Thumbnail": ThumbConfig,
"CORS": CORSConfig,
"Slave": SlaveConfig,
"Database": DatabaseConfig,
"System": SystemConfig,
"SSL": SSLConfig,
"UnixSocket": UnixConfig,
"Captcha": CaptchaConfig,
"Redis": RedisConfig,
"Thumbnail": ThumbConfig,
"CORS": CORSConfig,
"Slave": SlaveConfig,
}
for sectionName, sectionStruct := range sections {
err = mapSection(sectionName, sectionStruct)
+3 -2
View File
@@ -1,11 +1,12 @@
package conf
import (
"github.com/HFO4/cloudreve/pkg/util"
"github.com/stretchr/testify/assert"
"io/ioutil"
"os"
"testing"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/stretchr/testify/assert"
)
// 测试Init日志路径错误
+5
View File
@@ -4,6 +4,7 @@ import "github.com/mojocn/base64Captcha"
// RedisConfig Redis服务器配置
var RedisConfig = &redis{
Network: "tcp",
Server: "",
Password: "",
DB: "0",
@@ -65,3 +66,7 @@ var SSLConfig = &ssl{
CertPath: "",
KeyPath: "",
}
var UnixConfig = &unix{
Listen: "",
}
+3 -3
View File
@@ -1,13 +1,13 @@
package conf
// BackendVersion 当前后端版本号
var BackendVersion = "3.1.0"
var BackendVersion = "3.2.1"
// RequiredDBVersion 与当前版本匹配的数据库版本
var RequiredDBVersion = "3.1.0"
var RequiredDBVersion = "3.2.1"
// RequiredStaticVersion 与当前版本匹配的静态资源版本
var RequiredStaticVersion = "3.1.0"
var RequiredStaticVersion = "3.2.1"
// IsPro 是否为Pro版本
var IsPro = "false"
+108
View File
@@ -0,0 +1,108 @@
package crontab
import (
"net/http"
"encoding/json"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
)
type AddressJson struct {
Status int `json:"status"`
Message string `json:"message"`
RequestID string `json:"request_id"`
Result struct {
Location struct {
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
} `json:"location"`
Address string `json:"address"`
FormattedAddresses struct {
Recommend string `json:"recommend"`
Rough string `json:"rough"`
} `json:"formatted_addresses"`
AddressComponent struct {
Nation string `json:"nation"`
Province string `json:"province"`
City string `json:"city"`
District string `json:"district"`
Street string `json:"street"`
StreetNumber string `json:"street_number"`
} `json:"address_component"`
} `json:"result"`
}
type AddressExif struct{
Address string `json:"address"`
Nation string `json:"nation"`
Province string `json:"province"`
City string `json:"city"`
District string `json:"district"`
Street string `json:"street"`
StreetNumber string `json:"street_number"`
}
func syncPhotoAddress() {
// 同步照片的经纬度为文本地址
syncPhotoLatLongToAddress()
util.Log().Info("定时任务 [cron_sync_photo_lat_long_to_address] 执行完毕")
}
func syncPhotoLatLongToAddress() {
page := 1
pageSize := 10
for true{
files , _ := model.GetEmptyLocationFilesByPage(uint(page), uint(pageSize))
if len(files) <= 0 {
break
}
for i := 0; i < len(files); i++{
file := files[i]
util.Log().Debug("file name: %s",file.Name)
util.Log().Debug("file ExifLatLong: %s",file.ExifLatLong)
util.Log().Debug("file ExifAddress: %s",file.ExifAddress)
// 获取文件数据流
url := "https://apis.map.qq.com/ws/geocoder/v1/?location="+ file.ExifLatLong +"&get_poi=1&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"
client := request.HTTPClient{}
resp := client.Request(
"GET",
url,
nil,
request.WithHeader(
http.Header{"Referer": {"https://lbs.qq.com/"}},
),
)
respString, err := resp.GetResponse()
if err != nil{
util.Log().Warning("response error: %s",err)
}
var addressJson AddressJson
err = json.Unmarshal([]byte(respString), &addressJson)
if err != nil {
util.Log().Warning("解析经纬度结果错误原始文本: %s",respString)
util.Log().Warning("解析经纬度结果错误: %s",err)
continue
}
var addressInfo AddressExif
addressInfo.Address = addressJson.Result.Address
addressInfo.StreetNumber = addressJson.Result.AddressComponent.StreetNumber
addressInfo.District = addressJson.Result.AddressComponent.District
addressInfo.City = addressJson.Result.AddressComponent.City
addressInfo.Province = addressJson.Result.AddressComponent.Province
addressInfo.Nation = addressJson.Result.AddressComponent.Nation
if addressInfoStr, err := json.Marshal(addressInfo); err == nil {
file.UpdatePicExifAddress(string(addressInfoStr))
util.Log().Debug("解析经纬度结果重组: %s",string(addressInfoStr))
}
}
page++
}
}
+4 -3
View File
@@ -1,13 +1,14 @@
package crontab
import (
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/HFO4/cloudreve/pkg/util"
"os"
"path/filepath"
"strings"
"time"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
)
func garbageCollect() {
+5 -3
View File
@@ -1,8 +1,8 @@
package crontab
import (
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/util"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/robfig/cron/v3"
)
@@ -21,13 +21,15 @@ func Reload() {
func Init() {
util.Log().Info("初始化定时任务...")
// 读取cron日程设置
options := model.GetSettingByNames("cron_garbage_collect")
options := model.GetSettingByNames("cron_garbage_collect","cron_sync_photo_lat_long_to_address")
Cron := cron.New()
for k, v := range options {
var handler func()
switch k {
case "cron_garbage_collect":
handler = garbageCollect
case "cron_sync_photo_lat_long_to_address":
handler = syncPhotoAddress
default:
util.Log().Warning("未知定时任务类型 [%s],跳过", k)
continue
+3 -2
View File
@@ -1,9 +1,10 @@
package email
import (
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/util"
"sync"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
)
// Client 默认的邮件发送客户端
+3 -2
View File
@@ -1,9 +1,10 @@
package email
import (
"github.com/HFO4/cloudreve/pkg/util"
"github.com/go-mail/mail"
"time"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/go-mail/mail"
)
// SMTP SMTP协议发送邮件
+3 -2
View File
@@ -2,8 +2,9 @@ package email
import (
"fmt"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/util"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
)
// NewActivationEmail 新建激活邮件
+7 -6
View File
@@ -5,12 +5,6 @@ import (
"bytes"
"context"
"fmt"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/gin-gonic/gin"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
"io"
"io/ioutil"
"os"
@@ -19,6 +13,13 @@ import (
"strings"
"sync"
"time"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gin-gonic/gin"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
)
/* ===============
+10 -9
View File
@@ -3,19 +3,20 @@ package filesystem
import (
"context"
"errors"
"github.com/DATA-DOG/go-sqlmock"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
"github.com/HFO4/cloudreve/pkg/request"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
testMock "github.com/stretchr/testify/mock"
"io"
"os"
"strings"
"testing"
"github.com/DATA-DOG/go-sqlmock"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
testMock "github.com/stretchr/testify/mock"
)
func TestFileSystem_Compress(t *testing.T) {
+8 -7
View File
@@ -8,13 +8,6 @@ import (
"encoding/json"
"errors"
"fmt"
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/request"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/google/go-querystring/query"
cossdk "github.com/tencentyun/cos-go-sdk-v5"
"io"
"net/http"
"net/url"
@@ -22,6 +15,14 @@ import (
"path/filepath"
"strings"
"time"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/response"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/google/go-querystring/query"
cossdk "github.com/tencentyun/cos-go-sdk-v5"
)
// UploadPolicy 腾讯云COS上传策略
+6 -5
View File
@@ -4,17 +4,18 @@ import (
"archive/zip"
"bytes"
"encoding/base64"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/hashid"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
scf "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf/v20180416"
"io"
"io/ioutil"
"net/url"
"strconv"
"strings"
"time"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/hashid"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
scf "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf/v20180416"
)
const scfFunc = `# -*- coding: utf8 -*-
+25 -13
View File
@@ -4,18 +4,19 @@ import (
"context"
"errors"
"fmt"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/auth"
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/HFO4/cloudreve/pkg/conf"
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
"github.com/HFO4/cloudreve/pkg/filesystem/response"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/HFO4/cloudreve/pkg/util"
"io"
"net/url"
"os"
"path/filepath"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/auth"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/response"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
)
// Driver 本地策略适配器
@@ -99,6 +100,14 @@ func (handler Driver) Put(ctx context.Context, file io.ReadCloser, dst string, s
defer file.Close()
dst = util.RelativePath(filepath.FromSlash(dst))
// 如果禁止了 Overwrite,则检查是否有重名冲突
if ctx.Value(fsctx.DisableOverwrite) != nil {
if util.Exists(dst) {
util.Log().Warning("物理同名文件已存在或不可用: %s", dst)
return errors.New("物理同名文件已存在或不可用")
}
}
// 如果目标目录不存在,创建
basePath := filepath.Dir(dst)
if !util.Exists(basePath) {
@@ -129,11 +138,14 @@ func (handler Driver) Delete(ctx context.Context, files []string) ([]string, err
var retErr error
for _, value := range files {
err := os.Remove(util.RelativePath(filepath.FromSlash(value)))
if err != nil {
util.Log().Warning("无法删除文件,%s", err)
retErr = err
deleteFailed = append(deleteFailed, value)
filePath := util.RelativePath(filepath.FromSlash(value))
if util.Exists(filePath) {
err := os.Remove(filePath)
if err != nil {
util.Log().Warning("无法删除文件,%s", err)
retErr = err
deleteFailed = append(deleteFailed, value)
}
}
// 尝试删除文件的缩略图(如果有)
+29 -13
View File
@@ -2,11 +2,11 @@ package local
import (
"context"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/auth"
"github.com/HFO4/cloudreve/pkg/conf"
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
"github.com/HFO4/cloudreve/pkg/util"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/auth"
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
"io"
@@ -20,7 +20,8 @@ import (
func TestHandler_Put(t *testing.T) {
asserts := assert.New(t)
handler := Driver{}
ctx := context.Background()
ctx := context.WithValue(context.Background(), fsctx.DisableOverwrite, true)
os.Remove(util.RelativePath("test/test/txt"))
testCases := []struct {
file io.ReadCloser
@@ -32,6 +33,11 @@ func TestHandler_Put(t *testing.T) {
dst: "test/test/txt",
err: false,
},
{
file: ioutil.NopCloser(strings.NewReader("test input file")),
dst: "test/test/txt",
err: true,
},
{
file: ioutil.NopCloser(strings.NewReader("test input file")),
dst: "/notexist:/S.TXT",
@@ -54,24 +60,34 @@ func TestHandler_Delete(t *testing.T) {
asserts := assert.New(t)
handler := Driver{}
ctx := context.Background()
filePath := util.RelativePath("test.file")
file, err := os.Create(util.RelativePath("test.file"))
file, err := os.Create(filePath)
asserts.NoError(err)
_ = file.Close()
list, err := handler.Delete(ctx, []string{"test.file"})
asserts.Equal([]string{}, list)
asserts.NoError(err)
file, err = os.Create(util.RelativePath("test.file"))
asserts.NoError(err)
file, err = os.Create(filePath)
_ = file.Close()
file, _ = os.OpenFile(filePath, os.O_RDWR, os.FileMode(0))
asserts.NoError(err)
list, err = handler.Delete(ctx, []string{"test.file", "test.notexist"})
asserts.Equal([]string{"test.notexist"}, list)
asserts.Error(err)
file.Close()
asserts.Equal([]string{}, list)
asserts.NoError(err)
list, err = handler.Delete(ctx, []string{"test.notexist"})
asserts.Equal([]string{"test.notexist"}, list)
asserts.Error(err)
asserts.Equal([]string{}, list)
asserts.NoError(err)
file, err = os.Create(filePath)
asserts.NoError(err)
list, err = handler.Delete(ctx, []string{"test.file"})
_ = file.Close()
asserts.Equal([]string{}, list)
asserts.NoError(err)
}
func TestHandler_Get(t *testing.T) {
+25 -12
View File
@@ -6,11 +6,6 @@ import (
"encoding/json"
"errors"
"fmt"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
"github.com/HFO4/cloudreve/pkg/request"
"github.com/HFO4/cloudreve/pkg/util"
"io"
"io/ioutil"
"net/http"
@@ -19,6 +14,12 @@ import (
"strconv"
"strings"
"time"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
)
const (
@@ -219,15 +220,21 @@ func (client *Client) UploadChunk(ctx context.Context, uploadURL string, chunk *
// Upload 上传文件
func (client *Client) Upload(ctx context.Context, dst string, size int, file io.Reader) error {
// 决定是否覆盖文件
overwrite := "replace"
if ctx.Value(fsctx.DisableOverwrite) != nil {
overwrite = "fail"
}
// 小文件,使用简单上传接口上传
if size <= int(SmallFileSize) {
_, err := client.SimpleUpload(ctx, dst, file, int64(size))
_, err := client.SimpleUpload(ctx, dst, file, int64(size), WithConflictBehavior(overwrite))
return err
}
// 大文件,进行分片
// 创建上传会话
uploadURL, err := client.CreateUploadSession(ctx, dst, WithConflictBehavior("replace"))
uploadURL, err := client.CreateUploadSession(ctx, dst, WithConflictBehavior(overwrite))
if err != nil {
return err
}
@@ -286,9 +293,15 @@ func (client *Client) DeleteUploadSession(ctx context.Context, uploadURL string)
}
// SimpleUpload 上传小文件到dst
func (client *Client) SimpleUpload(ctx context.Context, dst string, body io.Reader, size int64) (*UploadResult, error) {
func (client *Client) SimpleUpload(ctx context.Context, dst string, body io.Reader, size int64, opts ...Option) (*UploadResult, error) {
options := newDefaultOption()
for _, o := range opts {
o.apply(options)
}
dst = strings.TrimPrefix(dst, "/")
requestURL := client.getRequestURL("me/drive/root:/" + dst + ":/content")
requestURL += ("?@microsoft.graph.conflictBehavior=" + options.conflictBehavior)
res, err := client.request(ctx, "PUT", requestURL, body, request.WithContentLength(int64(size)),
request.WithTimeout(time.Duration(150)*time.Second),
@@ -302,7 +315,7 @@ func (client *Client) SimpleUpload(ctx context.Context, dst string, body io.Read
retried++
util.Log().Debug("文件[%s]上传失败[%s]5秒钟后重试", dst, err)
time.Sleep(time.Duration(5) * time.Second)
return client.SimpleUpload(context.WithValue(ctx, fsctx.RetryCtx, retried), dst, body, size)
return client.SimpleUpload(context.WithValue(ctx, fsctx.RetryCtx, retried), dst, body, size, opts...)
}
return nil, err
}
@@ -369,7 +382,7 @@ func (client *Client) Delete(ctx context.Context, dst []string) ([]string, error
func getDeleteFailed(res *BatchResponses) []string {
var failed = make([]string, 0, len(res.Responses))
for _, v := range res.Responses {
if v.Status != 204 {
if v.Status != 204 && v.Status != 404 {
failed = append(failed, v.ID)
}
}
@@ -455,7 +468,7 @@ func (client *Client) MonitorUpload(uploadURL, callbackKey, path string, size ui
case <-time.After(time.Duration(ttl) * time.Second):
// 上传会话到期,仍未完成上传,创建占位符
client.DeleteUploadSession(context.Background(), uploadURL)
_, err := client.SimpleUpload(context.Background(), path, strings.NewReader(""), 0)
_, err := client.SimpleUpload(context.Background(), path, strings.NewReader(""), 0, WithConflictBehavior("replace"))
if err != nil {
util.Log().Debug("无法创建占位文件,%s", err)
}
@@ -503,7 +516,7 @@ func (client *Client) MonitorUpload(uploadURL, callbackKey, path string, size ui
// 取消上传会话,实测OneDrive取消上传会话后,客户端还是可以上传,
// 所以上传一个空文件占位,阻止客户端上传
client.DeleteUploadSession(context.Background(), uploadURL)
_, err := client.SimpleUpload(context.Background(), path, strings.NewReader(""), 0)
_, err := client.SimpleUpload(context.Background(), path, strings.NewReader(""), 0, WithConflictBehavior("replace"))
if err != nil {
util.Log().Debug("无法创建占位文件,%s", err)
}
+9 -6
View File
@@ -4,16 +4,18 @@ import (
"context"
"errors"
"fmt"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/HFO4/cloudreve/pkg/request"
"github.com/stretchr/testify/assert"
testMock "github.com/stretchr/testify/mock"
"io/ioutil"
"net/http"
"strings"
"testing"
"time"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
"github.com/stretchr/testify/assert"
testMock "github.com/stretchr/testify/mock"
)
func TestRequest(t *testing.T) {
@@ -498,11 +500,12 @@ func TestClient_Upload(t *testing.T) {
client, _ := NewClient(&model.Policy{})
client.Credential.AccessToken = "AccessToken"
client.Credential.ExpiresIn = time.Now().Add(time.Duration(100) * time.Hour).Unix()
ctx := context.WithValue(context.Background(), fsctx.DisableOverwrite, true)
// 小文件,简单上传,失败
{
client.Credential.ExpiresIn = 0
err := client.Upload(context.Background(), "123.jpg", 3, strings.NewReader("123"))
err := client.Upload(ctx, "123.jpg", 3, strings.NewReader("123"))
asserts.Error(err)
}
+3 -2
View File
@@ -2,8 +2,9 @@ package onedrive
import (
"errors"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/request"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
)
var (
@@ -1,9 +1,10 @@
package onedrive
import (
model "github.com/HFO4/cloudreve/models"
"github.com/stretchr/testify/assert"
"testing"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/stretchr/testify/assert"
)
func TestNewClient(t *testing.T) {
+37 -10
View File
@@ -4,18 +4,19 @@ import (
"context"
"errors"
"fmt"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
"github.com/HFO4/cloudreve/pkg/filesystem/response"
"github.com/HFO4/cloudreve/pkg/request"
"github.com/HFO4/cloudreve/pkg/serializer"
"io"
"net/url"
"path"
"path/filepath"
"strings"
"time"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/response"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
)
// Driver OneDrive 适配器
@@ -151,9 +152,14 @@ func (handler Driver) Source(
isDownload bool,
speed int,
) (string, error) {
cacheKey := fmt.Sprintf("onedrive_source_%d_%s", handler.Policy.ID, path)
if file, ok := ctx.Value(fsctx.FileModelCtx).(model.File); ok {
cacheKey = fmt.Sprintf("onedrive_source_file_%d_%d", file.UpdatedAt.Unix(), file.ID)
}
// 尝试从缓存中查找
if cachedURL, ok := cache.Get(fmt.Sprintf("onedrive_source_%d_%s", handler.Policy.ID, path)); ok {
return cachedURL.(string), nil
if cachedURL, ok := cache.Get(cacheKey); ok {
return handler.replaceSourceHost(cachedURL.(string))
}
// 缓存不存在,重新获取
@@ -161,15 +167,36 @@ func (handler Driver) Source(
if err == nil {
// 写入新的缓存
cache.Set(
fmt.Sprintf("onedrive_source_%d_%s", handler.Policy.ID, path),
cacheKey,
res.DownloadURL,
model.GetIntSetting("onedrive_source_timeout", 1800),
)
return res.DownloadURL, nil
return handler.replaceSourceHost(res.DownloadURL)
}
return "", err
}
func (handler Driver) replaceSourceHost(origin string) (string, error) {
if handler.Policy.OptionsSerialized.OdProxy != "" {
source, err := url.Parse(origin)
if err != nil {
return "", err
}
cdn, err := url.Parse(handler.Policy.OptionsSerialized.OdProxy)
if err != nil {
return "", err
}
// 替换反代地址
source.Scheme = cdn.Scheme
source.Host = cdn.Host
return source.String(), nil
}
return origin, nil
}
// Token 获取上传会话URL
func (handler Driver) Token(ctx context.Context, TTL int64, key string) (serializer.UploadCredential, error) {
+25 -8
View File
@@ -2,14 +2,7 @@ package onedrive
import (
"context"
"github.com/DATA-DOG/go-sqlmock"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
"github.com/HFO4/cloudreve/pkg/request"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/stretchr/testify/assert"
testMock "github.com/stretchr/testify/mock"
"fmt"
"io"
"io/ioutil"
"net/http"
@@ -17,6 +10,15 @@ import (
"strings"
"testing"
"time"
"github.com/DATA-DOG/go-sqlmock"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/stretchr/testify/assert"
testMock "github.com/stretchr/testify/mock"
)
func TestDriver_Token(t *testing.T) {
@@ -149,6 +151,21 @@ func TestDriver_Source(t *testing.T) {
asserts.Equal("res", res)
}
// 命中缓存 上下文存在文件 成功
{
file := model.File{}
file.ID = 1
file.UpdatedAt = time.Now()
ctx := context.WithValue(context.Background(), fsctx.FileModelCtx, file)
handler.Client.Credential.ExpiresIn = time.Now().Add(time.Duration(100) * time.Hour).Unix()
handler.Client.Credential.AccessToken = "1"
cache.Set(fmt.Sprintf("onedrive_source_file_%d_1", file.UpdatedAt.Unix()), "res", 0)
res, err := handler.Source(ctx, "123.jpg", url.URL{}, 0, true, 0)
cache.Deletes([]string{"0_123.jpg"}, "onedrive_source_")
asserts.NoError(err)
asserts.Equal("res", res)
}
// 成功
{
handler.Client.Credential.ExpiresIn = time.Now().Add(time.Duration(100) * time.Hour).Unix()
@@ -0,0 +1,38 @@
package onedrive
import (
model "github.com/cloudreve/Cloudreve/v3/models"
"testing"
)
func TestDriver_replaceSourceHost(t *testing.T) {
tests := []struct {
name string
origin string
cdn string
want string
wantErr bool
}{
{"TestNoReplace", "http://1dr.ms/download.aspx?123456", "", "http://1dr.ms/download.aspx?123456", false},
{"TestReplaceCorrect", "http://1dr.ms/download.aspx?123456", "https://test.com:8080", "https://test.com:8080/download.aspx?123456", false},
{"TestCdnFormatError", "http://1dr.ms/download.aspx?123456", string([]byte{0x7f}), "", true},
{"TestSrcFormatError", string([]byte{0x7f}), "https://test.com:8080", "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
policy := &model.Policy{}
policy.OptionsSerialized.OdProxy = tt.cdn
handler := Driver{
Policy: policy,
}
got, err := handler.replaceSourceHost(tt.origin)
if (err != nil) != tt.wantErr {
t.Errorf("replaceSourceHost() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("replaceSourceHost() got = %v, want %v", got, tt.want)
}
})
}
}
+4 -3
View File
@@ -3,14 +3,15 @@ package onedrive
import (
"context"
"encoding/json"
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/HFO4/cloudreve/pkg/request"
"github.com/HFO4/cloudreve/pkg/util"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
)
// Error 实现error接口
+8 -7
View File
@@ -4,13 +4,6 @@ import (
"context"
"database/sql"
"errors"
"github.com/DATA-DOG/go-sqlmock"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/HFO4/cloudreve/pkg/request"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
testMock "github.com/stretchr/testify/mock"
"io"
"io/ioutil"
"net/http"
@@ -18,6 +11,14 @@ import (
"strings"
"testing"
"time"
"github.com/DATA-DOG/go-sqlmock"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
testMock "github.com/stretchr/testify/mock"
)
var mock sqlmock.Sqlmock
+3 -2
View File
@@ -10,12 +10,13 @@ import (
"encoding/pem"
"errors"
"fmt"
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/HFO4/cloudreve/pkg/request"
"io/ioutil"
"net/http"
"net/url"
"strings"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
)
// GetPublicKey 从回调请求或缓存中获取OSS的回调签名公钥
+3 -2
View File
@@ -1,13 +1,14 @@
package oss
import (
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/stretchr/testify/assert"
"io/ioutil"
"net/http"
"net/url"
"strings"
"testing"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/stretchr/testify/assert"
)
func TestGetPublicKey(t *testing.T) {
+36 -15
View File
@@ -8,19 +8,20 @@ import (
"encoding/json"
"errors"
"fmt"
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/request"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"io"
"net/url"
"path"
"path/filepath"
"strings"
"time"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/response"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
)
// UploadPolicy 阿里云OSS上传策略
@@ -54,7 +55,7 @@ const (
// CORS 创建跨域策略
func (handler *Driver) CORS() error {
// 初始化客户端
if err := handler.InitOSSClient(); err != nil {
if err := handler.InitOSSClient(false); err != nil {
return err
}
@@ -76,14 +77,20 @@ func (handler *Driver) CORS() error {
}
// InitOSSClient 初始化OSS鉴权客户端
func (handler *Driver) InitOSSClient() error {
func (handler *Driver) InitOSSClient(forceUsePublicEndpoint bool) error {
if handler.Policy == nil {
return errors.New("存储策略为空")
}
if handler.client == nil {
// 决定是否使用内网 Endpoint
endpoint := handler.Policy.Server
if handler.Policy.OptionsSerialized.ServerSideEndpoint != "" && !forceUsePublicEndpoint {
endpoint = handler.Policy.OptionsSerialized.ServerSideEndpoint
}
// 初始化客户端
client, err := oss.New(handler.Policy.Server, handler.Policy.AccessKey, handler.Policy.SecretKey)
client, err := oss.New(endpoint, handler.Policy.AccessKey, handler.Policy.SecretKey)
if err != nil {
return err
}
@@ -104,7 +111,7 @@ func (handler *Driver) InitOSSClient() error {
// List 列出OSS上的文件
func (handler Driver) List(ctx context.Context, base string, recursive bool) ([]response.Object, error) {
// 初始化客户端
if err := handler.InitOSSClient(); err != nil {
if err := handler.InitOSSClient(false); err != nil {
return nil, err
}
@@ -178,6 +185,9 @@ func (handler Driver) Get(ctx context.Context, path string) (response.RSCloser,
// 通过VersionID禁止缓存
ctx = context.WithValue(ctx, VersionID, time.Now().UnixNano())
// 尽可能使用私有 Endpoint
ctx = context.WithValue(ctx, fsctx.ForceUsePublicEndpointCtx, false)
// 获取文件源地址
downloadURL, err := handler.Source(
ctx,
@@ -218,15 +228,22 @@ func (handler Driver) Put(ctx context.Context, file io.ReadCloser, dst string, s
defer file.Close()
// 初始化客户端
if err := handler.InitOSSClient(); err != nil {
if err := handler.InitOSSClient(false); err != nil {
return err
}
// 凭证有效期
credentialTTL := model.GetIntSetting("upload_credential_timeout", 3600)
// 是否允许覆盖
overwrite := true
if ctx.Value(fsctx.DisableOverwrite) != nil {
overwrite = false
}
options := []oss.Option{
oss.Expires(time.Now().Add(time.Duration(credentialTTL) * time.Second)),
oss.ForbidOverWrite(!overwrite),
}
// 上传文件
@@ -242,7 +259,7 @@ func (handler Driver) Put(ctx context.Context, file io.ReadCloser, dst string, s
// 返回未删除的文件
func (handler Driver) Delete(ctx context.Context, files []string) ([]string, error) {
// 初始化客户端
if err := handler.InitOSSClient(); err != nil {
if err := handler.InitOSSClient(false); err != nil {
return files, err
}
@@ -265,7 +282,7 @@ func (handler Driver) Delete(ctx context.Context, files []string) ([]string, err
// Thumb 获取文件缩略图
func (handler Driver) Thumb(ctx context.Context, path string) (*response.ContentResponse, error) {
// 初始化客户端
if err := handler.InitOSSClient(); err != nil {
if err := handler.InitOSSClient(true); err != nil {
return nil, err
}
@@ -306,7 +323,11 @@ func (handler Driver) Source(
speed int,
) (string, error) {
// 初始化客户端
if err := handler.InitOSSClient(); err != nil {
usePublicEndpoint := true
if forceUsePublicEndpoint, ok := ctx.Value(fsctx.ForceUsePublicEndpointCtx).(bool); ok {
usePublicEndpoint = forceUsePublicEndpoint
}
if err := handler.InitOSSClient(usePublicEndpoint); err != nil {
return "", err
}
+30 -9
View File
@@ -2,18 +2,19 @@ package oss
import (
"context"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
"github.com/HFO4/cloudreve/pkg/request"
"github.com/stretchr/testify/assert"
testMock "github.com/stretchr/testify/mock"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
"testing"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
"github.com/stretchr/testify/assert"
testMock "github.com/stretchr/testify/mock"
)
func TestDriver_InitOSSClient(t *testing.T) {
@@ -29,13 +30,19 @@ func TestDriver_InitOSSClient(t *testing.T) {
// 成功
{
asserts.NoError(handler.InitOSSClient())
asserts.NoError(handler.InitOSSClient(false))
}
// 使用内网Endpoint
{
handler.Policy.OptionsSerialized.ServerSideEndpoint = "endpoint2"
asserts.NoError(handler.InitOSSClient(false))
}
// 未指定存储策略
{
handler := Driver{}
asserts.Error(handler.InitOSSClient())
asserts.Error(handler.InitOSSClient(false))
}
}
@@ -181,6 +188,19 @@ func TestDriver_Source(t *testing.T) {
asserts.Empty(query.Get("Signature"))
asserts.Contains(resURL.String(), handler.Policy.BaseURL)
}
// 强制使用公网 Endpoint
{
handler.Policy.BaseURL = ""
handler.Policy.OptionsSerialized.ServerSideEndpoint = "endpoint.com"
res, err := handler.Source(context.WithValue(context.Background(), fsctx.ForceUsePublicEndpointCtx, false), "/123", url.URL{}, 10, false, 0)
asserts.NoError(err)
resURL, err := url.Parse(res)
asserts.NoError(err)
query := resURL.Query()
asserts.Empty(query.Get("Signature"))
asserts.Contains(resURL.String(), "endpoint.com")
}
}
func TestDriver_Thumb(t *testing.T) {
@@ -245,10 +265,11 @@ func TestDriver_Put(t *testing.T) {
},
}
cache.Set("setting_upload_credential_timeout", "3600", 0)
ctx := context.WithValue(context.Background(), fsctx.DisableOverwrite, true)
// 失败
{
err := handler.Put(context.Background(), ioutil.NopCloser(strings.NewReader("123")), "/123.txt", 3)
err := handler.Put(ctx, ioutil.NopCloser(strings.NewReader("123")), "/123.txt", 3)
asserts.Error(err)
}
}
+10 -9
View File
@@ -4,13 +4,6 @@ import (
"context"
"errors"
"fmt"
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/request"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/qiniu/api.v7/v7/auth/qbox"
"github.com/qiniu/api.v7/v7/storage"
"io"
"net/http"
"net/url"
@@ -18,6 +11,14 @@ import (
"path/filepath"
"strings"
"time"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/response"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/qiniu/api.v7/v7/auth/qbox"
"github.com/qiniu/api.v7/v7/storage"
)
// Driver 本地策略适配器
@@ -91,7 +92,7 @@ func (handler Driver) List(ctx context.Context, base string, recursive bool) ([]
RelativePath: filepath.ToSlash(rel),
Size: uint64(object.Fsize),
IsDir: false,
LastModify: time.Now(),
LastModify: time.Unix(object.PutTime/10000000, 0),
})
}
@@ -205,7 +206,7 @@ func (handler Driver) Delete(ctx context.Context, files []string) ([]string, err
if err != nil {
failed := make([]string, 0, len(rets))
for k, ret := range rets {
if ret.Code != 200 {
if ret.Code != 200 && ret.Code != 612 {
failed = append(failed, files[k])
}
}
+17 -7
View File
@@ -6,18 +6,19 @@ import (
"encoding/json"
"errors"
"fmt"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/auth"
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
"github.com/HFO4/cloudreve/pkg/filesystem/response"
"github.com/HFO4/cloudreve/pkg/request"
"github.com/HFO4/cloudreve/pkg/serializer"
"io"
"net/http"
"net/url"
"path"
"strings"
"time"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/auth"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/response"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
)
// Driver 远程存储策略适配器
@@ -154,6 +155,13 @@ func (handler Driver) Put(ctx context.Context, file io.ReadCloser, dst string, s
if err != nil {
return err
}
// 决定是否要禁用文件覆盖
overwrite := "true"
if ctx.Value(fsctx.DisableOverwrite) != nil {
overwrite = "false"
}
// 上传文件
resp, err := handler.Client.Request(
"POST",
@@ -163,6 +171,7 @@ func (handler Driver) Put(ctx context.Context, file io.ReadCloser, dst string, s
"Authorization": {credential.Token},
"X-Policy": {credential.Policy},
"X-FileName": {fileName},
"X-Overwrite": {overwrite},
}),
request.WithContentLength(int64(size)),
request.WithTimeout(time.Duration(0)),
@@ -320,7 +329,8 @@ func (handler Driver) getUploadCredential(ctx context.Context, policy serializer
// 签名上传策略
uploadRequest, _ := http.NewRequest("POST", "/api/v3/slave/upload", nil)
uploadRequest.Header = map[string][]string{
"X-Policy": {policyEncoded},
"X-Policy": {policyEncoded},
"X-Overwrite": {"false"},
}
auth.SignRequest(handler.AuthInstance, uploadRequest, TTL)
+11 -9
View File
@@ -2,20 +2,21 @@ package remote
import (
"context"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/auth"
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
"github.com/HFO4/cloudreve/pkg/request"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/stretchr/testify/assert"
testMock "github.com/stretchr/testify/mock"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
"testing"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/auth"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/stretchr/testify/assert"
testMock "github.com/stretchr/testify/mock"
)
func TestHandler_Token(t *testing.T) {
@@ -33,7 +34,7 @@ func TestHandler_Token(t *testing.T) {
},
AuthInstance: auth.HMACAuth{},
}
ctx := context.Background()
ctx := context.WithValue(context.Background(), fsctx.DisableOverwrite, true)
auth.General = auth.HMACAuth{SecretKey: []byte("test")}
// 成功
@@ -48,6 +49,7 @@ func TestHandler_Token(t *testing.T) {
asserts.Equal(true, policy.AutoRename)
asserts.Equal("dir", policy.SavePath)
asserts.Equal("file", policy.FileName)
asserts.Equal("file", policy.FileName)
asserts.Equal([]string{"txt"}, policy.AllowedExtension)
}
+459
View File
@@ -0,0 +1,459 @@
package s3
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"io"
"net/http"
"net/url"
"path"
"path/filepath"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/response"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
)
// Driver 适配器模板
type Driver struct {
Policy *model.Policy
sess *session.Session
svc *s3.S3
}
// UploadPolicy S3上传策略
type UploadPolicy struct {
Expiration string `json:"expiration"`
Conditions []interface{} `json:"conditions"`
}
//MetaData 文件信息
type MetaData struct {
Size uint64
Etag string
}
// InitS3Client 初始化S3会话
func (handler *Driver) InitS3Client() error {
if handler.Policy == nil {
return errors.New("存储策略为空")
}
if handler.svc == nil {
// 初始化会话
sess, err := session.NewSession(&aws.Config{
Credentials: credentials.NewStaticCredentials(handler.Policy.AccessKey, handler.Policy.SecretKey, ""),
Endpoint: &handler.Policy.Server,
Region: &handler.Policy.OptionsSerialized.Region,
S3ForcePathStyle: aws.Bool(true),
})
if err != nil {
return err
}
handler.sess = sess
handler.svc = s3.New(sess)
}
return nil
}
// List 列出给定路径下的文件
func (handler Driver) List(ctx context.Context, base string, recursive bool) ([]response.Object, error) {
// 初始化客户端
if err := handler.InitS3Client(); err != nil {
return nil, err
}
// 初始化列目录参数
base = strings.TrimPrefix(base, "/")
if base != "" {
base += "/"
}
opt := &s3.ListObjectsInput{
Bucket: &handler.Policy.BucketName,
Prefix: &base,
MaxKeys: aws.Int64(1000),
}
// 是否为递归列出
if !recursive {
opt.Delimiter = aws.String("/")
}
var (
objects []*s3.Object
commons []*s3.CommonPrefix
)
for {
res, err := handler.svc.ListObjectsWithContext(ctx, opt)
if err != nil {
return nil, err
}
objects = append(objects, res.Contents...)
commons = append(commons, res.CommonPrefixes...)
// 如果本次未列取完,则继续使用marker获取结果
if *res.IsTruncated {
opt.Marker = res.NextMarker
} else {
break
}
}
// 处理列取结果
res := make([]response.Object, 0, len(objects)+len(commons))
// 处理目录
for _, object := range commons {
rel, err := filepath.Rel(*opt.Prefix, *object.Prefix)
if err != nil {
continue
}
res = append(res, response.Object{
Name: path.Base(*object.Prefix),
RelativePath: filepath.ToSlash(rel),
Size: 0,
IsDir: true,
LastModify: time.Now(),
})
}
// 处理文件
for _, object := range objects {
rel, err := filepath.Rel(*opt.Prefix, *object.Key)
if err != nil {
continue
}
res = append(res, response.Object{
Name: path.Base(*object.Key),
Source: *object.Key,
RelativePath: filepath.ToSlash(rel),
Size: uint64(*object.Size),
IsDir: false,
LastModify: time.Now(),
})
}
return res, nil
}
// Get 获取文件
func (handler Driver) Get(ctx context.Context, path string) (response.RSCloser, error) {
// 获取文件源地址
downloadURL, err := handler.Source(
ctx,
path,
url.URL{},
int64(model.GetIntSetting("preview_timeout", 60)),
false,
0,
)
if err != nil {
return nil, err
}
// 获取文件数据流
client := request.HTTPClient{}
resp, err := client.Request(
"GET",
downloadURL,
nil,
request.WithContext(ctx),
request.WithHeader(
http.Header{"Cache-Control": {"no-cache", "no-store", "must-revalidate"}},
),
request.WithTimeout(time.Duration(0)),
).CheckHTTPResponse(200).GetRSCloser()
if err != nil {
return nil, err
}
resp.SetFirstFakeChunk()
// 尝试自主获取文件大小
if file, ok := ctx.Value(fsctx.FileModelCtx).(model.File); ok {
resp.SetContentLength(int64(file.Size))
}
return resp, nil
}
// Put 将文件流保存到指定目录
func (handler Driver) Put(ctx context.Context, file io.ReadCloser, dst string, size uint64) error {
// 初始化客户端
if err := handler.InitS3Client(); err != nil {
return err
}
uploader := s3manager.NewUploader(handler.sess)
_, err := uploader.Upload(&s3manager.UploadInput{
Bucket: &handler.Policy.BucketName,
Key: &dst,
Body: file,
})
if err != nil {
return err
}
return nil
}
// Delete 删除一个或多个文件,
// 返回未删除的文件,及遇到的最后一个错误
func (handler Driver) Delete(ctx context.Context, files []string) ([]string, error) {
// 初始化客户端
if err := handler.InitS3Client(); err != nil {
return files, err
}
failed := make([]string, 0, len(files))
deleted := make([]string, 0, len(files))
keys := make([]*s3.ObjectIdentifier, 0, len(files))
for _, file := range files {
filePath := file
keys = append(keys, &s3.ObjectIdentifier{Key: &filePath})
}
// 发送异步删除请求
res, err := handler.svc.DeleteObjects(
&s3.DeleteObjectsInput{
Bucket: &handler.Policy.BucketName,
Delete: &s3.Delete{
Objects: keys,
},
})
if err != nil {
return files, err
}
// 统计未删除的文件
for _, deleteRes := range res.Deleted {
deleted = append(deleted, *deleteRes.Key)
}
failed = util.SliceDifference(failed, deleted)
return failed, nil
}
// Thumb 获取文件缩略图
func (handler Driver) Thumb(ctx context.Context, path string) (*response.ContentResponse, error) {
return nil, errors.New("未实现")
}
// Source 获取外链URL
func (handler Driver) Source(
ctx context.Context,
path string,
baseURL url.URL,
ttl int64,
isDownload bool,
speed int,
) (string, error) {
// 尝试从上下文获取文件名
fileName := ""
if file, ok := ctx.Value(fsctx.FileModelCtx).(model.File); ok {
fileName = file.Name
}
// 初始化客户端
if err := handler.InitS3Client(); err != nil {
return "", err
}
req, _ := handler.svc.GetObjectRequest(
&s3.GetObjectInput{
Bucket: &handler.Policy.BucketName,
Key: &path,
ResponseContentDisposition: aws.String("attachment; filename=\"" + url.PathEscape(fileName) + "\""),
})
if ttl == 0 {
ttl = 3600
}
signedURL, _ := req.Presign(time.Duration(ttl) * time.Second)
// 将最终生成的签名URL域名换成用户自定义的加速域名(如果有)
finalURL, err := url.Parse(signedURL)
if err != nil {
return "", err
}
// 公有空间替换掉Key及不支持的头
if !handler.Policy.IsPrivate {
finalURL.RawQuery = ""
}
if handler.Policy.BaseURL != "" {
cdnURL, err := url.Parse(handler.Policy.BaseURL)
if err != nil {
return "", err
}
finalURL.Host = cdnURL.Host
finalURL.Scheme = cdnURL.Scheme
}
return finalURL.String(), nil
}
// Token 获取上传策略和认证Token
func (handler Driver) Token(ctx context.Context, TTL int64, key string) (serializer.UploadCredential, error) {
// 读取上下文中生成的存储路径和文件大小
savePath, ok := ctx.Value(fsctx.SavePathCtx).(string)
if !ok {
return serializer.UploadCredential{}, errors.New("无法获取存储路径")
}
// 生成回调地址
siteURL := model.GetSiteURL()
apiBaseURI, _ := url.Parse("/api/v3/callback/s3/" + key)
apiURL := siteURL.ResolveReference(apiBaseURI)
// 上传策略
putPolicy := UploadPolicy{
Expiration: time.Now().UTC().Add(time.Duration(TTL) * time.Second).Format(time.RFC3339),
Conditions: []interface{}{
map[string]string{"bucket": handler.Policy.BucketName},
[]string{"starts-with", "$key", savePath},
[]string{"starts-with", "$success_action_redirect", apiURL.String()},
[]string{"starts-with", "$name", ""},
[]string{"starts-with", "$Content-Type", ""},
map[string]string{"x-amz-algorithm": "AWS4-HMAC-SHA256"},
},
}
if handler.Policy.MaxSize > 0 {
putPolicy.Conditions = append(putPolicy.Conditions,
[]interface{}{"content-length-range", 0, handler.Policy.MaxSize})
}
// 生成上传凭证
return handler.getUploadCredential(ctx, putPolicy, apiURL)
}
// Meta 获取文件信息
func (handler Driver) Meta(ctx context.Context, path string) (*MetaData, error) {
// 初始化客户端
if err := handler.InitS3Client(); err != nil {
return nil, err
}
res, err := handler.svc.GetObject(
&s3.GetObjectInput{
Bucket: &handler.Policy.BucketName,
Key: &path,
})
if err != nil {
return nil, err
}
return &MetaData{
Size: uint64(*res.ContentLength),
Etag: *res.ETag,
}, nil
}
func (handler Driver) getUploadCredential(ctx context.Context, policy UploadPolicy, callback *url.URL) (serializer.UploadCredential, error) {
// 读取上下文中生成的存储路径和文件大小
savePath, ok := ctx.Value(fsctx.SavePathCtx).(string)
if !ok {
return serializer.UploadCredential{}, errors.New("无法获取存储路径")
}
longDate := time.Now().UTC().Format("20060102T150405Z")
shortDate := time.Now().UTC().Format("20060102")
credential := handler.Policy.AccessKey + "/" + shortDate + "/" + handler.Policy.OptionsSerialized.Region + "/s3/aws4_request"
policy.Conditions = append(policy.Conditions, map[string]string{"x-amz-credential": credential})
policy.Conditions = append(policy.Conditions, map[string]string{"x-amz-date": longDate})
// 编码上传策略
policyJSON, err := json.Marshal(policy)
if err != nil {
return serializer.UploadCredential{}, err
}
policyEncoded := base64.StdEncoding.EncodeToString(policyJSON)
//签名
signature := getHMAC([]byte("AWS4"+handler.Policy.SecretKey), []byte(shortDate))
signature = getHMAC(signature, []byte(handler.Policy.OptionsSerialized.Region))
signature = getHMAC(signature, []byte("s3"))
signature = getHMAC(signature, []byte("aws4_request"))
signature = getHMAC(signature, []byte(policyEncoded))
return serializer.UploadCredential{
Policy: policyEncoded,
Callback: callback.String(),
Token: hex.EncodeToString(signature),
AccessKey: credential,
Path: savePath,
KeyTime: longDate,
}, nil
}
func getHMAC(key []byte, data []byte) []byte {
hash := hmac.New(sha256.New, key)
hash.Write(data)
return hash.Sum(nil)
}
// CORS 创建跨域策略
func (handler Driver) CORS() error {
// 初始化客户端
if err := handler.InitS3Client(); err != nil {
return err
}
rule := s3.CORSRule{
AllowedMethods: aws.StringSlice([]string{
"GET",
"POST",
"PUT",
"DELETE",
"HEAD",
}),
AllowedOrigins: aws.StringSlice([]string{"*"}),
AllowedHeaders: aws.StringSlice([]string{"*"}),
MaxAgeSeconds: aws.Int64(3600),
}
_, err := handler.svc.PutBucketCors(&s3.PutBucketCorsInput{
Bucket: &handler.Policy.BucketName,
CORSConfiguration: &s3.CORSConfiguration{
CORSRules: []*s3.CORSRule{&rule},
},
})
return err
}
+4 -3
View File
@@ -3,11 +3,12 @@ package template
import (
"context"
"errors"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/filesystem/response"
"github.com/HFO4/cloudreve/pkg/serializer"
"io"
"net/url"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/response"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
)
// Driver 适配器模板
+8 -7
View File
@@ -9,12 +9,6 @@ import (
"encoding/json"
"errors"
"fmt"
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/request"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/upyun/go-sdk/upyun"
"io"
"net/http"
"net/url"
@@ -23,6 +17,13 @@ import (
"strings"
"sync"
"time"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/response"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/upyun/go-sdk/upyun"
)
// UploadPolicy 又拍云上传策略
@@ -271,7 +272,7 @@ func (handler Driver) Source(
return "", err
}
fileKey, err := url.Parse(path)
fileKey, err := url.Parse(url.PathEscape(path))
if err != nil {
return "", err
}

Some files were not shown because too many files have changed in this diff Show More