Initial commit

This commit is contained in:
微凉
2020-12-24 01:39:45 +08:00
commit 430b4cf723
30 changed files with 1223 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

23
.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
.idea/
.DS_Store
output/
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
*.db
*.bin
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
*.yml
bin/*

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 微凉
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

15
README.md Normal file
View File

@ -0,0 +1,15 @@
#### TODO
- [ ] 搜索与翻页
- [x] 排序
- [ ] 文件预览
- [ ] 图片([viewerjs](https://github.com/fengyuanchen/viewerjs)
- [ ] 视频 [vue-dplayer](https://github.com/MoePlayer/vue-dplayer)
- [ ] 音频([vue-aplayer](https://github.com/SevenOutman/vue-aplayer)
- [ ] Readme渲染
- [ ] 路径优化(不要重复请求)
- [x] 密码加密
- [ ] 缓存

74
alidrive/auth.go Normal file
View File

@ -0,0 +1,74 @@
package alidrive
import (
"encoding/json"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
)
func TokenLogin() (*TokenLoginResp, error) {
log.Infof("尝试使用token登录...")
url:="https://auth.aliyundrive.com/v2/oauth/token_login"
req:=TokenLoginReq{Token:conf.Conf.AliDrive.LoginToken}
log.Debugf("token_login_req:%v",req)
var tokenLogin TokenLoginResp
if body, err := DoPost(url, req,false); err != nil {
log.Errorf("tokenLogin-doPost出错:%s",err.Error())
return nil,err
}else {
if err = json.Unmarshal(body,&tokenLogin);err!=nil {
log.Errorf("解析json[%s]出错:%s",string(body),err.Error())
return nil,err
}
}
if tokenLogin.IsAvailable() {
return &tokenLogin,nil
}
return nil,fmt.Errorf("登录token失效,请更换:%s",tokenLogin.Message)
}
func GetToken(tokenLogin *TokenLoginResp) (*TokenResp,error) {
log.Infof("获取API token...")
url:="https://websv.aliyundrive.com/token/get"
code:=utils.GetCode(tokenLogin.Goto)
if code == "" {
return nil,fmt.Errorf("获取code出错")
}
req:=GetTokenReq{Code:code}
var token TokenResp
if body, err := DoPost(url, req,false); err != nil {
log.Errorf("tokenLogin-doPost出错:%s",err.Error())
return nil,err
}else {
if err = json.Unmarshal(body,&token);err!=nil {
log.Errorf("解析json[%s]出错:%s",string(body),err.Error())
log.Errorf("此处json解析失败应该是code失效")
return nil,fmt.Errorf("code失效")
}
}
return &token,nil
}
func RefreshToken() bool {
log.Infof("刷新token...")
url:="https://websv.aliyundrive.com/token/refresh"
req:=RefreshTokenReq{RefreshToken:conf.Conf.AliDrive.RefreshToken}
var token TokenResp
if body, err := DoPost(url, req,false); err != nil {
log.Errorf("tokenLogin-doPost出错:%s",err.Error())
return false
}else {
if err = json.Unmarshal(body,&token);err!=nil {
log.Errorf("解析json[%s]出错:%s",string(body),err.Error())
log.Errorf("此处json解析失败应该是refresh_token失效")
return false
}
}
//刷新成功 更新token
conf.Conf.AliDrive.AccessToken=token.AccessToken
conf.Conf.AliDrive.RefreshToken=token.RefreshToken
conf.Authorization=token.TokenType+"\t"+token.AccessToken
return true
}

5
alidrive/const.go Normal file
View File

@ -0,0 +1,5 @@
package alidrive
var (
User *UserInfo
)

46
alidrive/req_bean.go Normal file
View File

@ -0,0 +1,46 @@
package alidrive
type ListReq struct {
DriveId string `json:"drive_id"`
Fields string `json:"fields"`
ImageThumbnailProcess string `json:"image_thumbnail_process"`
ImageUrlProcess string `json:"image_url_process"`
Limit int `json:"limit"`
Marker string `json:"marker"`
OrderBy string `json:"order_by"`
OrderDirection string `json:"order_direction"`
ParentFileId string `json:"parent_file_id"`
VideoThumbnailProcess string `json:"video_thumbnail_process"`
}
type GetReq struct {
DriveId string `json:"drive_id"`
FileId string `json:"file_id"`
ImageThumbnailProcess string `json:"image_thumbnail_process"`
VideoThumbnailProcess string `json:"video_thumbnail_process"`
}
type SearchReq struct {
DriveId string `json:"drive_id"`
ImageThumbnailProcess string `json:"image_thumbnail_process"`
ImageUrlProcess string `json:"image_url_process"`
Limit int `json:"limit"`
Marker string `json:"marker"`
OrderBy string `json:"order_by"`//"type ASC,updated_at DESC"
Query string `json:"query"`// "name match '测试文件'"
VideoThumbnailProcess string `json:"video_thumbnail_process"`
}
type TokenLoginReq struct {
Token string `json:"token"`
}
type GetTokenReq struct {
Code string `json:"code"`
}
type RefreshTokenReq struct {
RefreshToken string `json:"refresh_token"`
}

200
alidrive/request.go Normal file
View File

@ -0,0 +1,200 @@
package alidrive
import (
"bytes"
"encoding/json"
"fmt"
"github.com/Xhofe/alist/conf"
log "github.com/sirupsen/logrus"
"io/ioutil"
"net/http"
"strings"
"time"
)
func GetFile(fileId string) (*File, error) {
url:=conf.Conf.AliDrive.ApiUrl+"/file/get"
req:=GetReq{
DriveId: User.DefaultDriveId,
FileId: fileId,
ImageThumbnailProcess: conf.ImageThumbnailProcess,
VideoThumbnailProcess: conf.VideoThumbnailProcess,
}
var file File
if body, err := DoPost(url, req,true); err != nil {
log.Errorf("doPost出错:%s",err.Error())
return nil,err
}else {
if err = json.Unmarshal(body,&file);err !=nil {
log.Errorf("解析json[%s]出错:%s",string(body),err.Error())
return nil,err
}
}
if file.IsAvailable() {
return &file,nil
}
if file.Code==conf.AccessTokenInvalid {
if RefreshToken() {
return GetFile(fileId)
}
}
return nil,fmt.Errorf(file.Message)
}
func Search(key string,limit int, marker string) (*Files, error) {
url:=conf.Conf.AliDrive.ApiUrl+"/file/search"
req:=SearchReq{
DriveId: User.DefaultDriveId,
ImageThumbnailProcess: conf.ImageThumbnailProcess,
ImageUrlProcess: conf.ImageUrlProcess,
Limit: limit,
Marker: marker,
OrderBy: conf.OrderSearch,
Query: fmt.Sprintf("name match '%s'",key),
VideoThumbnailProcess: conf.VideoThumbnailProcess,
}
var files Files
if body, err := DoPost(url, req,true); err != nil {
log.Errorf("doPost出错:%s",err.Error())
return nil,err
}else {
if err = json.Unmarshal(body,&files);err !=nil {
log.Errorf("解析json[%s]出错:%s",string(body),err.Error())
return nil,err
}
}
if files.IsAvailable() {
return &files,nil
}
if files.Code==conf.AccessTokenInvalid {
if RefreshToken() {
return Search(key,limit,marker)
}
}
return nil,fmt.Errorf(files.Message)
}
func GetRoot(limit int,marker string,orderBy string,orderDirection string) (*Files,error) {
return GetList(conf.Conf.AliDrive.RootFolder,limit,marker,orderBy,orderDirection)
}
func GetList(parent string,limit int,marker string,orderBy string,orderDirection string) (*Files,error) {
url:=conf.Conf.AliDrive.ApiUrl+"/file/list"
req:=ListReq{
DriveId: User.DefaultDriveId,
Fields: "*",
ImageThumbnailProcess: conf.ImageThumbnailProcess,
ImageUrlProcess: conf.ImageUrlProcess,
Limit: limit,
Marker: marker,
OrderBy: orderBy,
OrderDirection: orderDirection,
ParentFileId: parent,
VideoThumbnailProcess: conf.VideoThumbnailProcess,
}
var files Files
if body, err := DoPost(url, req,true); err != nil {
log.Errorf("doPost出错:%s",err.Error())
return nil,err
}else {
if err = json.Unmarshal(body,&files);err !=nil {
log.Errorf("解析json[%s]出错:%s",string(body),err.Error())
return nil,err
}
}
if files.IsAvailable() {
return &files,nil
}
if files.Code==conf.AccessTokenInvalid {
if RefreshToken() {
return GetRoot(limit,marker,orderBy,orderDirection)
}
}
return nil,fmt.Errorf(files.Message)
}
func GetUserInfo() (*UserInfo,error) {
url:=conf.Conf.AliDrive.ApiUrl+"/user/get"
var user UserInfo
if body, err := DoPost(url, map[string]interface{}{},true); err != nil {
log.Errorf("doPost出错:%s",err.Error())
return nil,err
}else {
if err = json.Unmarshal(body,&user);err !=nil {
log.Errorf("解析json[%s]出错:%s",string(body),err.Error())
return nil,err
}
}
if user.IsAvailable() {
return &user,nil
}
if user.Code==conf.AccessTokenInvalid {
if RefreshToken() {
return GetUserInfo()
}
}
return nil,fmt.Errorf(user.Message)
}
func DoPost(url string,request interface{},auth bool) (body []byte, err error) {
var(
resp *http.Response
)
requestBody := new(bytes.Buffer)
err = json.NewEncoder(requestBody).Encode(request)
if err !=nil {
log.Errorf("创建requestBody出错:%s",err.Error())
}
req,err:=http.NewRequest("POST",url,requestBody)
log.Debugf("do_post_req:%v",req)
if err != nil {
log.Errorf("创建request出错:%s",err.Error())
return
}
if auth {
req.Header.Set("authorization",conf.Authorization)
}
req.Header.Add("content-type","application/json")
req.Header.Add("user-agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36")
req.Header.Add("origin","https://aliyundrive.com")
req.Header.Add("accept","*/*")
req.Header.Add("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3")
req.Header.Add("Connection", "keep-alive")
for retryCount := 3; retryCount >= 0; retryCount-- {
if resp,err=conf.Client.Do(req);err!=nil&&strings.Contains(err.Error(),"timeout") {
<- time.After(time.Second)
}else {
break
}
}
if err!=nil {
log.Errorf("请求阿里云盘api时出错:%s",err.Error())
return
}
if body, err = ioutil.ReadAll(resp.Body); err != nil {
log.Errorf("读取api返回内容失败")
}
return
}
func GetPaths(fileId string) (*[]Path,error) {
paths:=make([]Path,0)
for fileId != conf.Conf.AliDrive.RootFolder && fileId != "root" {
file,err:=GetFile(fileId)
if err !=nil {
log.Errorf("获取path出错:%s",err.Error())
return nil,err
}
paths=append(paths,Path{
Name: file.Name,
FileId: file.FileId,
})
fileId=file.ParentFileId
}
paths=append(paths, Path{
Name: "Root",
FileId: "root",
})
return &paths,nil
}

