Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f779053040 | ||
|
|
6becac74d0 | ||
|
|
e64a73eb67 | ||
|
|
cffa82226d | ||
|
|
09d6db68ff | ||
|
|
f33145d02a | ||
|
|
a74e4f1a13 | ||
|
|
9344a831be | ||
|
|
4fd7e89ff8 | ||
|
|
d0a2774f65 | ||
|
|
fb563396fe | ||
|
|
73ad9f49ef | ||
|
|
10e6bf81f9 | ||
|
|
274b49d66f | ||
|
|
ab6749056a | ||
|
|
3be2e354c4 | ||
|
|
0c825c65d4 | ||
|
|
c6836f3654 | ||
|
|
c6a5db7740 | ||
|
|
179f34fad2 | ||
|
|
161096fbb2 | ||
|
|
69412300b6 | ||
|
|
329751350d | ||
|
|
90675e3ae8 | ||
|
|
238125eb8d | ||
|
|
d30f02b1c0 | ||
|
|
d9ec47f8ea | ||
|
|
6020915993 | ||
|
|
07ed4f7922 | ||
|
|
13b6d7f98d | ||
|
|
3752d42de8 | ||
|
|
847bcd746c | ||
|
|
e1e46457e7 | ||
|
|
a2751deac6 | ||
|
|
74c11e7539 | ||
|
|
cb0ee30c6b | ||
|
|
c518c0c1e6 | ||
|
|
8827c45df1 | ||
|
|
b91518136b | ||
|
|
b46cb2501b | ||
|
|
c1823e7a83 | ||
|
|
84d5f4e5e2 | ||
|
|
ff7416f858 | ||
|
|
b6b4cb1ce4 | ||
|
|
617d5df294 | ||
|
|
ff87ea8e39 | ||
|
|
36581ded16 | ||
|
|
333682325c | ||
|
|
1a1d9e8adf | ||
|
|
020e7b0944 | ||
|
|
e15bd00524 | ||
|
|
de1e2dbcbb | ||
|
|
7b24f1dedf | ||
|
|
ce94a35ed7 | ||
|
|
8a0c66ceff | ||
|
|
a92e172d26 | ||
|
|
a7bcc4d4fb | ||
|
|
19cfd3daf4 | ||
|
|
b82bac40fc | ||
|
|
21d61d9cd2 | ||
|
|
76e7359c69 | ||
|
|
82047f4504 | ||
|
|
c970acbe8b | ||
|
|
988d2caced | ||
|
|
c5111929a3 | ||
|
|
b6b72e0273 | ||
|
|
0d43439200 | ||
|
|
da0e68f95c | ||
|
|
fbea823ba1 | ||
|
|
7adf56ce0a | ||
|
|
7f394b327b | ||
|
|
7fe673068b | ||
|
|
f16d48b432 | ||
|
|
192df21da4 | ||
|
|
02e0f10016 | ||
|
|
24e8dc4e9f | ||
|
|
dbeef1cee4 | ||
|
|
096eb7c73e | ||
|
|
69820bfcb9 | ||
|
|
3c3b7c4389 | ||
|
|
822ebf4888 | ||
|
|
3b6c43ee23 | ||
|
|
c711423ef9 | ||
|
|
948c37c039 | ||
|
|
1f8e3cfb7a | ||
|
|
7b875c81d1 | ||
|
|
3fb1d874d5 | ||
|
|
601c60fca7 | ||
|
|
d052f75a1c | ||
|
|
51c81e19af | ||
|
|
480cbb73ea | ||
|
|
4a1e4c1d68 | ||
|
|
76c763b84d | ||
|
|
ed0aa017db | ||
|
|
a314d4dc54 | ||
|
|
4bfae38176 | ||
|
|
1f047e25c7 | ||
|
|
ff81448611 | ||
|
|
83cf140fa1 | ||
|
|
8fc1cd0430 | ||
|
|
7ff7907e11 | ||
|
|
4611defe36 | ||
|
|
eb89807cf1 | ||
|
|
bea9a0a56d | ||
|
|
fc2df7e501 | ||
|
|
bab3e80aec | ||
|
|
a556813b01 | ||
|
|
e90f922b58 | ||
|
|
7671564ea3 | ||
|
|
402dc5d970 | ||
|
|
51b0668b5d | ||
|
|
8686551e71 | ||
|
|
437e2c602a | ||
|
|
fe00d39bc1 | ||
|
|
5cb7998f12 | ||
|
|
6cec43bee6 |
@@ -12,10 +12,7 @@
|
||||
3. core/config.go
|
||||
VERSION = x.x.x
|
||||
|
||||
4. tank-front 修改 vuex/index.js
|
||||
versionName: 'x.x.x'
|
||||
|
||||
5. readme.md 修改 eyeblue/tank:x.x.x
|
||||
4. readme.md 修改 eyeblue/tank:x.x.x
|
||||
eyeblue/tank:x.x.x
|
||||
|
||||
|
||||
@@ -30,8 +27,8 @@ docker container prune
|
||||
# 构建新版本 (最后的.不能少)
|
||||
docker build -t eyeblue/tank:x.x.x .
|
||||
# 本地测试
|
||||
docker run --name dockermysql -p 13306:3306 -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_DATABASE=tank -e MYSQL_USER=tank -e MYSQL_PASSWORD=tank123 -v ~/data/dockermysql:/var/lib/mysql -d mysql:5.7
|
||||
docker run --name tank -p 6010:6010 --link dockermysql:mysql -v ~/data/dockermatter:/data/matter -d eyeblue/tank:x.x.x
|
||||
docker run --name dockermysql -p 13306:3306 -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_DATABASE=tank -e MYSQL_USER=tank -e MYSQL_PASSWORD=tank123 -v ~/data/dockermysql:/var/lib/mysql -e TZ=Asia/Shanghai -d mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
docker run --name tank -p 6010:6010 --link dockermysql:mysql -v ~/data/dockermatter:/data/build/matter -d eyeblue/tank:x.x.x
|
||||
# 没问题后,推送到远程
|
||||
docker push eyeblue/tank:x.x.x
|
||||
```
|
||||
@@ -50,6 +47,95 @@ c. 在github上发布新版本。
|
||||
|
||||
3.更新tank-doc 下载内容
|
||||
|
||||
## useful commands
|
||||
go get xxx/xxx
|
||||
go mod tidy
|
||||
|
||||
|
||||
tank-3.1.5 2022-03-21
|
||||
1. Support Sqlite.
|
||||
2. Upgrade webpack.
|
||||
|
||||
1. 支持Sqlite
|
||||
2. 升级了前端的webpack
|
||||
|
||||
|
||||
tank-3.1.4 2022-03-20
|
||||
1. Support Sqlite.
|
||||
2. Upgrade webpack.
|
||||
|
||||
1. 支持Sqlite
|
||||
2. 升级了前端的webpack
|
||||
|
||||
|
||||
|
||||
tank-3.1.3 2022-03-10
|
||||
1. Add kkfileview 3.x support
|
||||
2. Fix search bug https://github.com/eyebluecn/tank/issues/148
|
||||
3. Fix bug of totalSize in dashboard
|
||||
|
||||
1. 添加了kkfileview 3.x的预览支持
|
||||
2. 修复了一个搜索的bug https://github.com/eyebluecn/tank/issues/148
|
||||
3. 修复了文件大小统计的bug
|
||||
|
||||
|
||||
tank-3.1.2 2021-01-02
|
||||
1. Add scan manually once feature
|
||||
2. Add disable recycle bin feature
|
||||
3. WebDAV use hard delete now
|
||||
4. Support Chinese Username
|
||||
5. Normal user cannot change itself space limit
|
||||
6. Support the order sort by filename, size and update time
|
||||
7. Hide the side menu on phone by default
|
||||
8. Friendly hint for large file upload.
|
||||
9. Cancel during uploading.
|
||||
|
||||
1. 添加了手动扫描一次的功能
|
||||
2. 添加了禁用回收站功能
|
||||
3. WebDAV中的删除采用硬删除
|
||||
4. 支持中文的用户名
|
||||
5. 普通用户不能再修改自己的空间限制
|
||||
6. 文件支持按照名称排序,按照大小排序,按照修改日期排序
|
||||
7. 手机端默认隐藏左边菜单
|
||||
8. 大文件上传友好性提示
|
||||
9. 可以取消文件上传
|
||||
|
||||
|
||||
tank-3.1.1 2020-09-08
|
||||
1. Add upload folder feature.
|
||||
2. Fix the share auth bug.
|
||||
|
||||
1. 添加了文件夹上传功能
|
||||
2. 修复了分享时需要登陆的问题
|
||||
|
||||
tank-3.1.0
|
||||
1. Reconstruct frontend by typescript+react+antd.
|
||||
2. Enhance the webdav ability.
|
||||
3. Add charset in mysql config.
|
||||
4. Add preview_config.
|
||||
5. Add delete user feature.
|
||||
6. Add Scan config.
|
||||
7. Add recycle bin feature.
|
||||
|
||||
1. 前后进行了一次完全的重构。 采用Typescript+React+Antd
|
||||
2. 增强了WebDav的能力,现在能够适配更多的WebDav客户端了
|
||||
3. 数据库配置项中添加了编码支持,可以支持emoji了
|
||||
4. 添加了预览引擎配置,用户可以使用自己的引擎来预览特定格式文件
|
||||
5. 添加了删除用户的功能,删除用户后会清空用户所有的文件和痕迹
|
||||
6. 添加了扫描功能,可以定时同步物理文件到蓝眼云盘中
|
||||
7. 增加回收站功能
|
||||
|
||||
|
||||
2019-10-13
|
||||
tank-3.0.6
|
||||
1. Fix the Bug of limit size.
|
||||
2. Add the drag feature for uploading files.
|
||||
3. Fix the page bug of list.
|
||||
|
||||
2019-08-13
|
||||
tank-3.0.5
|
||||
1. Fix the Bug of sharing. [#56](https://github.com/eyebluecn/tank/issues/56)
|
||||
|
||||
2019-06-11
|
||||
tank-3.0.4
|
||||
1. Use relative assets in tank-front.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# use golang 1.12
|
||||
FROM golang:1.12
|
||||
# use golang 1.17
|
||||
FROM golang:1.17
|
||||
|
||||
# maintainer. Author's name and email.
|
||||
MAINTAINER eyeblue "eyebluecn@126.com"
|
||||
@@ -14,10 +14,14 @@ COPY . /data
|
||||
VOLUME /data/build/matter
|
||||
|
||||
# proxy
|
||||
ENV GOPROXY=https://athens.azurefd.net
|
||||
ENV GOPROXY=https://goproxy.cn
|
||||
|
||||
# Handle time-zone
|
||||
ENV TZ=Asia/Shanghai
|
||||
RUN apt-get install -y tzdata
|
||||
|
||||
# prepare the config file
|
||||
RUN go build -mod=readonly \
|
||||
RUN go build \
|
||||
&& cp -r /data/tank /data/build
|
||||
|
||||
# use 6010 as default.
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
[](https://github.com/eyebluecn/tank)
|
||||
[](https://github.com/eyebluecn/tank)
|
||||
|
||||
[English Version](./README_EN.md)
|
||||
[English Version](https://tank-doc.eyeblue.cn/en)
|
||||
|
||||
# 蓝眼云盘(3.0.4)
|
||||
# 蓝眼云盘(3.1.6)
|
||||
[在线Demo](https://tanker.eyeblue.cn) (体验账号: demo 密码:123456)
|
||||
|
||||
[在线Demo](https://tank.eyeblue.cn) (体验账号: demo 密码:123456)
|
||||
蓝眼云盘是蓝眼开源系列代表作品之一,致力于打造精致,优雅,简约的云盘。核心功能如下:
|
||||
1. 文件列表
|
||||
2. 文件/文件夹上传 + 拖拽上传
|
||||
3. 文件分享
|
||||
4. 文件监控统计
|
||||
5. 回收站
|
||||
6. 多用户
|
||||
7. [WebDav](https://tank-doc.eyeblue.cn/advance/webdav.html)
|
||||
8. [扫描磁盘任务](https://tank-doc.eyeblue.cn/advance/scan.html)
|
||||
9. [在线预览及自定义配置预览引擎](https://tank-doc.eyeblue.cn/advance/preview.html)
|
||||
10. 支持Sqlite和Mysql数据库用以存储文件元信息,Sqlite可以做到开箱即用。
|
||||
|
||||
[文档](https://tank-doc.eyeblue.cn/)
|
||||
[更多内容请移步至文档](https://tank-doc.eyeblue.cn/)
|
||||
|
||||
### 软件截图
|
||||
|
||||
@@ -18,27 +29,12 @@
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
#### 手机端截图
|
||||
|
||||

|
||||
|
||||
|
||||
#### [安装文档](https://tank-doc.eyeblue.cn/zh/basic/install.html)
|
||||
|
||||
|
||||
### 相关文档
|
||||
|
||||
[蓝眼云盘后端api](https://github.com/eyebluecn/tank/blob/master/build/doc/api_zh.md)
|
||||
|
||||
[蓝眼云盘编程接口](https://github.com/eyebluecn/tank/blob/master/build/doc/alien_zh.md)
|
||||
|
||||
[快速使用Let's Encrypt开启个人网站的https](https://blog.eyeblue.cn/home/article/9f580b3f-5679-4a9d-be6f-4d9f0dd417af)
|
||||
|
||||
[Docker 化你的开源项目](https://blog.eyeblue.cn/home/article/510f9316-9ca1-40fe-b1b3-5285505a527d)
|
||||
## [安装文档](https://tank-doc.eyeblue.cn/basic/install.html)
|
||||
|
||||
### Contribution
|
||||
|
||||
@@ -46,10 +42,19 @@
|
||||
|
||||
如果您也想参与进来,请尽情的fork, star, post issue, pull requests
|
||||
|
||||
当然你可以加入钉钉群一起直接交流
|
||||
当然你可以加入钉钉群一起直接交流,在钉钉群中可以获取最新beta版本。
|
||||
|
||||

|
||||
|
||||
群号:23156361
|
||||
|
||||
|
||||
### Support
|
||||
IDE for this project is supported by [Jetbrains](https://jb.gg/OpenSourceSupport).
|
||||
|
||||
[](https://jb.gg/OpenSourceSupport)
|
||||
|
||||
|
||||
### License
|
||||
|
||||
[MIT](http://opensource.org/licenses/MIT)
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
[](https://github.com/eyebluecn/tank)
|
||||
|
||||
[中文版](./README_EN.md)
|
||||
|
||||
# Eyeblue Cloud Disk(2.0.0)
|
||||
|
||||
[Online Demo](https://tank.eyeblue.cn) (demo@tank.eyeblue.cn 123456)
|
||||
|
||||
 [tank backend](https://github.com/eyebluecn/tank)
|
||||
|
||||
 [tank-front frontend](https://github.com/eyebluecn/tank-front)
|
||||
|
||||
### Description
|
||||
Eyeblue Cloud Disk is one of [Eyeblue Softwares](https://github.com/eyebluecn), and it is the basic software for other eyeblue softwares.Eyeblue Cloud Disk has the following features.
|
||||
|
||||
**1. Easy to use**
|
||||
|
||||
- Docker available
|
||||
|
||||
- [Install package](https://github.com/eyebluecn/tank/releases) is about 10M. Cross platform.
|
||||
|
||||
- Unpack the install zip, and run it with one command.
|
||||
|
||||
- Installation Guide
|
||||
|
||||
- Dashboard to view UV and PV.
|
||||
|
||||
- Preview usual files (doc,ppt,xls,pdf,mp3,mp4,jpg,png)
|
||||
|
||||
**2. Core features all in readiness**
|
||||
|
||||
- Manage your files on cloud.
|
||||
|
||||
- Responsive on PC and mobile.
|
||||
|
||||
- Normal user and administrator
|
||||
|
||||
**3. Custom Api supported**
|
||||
|
||||
- Support [Custom Api](https://github.com/eyebluecn/tank/blob/master/build/doc/alien_zh.md)
|
||||
|
||||
- Manage files by api.
|
||||
|
||||
If this project is helpful for you, star ⭐ it.
|
||||
|
||||
### Screen Shot
|
||||
|
||||
#### PC
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
#### Mobile
|
||||
|
||||

|
||||
|
||||
|
||||
### Installation by Docker
|
||||
|
||||
##### a) Run
|
||||
```
|
||||
docker run --name tank -p 6010:6010 -d eyeblue/tank:2.0.0
|
||||
```
|
||||
If you'd like to start you mysql by docker, use the following command.
|
||||
```
|
||||
docker run --name mysql4tank -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_DATABASE=tank -e MYSQL_USER=tank -e MYSQL_PASSWORD=tank123 -v ~/data/mysqldemo1:/var/lib/mysql -d mysql:5.7
|
||||
```
|
||||
|
||||
##### b) Validation
|
||||
Open http://127.0.0.1:6010 it succeed if you see the installation guide.
|
||||
|
||||
### Installation by installation package (windows/linux almost the same)
|
||||
|
||||
##### a) Prepare
|
||||
|
||||
1. [Mysql](https://www.mysql.com/downloads/) (Use utf8 charset)
|
||||
|
||||
2. [Here](https://github.com/eyebluecn/tank/releases) download installation package.
|
||||
|
||||
3. Unzip on server
|
||||
|
||||
##### b) Run
|
||||
|
||||
- Double click `tank.exe` on windows
|
||||
|
||||
- Run the following command on linux
|
||||
|
||||
```
|
||||
cd ApplicationDirectory
|
||||
./tank
|
||||
```
|
||||
|
||||
If you'd like to run in daemon mode.
|
||||
```shell
|
||||
# Run the application
|
||||
cd ApplicationDirectory/service
|
||||
./startup.sh
|
||||
|
||||
# Stop the application
|
||||
cd ApplicationDirectory/service
|
||||
./shutdown.sh
|
||||
|
||||
```
|
||||
|
||||
##### c) Validation
|
||||
|
||||
Open http://127.0.0.1:6010 it succeed if you see the installation guide.
|
||||
|
||||
### Reference
|
||||
|
||||
[api](https://github.com/eyebluecn/tank/blob/master/build/doc/api_zh.md)
|
||||
|
||||
[Custom API](https://github.com/eyebluecn/tank/blob/master/build/doc/alien_zh.md)
|
||||
|
||||
### Contribution
|
||||
|
||||
Thanks these authors [@zicla](https://github.com/zicla),[@seaheart](https://github.com/seaheart),[@yemuhe](https://github.com/yemuhe),[@hxsherry](https://github.com/hxsherry)
|
||||
|
||||
You can join us by scanning the following qcode.
|
||||
|
||||

|
||||
|
||||
### License
|
||||
|
||||
[MIT](http://opensource.org/licenses/MIT)
|
||||
|
||||
Copyright (c) 2017-present, eyeblue.cn
|
||||
@@ -0,0 +1,16 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| master | :white_check_mark: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We strongly encourage you to report security vulnerabilities to
|
||||
our private security mailing list: security@cilium.io - first, before
|
||||
disclosing them in any public forums.
|
||||
|
||||
This is a private mailing list where only members of the Cilium internal
|
||||
security team are subscribed to, and is treated as top priority.
|
||||
@@ -1,13 +0,0 @@
|
||||
# Eyeblue Tank
|
||||
|
||||
> The document is comming...
|
||||
|
||||
|
||||
go build -mod=readonly
|
||||
|
||||
|
||||
|
||||
### Program Arguments
|
||||
|
||||
-mode=mirror -username=admin -password=123456 -src=/Users/fusu/d/temp -dest=/morning
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# 蓝眼云盘
|
||||
|
||||
> 详细文档正在撰写中,敬请期待...
|
||||
@@ -1,132 +0,0 @@
|
||||
# 蓝眼云盘编程接口
|
||||
|
||||
- 蓝眼云盘提供了[编程接口](https://github.com/eyebluecn/tank/blob/master/build/doc/alien_zh.md),实现了云存储(如:[七牛云](https://www.qiniu.com),[阿里云OSS](https://www.aliyun.com/product/oss))的核心功能,可以使用编程接口上传文件,作为其他网站、系统、app的资源存储器。可以在下载图片时对图片做缩放裁剪处理,可以有效地节省客户端流量。
|
||||
|
||||
- 蓝眼系列开源软件之二的[《蓝眼博客》](https://github.com/eyebluecn/blog)正是使用蓝眼博客作为第三方资源存储器。蓝眼博客中的所有图片,附件均是存储在蓝眼云盘中。
|
||||
|
||||
所有的编程接口均定义在`alien_controller`中,主要包括以下几个接口:
|
||||
|
||||
`/api/alien/fetch/upload/token` 获取上传token
|
||||
|
||||
`/api/alien/fetch/download/token` 获取下载token
|
||||
|
||||
`/api/alien/confirm` 来蓝眼云盘确认文件
|
||||
|
||||
`/api/alien/upload` 使用form表单上传文件
|
||||
|
||||
`/api/alien/download/{uuid}/{filename}` 下载文件
|
||||
|
||||
### 上传时序图
|
||||
|
||||

|
||||
|
||||
### 下载时序图
|
||||
|
||||

|
||||
|
||||
### 接口详情
|
||||
|
||||
#### /api/alien/fetch/upload/token
|
||||
功能:一个蓝眼云盘受信任的用户请求一个`UploadToken`,用于给另一个用户向蓝眼云盘上传文件。
|
||||
|
||||
一般的使用场景是`应用服务器`向`蓝眼云盘`请求`UploadToken`,然后将此`UploadToken`交由`浏览器`去向`蓝眼云盘`上传文件。
|
||||
|
||||
参数 | 类型 | 描述
|
||||
--------- | ---- | -----------
|
||||
email | `string` | 【必填】邮箱,用于确定请求者身份
|
||||
password | `string` | 【必填】密码,用于确定请求者身份
|
||||
filename | `string` | 【必填】即将上传的文件名,不能包含以下特殊符号:`< > \| * ? / \`
|
||||
expire | `int` | 【选填】UploadToken过期时间,单位:s。默认 86400s 即24h
|
||||
privacy | `bool` | 【必填】文件的共有性。`true`表示文件私有,下载时必须要DownloadToken. `false`表示文件公有,任何人可以通过下载链接直接下载
|
||||
size | `int` | 【必填】文件的大小。单位:byte
|
||||
dir | `string` | 【必填】文件存放的路径。不能为空,必须以`/`开头,不能出现连续的`//`,不能包含以下特殊符号:`< > \| * ? \`。举例:`/app/blog/20180101121212001`
|
||||
|
||||
#### /api/alien/upload
|
||||
|
||||
功能:浏览器拿着`UploadToken`通过FormData向蓝眼云盘上传文件。
|
||||
|
||||
一般的使用场景是`应用服务器`向`蓝眼云盘`请求`UploadToken`,然后将此`UploadToken`交由`浏览器`去向`蓝眼云盘`上传文件。由于在请求`UploadToken`的时候已经传入了文件元信息,因此这里的文件信息必须要和`/api/alien/fetch/upload/token`传入的参数信息一致。
|
||||
|
||||
参数 | 类型 | 描述
|
||||
--------- | ---- | -----------
|
||||
uploadTokenUuid | `string` | 【必填】uploadToken标识,`/api/alien/fetch/upload/token`请求返回对象中的`uuid`
|
||||
file | `file` | 【必填】文件,在浏览器中是通过`<input type="file" name="file"/>`来选择的
|
||||
|
||||
#### /api/alien/confirm
|
||||
|
||||
功能:`应用服务器`向蓝眼云盘确认某个文件是否确实已经上传好了。
|
||||
|
||||
参数 | 类型 | 描述
|
||||
--------- | ---- | -----------
|
||||
email | `string` | 【必填】邮箱,用于确定请求者身份
|
||||
password | `string` | 【必填】密码,用于确定请求者身份
|
||||
matterUuid | `string` | 【必填】浏览器上传完毕后,蓝眼云盘返回给浏览器的`uuid`
|
||||
|
||||
#### /api/alien/fetch/download/token
|
||||
|
||||
功能:一个蓝眼云盘受信任的用户请求一个`DownloadToken`,用于给另一个用户下载蓝眼云盘上的私有文件。
|
||||
|
||||
一般的使用场景是`应用服务器`向`蓝眼云盘`请求`DownloadToken`,然后将此`DownloadToken`交由`浏览器`去向`蓝眼云盘`下载文件。
|
||||
|
||||
参数 | 类型 | 描述
|
||||
--------- | ---- | -----------
|
||||
email | `string` | 【必填】邮箱,用于确定请求者身份
|
||||
password | `string` | 【必填】密码,用于确定请求者身份
|
||||
matterUuid | `string` | 【必填】文件uuid,要想下载的文件`uuid`
|
||||
expire | `int` | 【选填】UploadToken过期时间,单位:s。默认 86400s 即24h
|
||||
|
||||
|
||||
#### /api/alien/download/{uuid}/{filename}
|
||||
|
||||
功能:在浏览器中下载文件
|
||||
|
||||
这个接口既可以下载公有文件,又可以下载私有文件。同时对于图片文件还可以做裁剪缩放等操作。
|
||||
|
||||
参数 | 类型 | 描述
|
||||
--------- | ---- | -----------
|
||||
uuid | `string` | 【必填】文件的uuid,该参数放在url的路径中
|
||||
filename | `string` | 【必填】文件的名称,该参数放在url的路径中
|
||||
downloadTokenUuid | `string` | 【选填】download的uuid,如果是私有文件该参数必须,公有文件无需填写。
|
||||
|
||||
该接口同时还可以对图片进行缩放预处理
|
||||
> 图片缩放支持的格式有:".jpg", ".jpeg", ".png", ".tif", ".tiff", ".bmp", ".gif"
|
||||
|
||||
##### 额外参数
|
||||
|
||||
| 参数 | 类型 | 描述 | 取值范围 |
|
||||
| ------------ | ---- | ------------ | ------------ |
|
||||
| imageProcess | `string` | 指定图片处理的方式,对于图片缩放固定为`resize` | 固定为`resize` |
|
||||
| imageResizeM | `string` | 指定图片缩放的策略,有三种策略,`fit` 表示固定一边,另一边按比例缩放;`fill`表示先将图片延伸出指定W与H的矩形框外,然后进行居中裁剪;`fixed`表示直接按照指定的W和H缩放图片,这种方式可能导致图片变形 | [`fit`,`fill`,`fixed`] 不填默认`fit` |
|
||||
| imageResizeW | `int` | 指定的宽度,对于`fit`可以不指定 | 1 ~ 4096 |
|
||||
| imageResizeH | `int` | 指定的高度,对于`fit`可以不指定 | 1 ~ 4096 |
|
||||
|
||||
##### 示例
|
||||
|
||||
原图:
|
||||
|
||||

|
||||
|
||||
1. 将宽度指定为200,高度等比例缩放
|
||||
|
||||