135
alidrive/resp_bean.go Normal file
View File

@ -0,0 +1,135 @@
package alidrive
import (
log "github.com/sirupsen/logrus"
"io/ioutil"
"net/http"
"strings"
"time"
)
type RespError struct {
Code string `json:"code"`
Message string `json:"message"`
}
type UserInfo struct {
RespError
DomainId string `json:"domain_id"`
UserId string `json:"user_id"`
Avatar string `json:"avatar"`
CreatedAt int `json:"created_at"`
UpdatedAt int `json:"updated_at"`
Email string `json:"email"`
NickName string `json:"nick_name"`
Phone string `json:"phone"`
Role string `json:"role"`
Status string `json:"status"`
UserName string `json:"user_name"`
Description string `json:"description"`
DefaultDriveId string `json:"default_drive_id"`
UserData map[string]interface{} `json:"user_data"`
}
type Files struct {
RespError
Items []File `json:"items"`
NextMarker string `json:"next_marker"`
Readme string `json:"readme"`
Paths []Path `json:"paths"`
}
type Path struct {
Name string `json:"name"`
FileId string `json:"file_id"`
}
type File struct {
RespError
DriveId string `json:"drive_id"`
CreatedAt *time.Time `json:"created_at"`
DomainId string `json:"domain_id"`
EncryptMode string `json:"encrypt_mode"`
FileExtension string `json:"file_extension"`
FileId string `json:"file_id"`
Hidden bool `json:"hidden"`
Name string `json:"name"`
ParentFileId string `json:"parent_file_id"`
Starred bool `json:"starred"`
Status string `json:"status"`
Type string `json:"type"`
UpdatedAt *time.Time `json:"updated_at"`
// 文件多出部分
Category string `json:"category"`
ContentHash string `json:"content_hash"`
ContentHashName string `json:"content_hash_name"`
ContentType string `json:"content_type"`
Crc64Hash string `json:"crc_64_hash"`
DownloadUrl string `json:"download_url"`
PunishFlag int `json:"punish_flag"`
Size int `json:"size"`
Thumbnail string `json:"thumbnail"`
Url string `json:"url"`
ImageMediaMetadata map[string]interface{} `json:"image_media_metadata"`
Paths []Path `json:"paths"`
}
func (resp *RespError) IsAvailable() bool {
return resp.Code == ""
}
type TokenLoginResp struct {
RespError
Goto string `json:"goto"`
}
type TokenResp struct {
RespError
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
ExpiresIn int `json:"expires_in"`
TokenType string `json:"token_type"`
UserInfo
DefaultSboxDriveId string `json:"default_sbox_drive_id"`
ExpireTime *time.Time `json:"expire_time"`
State string `json:"state"`
ExistLink []interface{} `json:"exist_link"`
NeedLink bool `json:"need_link"`
PinSetup bool `json:"pin_setup"`
IsFirstLogin bool `json:"is_first_login"`
NeedRpVerify bool `json:"need_rp_verify"`
DeviceId string `json:"device_id"`
}
func HasPassword(files *Files) string {
fileList := files.Items
for _, file := range fileList {
if strings.HasPrefix(file.Name, ".password-") {
return file.Name[10:]
}
}
return ""
}
func HasReadme(files *Files) string {
fileList := files.Items
for _, file := range fileList {
if file.Name == "Readme.md" {
resp, err := http.Get(file.Url)
if err != nil {
log.Errorf("Get Readme出错:%s", err.Error())
return ""
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Errorf("读取 Readme出错:%s", err.Error())
return ""
}
return string(data)
}
}
return ""
}