|
||||
|
||||
[http://tank.eyeblue.cn/api/alien/download/3f4b3090-e688-4d63-7705-93a120690505/horse.jpg?imageProcess=resize&imageResizeM=fit&imageResizeW=200](http://tank.eyeblue.cn/api/alien/download/3f4b3090-e688-4d63-7705-93a120690505/horse.jpg?imageProcess=resize&imageResizeM=fit&imageResizeW=200)
|
||||
|
||||
2. 将高度指定为200,宽度等比例缩放
|
||||
|
||||

|
||||
|
||||
[http://tank.eyeblue.cn/api/alien/download/3f4b3090-e688-4d63-7705-93a120690505/horse.jpg?imageProcess=resize&imageResizeM=fit&imageResizeH=200](http://tank.eyeblue.cn/api/alien/download/3f4b3090-e688-4d63-7705-93a120690505/horse.jpg?imageProcess=resize&imageResizeM=fit&imageResizeH=200)
|
||||
|
||||
|
||||
3. 图片自动填充在200*200的大小中 (这种情况用得最多)
|
||||
|
||||

|
||||
|
||||
[http://tank.eyeblue.cn/api/alien/download/3f4b3090-e688-4d63-7705-93a120690505/horse.jpg?imageProcess=resize&imageResizeM=fill&imageResizeW=200&imageResizeH=200](http://tank.eyeblue.cn/api/alien/download/3f4b3090-e688-4d63-7705-93a120690505/horse.jpg?imageProcess=resize&imageResizeM=fill&imageResizeW=200&imageResizeH=200)
|
||||
|
||||
4. 图片固定大小200*200 (一般会导致变形)
|
||||
|
||||

|
||||
|
||||
[http://tank.eyeblue.cn/api/alien/download/3f4b3090-e688-4d63-7705-93a120690505/horse.jpg?imageProcess=resize&imageResizeM=fixed&imageResizeW=200&imageResizeH=200](http://tank.eyeblue.cn/api/alien/download/3f4b3090-e688-4d63-7705-93a120690505/horse.jpg?imageProcess=resize&imageResizeM=fixed&imageResizeW=200&imageResizeH=200)
|
||||
@@ -1,798 +0,0 @@
|
||||
|
||||
# 蓝眼云盘api接口
|
||||
|
||||
|
||||
|
||||
|
||||
## 一、实体
|
||||
|
||||
在详细介绍各controller中的接口前,有必要先介绍蓝眼云盘中的各实体,所有的实体基类均为`Base`
|
||||
|
||||
#### Base
|
||||
|
||||
`Base`定义如下,所有会在数据库中持久化的实体均会继承`Base`,`Controller`在返回实体给前端时,会将字段和值序列化成json字符串,其中键就和每个实体字段后面的`json`标签一致,下文也会有详细例子介绍
|
||||
|
||||
```
|
||||
type Base struct {
|
||||
//唯一标识
|
||||
Uuid string `gorm:"primary_key" json:"uuid"`
|
||||
//排序用的字段,一般是时间戳来表示序号先后
|
||||
Sort int64 `json:"sort"`
|
||||
//修改时间
|
||||
ModifyTime time.Time `json:"modifyTime"`
|
||||
//创建时间
|
||||
CreateTime time.Time `json:"createTime"`
|
||||
}
|
||||
```
|
||||
|
||||
#### Preference
|
||||
|
||||
`Preference`是整个网站的偏好设置,网站的名称,logo,favicon,版权,备案号等信息均由这个实体负责。定义如下:
|
||||
|
||||
```
|
||||
type Preference struct {
|
||||
//继承Base,也就是说Base中的Uuid,Sort,ModifyTime,CreateTime这里也会有
|
||||
Base
|
||||
//网站名称
|
||||
Name string `json:"name"`
|
||||
//网站的logo url
|
||||
LogoUrl string `json:"logoUrl"`
|
||||
//网站的favicon.ico url
|
||||
FaviconUrl string `json:"faviconUrl"`
|
||||
//网站下方第一行信息,一般是版权信息
|
||||
FooterLine1 string `json:"footerLine1"`
|
||||
//网站下方的第二行信息,一般是本案信息
|
||||
FooterLine2 string `json:"footerLine2"`
|
||||
//当前运行的蓝眼博客版本,这个字段不可修改,每次发版时硬编码
|
||||
Version string `json:"version"`
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### Matter
|
||||
|
||||
`Matter`是代表文件(文件夹是一种特殊的文件),为了和系统的`file`重复,这里使用`matter`,这个实体是蓝眼云盘最重要的实体:
|
||||
|
||||
```
|
||||
type Matter struct {
|
||||
//继承Base,也就是说Base中的Uuid,Sort,ModifyTime,CreateTime这里也会有
|
||||
Base
|
||||
//所在的文件夹的uuid,如果在根目录下,这个字段为 root
|
||||
Puuid string `json:"puuid"`
|
||||
//创建这个文件的用户uuid
|
||||
UserUuid string `json:"userUuid"`
|
||||
//该文件是否是文件夹
|
||||
Dir bool `json:"dir"`
|
||||
//该文件是否是应用文件,比如用户上传的头像这个字段就为true. 一般上传的文件就为false
|
||||
Alien bool `json:"alien"`
|
||||
//文件名,带后缀名。例如:avatar.jpg
|
||||
Name string `json:"name"`
|
||||
//文件的md5值,目前该功能尚未实现,作为保留字段
|
||||
Md5 string `json:"md5"`
|
||||
//文件大小,单位 byte。比如某个文件1M大,那么这里就为: 1048576
|
||||
Size int64 `json:"size"`
|
||||
//文件是否为私有,如果true则该文件只能作者或超级管理员可以下载,如果false所有人均可以通过下载链接下载。
|
||||
Privacy bool `json:"privacy"`
|
||||
//文件在磁盘中的路径,前端无需关心这个字段。但是后端在寻找文件时这个字段非常关键。
|
||||
Path string `json:"path"`
|
||||
//该文件的父级matter,该字段不会持久化到数据集,属于获取matter详情时临时组装出来的。
|
||||
Parent *Matter `gorm:"-" json:"parent"`
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### User
|
||||
|
||||
`User`是代表用户:
|
||||
|
||||
```
|
||||
type User struct {
|
||||
//继承Base,也就是说Base中的Uuid,Sort,ModifyTime,CreateTime这里也会有
|
||||
Base
|
||||
//角色,有以下枚举值:GUEST(游客,不会持久化到数据库),USER(普通用户),ADMINISTRATOR(超级管理员)
|
||||
Role string `json:"role"`
|
||||
//用户名,在Matter的path字段中很有用。
|
||||
Username string `json:"username"`
|
||||
//密码,默认不会返回给前端
|
||||
Password string `json:"-"`
|
||||
//邮箱,作为登录时的账户
|
||||
Email string `json:"email"`
|
||||
//手机
|
||||
Phone string `json:"phone"`
|
||||
//性别,有以下枚举值:MALE(男性),FEMALE(女性),UNKNOWN(未知)
|
||||
Gender string `json:"gender"`
|
||||
//城市
|
||||
City string `json:"city"`
|
||||
//头像Url
|
||||
AvatarUrl string `json:"avatarUrl"`
|
||||
//上次登录时的ip
|
||||
LastIp string `json:"lastIp"`
|
||||
//上次登录的时间
|
||||
LastTime time.Time `json:"lastTime"`
|
||||
//该用户允许上传的单文件最大大小
|
||||
SizeLimit int64 `json:"sizeLimit"`
|
||||
//状态,有以下枚举值:OK(正常),DISABLED(被禁用)
|
||||
Status string `json:"status"`
|
||||
}
|
||||
```
|
||||
|
||||
### UploadToken
|
||||
|
||||
用于给陌生人上传的token
|
||||
|
||||
```
|
||||
type UploadToken struct {
|
||||
//继承Base,也就是说Base中的Uuid,Sort,ModifyTime,CreateTime这里也会有
|
||||
Base
|
||||
//颁发该token的用户,系统中任何用户都能颁发token
|
||||
UserUuid string `json:"userUuid"`
|
||||
//使用这个token上传文件就必须上传在这个文件夹下
|
||||
FolderUuid string `json:"folderUuid"`
|
||||
//陌生人上传好了的文件uuid
|
||||
MatterUuid string `json:"matterUuid"`
|
||||
//过期时间
|
||||
ExpireTime time.Time `json:"expireTime"`
|
||||
//使用这个token上传文件就必须是这个文件名
|
||||
Filename string `json:"filename"`
|
||||
//使用这个token上传文件就必须是这个公私有性
|
||||
Privacy bool `json:"privacy"`
|
||||
//使用这个token上传文件就必须这个大小
|
||||
Size int64 `json:"size"`
|
||||
//使用这个token上传文件陌生人的ip
|
||||
Ip string `json:"ip"`
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### DownloadToken
|
||||
|
||||
用于给陌生人下载的token,一个matter如果Privacy=true,那么就意味着只有自己或者超级管理员可以下载,如果让某些自己信任的用户也能下载,那么就需要生成`DownloadToken`给这些用户来下载。
|
||||
|
||||
```
|
||||
type DownloadToken struct {
|
||||
//继承Base,也就是说Base中的Uuid,Sort,ModifyTime,CreateTime这里也会有
|
||||
Base
|
||||
//颁发该token的用户
|
||||
UserUuid string `json:"userUuid"`
|
||||
//该token只能下载这个文件
|
||||
MatterUuid string `json:"matterUuid"`
|
||||
//有效期截止
|
||||
ExpireTime time.Time `json:"expireTime"`
|
||||
//下载者的ip
|
||||
Ip string `json:"ip"`
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Pager
|
||||
|
||||
在前端请求一个列表时,通常返回的都是一个`Pager`,`Pager`中就是装的各个实体的列表。
|
||||
|
||||
```
|
||||
type Pager struct {
|
||||
//当前页数,0基
|
||||
Page int `json:"page"`
|
||||
//每页的大小
|
||||
PageSize int `json:"pageSize"`
|
||||
//总的条目数
|
||||
TotalItems int `json:"totalItems"`
|
||||
//总的页数
|
||||
TotalPages int `json:"totalPages"`
|
||||
//实体的数组
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
```
|
||||
|
||||
### WebResult
|
||||
|
||||
`WebResult`并不是会持久化到数据库中实体,`WebResult`是在`controller`返回数据给前端时包装的一层,有了`WebResult`后每个接口返回的数据会更加统一,方便了前端的统一处理。
|
||||
|
||||
```
|
||||
type WebResult struct {
|
||||
//状态码,具体每个码的意义参考下文
|
||||
Code int `json:"code"`
|
||||
//一句话描述请求结果,通常会是出错时指明出错原因,或者修改权限等小操作时提示的`操作成功`
|
||||
Msg string `json:"msg"`
|
||||
//内容可能是一个实体,也可能是一个 Pager.
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
状态码对应关系如下:
|
||||
|
||||
```
|
||||
const (
|
||||
//正常
|
||||
RESULT_CODE_OK = 200
|
||||
//未登录
|
||||
RESULT_CODE_LOGIN = -400
|
||||
//没有权限
|
||||
RESULT_CODE_UNAUTHORIZED = -401
|
||||
//请求错误
|
||||
RESULT_CODE_BAD_REQUEST = -402
|
||||
//没有找到
|
||||
RESULT_CODE_NOT_FOUND = -404
|
||||
//登录过期
|
||||
RESULT_CODE_LOGIN_EXPIRED = -405
|
||||
//该登录用户不是有效用户
|
||||
RESULT_CODE_LOGIN_INVALID = -406
|
||||
//提交的表单验证不通过
|
||||
RESULT_CODE_FORM_INVALID = -410
|
||||
//请求太频繁
|
||||
RESULT_CODE_FREQUENCY = -420
|
||||
//服务器出错。
|
||||
RESULT_CODE_SERVER_ERROR = -500
|
||||
//远程服务不可用
|
||||
RESULT_CODE_NOT_AVAILABLE = -501
|
||||
//并发异常
|
||||
RESULT_CODE_CONCURRENCY = -511
|
||||
//远程微服务没有找到
|
||||
RESULT_CODE_SERVICE_NOT_FOUND = -600
|
||||
//远程微服务连接超时
|
||||
RESULT_CODE_SERVICE_TIME_OUT = -610
|
||||
//通用的异常
|
||||
RESULT_CODE_UTIL_EXCEPTION = -700
|
||||
)
|
||||
```
|
||||
|
||||
## 二、返回规范
|
||||
|
||||
蓝眼云盘采用前后端分离的模式,前端调用后端接口时,url均以`/api`开头,返回均是json字符串。
|
||||
|
||||
- 返回的json字符串的key均为小写开头的驼峰法,具体参考实体类中`json`标签
|
||||
|
||||
- 返回的时间格式均为 `YYYY-MM-dd HH:mm:ss`(例如:2018-01-06 17:57:00)
|
||||
|
||||
返回内容均是由`WebResult`进行包装,因此具有高度的统一性,在这里我们约定一些说法,后面介绍`Controller`时便不再赘述。
|
||||
|
||||
1. 返回一个`XX`实体
|
||||
|
||||
指的是`WebResult`的`Code=200`, `Data=一个XX实体对象`。
|
||||
|
||||
例:返回一个User,则前端会收到以下json字符串:
|
||||
```
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "",
|
||||
"data": {
|
||||
"uuid": "eed2c66d-1de6-47ff-645e-b67beaa10365",
|
||||
"sort": 1514803034507,
|
||||
"modifyTime": "2018-01-06 18:00:58",
|
||||
"createTime": "2018-01-01 18:37:15",
|
||||
"role": "USER",
|
||||
"username": "demo",
|
||||
"email": "demo@tank.eyeblue.cn",
|
||||
"phone": "",
|
||||
"gender": "UNKNOWN",
|
||||
"city": "",
|
||||
"avatarUrl": "/api/alien/download/ea490cb6-368e-436d-71c0-fcfb08854c80/1180472.png",
|
||||
"lastIp": "124.78.220.82",
|
||||
"lastTime": "2018-01-06 18:00:58",
|
||||
"sizeLimit": 1048576,
|
||||
"status": "OK"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. 返回`XX`的`Pager`
|
||||
|
||||
指的是`WebResult`的`Code=200`, `Data=XX的Pager`。
|
||||
|
||||
例:返回`User`的`Pager`,则前端会收到以下json字符串:
|
||||
```
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "",
|
||||
"data": {
|
||||
"page": 0,
|
||||
"pageSize": 10,
|
||||
"totalItems": 2,
|
||||
"totalPages": 1,
|
||||
"data": [
|
||||
{
|
||||
"uuid": "6a661938-8289-4957-4096-5a1b584bf371",
|
||||
"sort": 1515057859613,
|
||||
"modifyTime": "2018-01-04 17:26:01",
|
||||
"createTime": "2018-01-04 17:24:20",
|
||||
"role": "ADMINISTRATOR",
|
||||
"username": "simba",
|
||||
"email": "y******5@163.com",
|
||||
"phone": "",
|
||||
"gender": "MALE",
|
||||
"city": "",
|
||||
"avatarUrl": "/api/alien/download/d1e453cb-3170-4bdb-73f2-fa0372ee017b/1180480.png",
|
||||
"lastIp": "180.173.103.207",
|
||||
"lastTime": "2018-01-04 17:26:01",
|
||||
"sizeLimit": -1,
|
||||
"status": "OK"
|
||||
},
|
||||
{
|
||||
"uuid": "e59be6a3-f806-463e-553a-4c5892eedf78",
|
||||
"sort": 1514881002975,
|
||||
"modifyTime": "2018-01-02 16:16:43",
|
||||
"createTime": "2018-01-02 16:16:43",
|
||||
"role": "USER",
|
||||
"username": "blog_dev",
|
||||
"email": "b*****@tank.eyeblue.cn",
|
||||
"phone": "",
|
||||
"gender": "MALE",
|
||||
"city": "",
|
||||
"avatarUrl": "/api/alien/download/fdca6eee-d009-4eb3-5ad4-15ba3701cb2e/jump.jpg",
|
||||
"lastIp": "",
|
||||
"lastTime": "2018-01-02 16:16:43",
|
||||
"sizeLimit": 1048576,
|
||||
"status": "OK"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
3. 返回错误信息:yyy
|
||||
|
||||
指的是`WebResult`的`Code=-400`, `Msg=yyy`。(这里的Code具体值参考上文的code表)
|
||||
|
||||
例:返回错误信息:"【新建文件夹】已经存在了,请使用其他名称。",则前端会收到以下json字符串:
|
||||
```
|
||||
{
|
||||
"code": -700,
|
||||
"msg": "【新建文件夹】已经存在了,请使用其他名称。",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
4. 返回成功信息:zzz
|
||||
|
||||
指的是`WebResult`的`Code=200`, `Msg=zzz`。(这里的Code具体值参考上文的code表)
|
||||
|
||||
例:返回成功信息:"删除成功。",则前端会收到以下json字符串:
|
||||
```
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "删除成功。",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 三、接口
|
||||
|
||||
蓝眼云盘所有的接口均定义在`controller`中,总共定义了以下`controller`:
|
||||
|
||||
名称 | 所在文件 | 描述
|
||||
--------- | ---- | -----------
|
||||
PreferenceController | `preference_controller.go` | 网站标题,logo,版权说明等信息的增删改查
|
||||
MatterController | `matter_controller.go` | 站内创建文件夹,上传文件,删除文件,修改权限等
|
||||
UserController | `user_controller.go` | 登录,管理操作站内用户
|
||||
AlienController | `alien_controller.go` | 第三方授权上传,下载,预处理
|
||||
|
||||
每个接口都有不同的访问级别,系统中定义了三种访问级别,分别是:
|
||||
|
||||
`游客` < `注册用户` < `管理员`
|
||||
|
||||
### PreferenceController
|
||||
|
||||
该Controller负责网站中的偏好设置,主要操作`Preference`实体
|
||||
|
||||
----------
|
||||
|
||||
#### /api/preference/fetch
|
||||
|
||||
**功能**:读取网站偏好设置,网站名称,logo,版权,备案信息均从此接口读取。
|
||||
|
||||
**访问级别**:`游客`,`注册用户`,`管理员`
|
||||
|
||||
**请求参数**:无
|
||||
|
||||
**返回**: 一个`Preference`实体
|
||||
|
||||
|
||||
----------
|
||||
|
||||
|
||||
#### /api/preference/edit
|
||||
|
||||
**功能**:编辑网站偏好设置,修改网站名称,logo,版权,备案信息。
|
||||
|
||||
**访问级别**:`管理员`
|
||||
|
||||
**请求参数**:
|
||||
|
||||
名称 | 类型 | 必填性 | 描述
|
||||
--------- | ---- | ---- | -----------
|
||||
name | `string` | 必填 | 网站名称
|
||||
logoUrl | `string` | 选填 | 网站logoUrl,如果不填默认使用蓝眼云盘logo
|
||||
faviconUrl | `string` | 选填 | 网站faviconUrl,如果不填默认使用蓝眼云盘favicon.ico
|
||||
footerLine1 | `string` | 选填 | 网站版权所有信息
|
||||
footerLine2 | `string` | 选填 | 网站备案信息
|
||||
|
||||
**返回**: 一个`Preference`实体
|
||||
|
||||
----------
|
||||
|
||||
### MatterController
|
||||
|
||||
该Controller负责站内创建文件夹,上传文件,删除文件,修改权限等,主要操作`Matter`实体
|
||||
|
||||
----------
|
||||
|
||||
#### /api/matter/create/directory
|
||||
|
||||
**功能**:创建文件夹
|
||||
|
||||
**访问级别**:`注册用户`,`管理员`
|
||||
|
||||
**请求参数**:
|
||||
|
||||
名称 | 类型 | 必填性 | 描述
|
||||
--------- | ---- | ---- | -----------
|
||||
puuid | `string` | 必填 | 准备创建的目录所在的目录,如果在根目录下创建传`root`
|
||||
name | `string` | 必填 | 文件夹名称, 不能包含以下特殊符号:`< > \| * ? / \`
|
||||
|
||||
**返回**: 新建的这个文件夹的`Matter`实体
|
||||
|
||||
----------
|
||||
|
||||
|
||||
#### /api/matter/upload
|
||||
|
||||
**功能**:上传文件
|
||||
|
||||
**访问级别**:`注册用户`,`管理员`
|
||||
|
||||
**请求参数**:
|
||||
|
||||
名称 | 类型 | 必填性 | 描述
|
||||
--------- | ---- | ---- | -----------
|
||||
userUuid | `string` | 选填 | 该文件的归属人,如果不填则使用当前登录的用户uuid;如果是管理员要给用户A上传文件,那么这里应该填A的uuid.
|
||||
alien | `bool` | 选填 | 是否是应用数据,true表示应用数据,这时会自动放置在`应用数据`这个文件夹下,比如上传头像。false表示非应用数据,这时`puuid`字段为必填
|
||||
puuid | `bool` | 选填 | 文件上传到哪个目录下。 如果`alien`=false,这个字段必填。
|
||||
privacy | `bool` | 选填 | 文件的私有性,默认`false`
|
||||
file | `file` | 必填 | 文件,在浏览器中是通过`<input type="file" name="file"/>`来选择的
|
||||
|
||||
|
||||
**返回**: 刚上传的这个文件的`Matter`实体
|
||||
|
||||
----------
|
||||
|
||||
|
||||
#### /api/matter/delete
|
||||
|
||||
**功能**:删除文件或者文件夹
|
||||
|
||||
**访问级别**:`注册用户`,`管理员`
|
||||
|
||||
**请求参数**:
|
||||
|
||||
名称 | 类型 | 必填性 | 描述
|
||||
--------- | ---- | ---- | -----------
|
||||
uuid | `string` | 必填 | 待删除的文件或文件夹的uuid
|
||||
|
||||
|
||||
**返回**: 成功信息“删除成功”
|
||||
|
||||
----------
|
||||
|
||||
|
||||
#### /api/matter/delete/batch
|
||||
|
||||
**功能**:批量删除文件或文件夹
|
||||
|
||||
**访问级别**:`注册用户`,`管理员`
|
||||
|
||||
**请求参数**:
|
||||
|
||||
名称 | 类型 | 必填性 | 描述
|
||||
--------- | ---- | ---- | -----------
|
||||
uuids | `string` | 必填 | 待删除的文件或文件夹的uuids,用逗号(,)分隔。
|
||||
|
||||
**返回**: 成功信息“删除成功”
|
||||
|
||||
----------
|
||||
|
||||
#### /api/matter/rename
|
||||
|
||||
**功能**:重命名文件或文件夹
|
||||
|
||||
**访问级别**:`注册用户`,`管理员`
|
||||
|
||||
**请求参数**:
|
||||
|
||||
名称 | 类型 | 必填性 | 描述
|
||||
--------- | ---- | ---- | -----------
|
||||
uuid | `string` | 必填 | 文件的uuid
|
||||
name | `string` | 必填 | 新名字,不能包含以下特殊符号:`< > \| * ? / \`
|
||||
|
||||
**返回**: 刚重命名的这个文件的`Matter`实体
|
||||
|
||||
----------
|
||||
#### /api/matter/change/privacy
|
||||
|
||||
**功能**:改变文件的公私有属性
|
||||
|
||||
**访问级别**:`注册用户`,`管理员`
|
||||
|
||||
**请求参数**:
|
||||
|
||||
名称 | 类型 | 必填性 | 描述
|
||||
--------- | ---- | ---- | -----------
|
||||
uuid | `string` | 必填 | 文件的uuid
|
||||
privacy | `bool` | 选填 | 文件的私有性,默认`false`
|
||||
|
||||
**返回**: 成功信息“设置成功”
|
||||
|
||||
----------
|
||||
|
||||
#### /api/matter/move
|
||||
|
||||
**功能**:将一个文件夹或者文件移入到另一个文件夹下。
|
||||
|
||||
**访问级别**:`注册用户`,`管理员`
|
||||
|
||||
**请求参数**:
|
||||
|
||||
名称 | 类型 | 必填性 | 描述
|
||||
--------- | ---- | ---- | -----------
|
||||
srcUuids | `string` | 必填 | 待移动的文件或文件夹的uuids,用逗号(,)分隔。
|
||||
destUuid | `string` | 必填 | 目标文件夹,根目录用`root`
|
||||
|
||||
**返回**: 成功信息“设置成功”
|
||||
|
||||
----------
|
||||
|
||||
|
||||
#### /api/matter/detail
|
||||
|
||||
**功能**:产看文件详情
|
||||
|
||||
**访问级别**:`注册用户`,`管理员`
|
||||
|
||||
**请求参数**:
|
||||
|
||||
名称 | 类型 | 必填性 | 描述
|
||||
--------- | ---- | ---- | -----------
|
||||
uuid | `string` | 必填 | 该文件的uuid。
|
||||
destUuid | `string` | 必填 | 目标文件夹,根目录用`root`
|
||||
|
||||
**返回**: 这个文件的`Matter`实体
|
||||
|
||||
----------
|
||||
|
||||
#### /api/matter/page
|
||||
|
||||
**功能**:按照分页的方式获取某个文件夹下文件和子文件夹的列表
|
||||
|
||||
**访问级别**:`注册用户`,`管理员`
|
||||
|
||||
**请求参数**:
|
||||
|
||||
名称 | 类型 | 必填性 | 描述
|
||||
--------- | ---- | ---- | -----------
|
||||
puuid | `string` | 选填 | 文件夹uuid,如果根目录填`root`
|
||||
page | `int` | 选填 | 当前页数,0基,默认0
|
||||
pageSize | `int` | 选填 | 每页条目数,默认200
|
||||
userUuid | `string` | 选填 | 筛选文件拥有者,对于普通用户使用当前登录的用户uuid.
|
||||
name | `string` | 选填 | 模糊筛选文件名
|
||||
dir | `bool` | 选填 | 筛选是否为文件夹
|
||||
orderDir | `DESC`或`ASC` | 选填 | 按文件夹排序,`DESC`降序排,`ASC`升序排
|
||||
orderCreateTime | `DESC`或`ASC` | 选填 | 按创建时间排序,`DESC`降序排,`ASC`升序排
|
||||
orderSize | `DESC`或`ASC` | 选填 | 按文件大小排序,`DESC`降序排,`ASC`升序排
|
||||
orderName | `DESC`或`ASC` | 选填 | 按名称排序,`DESC`降序排,`ASC`升序排
|
||||
extensions | `string` | 选填 | 按文件后缀名筛选,逗号(,)分隔。例:`jpg,png,pdf`
|
||||
|
||||
|
||||
**返回**: `Matter`的`Pager`
|
||||
|
||||
----------
|
||||
|
||||
#### /api/alien/download/{uuid}/{filename}
|
||||
|
||||
这个接口的定义在在`AlienController`中的,具体使用请参考[蓝眼云盘编程接口](https://github.com/eyebluecn/tank/blob/master/build/doc/alien_zh.md) 这里只做简单的下载使用。
|
||||
|
||||
**功能**:下载文件。
|
||||
|
||||
**访问级别**:`游客`,`注册用户`,`管理员`
|
||||
|
||||
**请求参数**: 均是放置在url中
|
||||
|
||||
名称 | 类型 | 必填性 | 描述
|
||||
--------- | ---- | ---- | -----------
|
||||
uuid | `string` | 必填 | 要下载的文件uuid
|
||||
filename | `string` | 必填 | 要下载的文件名
|
||||
|
||||
**返回**: 二进制的文件
|
||||
|
||||
----------
|
||||
|
||||
### UserController
|
||||
|
||||
该Controller负责站内创建文件夹,上传文件,删除文件,修改权限等,主要操作`Matter`实体
|
||||
|
||||
----------
|
||||
|
||||
#### /api/user/create
|
||||
|
||||
**功能**:创建用户
|
||||
|
||||
**访问级别**:`管理员`
|
||||
|
||||
**请求参数**:
|
||||
|
||||
名称 | 类型 | 必填性 | 描述
|
||||
--------- | ---- | ---- | -----------
|
||||
username | `string` | 必填 | 用户名,且只能包含字母,数字和'_'
|
||||
password | `string` | 必填 | 密码,密码长度至少为6位
|
||||
email | `string` | 必填 | 邮箱,作为用户登录的凭证
|
||||
avatarUrl | `string` | 选填 | 头像
|
||||
phone | `string` | 选填 | 电话
|
||||
gender | `string` | 选填 | 性别
|
||||
role | `string` | 选填 | 角色
|
||||
city | `string` | 选填 | 城市
|
||||
sizeLimit | `string` | 选填 | 用户上传单文件限制,单位byte. 如果负数表示无限制。
|
||||
|
||||
|
||||
**返回**: 新建的`User`实体
|
||||
|
||||
----------
|
||||
|
||||
#### /api/user/edit
|
||||
|
||||
**功能**:编辑用户
|
||||
|
||||
**访问级别**:`注册用户`,`管理员`
|
||||
|
||||
**请求参数**:
|
||||
|
||||
名称 | 类型 | 必填性 | 描述
|
||||
--------- | ---- | ---- | -----------
|
||||
uuid | `string` | 必填 | 待编辑的用户uuid
|
||||
avatarUrl | `string` | 选填 | 头像
|
||||
phone | `string` | 选填 | 电话
|
||||
gender | `string` | 选填 | 性别
|
||||
role | `string` | 选填 | 角色
|
||||
city | `string` | 选填 | 城市
|
||||
sizeLimit | `string` | 选填 | 用户上传单文件限制,单位byte. 如果负数表示无限制。
|
||||
|
||||
|
||||
**返回**: 编辑的`User`实体
|
||||
|
||||
----------
|
||||
|
||||
#### /api/user/change/password
|
||||
|
||||
**功能**:用户修改密码
|
||||
|
||||
**访问级别**:`注册用户`,`管理员`
|
||||
|
||||
**请求参数**:
|
||||
|
||||
名称 | 类型 | 必填性 | 描述
|
||||
--------- | ---- | ---- | -----------
|
||||
oldPassword | `string` | 必填 | 旧密码
|
||||
newPassword | `string` | 必填 | 新密码
|
||||
|
||||
**返回**: 修改密码的`User`实体
|
||||
|
||||
----------
|
||||
|
||||
|
||||
#### /api/user/change/password
|
||||
|
||||
**功能**:管理员重置用户密码
|
||||
|
||||
**访问级别**:`管理员`
|
||||
|
||||
**请求参数**:
|
||||
|
||||
名称 | 类型 | 必填性 | 描述
|
||||
--------- | ---- | ---- | -----------
|
||||
userUuid | `string` | 必填 | 待重置密码的用户uuid
|
||||
password | `string` | 必填 | 密码
|
||||
|
||||
**返回**: 修改密码的`User`实体
|
||||
|
||||
----------
|
||||
|
||||
#### /api/user/login
|
||||
|
||||
**功能**:登录
|
||||
|
||||
**访问级别**:`游客`,`注册用户`,`管理员`
|
||||
|
||||
**请求参数**:
|
||||
|
||||
名称 | 类型 | 必填性 | 描述
|
||||
--------- | ---- | ---- | -----------
|
||||
email | `string` | 必填 | 邮箱
|
||||
password | `string` | 必填 | 密码
|
||||
|
||||
**返回**: 当前登录的`User`实体
|
||||
|
||||
----------
|
||||
|
||||
#### /api/user/logout
|
||||
|
||||
**功能**:登录
|
||||
|
||||
**访问级别**:`注册用户`,`管理员`
|
||||
|
||||
**请求参数**:无
|
||||
|
||||
**返回**: 成功信息"退出成功!"
|
||||
|
||||
----------
|
||||
|
||||
#### /api/user/detail
|
||||
|
||||
**功能**:查看用户详情
|
||||
|
||||
**访问级别**:`游客`,`注册用户`,`管理员`
|
||||
|
||||
**请求参数**:
|
||||
|
||||
名称 | 类型 | 必填性 | 描述
|
||||
--------- | ---- | ---- | -----------
|
||||
uuid | `string` | 必填 | 待查看的用户uuid
|
||||
|
||||
**返回**: `User`实体
|
||||
|
||||
----------
|
||||
|
||||
#### /api/user/page
|
||||
|
||||
**功能**:查看用户列表
|
||||
|
||||
**访问级别**:`管理员`
|
||||
|
||||
**请求参数**:
|
||||
|
||||
名称 | 类型 | 必填性 | 描述
|
||||
--------- | ---- | ---- | -----------
|
||||
page | `int` | 选填 | 当前页数,0基,默认0
|
||||
pageSize | `int` | 选填 | 每页条目数,默认200
|
||||
username | `string` | 选填 | 模糊筛选用户名
|
||||
email | `string` | 选填 | 模糊筛选邮箱
|
||||
phone | `string` | 选填 | 模糊筛选电话
|
||||
orderLastTime | `DESC`或`ASC` | 选填 | 按上次登录时间排序,`DESC`降序排,`ASC`升序排
|
||||
orderCreateTime | `DESC`或`ASC` | 选填 | 按创建时间排序,`DESC`降序排,`ASC`升序排
|
||||
|
||||
**返回**: `User`实体的`Pager`
|
||||
|
||||
----------
|
||||
|
||||
#### /api/user/disable
|
||||
|
||||
**功能**:禁用用户
|
||||
|
||||
**访问级别**:`管理员`
|
||||
|
||||
**请求参数**:
|
||||
|
||||
名称 | 类型 | 必填性 | 描述
|
||||
--------- | ---- | ---- | -----------
|
||||
uuid | `string` | 必填 | 待操作的用户
|
||||
|
||||
**返回**: 被操作的用户`User`实体
|
||||
|
||||
----------
|
||||
|
||||
#### /api/user/enable
|
||||
|
||||
**功能**:启用用户
|
||||
|
||||
**访问级别**:`管理员`
|
||||
|
||||
**请求参数**:
|
||||
|
||||
名称 | 类型 | 必填性 | 描述
|
||||
--------- | ---- | ---- | -----------
|
||||
uuid | `string` | 必填 | 待操作的用户
|
||||
|
||||
**返回**: 被操作的用户`User`实体
|
||||
|
||||
----------
|
||||
|
||||
### AlienController
|
||||
|
||||
这个Controller主要暴露给第三方使用,详情请参考:[蓝眼云盘编程接口](https://github.com/eyebluecn/tank/blob/master/build/doc/alien_zh.md)
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
# Package dependency
|
||||
|
||||
```
|
||||
main
|
||||
├── bin
|
||||
├── pkg
|
||||
├── src
|
||||
│ ├── github.com
|
||||
│ ├── golang.org
|
||||
│ ├── tank
|
||||
│ │ ├── build
|
||||
│ │ │ ├── conf
|
||||
│ │ │ ├── doc
|
||||
```
|
||||
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 208 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 222 KiB After Width: | Height: | Size: 135 KiB |
|
Before Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 49 KiB |
@@ -0,0 +1,193 @@
|
||||
CREATE TABLE `tank31_bridge` (
|
||||
`uuid` char(36) NOT NULL DEFAULT '',
|
||||
`sort` bigint(20) NOT NULL,
|
||||
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`create_time` timestamp NOT NULL DEFAULT '2018-01-01 00:00:00',
|
||||
`share_uuid` char(36) DEFAULT NULL,
|
||||
`matter_uuid` char(36) DEFAULT NULL,
|
||||
PRIMARY KEY (`uuid`),
|
||||
UNIQUE KEY `uuid` (`uuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE `tank31_dashboard` (
|
||||
`uuid` char(36) NOT NULL DEFAULT '',
|
||||
`sort` bigint(20) NOT NULL,
|
||||
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`create_time` timestamp NOT NULL DEFAULT '2018-01-01 00:00:00',
|
||||
`invoke_num` bigint(20) NOT NULL,
|
||||
`total_invoke_num` bigint(20) NOT NULL DEFAULT '0',
|
||||
`uv` bigint(20) NOT NULL DEFAULT '0',
|
||||
`total_uv` bigint(20) NOT NULL DEFAULT '0',
|
||||
`matter_num` bigint(20) NOT NULL DEFAULT '0',
|
||||
`total_matter_num` bigint(20) NOT NULL DEFAULT '0',
|
||||
`file_size` bigint(20) NOT NULL DEFAULT '0',
|
||||
`total_file_size` bigint(20) NOT NULL DEFAULT '0',
|
||||
`avg_cost` bigint(20) NOT NULL DEFAULT '0',
|
||||
`dt` varchar(45) NOT NULL,
|
||||
PRIMARY KEY (`uuid`),
|
||||
UNIQUE KEY `uuid` (`uuid`),
|
||||
KEY `idx_dt` (`dt`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE `tank31_download_token` (
|
||||
`uuid` char(36) NOT NULL DEFAULT '',
|
||||
`sort` bigint(20) NOT NULL,
|
||||
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`create_time` timestamp NOT NULL DEFAULT '2018-01-01 00:00:00',
|
||||
`user_uuid` char(36) NOT NULL,
|
||||
`matter_uuid` char(36) NOT NULL,
|
||||
`expire_time` timestamp NOT NULL DEFAULT '2018-01-01 00:00:00',
|
||||
`ip` varchar(128) NOT NULL,
|
||||
PRIMARY KEY (`uuid`),
|
||||
UNIQUE KEY `uuid` (`uuid`),
|
||||
KEY `idx_mu` (`matter_uuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE `tank31_footprint` (
|
||||
`uuid` char(36) NOT NULL DEFAULT '',
|
||||
`sort` bigint(20) NOT NULL,
|
||||
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`create_time` timestamp NOT NULL DEFAULT '2018-01-01 00:00:00',
|
||||
`user_uuid` char(36) DEFAULT NULL,
|
||||
`ip` varchar(128) NOT NULL,
|
||||
`host` varchar(45) NOT NULL,
|
||||
`uri` varchar(255) NOT NULL,
|
||||
`params` text,
|
||||
`cost` bigint(20) NOT NULL DEFAULT '0',
|
||||
`success` tinyint(1) NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`uuid`),
|
||||
UNIQUE KEY `uuid` (`uuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE `tank31_image_cache` (
|
||||
`uuid` char(36) NOT NULL DEFAULT '',
|
||||
`sort` bigint(20) NOT NULL,
|
||||
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`create_time` timestamp NOT NULL DEFAULT '2018-01-01 00:00:00',
|
||||
`name` varchar(255) NOT NULL,
|
||||
`user_uuid` char(36) DEFAULT NULL,
|
||||
`username` varchar(45) NOT NULL,
|
||||
`matter_uuid` char(36) DEFAULT NULL,
|
||||
`matter_name` varchar(255) NOT NULL,
|
||||
`mode` varchar(512) DEFAULT NULL,
|
||||
`md5` varchar(45) DEFAULT NULL,
|
||||
`size` bigint(20) NOT NULL DEFAULT '0',
|
||||
`path` varchar(512) DEFAULT NULL,
|
||||
PRIMARY KEY (`uuid`),
|
||||
UNIQUE KEY `uuid` (`uuid`),
|
||||
KEY `idx_mu` (`matter_uuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE `tank31_matter` (
|
||||
`uuid` char(36) NOT NULL DEFAULT '',
|
||||
`sort` bigint(20) NOT NULL,
|
||||
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`create_time` timestamp NOT NULL DEFAULT '2018-01-01 00:00:00',
|
||||
`puuid` char(36) DEFAULT NULL,
|
||||
`user_uuid` char(36) DEFAULT NULL,
|
||||
`username` varchar(45) NOT NULL,
|
||||
`dir` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`name` varchar(255) NOT NULL,
|
||||
`md5` varchar(45) DEFAULT NULL,
|
||||
`size` bigint(20) NOT NULL DEFAULT '0',
|
||||
`privacy` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`path` varchar(1024) DEFAULT NULL,
|
||||
`times` bigint(20) NOT NULL DEFAULT '0',
|
||||
`prop` varchar(1024) NOT NULL DEFAULT '{}',
|
||||
`visit_time` timestamp NOT NULL DEFAULT '2018-01-01 00:00:00',
|
||||
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`delete_time` timestamp NOT NULL DEFAULT '2018-01-01 00:00:00',
|
||||
PRIMARY KEY (`uuid`),
|
||||
UNIQUE KEY `uuid` (`uuid`),
|
||||
KEY `idx_puuid` (`puuid`),
|
||||
KEY `idx_uu` (`user_uuid`),
|
||||
KEY `idx_del` (`deleted`),
|
||||
KEY `idx_delt` (`delete_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE `tank31_preference` (
|
||||
`uuid` char(36) NOT NULL DEFAULT '',
|
||||
`sort` bigint(20) NOT NULL,
|
||||
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`create_time` timestamp NOT NULL DEFAULT '2018-01-01 00:00:00',
|
||||
`name` varchar(45) DEFAULT NULL,
|
||||
`logo_url` varchar(255) DEFAULT NULL,
|
||||
`favicon_url` varchar(255) DEFAULT NULL,
|
||||
`copyright` varchar(1024) DEFAULT NULL,
|
||||
`record` varchar(1024) DEFAULT NULL,
|
||||
`download_dir_max_size` bigint(20) NOT NULL DEFAULT '-1',
|
||||
`download_dir_max_num` bigint(20) NOT NULL DEFAULT '-1',
|
||||
`default_total_size_limit` bigint(20) NOT NULL DEFAULT '-1',
|
||||
`allow_register` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`preview_config` text,
|
||||
`scan_config` text,
|
||||
`deleted_keep_days` bigint(20) NOT NULL DEFAULT '7',
|
||||
PRIMARY KEY (`uuid`),
|
||||
UNIQUE KEY `uuid` (`uuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE `tank31_session` (
|
||||
`uuid` char(36) NOT NULL DEFAULT '',
|
||||
`sort` bigint(20) NOT NULL,
|
||||
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`create_time` timestamp NOT NULL DEFAULT '2018-01-01 00:00:00',
|
||||
`user_uuid` char(36) DEFAULT NULL,
|
||||
`ip` varchar(128) NOT NULL,
|
||||
`expire_time` timestamp NOT NULL DEFAULT '2018-01-01 00:00:00',
|
||||
PRIMARY KEY (`uuid`),
|
||||
UNIQUE KEY `uuid` (`uuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE `tank31_share` (
|
||||
`uuid` char(36) NOT NULL DEFAULT '',
|
||||
`sort` bigint(20) NOT NULL,
|
||||
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`create_time` timestamp NOT NULL DEFAULT '2018-01-01 00:00:00',
|
||||
`name` varchar(255) DEFAULT NULL,
|
||||
`share_type` varchar(45) DEFAULT NULL,
|
||||
`username` varchar(45) DEFAULT NULL,
|
||||
`user_uuid` char(36) DEFAULT NULL,
|
||||
`download_times` bigint(20) NOT NULL DEFAULT '0',
|
||||
`code` varchar(45) NOT NULL,
|
||||
`expire_infinity` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`expire_time` timestamp NOT NULL DEFAULT '2018-01-01 00:00:00',
|
||||
PRIMARY KEY (`uuid`),
|
||||
UNIQUE KEY `uuid` (`uuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE `tank31_upload_token` (
|
||||
`uuid` char(36) NOT NULL DEFAULT '',
|
||||
`sort` bigint(20) NOT NULL,
|
||||
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`create_time` timestamp NOT NULL DEFAULT '2018-01-01 00:00:00',
|
||||
`user_uuid` char(36) NOT NULL,
|
||||
`folder_uuid` char(36) NOT NULL,
|
||||
`matter_uuid` char(36) NOT NULL,
|
||||
`expire_time` timestamp NOT NULL DEFAULT '2018-01-01 00:00:00',
|
||||
`filename` varchar(255) NOT NULL,
|
||||
`privacy` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`size` bigint(20) NOT NULL DEFAULT '0',
|
||||
`ip` varchar(128) NOT NULL,
|
||||
PRIMARY KEY (`uuid`),
|
||||
UNIQUE KEY `uuid` (`uuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE `tank31_user` (
|
||||
`uuid` char(36) NOT NULL DEFAULT '',
|
||||
`sort` bigint(20) NOT NULL,
|
||||
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`create_time` timestamp NOT NULL DEFAULT '2018-01-01 00:00:00',
|
||||
`role` varchar(45) DEFAULT NULL,
|
||||
`username` varchar(45) NOT NULL,
|
||||
`password` varchar(255) DEFAULT NULL,
|
||||
`avatar_url` varchar(255) DEFAULT NULL,
|
||||
`last_ip` varchar(128) DEFAULT NULL,
|
||||
`last_time` timestamp NOT NULL DEFAULT '2018-01-01 00:00:00',
|
||||
`size_limit` bigint(20) NOT NULL DEFAULT '-1',
|
||||
`total_size_limit` bigint(20) NOT NULL DEFAULT '-1',
|
||||
`total_size` bigint(20) NOT NULL DEFAULT '0',
|
||||
`status` varchar(45) DEFAULT NULL,
|
||||
PRIMARY KEY (`uuid`),
|
||||
UNIQUE KEY `username` (`username`),
|
||||
UNIQUE KEY `uuid` (`uuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
@@ -1,267 +0,0 @@
|
||||
#### PROPFIND 列出目录情况
|
||||
request method
|
||||
```
|
||||
PROPFIND
|
||||
```
|
||||
|
||||
request header
|
||||
```
|
||||
Authorization=Basic YWRtaW46YWRtaW4=
|
||||
Content-Type=text/xml
|
||||
Accept-Encoding=gzip
|
||||
Depth=infinity
|
||||
```
|
||||
|
||||
request body
|
||||
```
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<D:propfind xmlns:D="DAV:">
|
||||
<D:prop>
|
||||
<D:resourcetype />
|
||||
<D:getcontentlength />
|
||||
<D:creationdate />
|
||||
<D:getlastmodified />
|
||||
</D:prop>
|
||||
</D:propfind>
|
||||
```
|
||||
|
||||
response body
|
||||
```
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<D:multistatus xmlns:D="DAV:">
|
||||
<D:response>
|
||||
<D:href>/api/dav/</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<D:displayname>dav</D:displayname>
|
||||
<D:getlastmodified>Tue, 16 Apr 2019 17:50:59 GMT</D:getlastmodified>
|
||||
<D:supportedlock>
|
||||
<D:lockentry xmlns:D="DAV:">
|
||||
<D:lockscope>
|
||||
<D:exclusive/>
|
||||
</D:lockscope>
|
||||
<D:locktype>
|
||||
<D:write/>
|
||||
</D:locktype>
|
||||
</D:lockentry>
|
||||
</D:supportedlock>
|
||||
<D:resourcetype>
|
||||
<D:collection xmlns:D="DAV:"/>
|
||||
</D:resourcetype>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
<D:response>
|
||||
<D:href>/api/dav/api/</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<D:displayname>api</D:displayname>
|
||||
<D:getlastmodified>Tue, 16 Apr 2019 17:51:03 GMT</D:getlastmodified>
|
||||
<D:supportedlock>
|
||||
<D:lockentry xmlns:D="DAV:">
|
||||
<D:lockscope>
|
||||
<D:exclusive/>
|
||||
</D:lockscope>
|
||||
<D:locktype>
|
||||
<D:write/>
|
||||
</D:locktype>
|
||||
</D:lockentry>
|
||||
</D:supportedlock>
|
||||
<D:resourcetype>
|
||||
<D:collection xmlns:D="DAV:"/>
|
||||
</D:resourcetype>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
<D:response>
|
||||
<D:href>/api/dav/api/dav/</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<D:displayname>dav</D:displayname>
|
||||
<D:getlastmodified>Tue, 16 Apr 2019 17:51:38 GMT</D:getlastmodified>
|
||||
<D:supportedlock>
|
||||
<D:lockentry xmlns:D="DAV:">
|
||||
<D:lockscope>
|
||||
<D:exclusive/>
|
||||
</D:lockscope>
|
||||
<D:locktype>
|
||||
<D:write/>
|
||||
</D:locktype>
|
||||
</D:lockentry>
|
||||
</D:supportedlock>
|
||||
<D:resourcetype>
|
||||
<D:collection xmlns:D="DAV:"/>
|
||||
</D:resourcetype>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
<D:response>
|
||||
<D:href>/api/dav/api/dav/body.txt</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<D:displayname>body.txt</D:displayname>
|
||||
<D:getlastmodified>Tue, 16 Apr 2019 17:51:38 GMT</D:getlastmodified>
|
||||
<D:getcontenttype>text/plain; charset=utf-8</D:getcontenttype>
|
||||
<D:getetag>"159605ccc1d0f3c410"</D:getetag>
|
||||
<D:supportedlock>
|
||||
<D:lockentry xmlns:D="DAV:">
|
||||
<D:lockscope>
|
||||
<D:exclusive/>
|
||||
</D:lockscope>
|
||||
<D:locktype>
|
||||
<D:write/>
|
||||
</D:locktype>
|
||||
</D:lockentry>
|
||||
</D:supportedlock>
|
||||
<D:resourcetype></D:resourcetype>
|
||||
<D:getcontentlength>16</D:getcontentlength>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
<D:response>
|
||||
<D:href>/api/dav/api/dav/cat.txt</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<D:resourcetype></D:resourcetype>
|
||||
<D:getcontentlength>24</D:getcontentlength>
|
||||
<D:getlastmodified>Tue, 16 Apr 2019 17:51:19 GMT</D:getlastmodified>
|
||||
<D:getcontenttype>text/plain; charset=utf-8</D:getcontenttype>
|
||||
<D:getetag>"159605c862b0d64c18"</D:getetag>
|
||||
<D:supportedlock>
|
||||
<D:lockentry xmlns:D="DAV:">
|
||||
<D:lockscope>
|
||||
<D:exclusive/>
|
||||
</D:lockscope>
|
||||
<D:locktype>
|
||||
<D:write/>
|
||||
</D:locktype>
|
||||
</D:lockentry>
|
||||
</D:supportedlock>
|
||||
<D:displayname>cat.txt</D:displayname>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
<D:response>
|
||||
<D:href>/api/dav/cat/</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<D:getlastmodified>Sat, 13 Apr 2019 16:55:54 GMT</D:getlastmodified>
|
||||
<D:supportedlock>
|
||||
<D:lockentry xmlns:D="DAV:">
|
||||
<D:lockscope>
|
||||
<D:exclusive/>
|
||||
</D:lockscope>
|
||||
<D:locktype>
|
||||
<D:write/>
|
||||
</D:locktype>
|
||||
</D:lockentry>
|
||||
</D:supportedlock>
|
||||
<D:displayname>cat</D:displayname>
|
||||
<D:resourcetype>
|
||||
<D:collection xmlns:D="DAV:"/>
|
||||
</D:resourcetype>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
<D:response>
|
||||
<D:href>/api/dav/cat/dog/</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<D:getlastmodified>Sat, 13 Apr 2019 16:55:58 GMT</D:getlastmodified>
|
||||
<D:supportedlock>
|
||||
<D:lockentry xmlns:D="DAV:">
|
||||
<D:lockscope>
|
||||
<D:exclusive/>
|
||||
</D:lockscope>
|
||||
<D:locktype>
|
||||
<D:write/>
|
||||
</D:locktype>
|
||||
</D:lockentry>
|
||||
</D:supportedlock>
|
||||
<D:displayname>dog</D:displayname>
|
||||
<D:resourcetype>
|
||||
<D:collection xmlns:D="DAV:"/>
|
||||
</D:resourcetype>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
<D:response>
|
||||
<D:href>/api/dav/cat/dog/pig/</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<D:resourcetype>
|
||||
<D:collection xmlns:D="DAV:"/>
|
||||
</D:resourcetype>
|
||||
<D:getlastmodified>Sat, 13 Apr 2019 16:56:08 GMT</D:getlastmodified>
|
||||
<D:supportedlock>
|
||||
<D:lockentry xmlns:D="DAV:">
|
||||
<D:lockscope>
|
||||
<D:exclusive/>
|
||||
</D:lockscope>
|
||||
<D:locktype>
|
||||
<D:write/>
|
||||
</D:locktype>
|
||||
</D:lockentry>
|
||||
</D:supportedlock>
|
||||
<D:displayname>pig</D:displayname>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
<D:response>
|
||||
<D:href>/api/dav/cat/dog/pig/hi.txt</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<D:getlastmodified>Sat, 13 Apr 2019 16:56:08 GMT</D:getlastmodified>
|
||||
<D:getcontenttype>text/plain; charset=utf-8</D:getcontenttype>
|
||||
<D:getetag>"15951707dc1116d87"</D:getetag>
|
||||
<D:supportedlock>
|
||||
<D:lockentry xmlns:D="DAV:">
|
||||
<D:lockscope>
|
||||
<D:exclusive/>
|
||||
</D:lockscope>
|
||||
<D:locktype>
|
||||
<D:write/>
|
||||
</D:locktype>
|
||||
</D:lockentry>
|
||||
</D:supportedlock>
|
||||
<D:displayname>hi.txt</D:displayname>
|
||||
<D:resourcetype></D:resourcetype>
|
||||
<D:getcontentlength>7</D:getcontentlength>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
<D:response>
|
||||
<D:href>/api/dav/morning.txt</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<D:resourcetype></D:resourcetype>
|
||||
<D:getcontentlength>13</D:getcontentlength>
|
||||
<D:displayname>morning.txt</D:displayname>
|
||||
<D:getlastmodified>Sat, 13 Apr 2019 16:52:08 GMT</D:getlastmodified>
|
||||
<D:getcontenttype>text/plain; charset=utf-8</D:getcontenttype>
|
||||
<D:getetag>"159516cfe790beecd"</D:getetag>
|
||||
<D:supportedlock>
|
||||
<D:lockentry xmlns:D="DAV:">
|
||||
<D:lockscope>
|
||||
<D:exclusive/>
|
||||
</D:lockscope>
|
||||
<D:locktype>
|
||||
<D:write/>
|
||||
</D:locktype>
|
||||
</D:lockentry>
|
||||
</D:supportedlock>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
</D:multistatus>
|
||||
```
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.b03c51fb.css",
|
||||
"main.js": "/static/js/main.b94b1947.js",
|
||||
"static/media/logo.png": "/static/media/logo.847e54ef7fb4b744fad4.png",
|
||||
"static/media/empty.svg": "/static/media/empty.20ceb38d310075aa4c3cdaae210afb65.svg",
|
||||
"static/media/psd.svg": "/static/media/psd.bf2ac5411c0132f292787f45855934f2.svg",
|
||||
"static/media/xls.svg": "/static/media/xls.02895077484083ddc5346ab720cd8d1b.svg",
|
||||
"static/media/ppt.svg": "/static/media/ppt.7dc0cfc7fdf0e6f66fb94c81fb34575f.svg",
|
||||
"static/media/doc.svg": "/static/media/doc.01619bc74278a86447e699223663ad64.svg",
|
||||
"static/media/pdf.svg": "/static/media/pdf.eba1e4d316279fc69682dabae6fe6fa4.svg",
|
||||
"static/media/video.svg": "/static/media/video.adecb1d58a645ca8d1b1bffad4dbe777.svg",
|
||||
"static/media/default-skin.svg": "/static/media/default-skin.f64c3af3d0d25b9e4e00.svg",
|
||||
"static/media/text.svg": "/static/media/text.8000463985257af8534ee29db23e1006.svg",
|
||||
"static/media/file.svg": "/static/media/file.074f42545edfc31c849bdddb013ad8b2.svg",
|
||||
"static/media/audio.svg": "/static/media/audio.e7112b210bfaa8d0a2ab69f74a66eea4.svg",
|
||||
"static/media/image.svg": "/static/media/image.7660bae05269b3c8f2cdf669f04aef3b.svg",
|
||||
"static/media/folder.svg": "/static/media/folder.0bdd7430280c98bf4970b6af5a061c2c.svg",
|
||||
"static/media/archive.svg": "/static/media/archive.684c1e42f233aa9d53a8910d4fee091e.svg",
|
||||
"index.html": "/index.html",
|
||||
"main.b03c51fb.css.map": "/static/css/main.b03c51fb.css.map",
|
||||
"main.b94b1947.js.map": "/static/js/main.b94b1947.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.b03c51fb.css",
|
||||
"static/js/main.b94b1947.js"
|
||||
]
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/favicon.ico><title>EyeblueTank</title><link href=/static/css/app.a7719e8c.css rel=preload as=style><link href=/static/css/chunk-vendors.fc2d671c.css rel=preload as=style><link href=/static/js/app.e7fcae17.js rel=preload as=script><link href=/static/js/chunk-vendors.220ccae9.js rel=preload as=script><link href=/static/css/chunk-vendors.fc2d671c.css rel=stylesheet><link href=/static/css/app.a7719e8c.css rel=stylesheet></head><body><noscript><strong>We're sorry but tank-front-tmp doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/static/js/chunk-vendors.220ccae9.js></script><script src=/static/js/app.e7fcae17.js></script></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="eyeblue tank"/><title>EyeblueTank</title><script defer="defer" src="/static/js/main.b94b1947.js"></script><link href="/static/css/main.b03c51fb.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run EyeblueTank.</noscript><div id="root"></div></body></html>
|
||||
|
Before Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 434 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1544085034574" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1828" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M0 512A512 512 0 1 0 512 0 512 512 0 0 0 0 512" fill="#36D49F" p-id="1829"></path><path d="M450.37037 552.580741l-115.105185-87.798519-50.820741 39.822222L483.365926 701.62963C517.499259 616.296296 625.777778 449.232593 758.518519 330.714074L746.382222 303.407407a1264.071111 1264.071111 0 0 0-296.011852 249.173334" fill="#FFFFFF" p-id="1830"></path></svg>
|
||||
|
Before Width: | Height: | Size: 743 B |
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
object-assign
|
||||
(c) Sindre Sorhus
|
||||
@license MIT
|
||||
*/
|
||||
|
||||
/*!
|
||||
Copyright (c) 2018 Jed Watson.
|
||||
Licensed under the MIT License (MIT), see
|
||||
http://jedwatson.github.io/classnames
|
||||
*/
|
||||
|
||||
/*! PhotoSwipe - v4.1.3 - 2019-01-08
|
||||
* http://photoswipe.com
|
||||
* Copyright (c) 2019 Dmitry Semenov; */
|
||||
|
||||
/*! PhotoSwipe Default UI - 4.1.3 - 2019-01-08
|
||||
* http://photoswipe.com
|
||||
* Copyright (c) 2019 Dmitry Semenov; */
|
||||
|
||||
/*! VelocityJS.org (1.5.2). (C) 2014 Julian Shapiro. MIT @license: en.wikipedia.org/wiki/MIT_License */
|
||||
|
||||
/*! VelocityJS.org jQuery Shim (1.0.1). (C) 2014 The jQuery Foundation. MIT @license: en.wikipedia.org/wiki/MIT_License. */
|
||||
|
||||
/** @license React v0.20.2
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.13.1
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v17.0.2
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v17.0.2
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v17.0.2
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
//! moment.js
|
||||
|
Before Width: | Height: | Size: 828 B After Width: | Height: | Size: 828 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 943 B After Width: | Height: | Size: 943 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
@@ -11,10 +11,10 @@
|
||||
@REM prepare the variables.
|
||||
|
||||
@REM version name
|
||||
SET VERSION_NAME=tank-3.0.4
|
||||
SET VERSION_NAME=tank-3.1.6
|
||||
ECHO VERSION_NAME: %VERSION_NAME%
|
||||
@REM golang proxy
|
||||
SET GOPROXY=https://athens.azurefd.net
|
||||
SET GOPROXY=https://goproxy.cn
|
||||
ECHO GOPROXY: %GOPROXY%
|
||||
@REM assign variable like Linux GOARCH=$(go env GOARCH) eg. amd64
|
||||
FOR /f %%i IN ('go env GOARCH') DO SET GOARCH=%%i
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
#prepare the variables.
|
||||
|
||||
# version name
|
||||
VERSION_NAME=tank-3.0.4
|
||||
VERSION_NAME=tank-3.1.6
|
||||
echo "VERSION_NAME: ${VERSION_NAME}"
|
||||
# golang proxy
|
||||
GOPROXY=https://athens.azurefd.net
|
||||
GOPROXY=https://goproxy.cn
|
||||
echo "GOPROXY: ${GOPROXY}"
|
||||
# eg. amd64
|
||||
GOARCH=$(go env GOARCH)
|
||||
@@ -47,8 +47,8 @@ echo "DIST_PATH: ${DIST_PATH}"
|
||||
|
||||
cd ${PROJECT_DIR}
|
||||
|
||||
echo "go build -mod=readonly"
|
||||
go build -mod=readonly
|
||||
echo "go build"
|
||||
go build
|
||||
|
||||
# if a directory
|
||||
if [[ -d COMPONENT_DIR ]] ; then
|
||||
|
||||
@@ -6,10 +6,10 @@ After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
DynamicUser=yes
|
||||
#DynamicUser=yes
|
||||
ExecStart=/data/program/tank/tank
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
WantedBy=multi-user.target
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package core
|
||||
|
||||
import "gorm.io/gorm/schema"
|
||||
|
||||
const (
|
||||
//authentication key of cookie
|
||||
COOKIE_AUTH_KEY = "_ak"
|
||||
@@ -9,19 +11,25 @@ const (
|
||||
|
||||
DEFAULT_SERVER_PORT = 6010
|
||||
|
||||
//db table's prefix. tank30_ means current version is tank:3.0.x
|
||||
TABLE_PREFIX = "tank30_"
|
||||
//db table's prefix. tank31_ means current version is tank:3.1.x
|
||||
TABLE_PREFIX = "tank31_"
|
||||
|
||||
VERSION = "3.0.4"
|
||||
VERSION = "3.1.6"
|
||||
)
|
||||
|
||||
type Config interface {
|
||||
Installed() bool
|
||||
ServerPort() int
|
||||
//get the db type
|
||||
DbType() string
|
||||
//get the mysql url. eg. tank:tank123@tcp(127.0.0.1:3306)/tank?charset=utf8&parseTime=True&loc=Local
|
||||
MysqlUrl() string
|
||||
//get the sqlite path
|
||||
SqliteFolder() string
|
||||
//files storage location.
|
||||
MatterPath() string
|
||||
//table name strategy
|
||||
NamingStrategy() schema.NamingStrategy
|
||||
//when installed by user. Write configs to tank.json
|
||||
FinishInstall(mysqlPort int, mysqlHost string, mysqlSchema string, mysqlUsername string, mysqlPassword string)
|
||||
FinishInstall(dbType string, mysqlPort int, mysqlHost string, mysqlSchema string, mysqlUsername string, mysqlPassword string, mysqlCharset string)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package core
|
||||
|
||||
import (
|
||||
"github.com/eyebluecn/tank/code/tool/cache"
|
||||
"github.com/jinzhu/gorm"
|
||||
"gorm.io/gorm"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package rest
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -11,26 +10,11 @@ const (
|
||||
|
||||
DIRECTION_ASC = "ASC"
|
||||
DIRECTION_DESC = "DESC"
|
||||
|
||||
EMPTY_JSON_MAP = "{}"
|
||||
EMPTY_JSON_ARRAY = "[]"
|
||||
)
|
||||
|
||||
type IBase interface {
|
||||
//name of db table
|
||||
TableName() string
|
||||
}
|
||||
|
||||
// Mysql 5.5 only support one CURRENT_TIMESTAMP
|
||||
// so we use 2018-01-01 00:00:00 as default, which is the first release date of EyeblueTank
|
||||
type Base struct {
|
||||
Uuid string `json:"uuid" gorm:"type:char(36);primary_key;unique"`
|
||||
Sort int64 `json:"sort" gorm:"type:bigint(20) not null"`
|
||||
UpdateTime time.Time `json:"updateTime" gorm:"type:timestamp not null;default:CURRENT_TIMESTAMP"`
|
||||
CreateTime time.Time `json:"createTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
}
|
||||
|
||||
func (this *Base) TableName() string {
|
||||
panic("you should overwrite TableName()")
|
||||
}
|
||||
|
||||
//pager
|
||||
type Pager struct {
|
||||
Page int `json:"page"`
|
||||
|
||||
@@ -4,9 +4,8 @@ import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"github.com/eyebluecn/tank/code/tool/builder"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/jinzhu/gorm"
|
||||
|
||||
"github.com/nu7hatch/gouuid"
|
||||
"github.com/eyebluecn/tank/code/tool/uuid"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -59,7 +58,7 @@ func (this *BridgeDao) CheckByShareUuidAndMatterUuid(shareUuid string, matterUui
|
||||
}
|
||||
|
||||
//get pager
|
||||
func (this *BridgeDao) Page(page int, pageSize int, shareUuid string, sortArray []builder.OrderPair) *Pager {
|
||||
func (this *BridgeDao) PlainPage(page int, pageSize int, shareUuid string, sortArray []builder.OrderPair) (int, []*Bridge) {
|
||||
|
||||
var wp = &builder.WherePair{}
|
||||
|
||||
@@ -70,13 +69,21 @@ func (this *BridgeDao) Page(page int, pageSize int, shareUuid string, sortArray
|
||||
var conditionDB *gorm.DB
|
||||
conditionDB = core.CONTEXT.GetDB().Model(&Bridge{}).Where(wp.Query, wp.Args...)
|
||||
|
||||
count := 0
|
||||
var count int64 = 0
|
||||
db := conditionDB.Count(&count)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
var bridges []*Bridge
|
||||
db = conditionDB.Order(this.GetSortString(sortArray)).Offset(page * pageSize).Limit(pageSize).Find(&bridges)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
return int(count), bridges
|
||||
}
|
||||
|
||||
//get pager
|
||||
func (this *BridgeDao) Page(page int, pageSize int, shareUuid string, sortArray []builder.OrderPair) *Pager {
|
||||
|
||||
count, bridges := this.PlainPage(page, pageSize, shareUuid, sortArray)
|
||||
pager := NewPager(page, pageSize, count, bridges)
|
||||
|
||||
return pager
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
)
|
||||
import "time"
|
||||
|
||||
/**
|
||||
* the link table for Share and Matter.
|
||||
*/
|
||||
type Bridge struct {
|
||||
Base
|
||||
ShareUuid string `json:"shareUuid" gorm:"type:char(36)"`
|
||||
MatterUuid string `json:"matterUuid" gorm:"type:char(36)"`
|
||||
}
|
||||
|
||||
func (this *Bridge) TableName() string {
|
||||
return core.TABLE_PREFIX + "bridge"
|
||||
Uuid string `json:"uuid" gorm:"type:char(36);primary_key;unique"`
|
||||
Sort int64 `json:"sort" gorm:"type:bigint(20) not null"`
|
||||
UpdateTime time.Time `json:"updateTime" gorm:"type:timestamp not null;default:CURRENT_TIMESTAMP"`
|
||||
CreateTime time.Time `json:"createTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
ShareUuid string `json:"shareUuid" gorm:"type:char(36)"`
|
||||
MatterUuid string `json:"matterUuid" gorm:"type:char(36)"`
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ func (this *DashboardController) RegisterRoutes() map[string]func(writer http.Re
|
||||
|
||||
routeMap["/api/dashboard/page"] = this.Wrap(this.Page, USER_ROLE_ADMINISTRATOR)
|
||||
routeMap["/api/dashboard/active/ip/top10"] = this.Wrap(this.ActiveIpTop10, USER_ROLE_ADMINISTRATOR)
|
||||
routeMap["/api/dashboard/etl"] = this.Wrap(this.Etl, USER_ROLE_ADMINISTRATOR)
|
||||
|
||||
return routeMap
|
||||
}
|
||||
@@ -89,3 +90,9 @@ func (this *DashboardController) ActiveIpTop10(writer http.ResponseWriter, reque
|
||||
list := this.dashboardDao.ActiveIpTop10()
|
||||
return this.Success(list)
|
||||
}
|
||||
|
||||
func (this *DashboardController) Etl(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
this.dashboardService.Etl()
|
||||
return this.Success("OK")
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ package rest
|
||||
import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"github.com/eyebluecn/tank/code/tool/builder"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/nu7hatch/gouuid"
|
||||
"github.com/eyebluecn/tank/code/tool/uuid"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -61,14 +61,14 @@ func (this *DashboardDao) Page(page int, pageSize int, dt string, sortArray []bu
|
||||
var conditionDB *gorm.DB
|
||||
conditionDB = core.CONTEXT.GetDB().Model(&Dashboard{}).Where(wp.Query, wp.Args...)
|
||||
|
||||
count := 0
|
||||
var count int64 = 0
|
||||
db := conditionDB.Count(&count)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
var dashboards []*Dashboard
|
||||
db = conditionDB.Order(this.GetSortString(sortArray)).Offset(page * pageSize).Limit(pageSize).Find(&dashboards)
|
||||
this.PanicError(db.Error)
|
||||
pager := NewPager(page, pageSize, count, dashboards)
|
||||
pager := NewPager(page, pageSize, int(count), dashboards)
|
||||
|
||||
return pager
|
||||
}
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
package rest
|
||||
|
||||
import "github.com/eyebluecn/tank/code/core"
|
||||
import "time"
|
||||
|
||||
/**
|
||||
* application's dashboard.
|
||||
*/
|
||||
type Dashboard struct {
|
||||
Base
|
||||
InvokeNum int64 `json:"invokeNum" gorm:"type:bigint(20) not null"` //api invoke num.
|
||||
TotalInvokeNum int64 `json:"totalInvokeNum" gorm:"type:bigint(20) not null;default:0"` //total invoke num up to now.
|
||||
Uv int64 `json:"uv" gorm:"type:bigint(20) not null;default:0"` //today's uv
|
||||
TotalUv int64 `json:"totalUv" gorm:"type:bigint(20) not null;default:0"` //total uv
|
||||
MatterNum int64 `json:"matterNum" gorm:"type:bigint(20) not null;default:0"` //file's num
|
||||
TotalMatterNum int64 `json:"totalMatterNum" gorm:"type:bigint(20) not null;default:0"` //file's total number
|
||||
FileSize int64 `json:"fileSize" gorm:"type:bigint(20) not null;default:0"` //today's file size
|
||||
TotalFileSize int64 `json:"totalFileSize" gorm:"type:bigint(20) not null;default:0"` //total file's size
|
||||
AvgCost int64 `json:"avgCost" gorm:"type:bigint(20) not null;default:0"` //api time cost in ms
|
||||
Dt string `json:"dt" gorm:"type:varchar(45) not null;index:idx_dt"` //date
|
||||
}
|
||||
|
||||
// set File's table name to be `profiles`
|
||||
func (this *Dashboard) TableName() string {
|
||||
return core.TABLE_PREFIX + "dashboard"
|
||||
Uuid string `json:"uuid" gorm:"type:char(36);primary_key;unique"`
|
||||
Sort int64 `json:"sort" gorm:"type:bigint(20) not null"`
|
||||
UpdateTime time.Time `json:"updateTime" gorm:"type:timestamp not null;default:CURRENT_TIMESTAMP"`
|
||||
CreateTime time.Time `json:"createTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
InvokeNum int64 `json:"invokeNum" gorm:"type:bigint(20) not null"` //api invoke num.
|
||||
TotalInvokeNum int64 `json:"totalInvokeNum" gorm:"type:bigint(20) not null;default:0"` //total invoke num up to now.
|
||||
Uv int64 `json:"uv" gorm:"type:bigint(20) not null;default:0"` //today's uv
|
||||
TotalUv int64 `json:"totalUv" gorm:"type:bigint(20) not null;default:0"` //total uv
|
||||
MatterNum int64 `json:"matterNum" gorm:"type:bigint(20) not null;default:0"` //file's num
|
||||
TotalMatterNum int64 `json:"totalMatterNum" gorm:"type:bigint(20) not null;default:0"` //file's total number
|
||||
FileSize int64 `json:"fileSize" gorm:"type:bigint(20) not null;default:0"` //today's file size
|
||||
TotalFileSize int64 `json:"totalFileSize" gorm:"type:bigint(20) not null;default:0"` //total file's size
|
||||
AvgCost int64 `json:"avgCost" gorm:"type:bigint(20) not null;default:0"` //api time cost in ms
|
||||
Dt string `json:"dt" gorm:"type:varchar(45) not null;index:idx_dashboard_dt"` //date. index should unique globally.
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,6 @@ package rest
|
||||
import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"github.com/eyebluecn/tank/code/tool/util"
|
||||
"github.com/robfig/cron"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -49,25 +48,24 @@ func (this *DashboardService) Init() {
|
||||
|
||||
func (this *DashboardService) Bootstrap() {
|
||||
|
||||
this.logger.Info("[cron job] Everyday 00:05 ETL dashboard data.")
|
||||
expression := "0 5 0 * * ?"
|
||||
cronJob := cron.New()
|
||||
err := cronJob.AddFunc(expression, this.etl)
|
||||
core.PanicError(err)
|
||||
cronJob.Start()
|
||||
this.logger.Info("Immediately ETL dashboard data.")
|
||||
|
||||
//do the etl method now.
|
||||
go core.RunWithRecovery(this.etl)
|
||||
go core.RunWithRecovery(this.Etl)
|
||||
}
|
||||
|
||||
func (this *DashboardService) Etl() {
|
||||
this.etlOneDay(util.Yesterday())
|
||||
this.etlOneDay(time.Now())
|
||||
}
|
||||
|
||||
// handle the dashboard data.
|
||||
func (this *DashboardService) etl() {
|
||||
func (this *DashboardService) etlOneDay(thenTime time.Time) {
|
||||
|
||||
this.logger.Info("ETL dashboard data.")
|
||||
|
||||
startTime := util.FirstSecondOfDay(util.Yesterday())
|
||||
endTime := util.LastSecondOfDay(util.Yesterday())
|
||||
startTime := util.FirstSecondOfDay(thenTime)
|
||||
endTime := util.LastSecondOfDay(thenTime)
|
||||
dt := util.ConvertTimeToDateString(startTime)
|
||||
now := time.Now()
|
||||
longTimeAgo := time.Now()
|
||||
longTimeAgo = longTimeAgo.AddDate(-20, 0, 0)
|
||||
|
||||
@@ -81,19 +79,19 @@ func (this *DashboardService) etl() {
|
||||
}
|
||||
|
||||
invokeNum := this.footprintDao.CountBetweenTime(startTime, endTime)
|
||||
totalInvokeNum := this.footprintDao.CountBetweenTime(longTimeAgo, endTime)
|
||||
totalInvokeNum := this.footprintDao.CountBetweenTime(longTimeAgo, now)
|
||||
uv := this.footprintDao.UvBetweenTime(startTime, endTime)
|
||||
totalUv := this.footprintDao.UvBetweenTime(longTimeAgo, endTime)
|
||||
totalUv := this.footprintDao.UvBetweenTime(longTimeAgo, now)
|
||||
matterNum := this.matterDao.CountBetweenTime(startTime, endTime)
|
||||
totalMatterNum := this.matterDao.CountBetweenTime(longTimeAgo, endTime)
|
||||
totalMatterNum := this.matterDao.CountBetweenTime(longTimeAgo, now)
|
||||
|
||||
matterSize := this.matterDao.SizeBetweenTime(startTime, endTime)
|
||||
|
||||
totalMatterSize := this.matterDao.SizeBetweenTime(longTimeAgo, endTime)
|
||||
totalMatterSize := this.matterDao.SizeBetweenTime(longTimeAgo, now)
|
||||
|
||||
cacheSize := this.imageCacheDao.SizeBetweenTime(startTime, endTime)
|
||||
|
||||
totalCacheSize := this.imageCacheDao.SizeBetweenTime(longTimeAgo, endTime)
|
||||
totalCacheSize := this.imageCacheDao.SizeBetweenTime(longTimeAgo, now)
|
||||
|
||||
avgCost := this.footprintDao.AvgCostBetweenTime(startTime, endTime)
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
* WebDav document
|
||||
* https://tools.ietf.org/html/rfc4918
|
||||
* http://www.webdav.org/specs/rfc4918.html
|
||||
*
|
||||
* test machine: http://www.webdav.org/neon/litmus/
|
||||
*/
|
||||
|
||||
type DavController struct {
|
||||
@@ -145,7 +145,7 @@ func (this *DavController) debug(writer http.ResponseWriter, request *http.Reque
|
||||
|
||||
bodyBytes, err := ioutil.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
fmt.Println("读取body时出错" + err.Error())
|
||||
fmt.Println("occur error when reading body: " + err.Error())
|
||||
}
|
||||
fmt.Println(string(bodyBytes))
|
||||
|
||||
@@ -162,6 +162,7 @@ func (this *DavController) debug(writer http.ResponseWriter, request *http.Reque
|
||||
|
||||
func (this *DavController) Index(writer http.ResponseWriter, request *http.Request, subPath string) {
|
||||
|
||||
//when debugging. open it.
|
||||
//this.debug(writer, request, subPath)
|
||||
|
||||
user := this.CheckCurrentUser(writer, request)
|
||||
|
||||
@@ -7,11 +7,14 @@ import (
|
||||
"github.com/eyebluecn/tank/code/tool/dav/xml"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/eyebluecn/tank/code/tool/util"
|
||||
"github.com/eyebluecn/tank/code/tool/webdav"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -26,6 +29,7 @@ type DavService struct {
|
||||
BaseBean
|
||||
matterDao *MatterDao
|
||||
matterService *MatterService
|
||||
lockSystem webdav.LockSystem
|
||||
}
|
||||
|
||||
func (this *DavService) Init() {
|
||||
@@ -41,6 +45,8 @@ func (this *DavService) Init() {
|
||||
this.matterService = b
|
||||
}
|
||||
|
||||
// init the webdav lock system.
|
||||
this.lockSystem = webdav.NewMemLS()
|
||||
}
|
||||
|
||||
//get the depth in header. Not support infinity yet.
|
||||
@@ -87,7 +93,8 @@ func (this *DavService) PropstatsFromXmlNames(user *User, matter *Matter, xmlNam
|
||||
|
||||
propstats := make([]dav.Propstat, 0)
|
||||
|
||||
var properties []dav.Property
|
||||
var okProperties []dav.Property
|
||||
var notFoundProperties []dav.Property
|
||||
|
||||
for _, xmlName := range xmlNames {
|
||||
//TODO: deadprops not implement yet.
|
||||
@@ -96,22 +103,48 @@ func (this *DavService) PropstatsFromXmlNames(user *User, matter *Matter, xmlNam
|
||||
if liveProp := LivePropMap[xmlName]; liveProp.findFn != nil && (liveProp.dir || !matter.Dir) {
|
||||
innerXML := liveProp.findFn(user, matter)
|
||||
|
||||
properties = append(properties, dav.Property{
|
||||
okProperties = append(okProperties, dav.Property{
|
||||
XMLName: xmlName,
|
||||
InnerXML: []byte(innerXML),
|
||||
})
|
||||
} else {
|
||||
this.logger.Info("%s %s cannot finish.", matter.Path, xmlName.Local)
|
||||
this.logger.Info("handle props %s %s.", matter.Path, xmlName.Local)
|
||||
|
||||
propMap := matter.FetchPropMap()
|
||||
if value, isPresent := propMap[xmlName.Local]; isPresent {
|
||||
okProperties = append(okProperties, dav.Property{
|
||||
XMLName: xmlName,
|
||||
InnerXML: []byte(value),
|
||||
})
|
||||
} else {
|
||||
|
||||
//only accept Space not null.
|
||||
if xmlName.Space != "" {
|
||||
|
||||
//collect not found props
|
||||
notFoundProperties = append(notFoundProperties, dav.Property{
|
||||
XMLName: xmlName,
|
||||
InnerXML: []byte(""),
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(properties) == 0 {
|
||||
if len(okProperties) == 0 && len(notFoundProperties) == 0 {
|
||||
panic(result.BadRequest("cannot parse request properties"))
|
||||
}
|
||||
|
||||
okPropstat := dav.Propstat{Status: http.StatusOK, Props: properties}
|
||||
if len(okProperties) != 0 {
|
||||
okPropstat := dav.Propstat{Status: http.StatusOK, Props: okProperties}
|
||||
propstats = append(propstats, okPropstat)
|
||||
}
|
||||
|
||||
propstats = append(propstats, okPropstat)
|
||||
if len(notFoundProperties) != 0 {
|
||||
notFoundPropstat := dav.Propstat{Status: http.StatusNotFound, Props: notFoundProperties}
|
||||
propstats = append(propstats, notFoundPropstat)
|
||||
}
|
||||
|
||||
return propstats
|
||||
|
||||
@@ -154,6 +187,7 @@ func (this *DavService) HandlePropfind(writer http.ResponseWriter, request *http
|
||||
|
||||
fmt.Printf("PROPFIND %s\n", subPath)
|
||||
|
||||
// read depth
|
||||
depth := this.ParseDepth(request)
|
||||
|
||||
propfind := dav.ReadPropfind(request.Body)
|
||||
@@ -166,7 +200,7 @@ func (this *DavService) HandlePropfind(writer http.ResponseWriter, request *http
|
||||
matters = []*Matter{matter}
|
||||
} else {
|
||||
// len(matters) == 0 means empty directory
|
||||
matters = this.matterDao.FindByPuuidAndUserUuid(matter.Uuid, user.Uuid, nil)
|
||||
matters = this.matterDao.FindByPuuidAndUserUuidAndDeleted(matter.Uuid, user.Uuid, FALSE, nil)
|
||||
|
||||
//add this matter to head.
|
||||
matters = append([]*Matter{matter}, matters...)
|
||||
@@ -194,6 +228,74 @@ func (this *DavService) HandlePropfind(writer http.ResponseWriter, request *http
|
||||
|
||||
}
|
||||
|
||||
//change the file's property
|
||||
func (this *DavService) HandleProppatch(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
|
||||
|
||||
fmt.Printf("PROPPATCH %s\n", subPath)
|
||||
|
||||
// handle the lock feature.
|
||||
reqPath, status, err := this.stripPrefix(request.URL.Path)
|
||||
if err != nil {
|
||||
panic(result.StatusCodeWebResult(status, err.Error()))
|
||||
}
|
||||
release, status, err := this.confirmLocks(request, reqPath, "")
|
||||
if err != nil {
|
||||
panic(result.StatusCodeWebResult(status, err.Error()))
|
||||
}
|
||||
if release != nil {
|
||||
defer release()
|
||||
}
|
||||
|
||||
matter := this.matterDao.checkByUserUuidAndPath(user.Uuid, subPath)
|
||||
|
||||
patches, status, err := webdav.ReadProppatch(request.Body)
|
||||
this.PanicError(err)
|
||||
|
||||
fmt.Println("status:%v", status)
|
||||
|
||||
//prepare a multiStatusWriter.
|
||||
multiStatusWriter := &dav.MultiStatusWriter{Writer: writer}
|
||||
|
||||
propstats := make([]dav.Propstat, 0)
|
||||
propMap := matter.FetchPropMap()
|
||||
for _, patch := range patches {
|
||||
propStat := dav.Propstat{Status: http.StatusOK}
|
||||
if patch.Remove {
|
||||
|
||||
if len(patch.Props) > 0 {
|
||||
property := patch.Props[0]
|
||||
if _, isPresent := propMap[property.XMLName.Local]; isPresent {
|
||||
//delete the prop.
|
||||
delete(propMap, property.XMLName.Local)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, prop := range patch.Props {
|
||||
propMap[prop.XMLName.Local] = string(prop.InnerXML)
|
||||
|
||||
propStat.Props = append(propStat.Props, dav.Property{XMLName: xml.Name{Space: prop.XMLName.Space, Local: prop.XMLName.Local}})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
propstats = append(propstats, propStat)
|
||||
|
||||
}
|
||||
matter.SetPropMap(propMap)
|
||||
// update the matter
|
||||
this.matterDao.Save(matter)
|
||||
|
||||
visitPath := fmt.Sprintf("%s%s", WEBDAV_PREFIX, matter.Path)
|
||||
response := this.makePropstatResponse(visitPath, propstats)
|
||||
|
||||
err1 := multiStatusWriter.Write(response)
|
||||
this.PanicError(err1)
|
||||
|
||||
err2 := multiStatusWriter.Close()
|
||||
this.PanicError(err2)
|
||||
|
||||
}
|
||||
|
||||
//handle download
|
||||
func (this *DavService) HandleGetHeadPost(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
|
||||
|
||||
@@ -217,6 +319,23 @@ func (this *DavService) HandlePut(writer http.ResponseWriter, request *http.Requ
|
||||
|
||||
fmt.Printf("PUT %s\n", subPath)
|
||||
|
||||
// handle the lock feature.
|
||||
reqPath, status, err := this.stripPrefix(request.URL.Path)
|
||||
if err != nil {
|
||||
panic(result.StatusCodeWebResult(status, err.Error()))
|
||||
}
|
||||
release, status, err := this.confirmLocks(request, reqPath, "")
|
||||
if err != nil {
|
||||
|
||||
//if status == http.StatusLocked {
|
||||
// status = http.StatusPreconditionFailed
|
||||
//}
|
||||
panic(result.StatusCodeWebResult(status, err.Error()))
|
||||
}
|
||||
if release != nil {
|
||||
defer release()
|
||||
}
|
||||
|
||||
filename := util.GetFilenameOfPath(subPath)
|
||||
dirPath := util.GetDirOfPath(subPath)
|
||||
|
||||
@@ -230,16 +349,31 @@ func (this *DavService) HandlePut(writer http.ResponseWriter, request *http.Requ
|
||||
|
||||
this.matterService.Upload(request, request.Body, user, dirMatter, filename, true)
|
||||
|
||||
//set the status code 201
|
||||
writer.WriteHeader(http.StatusCreated)
|
||||
|
||||
}
|
||||
|
||||
//delete file
|
||||
func (this *DavService) HandleDelete(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
|
||||
func (this *DavService) HandleDelete(w http.ResponseWriter, r *http.Request, user *User, subPath string) {
|
||||
|
||||
fmt.Printf("DELETE %s\n", subPath)
|
||||
|
||||
reqPath, status, err := this.stripPrefix(r.URL.Path)
|
||||
if err != nil {
|
||||
panic(result.StatusCodeWebResult(status, err.Error()))
|
||||
}
|
||||
release, status, err := this.confirmLocks(r, reqPath, "")
|
||||
if err != nil {
|
||||
panic(result.StatusCodeWebResult(status, err.Error()))
|
||||
}
|
||||
if release != nil {
|
||||
defer release()
|
||||
}
|
||||
|
||||
matter := this.matterDao.CheckWithRootByPath(subPath, user)
|
||||
|
||||
this.matterService.AtomicDelete(request, matter, user)
|
||||
this.matterService.AtomicDelete(r, matter, user)
|
||||
}
|
||||
|
||||
//crate a directory
|
||||
@@ -247,10 +381,37 @@ func (this *DavService) HandleMkcol(writer http.ResponseWriter, request *http.Re
|
||||
|
||||
fmt.Printf("MKCOL %s\n", subPath)
|
||||
|
||||
//the body of MKCOL request MUST be empty. (RFC2518:8.3.1)
|
||||
bodyBytes, err := ioutil.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
fmt.Println("occur error when reading body: " + err.Error())
|
||||
} else {
|
||||
if len(bodyBytes) != 0 {
|
||||
//throw conflict error
|
||||
panic(result.CustomWebResult(result.UNSUPPORTED_MEDIA_TYPE, fmt.Sprintf("%s MKCOL should NO body", subPath)))
|
||||
}
|
||||
}
|
||||
|
||||
thisDirName := util.GetFilenameOfPath(subPath)
|
||||
dirPath := util.GetDirOfPath(subPath)
|
||||
|
||||
dirMatter := this.matterDao.CheckWithRootByPath(dirPath, user)
|
||||
dirMatter := this.matterDao.FindWithRootByPath(dirPath, user)
|
||||
if dirMatter == nil {
|
||||
//throw conflict error
|
||||
panic(result.CustomWebResult(result.CONFLICT, fmt.Sprintf("%s not exist", dirPath)))
|
||||
}
|
||||
|
||||
//check whether col exists. (RFC2518:8.3.1)
|
||||
dbMatter := this.matterDao.FindByUserUuidAndPuuidAndDirAndName(user.Uuid, dirMatter.Uuid, TRUE, thisDirName)
|
||||
if dbMatter != nil {
|
||||
panic(result.CustomWebResult(result.METHOD_NOT_ALLOWED, fmt.Sprintf("%s already exists", dirPath)))
|
||||
}
|
||||
|
||||
//check whether file exists. (RFC2518:8.3.1)
|
||||
fileMatter := this.matterDao.FindByUserUuidAndPuuidAndDirAndName(user.Uuid, dirMatter.Uuid, FALSE, thisDirName)
|
||||
if fileMatter != nil {
|
||||
panic(result.CustomWebResult(result.METHOD_NOT_ALLOWED, fmt.Sprintf("%s file already exists", dirPath)))
|
||||
}
|
||||
|
||||
this.matterService.AtomicCreateDirectory(request, dirMatter, thisDirName, user)
|
||||
|
||||
@@ -352,7 +513,11 @@ func (this *DavService) prepareMoveCopy(
|
||||
panic(result.BadRequest("you cannot move the root directory"))
|
||||
}
|
||||
|
||||
destDirMatter = this.matterDao.CheckWithRootByPath(destinationDirPath, user)
|
||||
destDirMatter = this.matterDao.FindWithRootByPath(destinationDirPath, user)
|
||||
if destDirMatter == nil {
|
||||
//throw conflict error
|
||||
panic(result.CustomWebResult(result.CONFLICT, fmt.Sprintf("%s not exist", destinationDirPath)))
|
||||
}
|
||||
|
||||
return srcMatter, destDirMatter, srcDirPath, destinationDirPath, destinationName, overwrite
|
||||
|
||||
@@ -363,16 +528,38 @@ func (this *DavService) HandleMove(writer http.ResponseWriter, request *http.Req
|
||||
|
||||
fmt.Printf("MOVE %s\n", subPath)
|
||||
|
||||
// handle the lock feature.
|
||||
reqPath, status, err := this.stripPrefix(request.URL.Path)
|
||||
if err != nil {
|
||||
panic(result.StatusCodeWebResult(status, err.Error()))
|
||||
}
|
||||
release, status, err := this.confirmLocks(request, reqPath, "")
|
||||
if err != nil {
|
||||
panic(result.StatusCodeWebResult(status, err.Error()))
|
||||
}
|
||||
if release != nil {
|
||||
defer release()
|
||||
}
|
||||
|
||||
srcMatter, destDirMatter, srcDirPath, destinationDirPath, destinationName, overwrite := this.prepareMoveCopy(writer, request, user, subPath)
|
||||
|
||||
//move to the new directory
|
||||
if destinationDirPath == srcDirPath {
|
||||
//if destination path not change. it means rename.
|
||||
this.matterService.AtomicRename(request, srcMatter, destinationName, user)
|
||||
this.matterService.AtomicRename(request, srcMatter, destinationName, overwrite, user)
|
||||
} else {
|
||||
this.matterService.AtomicMove(request, srcMatter, destDirMatter, overwrite, user)
|
||||
}
|
||||
|
||||
this.logger.Info("finish moving %s => %s", subPath, destDirMatter.Path)
|
||||
|
||||
if overwrite {
|
||||
//overwrite old. set the status code 204
|
||||
writer.WriteHeader(http.StatusNoContent)
|
||||
} else {
|
||||
//copy new. set the status code 201
|
||||
writer.WriteHeader(http.StatusCreated)
|
||||
}
|
||||
}
|
||||
|
||||
//copy file/directory
|
||||
@@ -382,29 +569,245 @@ func (this *DavService) HandleCopy(writer http.ResponseWriter, request *http.Req
|
||||
|
||||
srcMatter, destDirMatter, _, _, destinationName, overwrite := this.prepareMoveCopy(writer, request, user, subPath)
|
||||
|
||||
// handle the lock feature.
|
||||
release, status, err := this.confirmLocks(request, destDirMatter.Path+"/"+destinationName, "")
|
||||
if err != nil {
|
||||
panic(result.StatusCodeWebResult(status, err.Error()))
|
||||
}
|
||||
if release != nil {
|
||||
defer release()
|
||||
}
|
||||
|
||||
//copy to the new directory
|
||||
this.matterService.AtomicCopy(request, srcMatter, destDirMatter, destinationName, overwrite, user)
|
||||
|
||||
this.logger.Info("finish copying %s => %s", subPath, destDirMatter.Path)
|
||||
|
||||
if overwrite {
|
||||
//overwrite old. set the status code 204
|
||||
writer.WriteHeader(http.StatusNoContent)
|
||||
} else {
|
||||
//copy new. set the status code 201
|
||||
writer.WriteHeader(http.StatusCreated)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (h *DavService) stripPrefix(p string) (string, int, error) {
|
||||
if r := strings.TrimPrefix(p, WEBDAV_PREFIX); len(r) < len(p) {
|
||||
return r, http.StatusOK, nil
|
||||
}
|
||||
return p, http.StatusNotFound, webdav.ErrPrefixMismatch
|
||||
}
|
||||
|
||||
func (h *DavService) lock(now time.Time, root string) (token string, status int, err error) {
|
||||
token, err = h.lockSystem.Create(now, webdav.LockDetails{
|
||||
Root: root,
|
||||
Duration: webdav.InfiniteTimeout,
|
||||
ZeroDepth: true,
|
||||
})
|
||||
if err != nil {
|
||||
if err == webdav.ErrLocked {
|
||||
return "", webdav.StatusLocked, err
|
||||
}
|
||||
return "", http.StatusInternalServerError, err
|
||||
}
|
||||
return token, 0, nil
|
||||
}
|
||||
|
||||
func (h *DavService) confirmLocks(r *http.Request, src, dst string) (release func(), status int, err error) {
|
||||
hdr := r.Header.Get("If")
|
||||
if hdr == "" {
|
||||
// An empty If header means that the client hasn't previously created locks.
|
||||
// Even if this client doesn't care about locks, we still need to check that
|
||||
// the resources aren't locked by another client, so we create temporary
|
||||
// locks that would conflict with another client's locks. These temporary
|
||||
// locks are unlocked at the end of the HTTP request.
|
||||
now, srcToken, dstToken := time.Now(), "", ""
|
||||
if src != "" {
|
||||
srcToken, status, err = h.lock(now, src)
|
||||
if err != nil {
|
||||
return nil, status, err
|
||||
}
|
||||
}
|
||||
if dst != "" {
|
||||
dstToken, status, err = h.lock(now, dst)
|
||||
if err != nil {
|
||||
if srcToken != "" {
|
||||
h.lockSystem.Unlock(now, srcToken)
|
||||
}
|
||||
return nil, status, err
|
||||
}
|
||||
}
|
||||
|
||||
return func() {
|
||||
if dstToken != "" {
|
||||
h.lockSystem.Unlock(now, dstToken)
|
||||
}
|
||||
if srcToken != "" {
|
||||
h.lockSystem.Unlock(now, srcToken)
|
||||
}
|
||||
}, 0, nil
|
||||
}
|
||||
|
||||
ih, ok := webdav.ParseIfHeader(hdr)
|
||||
if !ok {
|
||||
return nil, http.StatusBadRequest, webdav.ErrInvalidIfHeader
|
||||
}
|
||||
// ih is a disjunction (OR) of ifLists, so any IfList will do.
|
||||
for _, l := range ih.Lists {
|
||||
lsrc := l.ResourceTag
|
||||
if lsrc == "" {
|
||||
lsrc = src
|
||||
} else {
|
||||
u, err := url.Parse(lsrc)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if u.Host != r.Host {
|
||||
continue
|
||||
}
|
||||
lsrc, status, err = h.stripPrefix(u.Path)
|
||||
if err != nil {
|
||||
return nil, status, err
|
||||
}
|
||||
}
|
||||
release, err = h.lockSystem.Confirm(time.Now(), lsrc, dst, l.Conditions...)
|
||||
if err == webdav.ErrConfirmationFailed {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return nil, http.StatusInternalServerError, err
|
||||
}
|
||||
return release, 0, nil
|
||||
}
|
||||
// Section 10.4.1 says that "If this header is evaluated and all state lists
|
||||
// fail, then the request must fail with a 412 (Precondition Failed) status."
|
||||
// We follow the spec even though the cond_put_corrupt_token test case from
|
||||
// the litmus test warns on seeing a 412 instead of a 423 (Locked).
|
||||
return nil, http.StatusLocked, webdav.ErrLocked
|
||||
}
|
||||
|
||||
//lock.
|
||||
func (this *DavService) HandleLock(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
|
||||
func (this *DavService) HandleLock(w http.ResponseWriter, r *http.Request, user *User, subPath string) {
|
||||
|
||||
duration, err := webdav.ParseTimeout(r.Header.Get("Timeout"))
|
||||
if err != nil {
|
||||
panic(result.BadRequest(err.Error()))
|
||||
}
|
||||
li, status, err := webdav.ReadLockInfo(r.Body)
|
||||
if err != nil {
|
||||
panic(result.BadRequest(fmt.Sprintf("error:%s, status=%d", err.Error(), status)))
|
||||
}
|
||||
|
||||
token, ld, now, created := "", webdav.LockDetails{}, time.Now(), false
|
||||
if li == (webdav.LockInfo{}) {
|
||||
// An empty LockInfo means to refresh the lock.
|
||||
ih, ok := webdav.ParseIfHeader(r.Header.Get("If"))
|
||||
if !ok {
|
||||
panic(result.BadRequest(webdav.ErrInvalidIfHeader.Error()))
|
||||
}
|
||||
if len(ih.Lists) == 1 && len(ih.Lists[0].Conditions) == 1 {
|
||||
token = ih.Lists[0].Conditions[0].Token
|
||||
}
|
||||
if token == "" {
|
||||
panic(result.BadRequest(webdav.ErrInvalidLockToken.Error()))
|
||||
}
|
||||
ld, err = this.lockSystem.Refresh(now, token, duration)
|
||||
if err != nil {
|
||||
if err == webdav.ErrNoSuchLock {
|
||||
panic(result.StatusCodeWebResult(http.StatusPreconditionFailed, err.Error()))
|
||||
}
|
||||
panic(result.StatusCodeWebResult(http.StatusInternalServerError, err.Error()))
|
||||
}
|
||||
|
||||
} else {
|
||||
// Section 9.10.3 says that "If no Depth header is submitted on a LOCK request,
|
||||
// then the request MUST act as if a "Depth:infinity" had been submitted."
|
||||
depth := webdav.InfiniteDepth
|
||||
if hdr := r.Header.Get("Depth"); hdr != "" {
|
||||
depth = webdav.ParseDepth(hdr)
|
||||
if depth != 0 && depth != webdav.InfiniteDepth {
|
||||
// Section 9.10.3 says that "Values other than 0 or infinity must not be
|
||||
// used with the Depth header on a LOCK method".
|
||||
panic(result.StatusCodeWebResult(http.StatusBadRequest, webdav.ErrInvalidDepth.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
reqPath, status, err := this.stripPrefix(r.URL.Path)
|
||||
if err != nil {
|
||||
panic(result.StatusCodeWebResult(status, err.Error()))
|
||||
}
|
||||
|
||||
ld = webdav.LockDetails{
|
||||
Root: reqPath,
|
||||
Duration: duration,
|
||||
OwnerXML: li.Owner.InnerXML,
|
||||
ZeroDepth: depth == 0,
|
||||
}
|
||||
token, err = this.lockSystem.Create(now, ld)
|
||||
if err != nil {
|
||||
if err == webdav.ErrLocked {
|
||||
panic(result.StatusCodeWebResult(http.StatusLocked, err.Error()))
|
||||
}
|
||||
panic(result.StatusCodeWebResult(http.StatusInternalServerError, err.Error()))
|
||||
}
|
||||
defer func() {
|
||||
//when error occur, rollback.
|
||||
//this.lockSystem.Unlock(now, token)
|
||||
}()
|
||||
|
||||
// Create the resource if it didn't previously exist.
|
||||
// ctx := r.Context()
|
||||
//if _, err := this.FileSystem.Stat(ctx, subPath); err != nil {
|
||||
// f, err := h.FileSystem.OpenFile(ctx, reqPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
// if err != nil {
|
||||
// // TODO: detect missing intermediate dirs and return http.StatusConflict?
|
||||
// return http.StatusInternalServerError, err
|
||||
// }
|
||||
// f.Close()
|
||||
// created = true
|
||||
//}
|
||||
|
||||
// http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
|
||||
// Lock-Token value is a Coded-URL. We add angle brackets.
|
||||
w.Header().Set("Lock-Token", "<"+token+">")
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/xml; charset=utf-8")
|
||||
if created {
|
||||
// This is "w.WriteHeader(http.StatusCreated)" and not "return
|
||||
// http.StatusCreated, nil" because we write our own (XML) response to w
|
||||
// and Handler.ServeHTTP would otherwise write "Created".
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
}
|
||||
_, _ = webdav.WriteLockInfo(w, token, ld)
|
||||
|
||||
panic(result.BadRequest("not support LOCK yet."))
|
||||
}
|
||||
|
||||
//unlock
|
||||
func (this *DavService) HandleUnlock(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
|
||||
func (this *DavService) HandleUnlock(w http.ResponseWriter, r *http.Request, user *User, subPath string) {
|
||||
|
||||
panic(result.BadRequest("not support UNLOCK yet."))
|
||||
}
|
||||
// http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
|
||||
// Lock-Token value is a Coded-URL. We strip its angle brackets.
|
||||
t := r.Header.Get("Lock-Token")
|
||||
if len(t) < 2 || t[0] != '<' || t[len(t)-1] != '>' {
|
||||
panic(result.StatusCodeWebResult(http.StatusBadRequest, webdav.ErrInvalidLockToken.Error()))
|
||||
}
|
||||
t = t[1 : len(t)-1]
|
||||
|
||||
//change the file's property
|
||||
func (this *DavService) HandleProppatch(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
|
||||
|
||||
panic(result.BadRequest("not support PROPPATCH yet."))
|
||||
switch err := this.lockSystem.Unlock(time.Now(), t); err {
|
||||
case nil:
|
||||
panic(result.StatusCodeWebResult(http.StatusNoContent, ""))
|
||||
case webdav.ErrForbidden:
|
||||
panic(result.StatusCodeWebResult(http.StatusForbidden, err.Error()))
|
||||
case webdav.ErrLocked:
|
||||
panic(result.StatusCodeWebResult(http.StatusLocked, err.Error()))
|
||||
case webdav.ErrNoSuchLock:
|
||||
panic(result.StatusCodeWebResult(http.StatusConflict, err.Error()))
|
||||
default:
|
||||
panic(result.StatusCodeWebResult(http.StatusInternalServerError, err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
//hanle all the request.
|
||||
|
||||
@@ -3,7 +3,7 @@ package rest
|
||||
import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/nu7hatch/gouuid"
|
||||
"github.com/eyebluecn/tank/code/tool/uuid"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -58,6 +58,13 @@ func (this *DownloadTokenDao) Save(downloadToken *DownloadToken) *DownloadToken
|
||||
return downloadToken
|
||||
}
|
||||
|
||||
func (this *DownloadTokenDao) DeleteByUserUuid(userUuid string) {
|
||||
|
||||
db := core.CONTEXT.GetDB().Where("user_uuid = ?", userUuid).Delete(DownloadToken{})
|
||||
this.PanicError(db.Error)
|
||||
|
||||
}
|
||||
|
||||
func (this *DownloadTokenDao) Cleanup() {
|
||||
this.logger.Info("[DownloadTokenDao] clean up. Delete all DownloadToken")
|
||||
db := core.CONTEXT.GetDB().Where("uuid is not null").Delete(DownloadToken{})
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DownloadToken struct {
|
||||
Base
|
||||
Uuid string `json:"uuid" gorm:"type:char(36);primary_key;unique"`
|
||||
Sort int64 `json:"sort" gorm:"type:bigint(20) not null"`
|
||||
UpdateTime time.Time `json:"updateTime" gorm:"type:timestamp not null;default:CURRENT_TIMESTAMP"`
|
||||
CreateTime time.Time `json:"createTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
UserUuid string `json:"userUuid" gorm:"type:char(36) not null"`
|
||||
MatterUuid string `json:"matterUuid" gorm:"type:char(36) not null;index:idx_mu"`
|
||||
MatterUuid string `json:"matterUuid" gorm:"type:char(36) not null;index:idx_download_token_mu"` //index should unique globally.
|
||||
ExpireTime time.Time `json:"expireTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
Ip string `json:"ip" gorm:"type:varchar(128) not null"`
|
||||
}
|
||||
|
||||
func (this *DownloadToken) TableName() string {
|
||||
return core.TABLE_PREFIX + "download_token"
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"github.com/eyebluecn/tank/code/tool/builder"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/jinzhu/gorm"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/nu7hatch/gouuid"
|
||||
"github.com/eyebluecn/tank/code/tool/uuid"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -48,14 +48,14 @@ func (this *FootprintDao) Page(page int, pageSize int, userUuid string, sortArra
|
||||
var conditionDB *gorm.DB
|
||||
conditionDB = core.CONTEXT.GetDB().Model(&Footprint{}).Where(wp.Query, wp.Args...)
|
||||
|
||||
count := 0
|
||||
var count int64 = 0
|
||||
db := conditionDB.Count(&count)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
var footprints []*Footprint
|
||||
db = conditionDB.Order(this.GetSortString(sortArray)).Offset(page * pageSize).Limit(pageSize).Find(&footprints)
|
||||
this.PanicError(db.Error)
|
||||
pager := NewPager(page, pageSize, count, footprints)
|
||||
pager := NewPager(page, pageSize, int(count), footprints)
|
||||
|
||||
return pager
|
||||
}
|
||||
@@ -137,6 +137,13 @@ func (this *FootprintDao) DeleteByCreateTimeBefore(createTime time.Time) {
|
||||
this.PanicError(db.Error)
|
||||
}
|
||||
|
||||
func (this *FootprintDao) DeleteByUserUuid(userUuid string) {
|
||||
|
||||
db := core.CONTEXT.GetDB().Where("user_uuid = ?", userUuid).Delete(Footprint{})
|
||||
this.PanicError(db.Error)
|
||||
|
||||
}
|
||||
|
||||
//System cleanup.
|
||||
func (this *FootprintDao) Cleanup() {
|
||||
this.logger.Info("[FootprintDao][DownloadTokenDao] clean up. Delete all Footprint")
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
package rest
|
||||
|
||||
import "github.com/eyebluecn/tank/code/core"
|
||||
import "time"
|
||||
|
||||
/**
|
||||
* visit record.
|
||||
*/
|
||||
// Footprint /**
|
||||
// Mysql 5.5 only support one CURRENT_TIMESTAMP
|
||||
// so we use 2018-01-01 00:00:00 as default, which is the first release date of EyeblueTank
|
||||
type Footprint struct {
|
||||
Base
|
||||
UserUuid string `json:"userUuid" gorm:"type:char(36)"`
|
||||
Ip string `json:"ip" gorm:"type:varchar(128) not null"`
|
||||
Host string `json:"host" gorm:"type:varchar(45) not null"`
|
||||
Uri string `json:"uri" gorm:"type:varchar(255) not null"`
|
||||
Params string `json:"params" gorm:"type:text"`
|
||||
Cost int64 `json:"cost" gorm:"type:bigint(20) not null;default:0"`
|
||||
Success bool `json:"success" gorm:"type:tinyint(1) not null;default:0"`
|
||||
}
|
||||
|
||||
// set File's table name to be `profiles`
|
||||
func (this *Footprint) TableName() string {
|
||||
return core.TABLE_PREFIX + "footprint"
|
||||
Uuid string `json:"uuid" gorm:"type:char(36);primary_key;unique"`
|
||||
Sort int64 `json:"sort" gorm:"type:bigint(20) not null"`
|
||||
UpdateTime time.Time `json:"updateTime" gorm:"type:timestamp not null;default:CURRENT_TIMESTAMP"`
|
||||
CreateTime time.Time `json:"createTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
UserUuid string `json:"userUuid" gorm:"type:char(36)"`
|
||||
Ip string `json:"ip" gorm:"type:varchar(128) not null"`
|
||||
Host string `json:"host" gorm:"type:varchar(45) not null"`
|
||||
Uri string `json:"uri" gorm:"type:varchar(255) not null"`
|
||||
Params string `json:"params" gorm:"type:text"`
|
||||
Cost int64 `json:"cost" gorm:"type:bigint(20) not null;default:0"`
|
||||
Success bool `json:"success" gorm:"type:tinyint(1) not null;default:0"`
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"github.com/eyebluecn/tank/code/tool/util"
|
||||
"github.com/robfig/cron"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
@@ -94,18 +93,12 @@ func (this *FootprintService) Trace(request *http.Request, duration time.Duratio
|
||||
|
||||
func (this *FootprintService) Bootstrap() {
|
||||
|
||||
this.logger.Info("[cron job] Every day 00:10 delete Footprint data 8 days ago.")
|
||||
expression := "0 10 0 * * ?"
|
||||
cronJob := cron.New()
|
||||
err := cronJob.AddFunc(expression, this.cleanOldData)
|
||||
core.PanicError(err)
|
||||
cronJob.Start()
|
||||
|
||||
go core.RunWithRecovery(this.cleanOldData)
|
||||
this.logger.Info("Immediately delete Footprint data of 8 days ago.")
|
||||
|
||||
go core.RunWithRecovery(this.CleanOldData)
|
||||
}
|
||||
|
||||
func (this *FootprintService) cleanOldData() {
|
||||
func (this *FootprintService) CleanOldData() {
|
||||
|
||||
day8Ago := time.Now()
|
||||
day8Ago = day8Ago.AddDate(0, 0, -8)
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"github.com/eyebluecn/tank/code/tool/builder"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/eyebluecn/tank/code/tool/util"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/nu7hatch/gouuid"
|
||||
"github.com/eyebluecn/tank/code/tool/uuid"
|
||||
"gorm.io/gorm"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
@@ -67,7 +67,7 @@ func (this *ImageCacheDao) CheckByUuidAndUserUuid(uuid string, userUuid string)
|
||||
|
||||
// Read
|
||||
var imageCache = &ImageCache{}
|
||||
db := core.CONTEXT.GetDB().Where(&ImageCache{Base: Base{Uuid: uuid}, UserUuid: userUuid}).First(imageCache)
|
||||
db := core.CONTEXT.GetDB().Where(&ImageCache{Uuid: uuid, UserUuid: userUuid}).First(imageCache)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
return imageCache
|
||||
@@ -101,14 +101,14 @@ func (this *ImageCacheDao) Page(page int, pageSize int, userUuid string, matterU
|
||||
var conditionDB *gorm.DB
|
||||
conditionDB = core.CONTEXT.GetDB().Model(&ImageCache{}).Where(wp.Query, wp.Args...)
|
||||
|
||||
count := 0
|
||||
var count int64 = 0
|
||||
db := conditionDB.Count(&count)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
var imageCaches []*ImageCache
|
||||
db = conditionDB.Order(this.GetSortString(sortArray)).Offset(page * pageSize).Limit(pageSize).Find(&imageCaches)
|
||||
this.PanicError(db.Error)
|
||||
pager := NewPager(page, pageSize, count, imageCaches)
|
||||
pager := NewPager(page, pageSize, int(count), imageCaches)
|
||||
|
||||
return pager
|
||||
}
|
||||
@@ -184,6 +184,11 @@ func (this *ImageCacheDao) DeleteByMatterUuid(matterUuid string) {
|
||||
|
||||
}
|
||||
|
||||
func (this *ImageCacheDao) DeleteByUserUuid(userUuid string) {
|
||||
db := core.CONTEXT.GetDB().Where("user_uuid = ?", userUuid).Delete(ImageCache{})
|
||||
this.PanicError(db.Error)
|
||||
}
|
||||
|
||||
func (this *ImageCacheDao) SizeBetweenTime(startTime time.Time, endTime time.Time) int64 {
|
||||
|
||||
var wp = &builder.WherePair{Query: "create_time >= ? AND create_time <= ?", Args: []interface{}{startTime, endTime}}
|
||||
|
||||
@@ -1,29 +1,25 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
)
|
||||
import "time"
|
||||
|
||||
/**
|
||||
* image cache.
|
||||
*/
|
||||
type ImageCache struct {
|
||||
Base
|
||||
Name string `json:"name" gorm:"type:varchar(255) not null"`
|
||||
UserUuid string `json:"userUuid" gorm:"type:char(36)"`
|
||||
Username string `json:"username" gorm:"type:varchar(45) not null"`
|
||||
MatterUuid string `json:"matterUuid" gorm:"type:char(36);index:idx_mu"`
|
||||
MatterName string `json:"matterName" gorm:"type:varchar(255) not null"`
|
||||
Mode string `json:"mode" gorm:"type:varchar(512)"`
|
||||
Md5 string `json:"md5" gorm:"type:varchar(45)"`
|
||||
Size int64 `json:"size" gorm:"type:bigint(20) not null;default:0"`
|
||||
Path string `json:"path" gorm:"type:varchar(512)"`
|
||||
Matter *Matter `json:"matter" gorm:"-"`
|
||||
}
|
||||
|
||||
// set File's table name to be `profiles`
|
||||
func (this *ImageCache) TableName() string {
|
||||
return core.TABLE_PREFIX + "image_cache"
|
||||
Uuid string `json:"uuid" gorm:"type:char(36);primary_key;unique"`
|
||||
Sort int64 `json:"sort" gorm:"type:bigint(20) not null"`
|
||||
UpdateTime time.Time `json:"updateTime" gorm:"type:timestamp not null;default:CURRENT_TIMESTAMP"`
|
||||
CreateTime time.Time `json:"createTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
Name string `json:"name" gorm:"type:varchar(255) not null"`
|
||||
UserUuid string `json:"userUuid" gorm:"type:char(36)"`
|
||||
Username string `json:"username" gorm:"type:varchar(45) not null"`
|
||||
MatterUuid string `json:"matterUuid" gorm:"type:char(36);index:idx_image_cache_mu"` //index should unique globally.
|
||||
MatterName string `json:"matterName" gorm:"type:varchar(255) not null"`
|
||||
Mode string `json:"mode" gorm:"type:varchar(512)"`
|
||||
Md5 string `json:"md5" gorm:"type:varchar(45)"`
|
||||
Size int64 `json:"size" gorm:"type:bigint(20) not null;default:0"`
|
||||
Path string `json:"path" gorm:"type:varchar(512)"`
|
||||
Matter *Matter `json:"matter" gorm:"-"`
|
||||
}
|
||||
|
||||
// get the absolute path. path in db means relative path.
|
||||
|
||||
@@ -6,12 +6,20 @@ import (
|
||||
"github.com/eyebluecn/tank/code/tool/builder"
|
||||
"github.com/eyebluecn/tank/code/tool/i18n"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/eyebluecn/tank/code/tool/third"
|
||||
"github.com/eyebluecn/tank/code/tool/util"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/nu7hatch/gouuid"
|
||||
"github.com/eyebluecn/tank/code/tool/uuid"
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"gorm.io/gorm/schema"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -24,7 +32,7 @@ type InstallController struct {
|
||||
matterService *MatterService
|
||||
imageCacheDao *ImageCacheDao
|
||||
imageCacheService *ImageCacheService
|
||||
tableNames []IBase
|
||||
tableNames []interface{}
|
||||
}
|
||||
|
||||
func (this *InstallController) Init() {
|
||||
@@ -60,7 +68,7 @@ func (this *InstallController) Init() {
|
||||
this.imageCacheService = c
|
||||
}
|
||||
|
||||
this.tableNames = []IBase{
|
||||
this.tableNames = []interface{}{
|
||||
&Dashboard{},
|
||||
&Bridge{},
|
||||
&DownloadToken{},
|
||||
@@ -92,11 +100,13 @@ func (this *InstallController) RegisterRoutes() map[string]func(writer http.Resp
|
||||
}
|
||||
|
||||
func (this *InstallController) openDbConnection(writer http.ResponseWriter, request *http.Request) *gorm.DB {
|
||||
dbType := request.FormValue("dbType")
|
||||
mysqlPortStr := request.FormValue("mysqlPort")
|
||||
mysqlHost := request.FormValue("mysqlHost")
|
||||
mysqlSchema := request.FormValue("mysqlSchema")
|
||||
mysqlUsername := request.FormValue("mysqlUsername")
|
||||
mysqlPassword := request.FormValue("mysqlPassword")
|
||||
mysqlCharset := request.FormValue("mysqlCharset")
|
||||
|
||||
var mysqlPort int
|
||||
if mysqlPortStr != "" {
|
||||
@@ -105,55 +115,114 @@ func (this *InstallController) openDbConnection(writer http.ResponseWriter, requ
|
||||
mysqlPort = tmp
|
||||
}
|
||||
|
||||
mysqlUrl := util.GetMysqlUrl(mysqlPort, mysqlHost, mysqlSchema, mysqlUsername, mysqlPassword)
|
||||
//log config
|
||||
dbLogger := logger.New(
|
||||
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
|
||||
logger.Config{
|
||||
SlowThreshold: time.Second, // slow SQL 1s
|
||||
LogLevel: logger.Silent, // log level. open when debug.
|
||||
IgnoreRecordNotFoundError: true, // ignore ErrRecordNotFound
|
||||
Colorful: false, // colorful print
|
||||
},
|
||||
)
|
||||
//table name strategy
|
||||
namingStrategy := core.CONFIG.NamingStrategy()
|
||||
|
||||
this.logger.Info("Connect MySQL %s", mysqlUrl)
|
||||
if dbType == "sqlite" {
|
||||
|
||||
var err error = nil
|
||||
db, err := gorm.Open("mysql", mysqlUrl)
|
||||
this.PanicError(err)
|
||||
var err error = nil
|
||||
sqliteFolder := core.CONFIG.SqliteFolder() + "/tank.sqlite"
|
||||
this.logger.Info("Connect Sqlite %s", sqliteFolder)
|
||||
db, err := gorm.Open(sqlite.Open(sqliteFolder), &gorm.Config{Logger: dbLogger, NamingStrategy: namingStrategy})
|
||||
|
||||
db.LogMode(false)
|
||||
if err != nil {
|
||||
core.LOGGER.Panic("failed to connect sqlite database")
|
||||
}
|
||||
|
||||
return db
|
||||
//sqlite lock issue. https://gist.github.com/mrnugget/0eda3b2b53a70fa4a894
|
||||
phyDb, err := db.DB()
|
||||
phyDb.SetMaxOpenConns(1)
|
||||
|
||||
return db
|
||||
|
||||
} else {
|
||||
mysqlUrl := util.GetMysqlUrl(mysqlPort, mysqlHost, mysqlSchema, mysqlUsername, mysqlPassword, mysqlCharset)
|
||||
|
||||
this.logger.Info("Connect MySQL %s", mysqlUrl)
|
||||
|
||||
var err error = nil
|
||||
db, err := gorm.Open(mysql.Open(mysqlUrl), &gorm.Config{Logger: dbLogger, NamingStrategy: namingStrategy})
|
||||
this.PanicError(err)
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (this *InstallController) closeDbConnection(db *gorm.DB) {
|
||||
|
||||
if db != nil {
|
||||
err := db.Close()
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
this.logger.Error("occur error when close db. %v", err)
|
||||
core.LOGGER.Error("occur error when get *sql.DB %s", err.Error())
|
||||
}
|
||||
err = sqlDB.Close()
|
||||
if err != nil {
|
||||
core.LOGGER.Error("occur error when closing db %s", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *InstallController) getTableMeta(gormDb *gorm.DB, entity IBase) (bool, []*gorm.StructField, []*gorm.StructField) {
|
||||
// (tableName, exists, allFields, missingFields)
|
||||
func (this *InstallController) getTableMeta(gormDb *gorm.DB, entity interface{}) (string, bool, []*InstallFieldInfo, []*InstallFieldInfo) {
|
||||
|
||||
db := gormDb.Unscoped()
|
||||
scope := db.NewScope(entity)
|
||||
//get all useful fields from model.
|
||||
entitySchema, err := schema.Parse(entity, &sync.Map{}, core.CONFIG.NamingStrategy())
|
||||
this.PanicError(err)
|
||||
|
||||
tableName := scope.TableName()
|
||||
modelStruct := scope.GetModelStruct()
|
||||
allFields := modelStruct.StructFields
|
||||
var missingFields = make([]*gorm.StructField, 0)
|
||||
tableName := entitySchema.Table
|
||||
|
||||
if !scope.Dialect().HasTable(tableName) {
|
||||
var allFields = make([]*InstallFieldInfo, 0)
|
||||
|
||||
for _, field := range entitySchema.Fields {
|
||||
allFields = append(allFields, &InstallFieldInfo{
|
||||
Name: field.DBName,
|
||||
DataType: string(field.DataType),
|
||||
})
|
||||
}
|
||||
|
||||
var missingFields = make([]*InstallFieldInfo, 0)
|
||||
|
||||
if !gormDb.Migrator().HasTable(tableName) {
|
||||
missingFields = append(missingFields, allFields...)
|
||||
|
||||
return false, allFields, missingFields
|
||||
return tableName, false, allFields, missingFields
|
||||
} else {
|
||||
|
||||
for _, field := range allFields {
|
||||
if !scope.Dialect().HasColumn(tableName, field.DBName) {
|
||||
if field.IsNormal {
|
||||
missingFields = append(missingFields, field)
|
||||
//tag with `gorm:"-"` will be ""
|
||||
if field.Name != "" {
|
||||
|
||||
database := gormDb.Migrator().CurrentDatabase()
|
||||
|
||||
//if sqlite
|
||||
if _, ok := gormDb.Dialector.(*sqlite.Dialector); ok {
|
||||
|
||||
if !gormDb.Migrator().HasColumn(tableName, field.Name) {
|
||||
missingFields = append(missingFields, field)
|
||||
}
|
||||
} else {
|
||||
|
||||
if !third.MysqlMigratorHasColumn(gormDb, database, tableName, field.Name) {
|
||||
missingFields = append(missingFields, field)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return true, allFields, missingFields
|
||||
return tableName, true, allFields, missingFields
|
||||
}
|
||||
|
||||
}
|
||||
@@ -163,9 +232,10 @@ func (this *InstallController) getTableMetaList(db *gorm.DB) []*InstallTableInfo
|
||||
var installTableInfos []*InstallTableInfo
|
||||
|
||||
for _, iBase := range this.tableNames {
|
||||
exist, allFields, missingFields := this.getTableMeta(db, iBase)
|
||||
tableName, exist, allFields, missingFields := this.getTableMeta(db, iBase)
|
||||
|
||||
installTableInfos = append(installTableInfos, &InstallTableInfo{
|
||||
Name: iBase.TableName(),
|
||||
Name: tableName,
|
||||
TableExist: exist,
|
||||
AllFields: allFields,
|
||||
MissingFields: missingFields,
|
||||
@@ -184,7 +254,7 @@ func (this *InstallController) validateTableMetaList(tableInfoList []*InstallTab
|
||||
|
||||
var strs []string
|
||||
for _, v := range tableInfo.MissingFields {
|
||||
strs = append(strs, v.DBName)
|
||||
strs = append(strs, v.Name)
|
||||
}
|
||||
|
||||
panic(result.BadRequest(fmt.Sprintf("table %s miss the following fields %v", tableInfo.Name, strs)))
|
||||
@@ -203,7 +273,9 @@ func (this *InstallController) Verify(writer http.ResponseWriter, request *http.
|
||||
defer this.closeDbConnection(db)
|
||||
|
||||
this.logger.Info("Ping DB")
|
||||
err := db.DB().Ping()
|
||||
phyDb, err := db.DB()
|
||||
this.PanicError(err)
|
||||
err = phyDb.Ping()
|
||||
this.PanicError(err)
|
||||
|
||||
return this.Success("OK")
|
||||
@@ -226,13 +298,21 @@ func (this *InstallController) CreateTable(writer http.ResponseWriter, request *
|
||||
|
||||
for _, iBase := range this.tableNames {
|
||||
|
||||
//complete the missing fields or create table. use utf8 charset
|
||||
db1 := db.Set("gorm:table_options", "CHARSET=utf8").AutoMigrate(iBase)
|
||||
this.PanicError(db1.Error)
|
||||
if _, ok := db.Dialector.(*sqlite.Dialector); ok {
|
||||
//if sqlite. no need to set CHARSET.
|
||||
err := db.AutoMigrate(iBase)
|
||||
this.PanicError(err)
|
||||
} else {
|
||||
//use utf8 charset
|
||||
mysqlCharset := request.FormValue("mysqlCharset")
|
||||
err := db.Set("gorm:table_options", fmt.Sprintf("CHARSET=%s", mysqlCharset)).AutoMigrate(iBase)
|
||||
this.PanicError(err)
|
||||
}
|
||||
|
||||
exist, allFields, missingFields := this.getTableMeta(db, iBase)
|
||||
//complete the missing fields or create table.
|
||||
tableName, exist, allFields, missingFields := this.getTableMeta(db, iBase)
|
||||
installTableInfos = append(installTableInfos, &InstallTableInfo{
|
||||
Name: iBase.TableName(),
|
||||
Name: tableName,
|
||||
TableExist: exist,
|
||||
AllFields: allFields,
|
||||
MissingFields: missingFields,
|
||||
@@ -345,11 +425,13 @@ func (this *InstallController) ValidateAdmin(writer http.ResponseWriter, request
|
||||
//Finish the installation
|
||||
func (this *InstallController) Finish(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
dbType := request.FormValue("dbType")
|
||||
mysqlPortStr := request.FormValue("mysqlPort")
|
||||
mysqlHost := request.FormValue("mysqlHost")
|
||||
mysqlSchema := request.FormValue("mysqlSchema")
|
||||
mysqlUsername := request.FormValue("mysqlUsername")
|
||||
mysqlPassword := request.FormValue("mysqlPassword")
|
||||
mysqlCharset := request.FormValue("mysqlCharset")
|
||||
|
||||
var mysqlPort int
|
||||
if mysqlPortStr != "" {
|
||||
@@ -375,7 +457,7 @@ func (this *InstallController) Finish(writer http.ResponseWriter, request *http.
|
||||
}
|
||||
|
||||
//announce the config to write config to tank.json
|
||||
core.CONFIG.FinishInstall(mysqlPort, mysqlHost, mysqlSchema, mysqlUsername, mysqlPassword)
|
||||
core.CONFIG.FinishInstall(dbType, mysqlPort, mysqlHost, mysqlSchema, mysqlUsername, mysqlPassword, mysqlCharset)
|
||||
|
||||
//announce the context to broadcast the installation news to bean.
|
||||
core.CONTEXT.InstallOk()
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
package rest
|
||||
|
||||
import "github.com/jinzhu/gorm"
|
||||
// InstallTableInfo /**
|
||||
// table meta info.
|
||||
type InstallTableInfo struct {
|
||||
Name string `json:"name"`
|
||||
TableExist bool `json:"tableExist"`
|
||||
AllFields []*InstallFieldInfo `json:"allFields"`
|
||||
MissingFields []*InstallFieldInfo `json:"missingFields"`
|
||||
}
|
||||
|
||||
/**
|
||||
* table meta info.
|
||||
*/
|
||||
type InstallTableInfo struct {
|
||||
Name string `json:"name"`
|
||||
TableExist bool `json:"tableExist"`
|
||||
AllFields []*gorm.StructField `json:"allFields"`
|
||||
MissingFields []*gorm.StructField `json:"missingFields"`
|
||||
type InstallFieldInfo struct {
|
||||
Name string `json:"name"`
|
||||
DataType string `json:"dataType"`
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ type MatterController struct {
|
||||
BaseController
|
||||
matterDao *MatterDao
|
||||
matterService *MatterService
|
||||
preferenceService *PreferenceService
|
||||
downloadTokenDao *DownloadTokenDao
|
||||
imageCacheDao *ImageCacheDao
|
||||
shareDao *ShareDao
|
||||
@@ -73,8 +74,13 @@ func (this *MatterController) RegisterRoutes() map[string]func(writer http.Respo
|
||||
routeMap["/api/matter/create/directory"] = this.Wrap(this.CreateDirectory, USER_ROLE_USER)
|
||||
routeMap["/api/matter/upload"] = this.Wrap(this.Upload, USER_ROLE_USER)
|
||||
routeMap["/api/matter/crawl"] = this.Wrap(this.Crawl, USER_ROLE_USER)
|
||||
routeMap["/api/matter/soft/delete"] = this.Wrap(this.SoftDelete, USER_ROLE_USER)
|
||||
routeMap["/api/matter/soft/delete/batch"] = this.Wrap(this.SoftDeleteBatch, USER_ROLE_USER)
|
||||
routeMap["/api/matter/recovery"] = this.Wrap(this.Recovery, USER_ROLE_USER)
|
||||
routeMap["/api/matter/recovery/batch"] = this.Wrap(this.RecoveryBatch, USER_ROLE_USER)
|
||||
routeMap["/api/matter/delete"] = this.Wrap(this.Delete, USER_ROLE_USER)
|
||||
routeMap["/api/matter/delete/batch"] = this.Wrap(this.DeleteBatch, USER_ROLE_USER)
|
||||
routeMap["/api/matter/clean/expired/deleted/matters"] = this.Wrap(this.CleanExpiredDeletedMatters, USER_ROLE_ADMINISTRATOR)
|
||||
routeMap["/api/matter/rename"] = this.Wrap(this.Rename, USER_ROLE_USER)
|
||||
routeMap["/api/matter/change/privacy"] = this.Wrap(this.ChangePrivacy, USER_ROLE_USER)
|
||||
routeMap["/api/matter/move"] = this.Wrap(this.Move, USER_ROLE_USER)
|
||||
@@ -112,12 +118,14 @@ func (this *MatterController) Page(writer http.ResponseWriter, request *http.Req
|
||||
pageSizeStr := request.FormValue("pageSize")
|
||||
orderCreateTime := request.FormValue("orderCreateTime")
|
||||
orderUpdateTime := request.FormValue("orderUpdateTime")
|
||||
orderDeleteTime := request.FormValue("orderDeleteTime")
|
||||
orderSort := request.FormValue("orderSort")
|
||||
orderTimes := request.FormValue("orderTimes")
|
||||
|
||||
puuid := request.FormValue("puuid")
|
||||
name := request.FormValue("name")
|
||||
dir := request.FormValue("dir")
|
||||
deleted := request.FormValue("deleted")
|
||||
orderDir := request.FormValue("orderDir")
|
||||
orderSize := request.FormValue("orderSize")
|
||||
orderName := request.FormValue("orderName")
|
||||
@@ -143,7 +151,7 @@ func (this *MatterController) Page(writer http.ResponseWriter, request *http.Req
|
||||
user := this.findUser(request)
|
||||
|
||||
this.shareService.ValidateMatter(request, shareUuid, shareCode, user, shareRootUuid, dirMatter)
|
||||
userUuid = dirMatter.Uuid
|
||||
puuid = dirMatter.Uuid
|
||||
|
||||
} else {
|
||||
//if cannot auth by share. Then login is required.
|
||||
@@ -183,6 +191,10 @@ func (this *MatterController) Page(writer http.ResponseWriter, request *http.Req
|
||||
Key: "update_time",
|
||||
Value: orderUpdateTime,
|
||||
},
|
||||
{
|
||||
Key: "delete_time",
|
||||
Value: orderDeleteTime,
|
||||
},
|
||||
{
|
||||
Key: "sort",
|
||||
Value: orderSort,
|
||||
@@ -201,7 +213,7 @@ func (this *MatterController) Page(writer http.ResponseWriter, request *http.Req
|
||||
},
|
||||
}
|
||||
|
||||
pager := this.matterDao.Page(page, pageSize, puuid, userUuid, name, dir, extensions, sortArray)
|
||||
pager := this.matterDao.Page(page, pageSize, puuid, userUuid, name, dir, deleted, extensions, sortArray)
|
||||
|
||||
return this.Success(pager)
|
||||
}
|
||||
@@ -280,6 +292,112 @@ func (this *MatterController) Crawl(writer http.ResponseWriter, request *http.Re
|
||||
return this.Success(matter)
|
||||
}
|
||||
|
||||
//soft delete.
|
||||
func (this *MatterController) SoftDelete(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
uuid := request.FormValue("uuid")
|
||||
if uuid == "" {
|
||||
panic(result.BadRequest("uuid cannot be null"))
|
||||
}
|
||||
|
||||
matter := this.matterDao.CheckByUuid(uuid)
|
||||
|
||||
user := this.checkUser(request)
|
||||
if matter.UserUuid != user.Uuid {
|
||||
panic(result.UNAUTHORIZED)
|
||||
}
|
||||
|
||||
this.matterService.AtomicSoftDelete(request, matter, user)
|
||||
|
||||
return this.Success("OK")
|
||||
}
|
||||
|
||||
func (this *MatterController) SoftDeleteBatch(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
uuids := request.FormValue("uuids")
|
||||
if uuids == "" {
|
||||
panic(result.BadRequest("uuids cannot be null"))
|
||||
}
|
||||
user := this.checkUser(request)
|
||||
|
||||
uuidArray := strings.Split(uuids, ",")
|
||||
|
||||
matters := make([]*Matter, 0)
|
||||
for _, uuid := range uuidArray {
|
||||
|
||||
matter := this.matterDao.FindByUuid(uuid)
|
||||
|
||||
if matter == nil {
|
||||
this.logger.Warn("%s not exist anymore", uuid)
|
||||
continue
|
||||
}
|
||||
|
||||
if matter.UserUuid != user.Uuid {
|
||||
panic(result.UNAUTHORIZED)
|
||||
}
|
||||
|
||||
matters = append(matters, matter)
|
||||
}
|
||||
|
||||
for _, matter := range matters {
|
||||
this.matterService.AtomicSoftDelete(request, matter, user)
|
||||
}
|
||||
|
||||
return this.Success("OK")
|
||||
}
|
||||
|
||||
//recovery delete.
|
||||
func (this *MatterController) Recovery(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
uuid := request.FormValue("uuid")
|
||||
if uuid == "" {
|
||||
panic(result.BadRequest("uuid cannot be null"))
|
||||
}
|
||||
|
||||
matter := this.matterDao.CheckByUuid(uuid)
|
||||
|
||||
user := this.checkUser(request)
|
||||
if matter.UserUuid != user.Uuid {
|
||||
panic(result.UNAUTHORIZED)
|
||||
}
|
||||
|
||||
this.matterService.AtomicRecovery(request, matter, user)
|
||||
|
||||
return this.Success("OK")
|
||||
}
|
||||
|
||||
//recovery batch.
|
||||
func (this *MatterController) RecoveryBatch(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
uuids := request.FormValue("uuids")
|
||||
if uuids == "" {
|
||||
panic(result.BadRequest("uuids cannot be null"))
|
||||
}
|
||||
|
||||
uuidArray := strings.Split(uuids, ",")
|
||||
|
||||
for _, uuid := range uuidArray {
|
||||
|
||||
matter := this.matterDao.FindByUuid(uuid)
|
||||
|
||||
if matter == nil {
|
||||
this.logger.Warn("%s not exist anymore", uuid)
|
||||
continue
|
||||
}
|
||||
|
||||
user := this.checkUser(request)
|
||||
if matter.UserUuid != user.Uuid {
|
||||
panic(result.UNAUTHORIZED)
|
||||
}
|
||||
|
||||
this.matterService.AtomicRecovery(request, matter, user)
|
||||
|
||||
}
|
||||
|
||||
return this.Success("OK")
|
||||
}
|
||||
|
||||
//complete delete.
|
||||
func (this *MatterController) Delete(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
uuid := request.FormValue("uuid")
|
||||
@@ -307,7 +425,8 @@ func (this *MatterController) DeleteBatch(writer http.ResponseWriter, request *h
|
||||
}
|
||||
|
||||
uuidArray := strings.Split(uuids, ",")
|
||||
|
||||
user := this.checkUser(request)
|
||||
matters := make([]*Matter, 0)
|
||||
for _, uuid := range uuidArray {
|
||||
|
||||
matter := this.matterDao.FindByUuid(uuid)
|
||||
@@ -317,15 +436,26 @@ func (this *MatterController) DeleteBatch(writer http.ResponseWriter, request *h
|
||||
continue
|
||||
}
|
||||
|
||||
user := this.checkUser(request)
|
||||
if matter.UserUuid != user.Uuid {
|
||||
panic(result.UNAUTHORIZED)
|
||||
}
|
||||
|
||||
this.matterService.AtomicDelete(request, matter, user)
|
||||
|
||||
matters = append(matters, matter)
|
||||
}
|
||||
|
||||
for _, matter := range matters {
|
||||
|
||||
this.matterService.AtomicDelete(request, matter, user)
|
||||
}
|
||||
|
||||
return this.Success("OK")
|
||||
}
|
||||
|
||||
//manual clean expired deleted matters.
|
||||
func (this *MatterController) CleanExpiredDeletedMatters(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
this.matterService.CleanExpiredDeletedMatters()
|
||||
|
||||
return this.Success("OK")
|
||||
}
|
||||
|
||||
@@ -342,7 +472,7 @@ func (this *MatterController) Rename(writer http.ResponseWriter, request *http.R
|
||||
panic(result.UNAUTHORIZED)
|
||||
}
|
||||
|
||||
this.matterService.AtomicRename(request, matter, name, user)
|
||||
this.matterService.AtomicRename(request, matter, name, false, user)
|
||||
|
||||
return this.Success(matter)
|
||||
}
|
||||
@@ -357,6 +487,10 @@ func (this *MatterController) ChangePrivacy(writer http.ResponseWriter, request
|
||||
|
||||
matter := this.matterDao.CheckByUuid(uuid)
|
||||
|
||||
if matter.Deleted {
|
||||
panic(result.BadRequest("matter has been deleted. Cannot change privacy."))
|
||||
}
|
||||
|
||||
if matter.Privacy == privacy {
|
||||
panic(result.BadRequest("not changed. Invalid operation."))
|
||||
}
|
||||
@@ -395,6 +529,10 @@ func (this *MatterController) Move(writer http.ResponseWriter, request *http.Req
|
||||
panic(result.UNAUTHORIZED)
|
||||
}
|
||||
|
||||
if destMatter.Deleted {
|
||||
panic(result.BadRequest("dest matter has been deleted. Cannot move."))
|
||||
}
|
||||
|
||||
var srcMatters []*Matter
|
||||
for _, uuid := range srcUuids {
|
||||
srcMatter := this.matterDao.CheckByUuid(uuid)
|
||||
@@ -403,6 +541,10 @@ func (this *MatterController) Move(writer http.ResponseWriter, request *http.Req
|
||||
panic(result.BadRequest("no move, invalid operation"))
|
||||
}
|
||||
|
||||
if srcMatter.Deleted {
|
||||
panic(result.BadRequest("src matter has been deleted. Cannot move."))
|
||||
}
|
||||
|
||||
//check whether there are files with the same name.
|
||||
count := this.matterDao.CountByUserUuidAndPuuidAndDirAndName(user.Uuid, destMatter.Uuid, srcMatter.Dir, srcMatter.Name)
|
||||
|
||||
@@ -461,6 +603,13 @@ func (this *MatterController) Zip(writer http.ResponseWriter, request *http.Requ
|
||||
if matters == nil || len(matters) == 0 {
|
||||
panic(result.BadRequest("matters cannot be nil."))
|
||||
}
|
||||
|
||||
for _, matter := range matters {
|
||||
if matter.Deleted {
|
||||
panic(result.BadRequest("matter has been deleted. Cannot download batch."))
|
||||
}
|
||||
}
|
||||
|
||||
user := this.checkUser(request)
|
||||
puuid := matters[0].Puuid
|
||||
|
||||
|
||||
@@ -5,8 +5,9 @@ import (
|
||||
"github.com/eyebluecn/tank/code/tool/builder"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/eyebluecn/tank/code/tool/util"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/nu7hatch/gouuid"
|
||||
"github.com/eyebluecn/tank/code/tool/uuid"
|
||||
"gorm.io/gorm"
|
||||
"math"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
@@ -92,29 +93,19 @@ func (this *MatterDao) CheckWithRootByPath(path string, user *User) *Matter {
|
||||
return matter
|
||||
}
|
||||
|
||||
func (this *MatterDao) FindByUserUuidAndPuuidAndNameAndDirTrue(userUuid string, puuid string, name string) *Matter {
|
||||
// find by path. if path=/, then return the Root Matter
|
||||
func (this *MatterDao) FindWithRootByPath(path string, user *User) *Matter {
|
||||
|
||||
var wp = &builder.WherePair{}
|
||||
var matter *Matter
|
||||
|
||||
if userUuid != "" {
|
||||
wp = wp.And(&builder.WherePair{Query: "user_uuid = ?", Args: []interface{}{userUuid}})
|
||||
if user == nil {
|
||||
panic(result.BadRequest("user cannot be null."))
|
||||
}
|
||||
|
||||
if puuid != "" {
|
||||
wp = wp.And(&builder.WherePair{Query: "puuid = ?", Args: []interface{}{puuid}})
|
||||
}
|
||||
|
||||
if name != "" {
|
||||
wp = wp.And(&builder.WherePair{Query: "name = ?", Args: []interface{}{name}})
|
||||
}
|
||||
|
||||
wp = wp.And(&builder.WherePair{Query: "dir = ?", Args: []interface{}{1}})
|
||||
|
||||
var matter = &Matter{}
|
||||
db := core.CONTEXT.GetDB().Model(&Matter{}).Where(wp.Query, wp.Args...).First(matter)
|
||||
|
||||
if db.Error != nil {
|
||||
return nil
|
||||
if path == "" || path == "/" {
|
||||
matter = NewRootMatter(user)
|
||||
} else {
|
||||
matter = this.findByUserUuidAndPath(user.Uuid, path)
|
||||
}
|
||||
|
||||
return matter
|
||||
@@ -147,7 +138,7 @@ func (this *MatterDao) FindByUserUuidAndPuuidAndDirTrue(userUuid string, puuid s
|
||||
func (this *MatterDao) CheckByUuidAndUserUuid(uuid string, userUuid string) *Matter {
|
||||
|
||||
var matter = &Matter{}
|
||||
db := core.CONTEXT.GetDB().Where(&Matter{Base: Base{Uuid: uuid}, UserUuid: userUuid}).First(matter)
|
||||
db := core.CONTEXT.GetDB().Where(&Matter{Uuid: uuid, UserUuid: userUuid}).First(matter)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
return matter
|
||||
@@ -157,7 +148,7 @@ func (this *MatterDao) CheckByUuidAndUserUuid(uuid string, userUuid string) *Mat
|
||||
func (this *MatterDao) CountByUserUuidAndPuuidAndDirAndName(userUuid string, puuid string, dir bool, name string) int {
|
||||
|
||||
var matter Matter
|
||||
var count int
|
||||
var count int64
|
||||
|
||||
var wp = &builder.WherePair{}
|
||||
|
||||
@@ -181,10 +172,10 @@ func (this *MatterDao) CountByUserUuidAndPuuidAndDirAndName(userUuid string, puu
|
||||
Count(&count)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
return count
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func (this *MatterDao) FindByUserUuidAndPuuidAndDirAndName(userUuid string, puuid string, dir bool, name string) *Matter {
|
||||
func (this *MatterDao) FindByUserUuidAndPuuidAndDirAndName(userUuid string, puuid string, dir string, name string) *Matter {
|
||||
|
||||
var matter = &Matter{}
|
||||
|
||||
@@ -202,7 +193,11 @@ func (this *MatterDao) FindByUserUuidAndPuuidAndDirAndName(userUuid string, puui
|
||||
wp = wp.And(&builder.WherePair{Query: "name = ?", Args: []interface{}{name}})
|
||||
}
|
||||
|
||||
wp = wp.And(&builder.WherePair{Query: "dir = ?", Args: []interface{}{dir}})
|
||||
if dir == TRUE {
|
||||
wp = wp.And(&builder.WherePair{Query: "dir = ?", Args: []interface{}{true}})
|
||||
} else if dir == FALSE {
|
||||
wp = wp.And(&builder.WherePair{Query: "dir = ?", Args: []interface{}{false}})
|
||||
}
|
||||
|
||||
db := core.CONTEXT.GetDB().Where(wp.Query, wp.Args...).First(matter)
|
||||
|
||||
@@ -218,10 +213,19 @@ func (this *MatterDao) FindByUserUuidAndPuuidAndDirAndName(userUuid string, puui
|
||||
}
|
||||
|
||||
func (this *MatterDao) FindByPuuidAndUserUuid(puuid string, userUuid string, sortArray []builder.OrderPair) []*Matter {
|
||||
return this.FindByPuuidAndUserUuidAndDeleted(puuid, userUuid, "", sortArray)
|
||||
}
|
||||
|
||||
func (this *MatterDao) FindByPuuidAndUserUuidAndDeleted(puuid string, userUuid string, deleted string, sortArray []builder.OrderPair) []*Matter {
|
||||
var matters []*Matter
|
||||
|
||||
var wp = &builder.WherePair{}
|
||||
wp = wp.And(&builder.WherePair{Query: "puuid = ? AND user_uuid = ?", Args: []interface{}{puuid, userUuid}})
|
||||
if deleted == TRUE {
|
||||
wp = wp.And(&builder.WherePair{Query: "deleted = 1", Args: []interface{}{}})
|
||||
} else if deleted == FALSE {
|
||||
wp = wp.And(&builder.WherePair{Query: "deleted = 0", Args: []interface{}{}})
|
||||
}
|
||||
|
||||
if sortArray == nil {
|
||||
|
||||
@@ -251,7 +255,17 @@ func (this *MatterDao) FindByUuids(uuids []string, sortArray []builder.OrderPair
|
||||
|
||||
return matters
|
||||
}
|
||||
func (this *MatterDao) PlainPage(page int, pageSize int, puuid string, userUuid string, name string, dir string, extensions []string, sortArray []builder.OrderPair) (int, []*Matter) {
|
||||
func (this *MatterDao) PlainPage(
|
||||
page int,
|
||||
pageSize int,
|
||||
puuid string,
|
||||
userUuid string,
|
||||
name string,
|
||||
dir string,
|
||||
deleted string,
|
||||
deleteTimeBefore *time.Time,
|
||||
extensions []string,
|
||||
sortArray []builder.OrderPair) (int, []*Matter) {
|
||||
|
||||
var wp = &builder.WherePair{}
|
||||
|
||||
@@ -267,12 +281,22 @@ func (this *MatterDao) PlainPage(page int, pageSize int, puuid string, userUuid
|
||||
wp = wp.And(&builder.WherePair{Query: "name LIKE ?", Args: []interface{}{"%" + name + "%"}})
|
||||
}
|
||||
|
||||
if deleteTimeBefore != nil {
|
||||
wp = wp.And(&builder.WherePair{Query: "delete_time < ?", Args: []interface{}{&deleteTimeBefore}})
|
||||
}
|
||||
|
||||
if dir == TRUE {
|
||||
wp = wp.And(&builder.WherePair{Query: "dir = ?", Args: []interface{}{1}})
|
||||
} else if dir == FALSE {
|
||||
wp = wp.And(&builder.WherePair{Query: "dir = ?", Args: []interface{}{0}})
|
||||
}
|
||||
|
||||
if deleted == TRUE {
|
||||
wp = wp.And(&builder.WherePair{Query: "deleted = ?", Args: []interface{}{1}})
|
||||
} else if deleted == FALSE {
|
||||
wp = wp.And(&builder.WherePair{Query: "deleted = ?", Args: []interface{}{0}})
|
||||
}
|
||||
|
||||
var conditionDB *gorm.DB
|
||||
if extensions != nil && len(extensions) > 0 {
|
||||
var orWp = &builder.WherePair{}
|
||||
@@ -286,7 +310,7 @@ func (this *MatterDao) PlainPage(page int, pageSize int, puuid string, userUuid
|
||||
conditionDB = core.CONTEXT.GetDB().Model(&Matter{}).Where(wp.Query, wp.Args...)
|
||||
}
|
||||
|
||||
count := 0
|
||||
var count int64 = 0
|
||||
db := conditionDB.Count(&count)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
@@ -294,16 +318,51 @@ func (this *MatterDao) PlainPage(page int, pageSize int, puuid string, userUuid
|
||||
db = conditionDB.Order(this.GetSortString(sortArray)).Offset(page * pageSize).Limit(pageSize).Find(&matters)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
return count, matters
|
||||
return int(count), matters
|
||||
}
|
||||
func (this *MatterDao) Page(page int, pageSize int, puuid string, userUuid string, name string, dir string, extensions []string, sortArray []builder.OrderPair) *Pager {
|
||||
func (this *MatterDao) Page(page int, pageSize int, puuid string, userUuid string, name string, dir string, deleted string, extensions []string, sortArray []builder.OrderPair) *Pager {
|
||||
|
||||
count, matters := this.PlainPage(page, pageSize, puuid, userUuid, name, dir, extensions, sortArray)
|
||||
count, matters := this.PlainPage(page, pageSize, puuid, userUuid, name, dir, deleted, nil, extensions, sortArray)
|
||||
pager := NewPager(page, pageSize, count, matters)
|
||||
|
||||
return pager
|
||||
}
|
||||
|
||||
//handle matter page by page.
|
||||
func (this *MatterDao) PageHandle(
|
||||
puuid string,
|
||||
userUuid string,
|
||||
name string,
|
||||
dir string,
|
||||
deleted string,
|
||||
deleteTimeBefore *time.Time,
|
||||
sortArray []builder.OrderPair,
|
||||
fun func(matter *Matter)) {
|
||||
|
||||
pageSize := 1000
|
||||
if sortArray == nil || len(sortArray) == 0 {
|
||||
sortArray = []builder.OrderPair{
|
||||
{
|
||||
Key: "uuid",
|
||||
Value: DIRECTION_ASC,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
count, _ := this.PlainPage(0, pageSize, puuid, userUuid, name, dir, deleted, deleteTimeBefore, nil, sortArray)
|
||||
if count > 0 {
|
||||
var totalPages = int(math.Ceil(float64(count) / float64(pageSize)))
|
||||
|
||||
var page int
|
||||
for page = 0; page < totalPages; page++ {
|
||||
_, matters := this.PlainPage(0, pageSize, puuid, userUuid, name, dir, deleted, deleteTimeBefore, nil, sortArray)
|
||||
for _, matter := range matters {
|
||||
fun(matter)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *MatterDao) Create(matter *Matter) *Matter {
|
||||
|
||||
timeUUID, _ := uuid.NewV4()
|
||||
@@ -328,7 +387,7 @@ func (this *MatterDao) Save(matter *Matter) *Matter {
|
||||
|
||||
//download time add 1
|
||||
func (this *MatterDao) TimesIncrement(matterUuid string) {
|
||||
db := core.CONTEXT.GetDB().Model(&Matter{}).Where("uuid = ?", matterUuid).Update("times", gorm.Expr("times + 1"))
|
||||
db := core.CONTEXT.GetDB().Model(&Matter{}).Where("uuid = ?", matterUuid).Updates(map[string]interface{}{"times": gorm.Expr("times + 1"), "visit_time": time.Now()})
|
||||
this.PanicError(db.Error)
|
||||
}
|
||||
|
||||
@@ -391,6 +450,31 @@ func (this *MatterDao) Delete(matter *Matter) {
|
||||
}
|
||||
}
|
||||
|
||||
//soft delete a file or dir
|
||||
func (this *MatterDao) SoftDelete(matter *Matter) {
|
||||
|
||||
//soft delete from db.
|
||||
db := core.CONTEXT.GetDB().Model(&Matter{}).Where("uuid = ?", matter.Uuid).Updates(map[string]interface{}{"deleted": true, "delete_time": time.Now()})
|
||||
this.PanicError(db.Error)
|
||||
|
||||
}
|
||||
|
||||
//recovery a file
|
||||
func (this *MatterDao) Recovery(matter *Matter) {
|
||||
|
||||
//recovery from db.
|
||||
db := core.CONTEXT.GetDB().Model(&Matter{}).Where("uuid = ?", matter.Uuid).Updates(map[string]interface{}{"deleted": false, "delete_time": time.Now()})
|
||||
this.PanicError(db.Error)
|
||||
|
||||
}
|
||||
|
||||
func (this *MatterDao) DeleteByUserUuid(userUuid string) {
|
||||
|
||||
db := core.CONTEXT.GetDB().Where("user_uuid = ?", userUuid).Delete(Matter{})
|
||||
this.PanicError(db.Error)
|
||||
|
||||
}
|
||||
|
||||
func (this *MatterDao) CountBetweenTime(startTime time.Time, endTime time.Time) int64 {
|
||||
var count int64
|
||||
db := core.CONTEXT.GetDB().Model(&Matter{}).Where("create_time >= ? AND create_time <= ?", startTime, endTime).Count(&count)
|
||||
@@ -400,7 +484,7 @@ func (this *MatterDao) CountBetweenTime(startTime time.Time, endTime time.Time)
|
||||
|
||||
func (this *MatterDao) SizeBetweenTime(startTime time.Time, endTime time.Time) int64 {
|
||||
|
||||
var wp = &builder.WherePair{Query: "create_time >= ? AND create_time <= ?", Args: []interface{}{startTime, endTime}}
|
||||
var wp = &builder.WherePair{Query: "dir = 0 AND create_time >= ? AND create_time <= ?", Args: []interface{}{startTime, endTime}}
|
||||
|
||||
var count int64
|
||||
db := core.CONTEXT.GetDB().Model(&Matter{}).Where(wp.Query, wp.Args...).Count(&count)
|
||||
@@ -417,6 +501,7 @@ func (this *MatterDao) SizeBetweenTime(startTime time.Time, endTime time.Time) i
|
||||
return size
|
||||
}
|
||||
|
||||
//find by userUuid and path. if not found, return nil
|
||||
func (this *MatterDao) findByUserUuidAndPath(userUuid string, path string) *Matter {
|
||||
|
||||
var wp = &builder.WherePair{Query: "user_uuid = ? AND path = ?", Args: []interface{}{userUuid, path}}
|
||||
@@ -435,6 +520,7 @@ func (this *MatterDao) findByUserUuidAndPath(userUuid string, path string) *Matt
|
||||
return matter
|
||||
}
|
||||
|
||||
//find by userUuid and path. if not found, panic
|
||||
func (this *MatterDao) checkByUserUuidAndPath(userUuid string, path string) *Matter {
|
||||
|
||||
if path == "" {
|
||||
|
||||
@@ -6,9 +6,11 @@ import (
|
||||
"github.com/eyebluecn/tank/code/tool/i18n"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/eyebluecn/tank/code/tool/util"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -28,24 +30,26 @@ const (
|
||||
* file is too common. so we use matter as file.
|
||||
*/
|
||||
type Matter struct {
|
||||
Base
|
||||
Puuid string `json:"puuid" gorm:"type:char(36);index:idx_puuid"`
|
||||
UserUuid string `json:"userUuid" gorm:"type:char(36);index:idx_uu"`
|
||||
Username string `json:"username" gorm:"type:varchar(45) not null"`
|
||||
Dir bool `json:"dir" gorm:"type:tinyint(1) not null;default:0"`
|
||||
Name string `json:"name" gorm:"type:varchar(255) not null"`
|
||||
Md5 string `json:"md5" gorm:"type:varchar(45)"`
|
||||
Size int64 `json:"size" gorm:"type:bigint(20) not null;default:0"`
|
||||
Privacy bool `json:"privacy" gorm:"type:tinyint(1) not null;default:0"`
|
||||
Path string `json:"path" gorm:"type:varchar(1024)"`
|
||||
Times int64 `json:"times" gorm:"type:bigint(20) not null;default:0"`
|
||||
Parent *Matter `json:"parent" gorm:"-"`
|
||||
Children []*Matter `json:"-" gorm:"-"`
|
||||
}
|
||||
|
||||
// set File's table name to be `profiles`
|
||||
func (Matter) TableName() string {
|
||||
return core.TABLE_PREFIX + "matter"
|
||||
Uuid string `json:"uuid" gorm:"type:char(36);primary_key;unique"`
|
||||
Sort int64 `json:"sort" gorm:"type:bigint(20) not null"`
|
||||
UpdateTime time.Time `json:"updateTime" gorm:"type:timestamp not null;default:CURRENT_TIMESTAMP"`
|
||||
CreateTime time.Time `json:"createTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
Puuid string `json:"puuid" gorm:"type:char(36);index:idx_matter_puuid"` //index should unique globally.
|
||||
UserUuid string `json:"userUuid" gorm:"type:char(36);index:idx_matter_uu"`
|
||||
Username string `json:"username" gorm:"type:varchar(45) not null"`
|
||||
Dir bool `json:"dir" gorm:"type:tinyint(1) not null;default:0"`
|
||||
Name string `json:"name" gorm:"type:varchar(255) not null"`
|
||||
Md5 string `json:"md5" gorm:"type:varchar(45)"`
|
||||
Size int64 `json:"size" gorm:"type:bigint(20) not null;default:0"`
|
||||
Privacy bool `json:"privacy" gorm:"type:tinyint(1) not null;default:0"`
|
||||
Path string `json:"path" gorm:"type:varchar(1024)"`
|
||||
Times int64 `json:"times" gorm:"type:bigint(20) not null;default:0"`
|
||||
Parent *Matter `json:"parent" gorm:"-"`
|
||||
Children []*Matter `json:"-" gorm:"-"`
|
||||
Prop string `json:"prop" gorm:"type:varchar(1024) not null;default:'{}'"`
|
||||
VisitTime time.Time `json:"visitTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
Deleted bool `json:"deleted" gorm:"type:tinyint(1) not null;index:idx_matter_del;default:0"`
|
||||
DeleteTime time.Time `json:"deleteTime" gorm:"type:timestamp not null;index:idx_matter_delt;default:'2018-01-01 00:00:00'"`
|
||||
}
|
||||
|
||||
// get matter's absolute path. the Path property is relative path in db.
|
||||
@@ -67,10 +71,19 @@ func NewRootMatter(user *User) *Matter {
|
||||
matter.Path = ""
|
||||
matter.CreateTime = user.CreateTime
|
||||
matter.UpdateTime = user.UpdateTime
|
||||
matter.VisitTime = user.UpdateTime
|
||||
|
||||
return matter
|
||||
}
|
||||
|
||||
//get user's space absolute path
|
||||
func GetUserSpaceRootDir(username string) (rootDirPath string) {
|
||||
|
||||
rootDirPath = fmt.Sprintf("%s/%s", core.CONFIG.MatterPath(), username)
|
||||
|
||||
return rootDirPath
|
||||
}
|
||||
|
||||
//get user's root absolute path
|
||||
func GetUserMatterRootDir(username string) (rootDirPath string) {
|
||||
|
||||
@@ -113,3 +126,31 @@ func CheckMatterName(request *http.Request, name string) string {
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
//fetch the props
|
||||
func (this *Matter) FetchPropMap() map[string]string {
|
||||
|
||||
m := make(map[string]string)
|
||||
json := this.Prop
|
||||
if json == "" {
|
||||
json = EMPTY_JSON_MAP
|
||||
}
|
||||
|
||||
err := jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal([]byte(json), &m)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
//fetch the props
|
||||
func (this *Matter) SetPropMap(propMap map[string]string) {
|
||||
|
||||
b, err := jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(propMap)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
this.Prop = string(b)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"archive/zip"
|
||||
"fmt"
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"github.com/eyebluecn/tank/code/tool/builder"
|
||||
"github.com/eyebluecn/tank/code/tool/download"
|
||||
"github.com/eyebluecn/tank/code/tool/i18n"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
@@ -13,6 +14,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -254,6 +256,36 @@ func (this *MatterService) Delete(request *http.Request, matter *Matter, user *U
|
||||
this.ComputeRouteSize(matter.Puuid, user)
|
||||
}
|
||||
|
||||
//soft delete files.
|
||||
func (this *MatterService) SoftDelete(request *http.Request, matter *Matter, user *User) {
|
||||
|
||||
if matter == nil {
|
||||
panic(result.BadRequest("matter cannot be nil"))
|
||||
}
|
||||
|
||||
if matter.Deleted {
|
||||
panic(result.BadRequest("matter has been deleted"))
|
||||
}
|
||||
|
||||
this.matterDao.SoftDelete(matter)
|
||||
//no need to recompute size.
|
||||
}
|
||||
|
||||
//recovery delete files.
|
||||
func (this *MatterService) Recovery(request *http.Request, matter *Matter, user *User) {
|
||||
|
||||
if matter == nil {
|
||||
panic(result.BadRequest("matter cannot be nil"))
|
||||
}
|
||||
|
||||
if !matter.Deleted {
|
||||
panic(result.BadRequest("matter has not been deleted"))
|
||||
}
|
||||
|
||||
this.matterDao.Recovery(matter)
|
||||
//no need to recompute size.
|
||||
}
|
||||
|
||||
//atomic delete files
|
||||
func (this *MatterService) AtomicDelete(request *http.Request, matter *Matter, user *User) {
|
||||
|
||||
@@ -268,6 +300,49 @@ func (this *MatterService) AtomicDelete(request *http.Request, matter *Matter, u
|
||||
this.Delete(request, matter, user)
|
||||
}
|
||||
|
||||
//atomic soft delete files
|
||||
func (this *MatterService) AtomicSoftDelete(request *http.Request, matter *Matter, user *User) {
|
||||
|
||||
if matter == nil {
|
||||
panic(result.BadRequest("matter cannot be nil"))
|
||||
}
|
||||
|
||||
if matter.Deleted {
|
||||
panic(result.BadRequest("matter has been deleted"))
|
||||
}
|
||||
|
||||
//lock
|
||||
this.userService.MatterLock(matter.UserUuid)
|
||||
defer this.userService.MatterUnlock(matter.UserUuid)
|
||||
|
||||
//if disabled the recycle feature. then we hard delete.
|
||||
preference := this.preferenceService.Fetch()
|
||||
if preference.DeletedKeepDays == 0 {
|
||||
this.Delete(request, matter, user)
|
||||
} else {
|
||||
this.SoftDelete(request, matter, user)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//atomic recovery delete files
|
||||
func (this *MatterService) AtomicRecovery(request *http.Request, matter *Matter, user *User) {
|
||||
|
||||
if matter == nil {
|
||||
panic(result.BadRequest("matter cannot be nil"))
|
||||
}
|
||||
|
||||
if !matter.Deleted {
|
||||
panic(result.BadRequest("matter has not been deleted"))
|
||||
}
|
||||
|
||||
//lock
|
||||
this.userService.MatterLock(matter.UserUuid)
|
||||
defer this.userService.MatterUnlock(matter.UserUuid)
|
||||
|
||||
this.Recovery(request, matter, user)
|
||||
}
|
||||
|
||||
//upload files.
|
||||
func (this *MatterService) Upload(request *http.Request, file io.Reader, user *User, dirMatter *Matter, filename string, privacy bool) *Matter {
|
||||
|
||||
@@ -279,12 +354,15 @@ func (this *MatterService) Upload(request *http.Request, file io.Reader, user *U
|
||||
panic(result.BadRequest("dirMatter cannot be nil."))
|
||||
}
|
||||
|
||||
if dirMatter.Deleted {
|
||||
panic(result.BadRequest("Dir has been deleted. Cannot upload under it."))
|
||||
}
|
||||
|
||||
if len(filename) > MATTER_NAME_MAX_LENGTH {
|
||||
panic(result.BadRequestI18n(request, i18n.MatterNameLengthExceedLimit, len(filename), MATTER_NAME_MAX_LENGTH))
|
||||
}
|
||||
|
||||
dirAbsolutePath := dirMatter.AbsolutePath()
|
||||
dirRelativePath := dirMatter.Path
|
||||
|
||||
count := this.matterDao.CountByUserUuidAndPuuidAndDirAndName(user.Uuid, dirMatter.Uuid, false, filename)
|
||||
if count > 0 {
|
||||
@@ -292,7 +370,6 @@ func (this *MatterService) Upload(request *http.Request, file io.Reader, user *U
|
||||
}
|
||||
|
||||
fileAbsolutePath := dirAbsolutePath + "/" + filename
|
||||
fileRelativePath := dirRelativePath + "/" + filename
|
||||
|
||||
util.MakeDirAll(dirAbsolutePath)
|
||||
|
||||
@@ -340,17 +417,29 @@ func (this *MatterService) Upload(request *http.Request, file io.Reader, user *U
|
||||
}
|
||||
}
|
||||
|
||||
matter := this.createNonDirMatter(dirMatter, filename, fileSize, privacy, user)
|
||||
|
||||
return matter
|
||||
}
|
||||
|
||||
// create a non dir matter.
|
||||
func (this *MatterService) createNonDirMatter(dirMatter *Matter, filename string, fileSize int64, privacy bool, user *User) *Matter {
|
||||
dirRelativePath := dirMatter.Path
|
||||
fileRelativePath := dirRelativePath + "/" + filename
|
||||
|
||||
//write to db.
|
||||
matter := &Matter{
|
||||
Puuid: dirMatter.Uuid,
|
||||
UserUuid: user.Uuid,
|
||||
Username: user.Username,
|
||||
Dir: false,
|
||||
Name: filename,
|
||||
Md5: "",
|
||||
Size: fileSize,
|
||||
Privacy: privacy,
|
||||
Path: fileRelativePath,
|
||||
Puuid: dirMatter.Uuid,
|
||||
UserUuid: user.Uuid,
|
||||
Username: user.Username,
|
||||
Dir: false,
|
||||
Name: filename,
|
||||
Md5: "",
|
||||
Size: fileSize,
|
||||
Privacy: privacy,
|
||||
Path: fileRelativePath,
|
||||
Prop: EMPTY_JSON_MAP,
|
||||
VisitTime: time.Now(),
|
||||
}
|
||||
matter = this.matterDao.Create(matter)
|
||||
|
||||
@@ -362,6 +451,21 @@ func (this *MatterService) Upload(request *http.Request, file io.Reader, user *U
|
||||
return matter
|
||||
}
|
||||
|
||||
// create a non dir matter.
|
||||
func (this *MatterService) updateNonDirMatter(matter *Matter, fileSize int64, user *User) *Matter {
|
||||
|
||||
matter.Size = fileSize
|
||||
|
||||
matter = this.matterDao.Save(matter)
|
||||
|
||||
//compute the size of directory
|
||||
go core.RunWithRecovery(func() {
|
||||
this.ComputeRouteSize(matter.Puuid, user)
|
||||
})
|
||||
|
||||
return matter
|
||||
}
|
||||
|
||||
// compute route size. It will compute upward until root directory
|
||||
func (this *MatterService) ComputeRouteSize(matterUuid string, user *User) {
|
||||
|
||||
@@ -453,6 +557,10 @@ func (this *MatterService) createDirectory(request *http.Request, dirMatter *Mat
|
||||
panic(result.BadRequest("dirMatter must be directory"))
|
||||
}
|
||||
|
||||
if dirMatter.Deleted {
|
||||
panic(result.BadRequest("Dir has been deleted. Cannot create dir under it."))
|
||||
}
|
||||
|
||||
if dirMatter.UserUuid != user.Uuid {
|
||||
|
||||
panic(result.BadRequest("file's user not the same"))
|
||||
@@ -474,7 +582,7 @@ func (this *MatterService) createDirectory(request *http.Request, dirMatter *Mat
|
||||
}
|
||||
|
||||
//if exist. return.
|
||||
matter := this.matterDao.FindByUserUuidAndPuuidAndDirAndName(user.Uuid, dirMatter.Uuid, true, name)
|
||||
matter := this.matterDao.FindByUserUuidAndPuuidAndDirAndName(user.Uuid, dirMatter.Uuid, TRUE, name)
|
||||
if matter != nil {
|
||||
return matter
|
||||
}
|
||||
@@ -495,12 +603,13 @@ func (this *MatterService) createDirectory(request *http.Request, dirMatter *Mat
|
||||
|
||||
//create in db
|
||||
matter = &Matter{
|
||||
Puuid: dirMatter.Uuid,
|
||||
UserUuid: user.Uuid,
|
||||
Username: user.Username,
|
||||
Dir: true,
|
||||
Name: name,
|
||||
Path: relativePath,
|
||||
Puuid: dirMatter.Uuid,
|
||||
UserUuid: user.Uuid,
|
||||
Username: user.Username,
|
||||
Dir: true,
|
||||
Name: name,
|
||||
Path: relativePath,
|
||||
VisitTime: time.Now(),
|
||||
}
|
||||
|
||||
matter = this.matterDao.Create(matter)
|
||||
@@ -510,6 +619,10 @@ func (this *MatterService) createDirectory(request *http.Request, dirMatter *Mat
|
||||
|
||||
func (this *MatterService) AtomicCreateDirectory(request *http.Request, dirMatter *Matter, name string, user *User) *Matter {
|
||||
|
||||
if dirMatter.Deleted {
|
||||
panic(result.BadRequest("Dir has been deleted. Cannot create sub dir under it."))
|
||||
}
|
||||
|
||||
this.userService.MatterLock(user.Uuid)
|
||||
defer this.userService.MatterUnlock(user.Uuid)
|
||||
|
||||
@@ -528,7 +641,8 @@ func (this *MatterService) handleOverwrite(request *http.Request, user *User, de
|
||||
//delete.
|
||||
this.Delete(request, destMatter, user)
|
||||
} else {
|
||||
panic(result.BadRequestI18n(request, i18n.MatterExist, destMatter.Path))
|
||||
//throw precondition failed. (RFC4918:10.6)
|
||||
panic(result.CustomWebResult(result.PRECONDITION_FAILED, fmt.Sprintf("%s exists", destMatter.Path)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -670,22 +784,29 @@ func (this *MatterService) AtomicMoveBatch(request *http.Request, srcMatters []*
|
||||
//copy srcMatter to destMatter. invoker must handled the overwrite and lock.
|
||||
func (this *MatterService) copy(request *http.Request, srcMatter *Matter, destDirMatter *Matter, name string) {
|
||||
|
||||
this.logger.Info("copy srcPath = %s destPath = %s/%s", srcMatter.Path, destDirMatter.Path, name)
|
||||
|
||||
if srcMatter.Dir {
|
||||
|
||||
newMatter := &Matter{
|
||||
Puuid: destDirMatter.Uuid,
|
||||
UserUuid: srcMatter.UserUuid,
|
||||
Username: srcMatter.Username,
|
||||
Dir: srcMatter.Dir,
|
||||
Name: name,
|
||||
Md5: "",
|
||||
Size: srcMatter.Size,
|
||||
Privacy: srcMatter.Privacy,
|
||||
Path: destDirMatter.Path + "/" + name,
|
||||
Puuid: destDirMatter.Uuid,
|
||||
UserUuid: srcMatter.UserUuid,
|
||||
Username: srcMatter.Username,
|
||||
Dir: srcMatter.Dir,
|
||||
Name: name,
|
||||
Md5: "",
|
||||
Size: srcMatter.Size,
|
||||
Privacy: srcMatter.Privacy,
|
||||
Path: destDirMatter.Path + "/" + name,
|
||||
Prop: EMPTY_JSON_MAP,
|
||||
VisitTime: time.Now(),
|
||||
}
|
||||
|
||||
newMatter = this.matterDao.Create(newMatter)
|
||||
|
||||
//make the dir
|
||||
util.MakeDirAll(newMatter.AbsolutePath())
|
||||
|
||||
//copy children
|
||||
matters := this.matterDao.FindByPuuidAndUserUuid(srcMatter.Uuid, srcMatter.UserUuid, nil)
|
||||
for _, m := range matters {
|
||||
@@ -701,15 +822,17 @@ func (this *MatterService) copy(request *http.Request, srcMatter *Matter, destDi
|
||||
util.CopyFile(srcAbsolutePath, destAbsolutePath)
|
||||
|
||||
newMatter := &Matter{
|
||||
Puuid: destDirMatter.Uuid,
|
||||
UserUuid: srcMatter.UserUuid,
|
||||
Username: srcMatter.Username,
|
||||
Dir: srcMatter.Dir,
|
||||
Name: name,
|
||||
Md5: "",
|
||||
Size: srcMatter.Size,
|
||||
Privacy: srcMatter.Privacy,
|
||||
Path: destDirMatter.Path + "/" + name,
|
||||
Puuid: destDirMatter.Uuid,
|
||||
UserUuid: srcMatter.UserUuid,
|
||||
Username: srcMatter.Username,
|
||||
Dir: srcMatter.Dir,
|
||||
Name: name,
|
||||
Md5: "",
|
||||
Size: srcMatter.Size,
|
||||
Privacy: srcMatter.Privacy,
|
||||
Path: destDirMatter.Path + "/" + name,
|
||||
Prop: EMPTY_JSON_MAP,
|
||||
VisitTime: time.Now(),
|
||||
}
|
||||
newMatter = this.matterDao.Create(newMatter)
|
||||
|
||||
@@ -737,12 +860,18 @@ func (this *MatterService) AtomicCopy(request *http.Request, srcMatter *Matter,
|
||||
}
|
||||
|
||||
//rename matter to name
|
||||
func (this *MatterService) AtomicRename(request *http.Request, matter *Matter, name string, user *User) {
|
||||
func (this *MatterService) AtomicRename(request *http.Request, matter *Matter, name string, overwrite bool, user *User) {
|
||||
|
||||
this.logger.Info("Try to rename srcPath = %s to name = %s", matter.Path, name)
|
||||
|
||||
if user == nil {
|
||||
panic(result.BadRequest("user cannot be nil"))
|
||||
}
|
||||
|
||||
if matter.Deleted {
|
||||
panic(result.BadRequest("matter has been deleted. Cannot rename."))
|
||||
}
|
||||
|
||||
this.userService.MatterLock(user.Uuid)
|
||||
defer this.userService.MatterUnlock(user.Uuid)
|
||||
|
||||
@@ -752,12 +881,16 @@ func (this *MatterService) AtomicRename(request *http.Request, matter *Matter, n
|
||||
panic(result.BadRequestI18n(request, i18n.MatterNameNoChange))
|
||||
}
|
||||
|
||||
//判断同级文件夹中是否有同名的文件
|
||||
count := this.matterDao.CountByUserUuidAndPuuidAndDirAndName(user.Uuid, matter.Puuid, matter.Dir, name)
|
||||
//check whether the name used by another matter.
|
||||
oldMatter := this.matterDao.FindByUserUuidAndPuuidAndDirAndName(user.Uuid, matter.Puuid, "", name)
|
||||
if oldMatter != nil {
|
||||
if overwrite {
|
||||
//delete this one.
|
||||
this.Delete(request, oldMatter, user)
|
||||
} else {
|
||||
panic(result.CustomWebResult(result.PRECONDITION_FAILED, fmt.Sprintf("%s already exists", name)))
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
|
||||
panic(result.BadRequestI18n(request, i18n.MatterExist, name))
|
||||
}
|
||||
|
||||
if matter.Dir {
|
||||
@@ -826,6 +959,10 @@ func (this *MatterService) AtomicMirror(request *http.Request, srcPath string, d
|
||||
|
||||
destDirMatter := this.CreateDirectories(request, user, destPath)
|
||||
|
||||
if destDirMatter.Deleted {
|
||||
panic(result.BadRequest("dest matter has been deleted. Cannot mirror."))
|
||||
}
|
||||
|
||||
this.mirror(request, srcPath, destDirMatter, overwrite, user)
|
||||
}
|
||||
|
||||
@@ -853,7 +990,7 @@ func (this *MatterService) mirror(request *http.Request, srcPath string, destDir
|
||||
if fileStat.IsDir() {
|
||||
|
||||
//判断当前文件夹下,文件是否已经存在了。
|
||||
srcDirMatter := this.matterDao.FindByUserUuidAndPuuidAndDirAndName(user.Uuid, destDirMatter.Uuid, true, fileStat.Name())
|
||||
srcDirMatter := this.matterDao.FindByUserUuidAndPuuidAndDirAndName(user.Uuid, destDirMatter.Uuid, TRUE, fileStat.Name())
|
||||
|
||||
if srcDirMatter == nil {
|
||||
srcDirMatter = this.createDirectory(request, destDirMatter, fileStat.Name(), user)
|
||||
@@ -872,7 +1009,7 @@ func (this *MatterService) mirror(request *http.Request, srcPath string, destDir
|
||||
} else {
|
||||
|
||||
//判断当前文件夹下,文件是否已经存在了。
|
||||
matter := this.matterDao.FindByUserUuidAndPuuidAndDirAndName(user.Uuid, destDirMatter.Uuid, false, fileStat.Name())
|
||||
matter := this.matterDao.FindByUserUuidAndPuuidAndDirAndName(user.Uuid, destDirMatter.Uuid, FALSE, fileStat.Name())
|
||||
if matter != nil {
|
||||
//如果是覆盖,那么删除之前的文件
|
||||
if overwrite {
|
||||
@@ -945,13 +1082,17 @@ func (this *MatterService) WrapParentDetail(request *http.Request, matter *Matte
|
||||
panic(result.BadRequest("matter cannot be nil."))
|
||||
}
|
||||
|
||||
puuid := matter.Puuid
|
||||
tmpMatter := matter
|
||||
for puuid != MATTER_ROOT {
|
||||
pFile := this.matterDao.CheckByUuid(puuid)
|
||||
tmpMatter.Parent = pFile
|
||||
tmpMatter = pFile
|
||||
puuid = pFile.Puuid
|
||||
//when self not root.
|
||||
if matter.Uuid != MATTER_ROOT {
|
||||
|
||||
puuid := matter.Puuid
|
||||
tmpMatter := matter
|
||||
for puuid != MATTER_ROOT {
|
||||
pFile := this.matterDao.CheckByUuid(puuid)
|
||||
tmpMatter.Parent = pFile
|
||||
tmpMatter = pFile
|
||||
puuid = pFile.Puuid
|
||||
}
|
||||
}
|
||||
|
||||
return matter
|
||||
@@ -989,6 +1130,10 @@ func (this *MatterService) AtomicCrawl(request *http.Request, url string, filena
|
||||
panic(result.BadRequest("user cannot be nil."))
|
||||
}
|
||||
|
||||
if dirMatter.Deleted {
|
||||
panic(result.BadRequest("Dir has been deleted. Cannot crawl under it."))
|
||||
}
|
||||
|
||||
this.userService.MatterLock(user.Uuid)
|
||||
defer this.userService.MatterUnlock(user.Uuid)
|
||||
|
||||
@@ -1030,3 +1175,162 @@ func (this *MatterService) adjustPath(matter *Matter, parentMatter *Matter) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//delete someone's EyeblueTank files according to physics files.
|
||||
func (this *MatterService) DeleteByPhysics(request *http.Request, user *User) {
|
||||
|
||||
if user == nil {
|
||||
panic(result.BadRequest("user cannot be nil."))
|
||||
}
|
||||
|
||||
//scan user's file. scan level by level.
|
||||
rootMatter := NewRootMatter(user)
|
||||
this.deleteFolderByPhysics(request, rootMatter, user)
|
||||
|
||||
}
|
||||
|
||||
func (this *MatterService) deleteFolderByPhysics(request *http.Request, dirMatter *Matter, user *User) {
|
||||
|
||||
//scan user's file. scan level by level.
|
||||
this.matterDao.PageHandle(dirMatter.Uuid, user.Uuid, "", "", "", nil, nil, func(matter *Matter) {
|
||||
|
||||
if matter.Dir {
|
||||
//delete children first.
|
||||
this.deleteFolderByPhysics(request, matter, user)
|
||||
}
|
||||
|
||||
if !util.PathExists(matter.AbsolutePath()) {
|
||||
this.logger.Info("physics file not exist. delete from tank. %s", matter.Name)
|
||||
this.AtomicDelete(nil, matter, user)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
//scan someone's physics files to EyeblueTank
|
||||
func (this *MatterService) ScanPhysics(request *http.Request, user *User) {
|
||||
|
||||
if user == nil {
|
||||
panic(result.BadRequest("user cannot be nil."))
|
||||
}
|
||||
|
||||
rootDirPath := GetUserMatterRootDir(user.Username)
|
||||
this.logger.Info("scan %s's root dir %s", user.Username, rootDirPath)
|
||||
|
||||
rootExists := util.PathExists(rootDirPath)
|
||||
if !rootExists {
|
||||
util.MakeDirAll(rootDirPath)
|
||||
}
|
||||
rootFileInfo, err := os.Lstat(rootDirPath)
|
||||
if err != nil {
|
||||
panic(result.BadRequest("cannot get root file info."))
|
||||
}
|
||||
|
||||
rootMatter := NewRootMatter(user)
|
||||
this.scanPhysicsFolder(request, rootFileInfo, rootMatter, user)
|
||||
}
|
||||
|
||||
func (this *MatterService) scanPhysicsFolder(request *http.Request, dirInfo os.FileInfo, dirMatter *Matter, user *User) {
|
||||
if !dirInfo.IsDir() {
|
||||
return
|
||||
}
|
||||
|
||||
//fetch all matters under this folder.
|
||||
_, matters := this.matterDao.PlainPage(0, 1000, dirMatter.Uuid, user.Uuid, "", "", "", nil, nil, nil)
|
||||
nameMatterMap := make(map[string]*Matter)
|
||||
for _, m := range matters {
|
||||
nameMatterMap[m.Name] = m
|
||||
}
|
||||
|
||||
dirPath := dirMatter.AbsolutePath()
|
||||
names, err := util.ReadDirNames(dirPath)
|
||||
if err != nil {
|
||||
this.logger.Error("occur error when ReadDirNames %s %s", dirPath, err.Error())
|
||||
return
|
||||
}
|
||||
for _, name := range names {
|
||||
fileFullPath := filepath.Join(dirPath, name)
|
||||
fileInfo, err := os.Lstat(fileFullPath)
|
||||
if err != nil {
|
||||
this.logger.Error("occur error when Lstat %s %s", name, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
//find ther matter
|
||||
var matter *Matter
|
||||
_, ok := nameMatterMap[name]
|
||||
if ok {
|
||||
//exits. check the basic info.
|
||||
matter = nameMatterMap[name]
|
||||
//only check the fileSize.
|
||||
if !matter.Dir {
|
||||
if matter.Size != fileInfo.Size() {
|
||||
this.logger.Info("update matter: %s size:%d -> %d", name, matter.Size, fileInfo.Size())
|
||||
this.updateNonDirMatter(matter, fileInfo.Size(), user)
|
||||
}
|
||||
} else {
|
||||
|
||||
//recursive scan this folder.
|
||||
this.scanPhysicsFolder(request, fileInfo, matter, user)
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if fileInfo.IsDir() {
|
||||
|
||||
//create folder.
|
||||
matter = this.createDirectory(request, dirMatter, name, user)
|
||||
|
||||
//recursive scan this folder.
|
||||
this.scanPhysicsFolder(request, fileInfo, matter, user)
|
||||
|
||||
} else {
|
||||
|
||||
//not exist. add basic info.
|
||||
this.logger.Info("Create matter: %s size:%d", name, fileInfo.Size())
|
||||
matter = this.createNonDirMatter(dirMatter, name, fileInfo.Size(), true, user)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//clean all the expired deleted matters
|
||||
func (this *MatterService) CleanExpiredDeletedMatters() {
|
||||
//mock a request.
|
||||
request := &http.Request{}
|
||||
|
||||
preference := this.preferenceService.Fetch()
|
||||
|
||||
this.userDao.PageHandle("", "", func(user *User) {
|
||||
|
||||
this.logger.Info("Clean %s 's deleted matters", user.Username)
|
||||
|
||||
thenDate := time.Now()
|
||||
thenDate = thenDate.AddDate(0, 0, int(-preference.DeletedKeepDays))
|
||||
if preference.DeletedKeepDays != 0 {
|
||||
thenDate = util.FirstSecondOfDay(thenDate)
|
||||
}
|
||||
|
||||
//first remove all the matter(not dir).
|
||||
this.matterDao.PageHandle("", "", "", FALSE, TRUE, &thenDate, nil, func(matter *Matter) {
|
||||
this.Delete(request, matter, user)
|
||||
})
|
||||
|
||||
sortArray := []builder.OrderPair{
|
||||
{
|
||||
Key: "path",
|
||||
Value: DIRECTION_DESC,
|
||||
},
|
||||
}
|
||||
|
||||
//remove all the deleted directories. sort by path.
|
||||
this.matterDao.PageHandle("", "", "", TRUE, TRUE, &thenDate, sortArray, func(matter *Matter) {
|
||||
this.Delete(request, matter, user)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ package rest
|
||||
|
||||
import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"github.com/eyebluecn/tank/code/tool/i18n"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/eyebluecn/tank/code/tool/util"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
@@ -12,7 +14,9 @@ type PreferenceController struct {
|
||||
BaseController
|
||||
preferenceDao *PreferenceDao
|
||||
matterDao *MatterDao
|
||||
matterService *MatterService
|
||||
preferenceService *PreferenceService
|
||||
taskService *TaskService
|
||||
}
|
||||
|
||||
func (this *PreferenceController) Init() {
|
||||
@@ -27,11 +31,20 @@ func (this *PreferenceController) Init() {
|
||||
if b, ok := b.(*MatterDao); ok {
|
||||
this.matterDao = b
|
||||
}
|
||||
b = core.CONTEXT.GetBean(this.matterService)
|
||||
if b, ok := b.(*MatterService); ok {
|
||||
this.matterService = b
|
||||
}
|
||||
b = core.CONTEXT.GetBean(this.preferenceService)
|
||||
if b, ok := b.(*PreferenceService); ok {
|
||||
this.preferenceService = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.taskService)
|
||||
if b, ok := b.(*TaskService); ok {
|
||||
this.taskService = b
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (this *PreferenceController) RegisterRoutes() map[string]func(writer http.ResponseWriter, request *http.Request) {
|
||||
@@ -41,8 +54,10 @@ func (this *PreferenceController) RegisterRoutes() map[string]func(writer http.R
|
||||
routeMap["/api/preference/ping"] = this.Wrap(this.Ping, USER_ROLE_GUEST)
|
||||
routeMap["/api/preference/fetch"] = this.Wrap(this.Fetch, USER_ROLE_GUEST)
|
||||
routeMap["/api/preference/edit"] = this.Wrap(this.Edit, USER_ROLE_ADMINISTRATOR)
|
||||
routeMap["/api/preference/edit/preview/config"] = this.Wrap(this.EditPreviewConfig, USER_ROLE_ADMINISTRATOR)
|
||||
routeMap["/api/preference/edit/scan/config"] = this.Wrap(this.EditScanConfig, USER_ROLE_ADMINISTRATOR)
|
||||
routeMap["/api/preference/scan/once"] = this.Wrap(this.ScanOnce, USER_ROLE_ADMINISTRATOR)
|
||||
routeMap["/api/preference/system/cleanup"] = this.Wrap(this.SystemCleanup, USER_ROLE_ADMINISTRATOR)
|
||||
routeMap["/api/preference/migrate20to30"] = this.Wrap(this.Migrate20to30, USER_ROLE_ADMINISTRATOR)
|
||||
|
||||
return routeMap
|
||||
}
|
||||
@@ -61,6 +76,7 @@ func (this *PreferenceController) Fetch(writer http.ResponseWriter, request *htt
|
||||
return this.Success(preference)
|
||||
}
|
||||
|
||||
//edit basic info.
|
||||
func (this *PreferenceController) Edit(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
name := request.FormValue("name")
|
||||
@@ -73,6 +89,7 @@ func (this *PreferenceController) Edit(writer http.ResponseWriter, request *http
|
||||
downloadDirMaxNumStr := request.FormValue("downloadDirMaxNum")
|
||||
defaultTotalSizeLimitStr := request.FormValue("defaultTotalSizeLimit")
|
||||
allowRegisterStr := request.FormValue("allowRegister")
|
||||
deletedKeepDaysStr := request.FormValue("deletedKeepDays")
|
||||
|
||||
if name == "" {
|
||||
panic(result.BadRequest("name cannot be null"))
|
||||
@@ -105,12 +122,26 @@ func (this *PreferenceController) Edit(writer http.ResponseWriter, request *http
|
||||
defaultTotalSizeLimit = int64(intDefaultTotalSizeLimit)
|
||||
}
|
||||
|
||||
var deletedKeepDays int64 = 0
|
||||
if deletedKeepDaysStr == "" {
|
||||
panic(result.BadRequest("deletedKeepDays cannot be null"))
|
||||
} else {
|
||||
intDeletedKeepDays, err := strconv.Atoi(deletedKeepDaysStr)
|
||||
this.PanicError(err)
|
||||
deletedKeepDays = int64(intDeletedKeepDays)
|
||||
|
||||
if deletedKeepDays < 0 {
|
||||
panic(result.BadRequest("deletedKeepDays cannot less than 0"))
|
||||
}
|
||||
}
|
||||
|
||||
var allowRegister = false
|
||||
if allowRegisterStr == TRUE {
|
||||
allowRegister = true
|
||||
}
|
||||
|
||||
preference := this.preferenceDao.Fetch()
|
||||
oldDeletedKeepDays := preference.DeletedKeepDays
|
||||
preference.Name = name
|
||||
preference.LogoUrl = logoUrl
|
||||
preference.FaviconUrl = faviconUrl
|
||||
@@ -120,15 +151,81 @@ func (this *PreferenceController) Edit(writer http.ResponseWriter, request *http
|
||||
preference.DownloadDirMaxNum = downloadDirMaxNum
|
||||
preference.DefaultTotalSizeLimit = defaultTotalSizeLimit
|
||||
preference.AllowRegister = allowRegister
|
||||
preference.DeletedKeepDays = deletedKeepDays
|
||||
|
||||
preference = this.preferenceDao.Save(preference)
|
||||
preference = this.preferenceService.Save(preference)
|
||||
|
||||
//reset the preference cache
|
||||
this.preferenceService.Reset()
|
||||
//if changed the bin strategy. then trigger once.
|
||||
if oldDeletedKeepDays != deletedKeepDays {
|
||||
this.matterService.CleanExpiredDeletedMatters()
|
||||
}
|
||||
|
||||
return this.Success(preference)
|
||||
}
|
||||
|
||||
//edit preview config.
|
||||
func (this *PreferenceController) EditPreviewConfig(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
previewConfig := request.FormValue("previewConfig")
|
||||
|
||||
preference := this.preferenceDao.Fetch()
|
||||
preference.PreviewConfig = previewConfig
|
||||
|
||||
preference = this.preferenceService.Save(preference)
|
||||
|
||||
return this.Success(preference)
|
||||
}
|
||||
|
||||
func (this *PreferenceController) EditScanConfig(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
scanConfigStr := request.FormValue("scanConfig")
|
||||
if scanConfigStr == "" {
|
||||
panic(result.BadRequest("scanConfig cannot be null"))
|
||||
}
|
||||
|
||||
preference := this.preferenceDao.Fetch()
|
||||
|
||||
scanConfig := &ScanConfig{}
|
||||
err := jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal([]byte(scanConfigStr), &scanConfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
//validate the scan config.
|
||||
if scanConfig.Enable {
|
||||
//validate cron.
|
||||
if !util.ValidateCron(scanConfig.Cron) {
|
||||
panic(result.CustomWebResultI18n(request, result.SHARE_CODE_ERROR, i18n.CronValidateError))
|
||||
}
|
||||
|
||||
//validate scope.
|
||||
if scanConfig.Scope == SCAN_SCOPE_CUSTOM {
|
||||
if len(scanConfig.Usernames) == 0 {
|
||||
panic(result.BadRequest("scope cannot be null"))
|
||||
}
|
||||
} else if scanConfig.Scope == SCAN_SCOPE_ALL {
|
||||
|
||||
} else {
|
||||
panic(result.BadRequest("cannot recognize scope %s", scanConfig.Scope))
|
||||
}
|
||||
}
|
||||
|
||||
preference.ScanConfig = scanConfigStr
|
||||
preference = this.preferenceService.Save(preference)
|
||||
|
||||
//reinit the scan task.
|
||||
this.taskService.InitScanTask()
|
||||
|
||||
return this.Success(preference)
|
||||
}
|
||||
|
||||
//scan immediately according the current config.
|
||||
func (this *PreferenceController) ScanOnce(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
this.taskService.doScanTask()
|
||||
return this.Success("OK")
|
||||
}
|
||||
|
||||
//cleanup system data.
|
||||
func (this *PreferenceController) SystemCleanup(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
@@ -144,12 +241,3 @@ func (this *PreferenceController) SystemCleanup(writer http.ResponseWriter, requ
|
||||
|
||||
return this.Success("OK")
|
||||
}
|
||||
|
||||
//migrate 2.0's db data and file data to 3.0
|
||||
func (this *PreferenceController) Migrate20to30(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
this.logger.Info("start migrating from 2.0 to 3.0")
|
||||
|
||||
this.preferenceService.Migrate20to30(writer, request)
|
||||
return this.Success("OK")
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package rest
|
||||
import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/nu7hatch/gouuid"
|
||||
"github.com/eyebluecn/tank/code/tool/uuid"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -22,6 +22,8 @@ func (this *PreferenceDao) Fetch() *Preference {
|
||||
if db.Error.Error() == result.DB_ERROR_NOT_FOUND {
|
||||
preference.Name = "EyeblueTank"
|
||||
preference.Version = core.VERSION
|
||||
preference.PreviewConfig = "{}"
|
||||
preference.ScanConfig = "{}"
|
||||
this.Create(preference)
|
||||
return preference
|
||||
} else {
|
||||
|
||||
@@ -1,22 +1,65 @@
|
||||
package rest
|
||||
|
||||
import "github.com/eyebluecn/tank/code/core"
|
||||
import (
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Preference struct {
|
||||
Base
|
||||
Name string `json:"name" gorm:"type:varchar(45)"`
|
||||
LogoUrl string `json:"logoUrl" gorm:"type:varchar(255)"`
|
||||
FaviconUrl string `json:"faviconUrl" gorm:"type:varchar(255)"`
|
||||
Copyright string `json:"copyright" gorm:"type:varchar(1024)"`
|
||||
Record string `json:"record" gorm:"type:varchar(1024)"`
|
||||
DownloadDirMaxSize int64 `json:"downloadDirMaxSize" gorm:"type:bigint(20) not null;default:-1"`
|
||||
DownloadDirMaxNum int64 `json:"downloadDirMaxNum" gorm:"type:bigint(20) not null;default:-1"`
|
||||
DefaultTotalSizeLimit int64 `json:"defaultTotalSizeLimit" gorm:"type:bigint(20) not null;default:-1"`
|
||||
AllowRegister bool `json:"allowRegister" gorm:"type:tinyint(1) not null;default:0"`
|
||||
Version string `json:"version" gorm:"-"`
|
||||
Uuid string `json:"uuid" gorm:"type:char(36);primary_key;unique"`
|
||||
Sort int64 `json:"sort" gorm:"type:bigint(20) not null"`
|
||||
UpdateTime time.Time `json:"updateTime" gorm:"type:timestamp not null;default:CURRENT_TIMESTAMP"`
|
||||
CreateTime time.Time `json:"createTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
Name string `json:"name" gorm:"type:varchar(45)"`
|
||||
LogoUrl string `json:"logoUrl" gorm:"type:varchar(255)"`
|
||||
FaviconUrl string `json:"faviconUrl" gorm:"type:varchar(255)"`
|
||||
Copyright string `json:"copyright" gorm:"type:varchar(1024)"`
|
||||
Record string `json:"record" gorm:"type:varchar(1024)"`
|
||||
DownloadDirMaxSize int64 `json:"downloadDirMaxSize" gorm:"type:bigint(20) not null;default:-1"`
|
||||
DownloadDirMaxNum int64 `json:"downloadDirMaxNum" gorm:"type:bigint(20) not null;default:-1"`
|
||||
DefaultTotalSizeLimit int64 `json:"defaultTotalSizeLimit" gorm:"type:bigint(20) not null;default:-1"`
|
||||
AllowRegister bool `json:"allowRegister" gorm:"type:tinyint(1) not null;default:0"`
|
||||
PreviewConfig string `json:"previewConfig" gorm:"type:text"`
|
||||
ScanConfig string `json:"scanConfig" gorm:"type:text"`
|
||||
DeletedKeepDays int64 `json:"deletedKeepDays" gorm:"type:bigint(20) not null;default:7"`
|
||||
Version string `json:"version" gorm:"-"`
|
||||
}
|
||||
|
||||
// set File's table name to be `profiles`
|
||||
func (this *Preference) TableName() string {
|
||||
return core.TABLE_PREFIX + "preference"
|
||||
const (
|
||||
//scan scope all.
|
||||
SCAN_SCOPE_ALL = "ALL"
|
||||
//scan scope custom.
|
||||
SCAN_SCOPE_CUSTOM = "CUSTOM"
|
||||
)
|
||||
|
||||
//scan config struct.
|
||||
type ScanConfig struct {
|
||||
//whether enable the scan task.
|
||||
Enable bool `json:"enable"`
|
||||
//when to process the task. five fields. @every 1s
|
||||
Cron string `json:"cron"`
|
||||
//username
|
||||
Usernames []string `json:"usernames"`
|
||||
//scan scope. see SCAN_SCOPE
|
||||
Scope string `json:"scope"`
|
||||
}
|
||||
|
||||
//fetch the scan config
|
||||
func (this *Preference) FetchScanConfig() *ScanConfig {
|
||||
|
||||
json := this.ScanConfig
|
||||
if json == "" || json == EMPTY_JSON_MAP {
|
||||
|
||||
return &ScanConfig{
|
||||
Enable: false,
|
||||
}
|
||||
} else {
|
||||
m := &ScanConfig{}
|
||||
|
||||
err := jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal([]byte(json), &m)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return m
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"github.com/eyebluecn/tank/code/tool/i18n"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/eyebluecn/tank/code/tool/util"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//@Service
|
||||
@@ -64,6 +56,17 @@ func (this *PreferenceService) Reset() {
|
||||
|
||||
}
|
||||
|
||||
//清空单例配置。
|
||||
func (this *PreferenceService) Save(preference *Preference) *Preference {
|
||||
|
||||
preference = this.preferenceDao.Save(preference)
|
||||
|
||||
//clean cache.
|
||||
this.Reset()
|
||||
|
||||
return preference
|
||||
}
|
||||
|
||||
//System cleanup.
|
||||
func (this *PreferenceService) Cleanup() {
|
||||
|
||||
@@ -71,191 +74,3 @@ func (this *PreferenceService) Cleanup() {
|
||||
|
||||
this.Reset()
|
||||
}
|
||||
|
||||
//migrate 2.0's db data and file data to 3.0
|
||||
func (this *PreferenceService) Migrate20to30(writer http.ResponseWriter, request *http.Request) {
|
||||
|
||||
matterPath := request.FormValue("matterPath")
|
||||
|
||||
if matterPath == "" {
|
||||
panic(result.BadRequest("matterPath required"))
|
||||
}
|
||||
|
||||
this.logger.Info("start migrating from 2.0 to 3.0")
|
||||
|
||||
//lock
|
||||
if this.migrating {
|
||||
panic(result.BadRequest("migrating work is processing"))
|
||||
} else {
|
||||
this.migrating = true
|
||||
}
|
||||
defer func() {
|
||||
this.migrating = false
|
||||
}()
|
||||
|
||||
//delete all users with _20
|
||||
this.userDao.DeleteUsers20()
|
||||
|
||||
migrateDashboardSql := "INSERT INTO `tank`.`tank30_download_token` ( `uuid`, `sort`, `update_time`, `create_time`, `user_uuid`, `matter_uuid`, `expire_time`, `ip` ) ( SELECT `uuid`, `sort`, `update_time`, `create_time`, `user_uuid`, `matter_uuid`, `expire_time`, `ip` FROM `tank`.`tank20_download_token`)"
|
||||
this.logger.Info(migrateDashboardSql)
|
||||
db := core.CONTEXT.GetDB().Exec(migrateDashboardSql)
|
||||
if db.Error != nil {
|
||||
this.logger.Error("%v", db.Error)
|
||||
}
|
||||
|
||||
migrateDownloadTokenSql := "INSERT INTO `tank`.`tank30_dashboard` ( `uuid`, `sort`, `update_time`, `create_time`, `invoke_num`, `total_invoke_num`, `uv`, `total_uv`, `matter_num`, `total_matter_num`, `file_size`, `total_file_size`, `avg_cost`, `dt` ) ( SELECT `uuid`, `sort`, `update_time`, `create_time`, `invoke_num`, `total_invoke_num`, `uv`, `total_uv`, `matter_num`, `total_matter_num`, `file_size`, `total_file_size`, `avg_cost`, `dt` FROM `tank`.`tank20_dashboard` )"
|
||||
this.logger.Info(migrateDownloadTokenSql)
|
||||
db = core.CONTEXT.GetDB().Exec(migrateDownloadTokenSql)
|
||||
if db.Error != nil {
|
||||
this.logger.Error("%v", db.Error)
|
||||
}
|
||||
|
||||
migrateMatterSql := "INSERT INTO `tank`.`tank30_matter` ( `uuid`, `sort`, `update_time`, `create_time`, `puuid`, `user_uuid`, `username`, `dir`, `name`, `md5`, `size`, `privacy`, `path`, `times` ) ( SELECT `uuid`, `sort`, `update_time`, `create_time`, `puuid`, `user_uuid`, '', `dir`, `name`, `md5`, `size`, `privacy`, `path`, `times` FROM `tank`.`tank20_matter` ) "
|
||||
this.logger.Info(migrateMatterSql)
|
||||
db = core.CONTEXT.GetDB().Exec(migrateMatterSql)
|
||||
if db.Error != nil {
|
||||
this.logger.Error("%v", db.Error)
|
||||
}
|
||||
|
||||
migrateUploadTokenSql := "INSERT INTO `tank`.`tank30_upload_token` ( `uuid`, `sort`, `update_time`, `create_time`, `user_uuid`, `folder_uuid`, `matter_uuid`, `expire_time`, `filename`, `privacy`, `size`, `ip` ) ( SELECT `uuid`, `sort`, `update_time`, `create_time`, `user_uuid`, `folder_uuid`, `matter_uuid`, `expire_time`, `filename`, `privacy`, `size`, `ip` FROM `tank`.`tank20_upload_token` ) "
|
||||
this.logger.Info(migrateUploadTokenSql)
|
||||
db = core.CONTEXT.GetDB().Exec(migrateUploadTokenSql)
|
||||
if db.Error != nil {
|
||||
this.logger.Error("%v", db.Error)
|
||||
}
|
||||
|
||||
//username in tank2.0 add _20.
|
||||
migrateUserSql := "INSERT INTO `tank`.`tank30_user` ( `uuid`, `sort`, `update_time`, `create_time`, `role`, `username`, `password`, `avatar_url`, `last_ip`, `last_time`, `size_limit`, `total_size_limit`, `total_size`, `status` ) ( SELECT `uuid`, `sort`, `update_time`, `create_time`, `role`, CONCAT(`username`,'_20') as `username`, `password`, `avatar_url`, `last_ip`, `last_time`, `size_limit`, -1, 0, `status` FROM `tank`.`tank20_user` )"
|
||||
this.logger.Info(migrateUserSql)
|
||||
db = core.CONTEXT.GetDB().Exec(migrateUserSql)
|
||||
if db.Error != nil {
|
||||
this.logger.Error("%v", db.Error)
|
||||
}
|
||||
|
||||
//find all 2.0 users.
|
||||
users := this.userDao.FindUsers20()
|
||||
for _, user := range users {
|
||||
this.logger.Info("start handling matters for user %s %s", user.Uuid, user.Username)
|
||||
rootMatter := NewRootMatter(user)
|
||||
firstLevelMatters := this.matterDao.FindByPuuidAndUserUuid(MATTER_ROOT, user.Uuid, nil)
|
||||
for _, firstLevelMatter := range firstLevelMatters {
|
||||
this.HandleMatter20(request, matterPath, rootMatter, firstLevelMatter, user)
|
||||
}
|
||||
|
||||
//adjust all the size.
|
||||
this.matterService.ComputeAllDirSize(user)
|
||||
}
|
||||
}
|
||||
|
||||
//handle matter from 2.0
|
||||
func (this *PreferenceService) HandleMatter20(request *http.Request, matterPath string, dirMatter *Matter, matter *Matter, user *User) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
this.logger.Warn("HandleMatter20 occur error %v when handle matter %s %s. Ignore the error and continue. \r\n", err, matter.Uuid, matter.Name)
|
||||
}
|
||||
}()
|
||||
|
||||
this.logger.Info("start handling matter %s", matter.Name)
|
||||
|
||||
if matter == nil {
|
||||
panic(result.BadRequest("matter cannot be nil."))
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
panic(result.BadRequest("user cannot be nil."))
|
||||
}
|
||||
|
||||
if dirMatter == nil {
|
||||
panic(result.BadRequest("dirMatter cannot be nil"))
|
||||
}
|
||||
|
||||
if !dirMatter.Dir {
|
||||
panic(result.BadRequest("dirMatter must be directory"))
|
||||
}
|
||||
|
||||
if dirMatter.UserUuid != user.Uuid {
|
||||
|
||||
panic(result.BadRequest("file's user not the same"))
|
||||
}
|
||||
|
||||
name := matter.Name
|
||||
filename := name
|
||||
if name == "" {
|
||||
panic(result.BadRequest("name cannot be blank"))
|
||||
}
|
||||
|
||||
if len(name) > MATTER_NAME_MAX_LENGTH {
|
||||
|
||||
panic(result.BadRequestI18n(request, i18n.MatterNameLengthExceedLimit, len(name), MATTER_NAME_MAX_LENGTH))
|
||||
|
||||
}
|
||||
|
||||
//if directory. Create it.
|
||||
if matter.Dir {
|
||||
|
||||
if m, _ := regexp.MatchString(MATTER_NAME_PATTERN, name); m {
|
||||
panic(result.BadRequestI18n(request, i18n.MatterNameContainSpecialChars))
|
||||
}
|
||||
|
||||
parts := strings.Split(dirMatter.Path, "/")
|
||||
|
||||
if len(parts) > MATTER_NAME_MAX_DEPTH {
|
||||
panic(result.BadRequestI18n(request, i18n.MatterDepthExceedLimit, len(parts), MATTER_NAME_MAX_DEPTH))
|
||||
}
|
||||
|
||||
absolutePath := GetUserMatterRootDir(user.Username) + dirMatter.Path + "/" + name
|
||||
|
||||
relativePath := dirMatter.Path + "/" + name
|
||||
|
||||
//crate directory on disk.
|
||||
dirPath := util.MakeDirAll(absolutePath)
|
||||
this.logger.Info("Create Directory: %s", dirPath)
|
||||
|
||||
//change matter info.
|
||||
matter.Username = user.Username
|
||||
matter.Path = relativePath
|
||||
|
||||
matter = this.matterDao.Save(matter)
|
||||
|
||||
//handle its children.
|
||||
children := this.matterDao.FindByPuuidAndUserUuid(matter.Uuid, user.Uuid, nil)
|
||||
for _, child := range children {
|
||||
this.HandleMatter20(request, matterPath, matter, child, user)
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
//if file. copy and adjust it.
|
||||
|
||||
dirAbsolutePath := dirMatter.AbsolutePath()
|
||||
dirRelativePath := dirMatter.Path
|
||||
|
||||
fileAbsolutePath := dirAbsolutePath + "/" + filename
|
||||
fileRelativePath := dirRelativePath + "/" + filename
|
||||
|
||||
util.MakeDirAll(dirAbsolutePath)
|
||||
|
||||
//if exist, panic it.
|
||||
exist := util.PathExists(fileAbsolutePath)
|
||||
if exist {
|
||||
this.logger.Error("%s exits, overwrite it.", fileAbsolutePath)
|
||||
removeError := os.Remove(fileAbsolutePath)
|
||||
this.PanicError(removeError)
|
||||
}
|
||||
|
||||
srcAbsolutePath := fmt.Sprintf("%s%s", matterPath, matter.Path)
|
||||
|
||||
//find the 2.0 disk file.
|
||||
fileSize := util.CopyFile(srcAbsolutePath, fileAbsolutePath)
|
||||
|
||||
this.logger.Info("copy %s %v ", filename, util.HumanFileSize(fileSize))
|
||||
|
||||
//update info.
|
||||
matter.Path = fileRelativePath
|
||||
matter.Username = user.Username
|
||||
|
||||
matter = this.matterDao.Save(matter)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package rest
|
||||
import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/nu7hatch/gouuid"
|
||||
"github.com/eyebluecn/tank/code/tool/uuid"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -67,6 +67,13 @@ func (this *SessionDao) Delete(uuid string) {
|
||||
|
||||
}
|
||||
|
||||
func (this *SessionDao) DeleteByUserUuid(userUuid string) {
|
||||
|
||||
db := core.CONTEXT.GetDB().Where("user_uuid = ?", userUuid).Delete(Session{})
|
||||
this.PanicError(db.Error)
|
||||
|
||||
}
|
||||
|
||||
//System cleanup.
|
||||
func (this *SessionDao) Cleanup() {
|
||||
this.logger.Info("[SessionDao] clean up. Delete all Session")
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
Base
|
||||
Uuid string `json:"uuid" gorm:"type:char(36);primary_key;unique"`
|
||||
Sort int64 `json:"sort" gorm:"type:bigint(20) not null"`
|
||||
UpdateTime time.Time `json:"updateTime" gorm:"type:timestamp not null;default:CURRENT_TIMESTAMP"`
|
||||
CreateTime time.Time `json:"createTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
UserUuid string `json:"userUuid" gorm:"type:char(36)"`
|
||||
Ip string `json:"ip" gorm:"type:varchar(128) not null"`
|
||||
ExpireTime time.Time `json:"expireTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
}
|
||||
|
||||
// set User's table name to be `profiles`
|
||||
func (this *Session) TableName() string {
|
||||
return core.TABLE_PREFIX + "session"
|
||||
}
|
||||
|
||||
@@ -278,6 +278,10 @@ func (this *ShareController) Browse(writer http.ResponseWriter, request *http.Re
|
||||
user := this.findUser(request)
|
||||
share := this.shareService.CheckShare(request, shareUuid, code, user)
|
||||
bridges := this.bridgeDao.FindByShareUuid(share.Uuid)
|
||||
shareOwner := this.userDao.FindByUuid(share.UserUuid)
|
||||
if shareOwner.Status == USER_STATUS_DISABLED {
|
||||
panic(result.BadRequestI18n(request, i18n.UserDisabled))
|
||||
}
|
||||
|
||||
if puuid == MATTER_ROOT {
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"github.com/eyebluecn/tank/code/tool/builder"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/jinzhu/gorm"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/nu7hatch/gouuid"
|
||||
"github.com/eyebluecn/tank/code/tool/uuid"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -39,6 +39,14 @@ func (this *ShareDao) CheckByUuid(uuid string) *Share {
|
||||
|
||||
func (this *ShareDao) Page(page int, pageSize int, userUuid string, sortArray []builder.OrderPair) *Pager {
|
||||
|
||||
count, shares := this.PlainPage(page, pageSize, userUuid, sortArray)
|
||||
pager := NewPager(page, pageSize, count, shares)
|
||||
|
||||
return pager
|
||||
}
|
||||
|
||||
func (this *ShareDao) PlainPage(page int, pageSize int, userUuid string, sortArray []builder.OrderPair) (int, []*Share) {
|
||||
|
||||
var wp = &builder.WherePair{}
|
||||
|
||||
if userUuid != "" {
|
||||
@@ -48,16 +56,15 @@ func (this *ShareDao) Page(page int, pageSize int, userUuid string, sortArray []
|
||||
var conditionDB *gorm.DB
|
||||
conditionDB = core.CONTEXT.GetDB().Model(&Share{}).Where(wp.Query, wp.Args...)
|
||||
|
||||
count := 0
|
||||
var count int64 = 0
|
||||
db := conditionDB.Count(&count)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
var shares []*Share
|
||||
db = conditionDB.Order(this.GetSortString(sortArray)).Offset(page * pageSize).Limit(pageSize).Find(&shares)
|
||||
this.PanicError(db.Error)
|
||||
pager := NewPager(page, pageSize, count, shares)
|
||||
|
||||
return pager
|
||||
return int(count), shares
|
||||
}
|
||||
|
||||
func (this *ShareDao) Create(share *Share) *Share {
|
||||
|
||||