7
alist.go Normal file
View File

@ -0,0 +1,7 @@
package main
import "github.com/Xhofe/alist/bootstrap"
func main() {
bootstrap.Run()
}

34
bootstrap/alidrive.go Normal file
View File

@ -0,0 +1,34 @@
package bootstrap
import (
"github.com/Xhofe/alist/alidrive"
"github.com/Xhofe/alist/conf"
log "github.com/sirupsen/logrus"
)
func InitAliDrive() bool {
//首先token_login
if conf.Conf.AliDrive.RefreshToken == "" {
tokenLogin,err:=alidrive.TokenLogin()
if err!=nil {
log.Errorf("登录失败:%s",err.Error())
return false
}
//然后get_token
token,err:=alidrive.GetToken(tokenLogin)
if err!=nil {
return false
}
conf.Authorization=token.TokenType+" "+token.AccessToken
}
conf.Authorization=conf.Bearer+conf.Conf.AliDrive.AccessToken
log.Infof("token:%s",conf.Authorization)
user,err:=alidrive.GetUserInfo()
if err != nil {
log.Errorf("初始化用户失败:%s",err.Error())
return false
}
log.Infof("当前用户信息:%v",user)
alidrive.User=user
return true
}

10
bootstrap/client.go Normal file
View File

@ -0,0 +1,10 @@
package bootstrap
import (
"github.com/Xhofe/alist/conf"
"net/http"
)
func InitClient() {
conf.Client=&http.Client{}
}

64
bootstrap/cmd.go Normal file
View File

@ -0,0 +1,64 @@
package bootstrap
import (
"flag"
"github.com/Xhofe/alist/conf"
serv "github.com/Xhofe/alist/server"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
func init() {
flag.BoolVar(&conf.Debug,"debug",false,"use debug mode")
flag.BoolVar(&conf.Help,"help",false,"show usage help")
flag.StringVar(&conf.Con,"conf","conf.yml","config file")
}
func Run() {
flag.Parse()
if conf.Help {
flag.Usage()
return
}
start()
}
func printASC() {
log.Info(`
________ ___ ___ ________ _________
|\ __ \|\ \ |\ \|\ ____\|\___ ___\
\ \ \|\ \ \ \ \ \ \ \ \___|\|___ \ \_|
\ \ __ \ \ \ \ \ \ \_____ \ \ \ \
\ \ \ \ \ \ \____\ \ \|____|\ \ \ \ \
\ \__\ \__\ \_______\ \__\____\_\ \ \ \__\
\|__|\|__|\|_______|\|__|\_________\ \|__|
\|_________|
`)
}
func start() {
InitLog()
printASC()
InitClient()
if !ReadConf(conf.Con) {
log.Errorf("读取配置文件时出现错误,启动失败.")
return
}
if !InitAliDrive() {
log.Errorf("初始化阿里云盘出现错误,启动失败.")
return
}
InitCron()
server()
}
func server() {
baseServer:="0.0.0.0:"+conf.Conf.Server.Port
log.Infof("Starting server @ %s",baseServer)
r:=gin.Default()
serv.InitRouter(r)
err:=r.Run(baseServer)
if err!=nil {
log.Errorf("Server failed start:%s",err.Error())
}
}

28
bootstrap/config.go Normal file
View File

@ -0,0 +1,28 @@
package bootstrap
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
"io/ioutil"
)
func ReadConf(config string) bool {
if !utils.Exists(config) {
log.Infof("找不到配置文件:%s",config)
return false
}
confFile,err:=ioutil.ReadFile(config)
if err !=nil {
log.Errorf("读取配置文件时发生错误:%s",err.Error())
return false
}
err = yaml.Unmarshal(confFile, conf.Conf)
if err !=nil {
log.Errorf("加载配置文件时发生错误:%s",err.Error())
return false
}
log.Debugf("config:%v",conf.Conf)
return true
}

26
bootstrap/cron.go Normal file
View File

@ -0,0 +1,26 @@
package bootstrap
import (
"github.com/Xhofe/alist/alidrive"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/utils"
"github.com/robfig/cron/v3"
log "github.com/sirupsen/logrus"
)
var Cron *cron.Cron
func refreshToken() {
alidrive.RefreshToken()
utils.WriteToYml(conf.Con,conf.Conf)
}
func InitCron() {
log.Infof("初始化定时任务:刷新token")
Cron=cron.New()
_,err:=Cron.AddFunc("@every 2h",refreshToken)
if err!=nil {
log.Errorf("添加启动任务失败:%s",err.Error())
}
Cron.Start()
}

21
bootstrap/log.go Normal file
View File

@ -0,0 +1,21 @@
package bootstrap
import (
"github.com/Xhofe/alist/conf"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
func InitLog() {
if conf.Debug {
log.SetLevel(log.DebugLevel)
}else {
gin.SetMode(gin.ReleaseMode)
}
log.SetFormatter(&log.TextFormatter{
ForceColors:true,
EnvironmentOverrideColors:true,
TimestampFormat:"2006-01-02 15:04:05",
FullTimestamp:true,
})
}

11
conf.yml.example Normal file
View File

@ -0,0 +1,11 @@
server:
site_url: http://localhost
port: 5244
search: true
ali_drive:
api_url: https://api.aliyundrive.com/v2
root_folder: root
token:
access_token:
refresh_token: need
max_files_count: 3000

18
conf/config.go Normal file
View File

@ -0,0 +1,18 @@
package conf
type Config struct {
Server struct{
SiteUrl string `yaml:"site_url"`//网站url
Port string `yaml:"port"`//端口
Search bool `yaml:"search"`//允许搜索
} `yaml:"server"`
AliDrive struct{
ApiUrl string `yaml:"api_url"`//阿里云盘api
RootFolder string `yaml:"root_folder"`//根目录id
//Authorization string `yaml:"authorization"`//授权token
LoginToken string `yaml:"login_token"`
AccessToken string `yaml:"access_token"`
RefreshToken string `yaml:"refresh_token"`
MaxFilesCount int `yaml:"max_files_count"`
} `yaml:"ali_drive"`
}

30
conf/const.go Normal file
View File

@ -0,0 +1,30 @@
package conf
import (
"net/http"
)
var(
Debug bool
Help bool
Con string
Client *http.Client
Authorization string
)
var Conf = new(Config)
const (
ImageThumbnailProcess="image/resize,w_50"
VideoThumbnailProcess="video/snapshot,t_0,f_jpg,w_50"
ImageUrlProcess="image/resize,w_1920/format,jpeg"
ASC="ASC"
DESC="DESC"
OrderUpdatedAt="updated_at"
OrderCreatedAt="created_at"
OrderSize="size"
OrderName="name"
OrderSearch="type ASC,updated_at DESC"
AccessTokenInvalid="AccessTokenInvalid"
Bearer="Bearer\t"
)

20
go.mod Normal file
View File

@ -0,0 +1,20 @@
module github.com/Xhofe/alist
go 1.15
require (
github.com/gin-gonic/gin v1.6.3
github.com/go-playground/validator/v10 v10.4.1 // indirect
github.com/golang/protobuf v1.4.3 // indirect
github.com/json-iterator/go v1.1.10 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/robfig/cron/v3 v3.0.0
github.com/sirupsen/logrus v1.7.0
github.com/ugorji/go v1.2.2 // indirect
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 // indirect
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742 // indirect
google.golang.org/protobuf v1.25.0 // indirect
gopkg.in/yaml.v2 v2.4.0
)

142
go.sum Normal file
View File

@ -0,0 +1,142 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E=
github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go v1.2.2 h1:60ZHIOcsJlo3bJm9CbTVu7OSqT2mxaEmyQbK2NwCkn0=
github.com/ugorji/go v1.2.2/go.mod h1:bitgyERdV7L7Db/Z5gfd5v2NQMNhhiFiZwpgMw2SP7k=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.2 h1:08Gah8d+dXj4cZNUHhtuD/S4PXD5WpVbj5B8/ClELAQ=
github.com/ugorji/go/codec v1.2.2/go.mod h1:OM8g7OAy52uYl3Yk+RE/3AS1nXFn1Wh4PPLtupCxbuU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 h1:3wPMTskHO3+O6jqTEXyFcsnuxMQOqYSaHsDxcbUXpqA=
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742 h1:+CBz4km/0KPU3RGTwARGh/noP3bEwtHcq+0YcBQM2JQ=
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

22
server/common.go Normal file
View File

@ -0,0 +1,22 @@
package server
import "github.com/gin-gonic/gin"
func metaResponse(code int, msg string) gin.H {
return gin.H{
"meta":gin.H{
"code":code,
"msg":msg,
},
}
}
func dataResponse(data interface{}) gin.H {
return gin.H{
"meta":gin.H{
"code":200,
"msg":"success",
},
"data":data,
}
}

102
server/controllers.go Normal file
View File

@ -0,0 +1,102 @@
package server
import (
"github.com/Xhofe/alist/alidrive"
"github.com/Xhofe/alist/conf"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
// TODO:添加前置路由信息
func Get(c *gin.Context) {
var get alidrive.GetReq
if err := c.ShouldBindJSON(&get); err != nil {
c.JSON(200,metaResponse(400,"Bad Request"))
return
}
log.Debugf("get:%v",get)
file,err:=alidrive.GetFile(get.FileId)
if err !=nil {
c.JSON(200,metaResponse(500,err.Error()))
return
}
paths,err:=alidrive.GetPaths(get.FileId)
if err!=nil {
c.JSON(200,metaResponse(500,err.Error()))
return
}
file.Paths=*paths
c.JSON(200,dataResponse(file))
}
func List(c *gin.Context) {
var list ListReq
if err := c.ShouldBindJSON(&list);err!=nil {
c.JSON(200,metaResponse(400,"Bad Request"))
return
}
log.Debugf("list:%v",list)
var (
files *alidrive.Files
err error
)
if list.Limit == 0 {
list.Limit=50
}
if conf.Conf.AliDrive.MaxFilesCount!=0 {
list.Limit=conf.Conf.AliDrive.MaxFilesCount
}
if list.ParentFileId == "root" {
files,err=alidrive.GetRoot(list.Limit,list.Marker,list.OrderBy,list.OrderDirection)
}else {
files,err=alidrive.GetList(list.ParentFileId,list.Limit,list.Marker,list.OrderBy,list.OrderDirection)
}
if err!=nil {
c.JSON(200,metaResponse(500,err.Error()))
return
}
password:=alidrive.HasPassword(files)
if password!="" && password!=list.Password {
if list.Password=="" {
c.JSON(200,metaResponse(401,"need password."))
return
}
c.JSON(200,metaResponse(401,"wrong password."))
return
}
paths,err:=alidrive.GetPaths(list.ParentFileId)
if err!=nil {
c.JSON(200,metaResponse(500,err.Error()))
return
}
files.Paths=*paths
files.Readme=alidrive.HasReadme(files)
c.JSON(200,dataResponse(files))
}
func Search(c *gin.Context) {
if !conf.Conf.Server.Search {
c.JSON(200,metaResponse(403,"Not allow search."))
return
}
var search alidrive.SearchReq
if err := c.ShouldBindJSON(&search); err != nil {
c.JSON(200,metaResponse(400,"Bad Request"))
return
}
log.Debugf("search:%v",search)
if search.Limit == 0 {
search.Limit=50
}
// Search只支持0-100
//if conf.Conf.AliDrive.MaxFilesCount!=0 {
// search.Limit=conf.Conf.AliDrive.MaxFilesCount
//}
files,err:=alidrive.Search(search.Query,search.Limit,search.OrderBy)
if err != nil {
c.JSON(200,metaResponse(500,err.Error()))
return
}
c.JSON(200,dataResponse(files))
}

27
server/middlewares.go Normal file
View File

@ -0,0 +1,27 @@
package server
import (
"github.com/gin-gonic/gin"
"net/http"
)
func CrosHandler() gin.HandlerFunc {
return func(context *gin.Context) {
method := context.Request.Method
context.Writer.Header().Set("Access-Control-Allow-Origin", "*")
context.Header("Access-Control-Allow-Origin", "*") // 设置允许访问所有域
context.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE")
context.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma,token,openid,opentoken")
context.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar")
context.Header("Access-Control-Max-Age", "172800")
context.Header("Access-Control-Allow-Credentials", "false")
context.Set("content-type", "application/json") //设置返回格式是json
if method == "OPTIONS" {
context.JSON(http.StatusOK, metaResponse(200,"Options Request!"))
}
//处理请求
context.Next()
}
}

8
server/req_bean.go Normal file
View File

@ -0,0 +1,8 @@
package server
import "github.com/Xhofe/alist/alidrive"
type ListReq struct {
Password string `json:"password"`
alidrive.ListReq
}

17
server/router.go Normal file
View File

@ -0,0 +1,17 @@
package server
import "github.com/gin-gonic/gin"
func InitRouter(engine *gin.Engine) {
engine.Use(CrosHandler())
InitApiRouter(engine)
}
func InitApiRouter(engine *gin.Engine) {
v2:=engine.Group("/api/v2")
{
v2.POST("/get",Get)
v2.POST("/list",List)
v2.POST("/search",Search)
}
}

40
test/alidrive_test.go Normal file
View File

@ -0,0 +1,40 @@
package test
import (
"fmt"
"github.com/Xhofe/alist/alidrive"
"github.com/Xhofe/alist/bootstrap"
"github.com/Xhofe/alist/conf"
"testing"
)
func init() {
bootstrap.InitLog()
bootstrap.ReadConf("../conf.yml")
bootstrap.InitClient()
bootstrap.InitAliDrive()
}
func TestGetUserInfo(t *testing.T) {
user,err:= alidrive.GetUserInfo()
fmt.Println(err)
fmt.Println(user)
}
func TestGetRoot(t *testing.T) {
files,err:=alidrive.GetRoot(50,"",conf.OrderUpdatedAt,conf.DESC)
fmt.Println(err)
fmt.Println(files)
}
func TestSearch(t *testing.T) {
files,err:=alidrive.Search("测试文件",50,"")
fmt.Println(err)
fmt.Println(files)
}
func TestGet(t *testing.T) {
file,err:=alidrive.GetFile("5fb7c80e85e4f335cd344008be1b1b5349f74414")
fmt.Println(err)
fmt.Println(file)
}

18
test/utils_test.go Normal file
View File

@ -0,0 +1,18 @@
package test
import (
"fmt"
"github.com/Xhofe/alist/alidrive"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/utils"
"testing"
)
func TestStr(t *testing.T) {
fmt.Println(".password-"[10:])
}
func TestWriteYml(t *testing.T) {
alidrive.RefreshToken()
utils.WriteToYml("../conf.yml",conf.Conf)
}

16
utils/check.go Normal file
View File

@ -0,0 +1,16 @@
package utils
import (
log "github.com/sirupsen/logrus"
"net/url"
)
func GetCode(rawUrl string) string {
u,err:=url.Parse(rawUrl)
if err!=nil {
log.Errorf("解析url出错:%s",err.Error())
return ""
}
code:=u.Query().Get("code")
return code
}

41
utils/file.go Normal file
View File

@ -0,0 +1,41 @@
package utils
import (
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
"io/ioutil"
"os"
"path/filepath"
)
func Exists(name string) bool {
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
func CreatNestedFile(path string) (*os.File, error) {
basePath := filepath.Dir(path)
if !Exists(basePath) {
err := os.MkdirAll(basePath, 0700)
if err != nil {
log.Errorf("无法创建目录,%s", err)
return nil, err
}
}
return os.Create(path)
}
func WriteToYml(src string,conf interface{}){
data,err := yaml.Marshal(conf)
if err!=nil {
log.Errorf("Conf转[]byte失败:%s",err.Error())
}
err = ioutil.WriteFile(src,data,0777)
if err!=nil {
log.Errorf("写yml文件失败",err.Error())
}
}