Browse Source

refactoring

joe 4 năm trước cách đây
mục cha
commit
001b2e1013
61 tập tin đã thay đổi với 1086 bổ sung576 xóa
  1. 1 0
      .gitignore
  2. 1 0
      Makefile
  3. 36 6
      cmd/install.go
  4. 11 2
      cmd/root.go
  5. 47 36
      cmd/server.go
  6. 26 0
      cmd/test.go
  7. 2 2
      cmd/version.go
  8. 59 122
      config/config.go
  9. 24 25
      config/flytalk.sql
  10. 12 7
      config/flytalk.toml
  11. 0 54
      config/lang.go
  12. 0 47
      config/language.go
  13. 5 6
      controller/auth.go
  14. 1 1
      controller/chat.go
  15. 5 8
      controller/folder.go
  16. 3 1
      controller/ip.go
  17. 6 0
      controller/kefu.go
  18. 12 13
      controller/main.go
  19. 10 6
      controller/message.go
  20. 45 50
      controller/mysql.go
  21. 4 0
      controller/notice.go
  22. 1 0
      controller/response.go
  23. 1 0
      controller/role.go
  24. 1 0
      controller/setting.go
  25. 2 0
      controller/tcp.go
  26. 5 2
      controller/visitor.go
  27. 0 27
      database/mysql.go
  28. 1 2
      docs/docs.go
  29. 36 0
      flytalk.toml.sample
  30. 116 0
      g/lang.go
  31. 13 0
      g/lang_test.go
  32. 15 4
      go.mod
  33. 261 0
      go.sum
  34. 17 0
      lang/cn/flytalk.json
  35. 0 0
      lang/en/flytalk.js
  36. 17 0
      lang/en/flytalk.json
  37. 0 0
      lang/zh/flytalk.js
  38. 4 2
      models/abouts.go
  39. 7 4
      models/configs.go
  40. 8 6
      models/ipblacks.go
  41. 17 17
      models/messages.go
  42. 14 15
      models/models.go
  43. 5 5
      models/roles.go
  44. 12 11
      models/users.go
  45. 16 16
      models/visitors.go
  46. 6 7
      models/welcomes.go
  47. 2 2
      static/html/chat_kf_page.html
  48. 2 2
      static/html/chat_main.html
  49. 1 1
      static/html/chat_page.html
  50. 1 1
      static/html/header.html
  51. 136 3
      static/html/index.html
  52. 1 1
      static/html/login.html
  53. 2 2
      static/html/main.html
  54. 6 5
      tmpl/chat.go
  55. 23 26
      tmpl/common.go
  56. 1 1
      tmpl/setting.go
  57. 4 2
      tools/ip.go
  58. 10 15
      user/provider/twong/user.go
  59. 1 0
      ws/user.go
  60. 1 0
      ws/visitor.go
  61. 10 11
      ws/ws.go

+ 1 - 0
.gitignore

@@ -5,3 +5,4 @@ logs/
 static/upload/
 *.tar.gz
 bin/
+flytalk.toml

+ 1 - 0
Makefile

@@ -0,0 +1 @@
+#

+ 36 - 6
cmd/install.go

@@ -8,23 +8,29 @@ import (
 	"github.com/spf13/cobra"
 	"io/ioutil"
 	"os"
+	"path/filepath"
 	"strings"
 )
 
 var installCmd = &cobra.Command{
 	Use:   "install",
-	Short: "example:flytalk install",
+	Short: "install database specified in config file",
 	Run: func(cmd *cobra.Command, args []string) {
 		install()
 	},
 }
 
 func install() {
-	sqlFile := config.Dir + "flytalk.sql"
-	isExit, _ := tools.IsFileExist(config.MysqlConf)
-	dataExit, _ := tools.IsFileExist(sqlFile)
-	if !isExit || !dataExit {
-		fmt.Println("config/mysql.json 数据库配置文件或者数据库文件flytalk.sql不存在")
+	_, err := config.LoadConf(Confile)
+	if err != nil {
+		panic(err)
+	}
+	models.InitDb()
+
+	sqlFile := filepath.Join(config.C.Basic.DataDir, "flytalk.sql")
+	fileExists, _ := tools.IsFileExist(sqlFile)
+	if !fileExists {
+		fmt.Printf("sql-file not exists:%v\n", sqlFile)
 		os.Exit(1)
 	}
 	sqls, _ := ioutil.ReadFile(sqlFile)
@@ -36,3 +42,27 @@ func install() {
 		models.Execute(sql)
 	}
 }
+
+// gorm 的实现功能不强, 很难通过映射的方式去完整的还原表结构
+//func installByGorm() {
+//	_, err := config.LoadConf(Confile)
+//	if err != nil {
+//		panic(err)
+//	}
+//	models.InitDb()
+//	models.DB.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(
+//		&models.Message{},
+//		&models.User{},
+//		&models.Visitor{},
+//		&models.Role{},
+//		&models.User_role{},
+//
+//		&models.About{},
+//		&models.Config{},
+//		&models.Ipblack{},
+//		&models.Welcome{},
+//	)
+//	if models.DB.Error != nil {
+//		panic(models.DB.Error)
+//	}
+//}

+ 11 - 2
cmd/root.go

@@ -10,7 +10,7 @@ import (
 var rootCmd = &cobra.Command{
 	Use:   "flytalk",
 	Short: "flytalk",
-	Long:  `简洁快速的在线 Web 客服系统`,
+	Long:  `Fast online message service`,
 	Args:  args,
 	Run: func(cmd *cobra.Command, args []string) {
 
@@ -19,7 +19,7 @@ var rootCmd = &cobra.Command{
 
 func args(cmd *cobra.Command, args []string) error {
 	if len(args) < 1 {
-		return errors.New("至少需要一个参数!")
+		return errors.New("At least one argument is needed")
 	}
 	return nil
 }
@@ -32,7 +32,16 @@ func Execute() {
 }
 
 func init() {
+	//serverCmd.PersistentFlags().StringVarP(&Addr, "addr", "a", "127.0.0.1:10016", "server address listening on")
+	serverCmd.PersistentFlags().StringVarP(&Confile, "config", "c", "flytalk.toml", "specify the config file")
+	//serverCmd.PersistentFlags().BoolVarP(&Daemon, "daemon", "d", false, "run in daemon mode")
+
+	installCmd.PersistentFlags().StringVarP(&Confile, "config", "c", "flytalk.toml", "specify the config file")
+
+	testCmd.PersistentFlags().StringVarP(&Confile, "config", "c", "flytalk.toml", "specify the tested config file")
+
 	rootCmd.AddCommand(versionCmd)
 	rootCmd.AddCommand(serverCmd)
 	rootCmd.AddCommand(installCmd)
+	rootCmd.AddCommand(testCmd)
 }

+ 47 - 36
cmd/server.go

@@ -1,14 +1,17 @@
 package cmd
 
 import (
-	"git.wanbits.cc/sin/flytalk/docs"
+	"fmt"
+	"git.wanbits.cc/sin/flytalk/config"
+	"git.wanbits.cc/sin/flytalk/controller"
+	"git.wanbits.cc/sin/flytalk/g"
+	"git.wanbits.cc/sin/flytalk/models"
 	"git.wanbits.cc/sin/flytalk/router"
 	"git.wanbits.cc/sin/flytalk/tools"
+	"git.wanbits.cc/sin/flytalk/user/provider/twong"
 	"github.com/gin-contrib/cors"
 	"github.com/gin-gonic/gin"
 	"github.com/spf13/cobra"
-	ginSwagger "github.com/swaggo/gin-swagger"
-	"github.com/swaggo/gin-swagger/swaggerFiles"
 	"log"
 	"os"
 	"os/exec"
@@ -16,30 +19,32 @@ import (
 )
 
 var (
-	addr        string
-	port        string
-	tcpport     string
-	daemon      bool
+	Addr    string
+	Confile string
+	Daemon  bool
 )
 
 var serverCmd = &cobra.Command{
-	Use:     "server",
-	Short:   "example:flytalk server port 8081",
-	Example: "flytalk server -c config/",
+	Use:     "serve",
+	Short:   "start serving",
+	Example: "flytalk serve -c config",
 	Run: func(cmd *cobra.Command, args []string) {
 		run()
 	},
 }
 
-func init() {
-	serverCmd.PersistentFlags().StringVarP(&addr, "addr", "a", "127.0.0.1", "监听地址")
-	serverCmd.PersistentFlags().StringVarP(&port, "port", "p", "8081", "监听端口号")
-	serverCmd.PersistentFlags().StringVarP(&tcpport, "tcpport", "t", "8082", "监听tcp端口号")
-	serverCmd.PersistentFlags().BoolVarP(&daemon, "daemon", "d", false, "是否为守护进程模式")
-}
-
 func run() {
-	if daemon == true {
+	// load config
+	_, err := config.LoadConf(Confile)
+	if err != nil {
+		panic(fmt.Sprintf("load config file failed:%v", Confile))
+	}
+	fmt.Println("============Config============")
+	fmt.Printf("Basic:%+v\n", config.C.Basic)
+	fmt.Printf("Database:%+v\n", config.C.Database)
+	fmt.Printf("Imap:%+v\n", config.C.Imap)
+	fmt.Printf("Twong:%+v\n", config.C.Twong)
+	if config.C.Basic.Daemon {
 		if os.Getppid() != 1 {
 			// 将命令行参数中执行文件路径转换成可用路径
 			filePath, _ := filepath.Abs(os.Args[0])
@@ -53,34 +58,40 @@ func run() {
 		}
 	}
 
-	baseServer := addr + ":" + port
-	//tcpBaseServer := "0.0.0.0:"+tcpport
-	log.Println("start server...\r\ngo:http://" + baseServer)
+	// load i18n
+	l := g.NewLang(config.C.Basic.LangDir)
+	g.SetDefaultLang(l)
+
+	// connect to db
+	models.InitDb()
+	twong.InitDb()
+	controller.Init()
+
+	log.Println("start server at " + config.C.Basic.Addr)
+
 	gin.SetMode(gin.ReleaseMode)
+
 	engine := gin.Default()
 	engine.Use(cors.Default())
+	//htmlGlobDir := filepath.Join(config.C.Basic.ResDir, "html/*")
 	engine.LoadHTMLGlob("static/html/*")
 	engine.Static("/static", "./static")
 
 	//记录日志
 	engine.Use(tools.LoggerToFile())
+
 	router.InitViewRouter(engine)
 	router.InitApiRouter(engine)
-
 	//文档服务
-	docs.SwaggerInfo.Title = "接口文档"
-	docs.SwaggerInfo.Description = "flytalk 客服系统 , 测试账户:kefu2 测试密码:123 类型:kefu"
-	docs.SwaggerInfo.Version = "0.0.7"
-	//docs.SwaggerInfo.Host = "127.0.0.1:"+port
-	docs.SwaggerInfo.Host = "flytalk.sopans.com"
-	docs.SwaggerInfo.BasePath = "/"
-	docs.SwaggerInfo.Schemes = []string{"https"}
-	engine.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
-
-	//logFile, _ := os.OpenFile("./fatal.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0660)
-	//tools.RedirectStderr(logFile)
+	//docs.SwaggerInfo.Title = "接口文档"
+	//docs.SwaggerInfo.Description = "flytalk 客服系统 , 测试账户:kefu2 测试密码:123 类型:kefu"
+	//docs.SwaggerInfo.Version = "0.0.7"
+	////docs.SwaggerInfo.Host = "127.0.0.1:"+port
+	//docs.SwaggerInfo.Host = "flytalk.sopans.com"
+	//docs.SwaggerInfo.BasePath = "/"
+	//docs.SwaggerInfo.Schemes = []string{"https"}
+	//engine.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
 
-	//tcp服务
-	//go controller.NewTcpServer(tcpBaseServer)
-	engine.Run(baseServer)
+	err = engine.Run(config.C.Basic.Addr)
+	fmt.Println("quit:", err)
 }

+ 26 - 0
cmd/test.go

@@ -0,0 +1,26 @@
+package cmd
+
+import (
+	"fmt"
+	"git.wanbits.cc/sin/flytalk/config"
+	"git.wanbits.cc/sin/flytalk/models"
+	"github.com/spf13/cobra"
+)
+
+var testCmd = &cobra.Command{
+	Use:   "test",
+	Short: "test config file",
+	Run: func(cmd *cobra.Command, args []string) {
+		test()
+	},
+}
+
+func test() {
+	_, err := config.LoadConf(Confile)
+	if err != nil {
+		panic(err)
+	}
+	models.InitDb()
+
+	fmt.Printf("config file <%v> tested successful. and connect database well.\n", Confile)
+}

+ 2 - 2
cmd/version.go

@@ -8,8 +8,8 @@ import (
 
 var versionCmd = &cobra.Command{
 	Use:   "version",
-	Short: "example:flytalk version",
+	Short: "print version string",
 	Run: func(cmd *cobra.Command, args []string) {
-		fmt.Println("flytalk " + config.VERSION)
+		fmt.Println("flytalk-v" + config.VERSION)
 	},
 }

+ 59 - 122
config/config.go

@@ -1,18 +1,12 @@
 package config
 
 import (
-	"encoding/json"
-	"fmt"
-	"git.wanbits.cc/sin/flytalk/tools"
-	"io/ioutil"
-	"os"
+	"github.com/spf13/viper"
 )
 
 var (
 	PageSize uint = 10
-
-	GoflyConfig *Config
-	QiniuConfig *Qiniu
+	C        *Config
 )
 
 const (
@@ -21,19 +15,7 @@ const (
 	DIR               = "config"
 )
 
-const Dir = "config/"
-const MysqlConf = Dir + "mysql.json"
-const QiniuFile = Dir + "qiniu.json"
-const MailConf = Dir + "mail.json"
-const MainConf = Dir + "config.json"
-
-func init() {
-	//配置文件
-	GoflyConfig = CreateConfig()
-	QiniuConfig = CreateQiniu(QiniuFile)
-}
-
-type Database struct {
+type DatabaseConf struct {
 	Driver   string
 	Server   string
 	Port     string
@@ -42,7 +24,7 @@ type Database struct {
 	Password string
 }
 
-type Qiniu struct {
+type QiniuConf struct {
 	Access string
 	Secret string
 	Bucket string
@@ -50,122 +32,77 @@ type Qiniu struct {
 	Domain string
 }
 
-type MailServer struct {
+type ImapConf struct {
 	Server, Email, Password string
 }
 
-type Config struct {
+type BasicConf struct {
+	Addr         string
+	Daemon       bool
 	Upload       string
 	NoticeServer bool
+	LangDir      string
+	DataDir      string
+	ResDir       string
 }
 
-func CreateConfig() *Config {
-	var configObj Config
-	c := &Config{
-		Upload:       "static/upload/",
-		NoticeServer: false,
-	}
-	isExist, _ := tools.IsFileExist(MainConf)
-	if !isExist {
-		return c
-	}
-	info, err := ioutil.ReadFile(MainConf)
-	if err != nil {
-		return c
-	}
-	err = json.Unmarshal(info, &configObj)
-	return &configObj
+type Config struct {
+	Basic    *BasicConf
+	Database *DatabaseConf
+	Imap     *ImapConf
+	Qiniu    *QiniuConf
+	Twong    *DatabaseConf
 }
 
-func CreateMailServer() *MailServer {
-	var imap MailServer
-	isExist, _ := tools.IsFileExist(MailConf)
-	if !isExist {
-		return &imap
-	}
-	info, err := ioutil.ReadFile(MailConf)
-	if err != nil {
-		return &imap
-	}
-
-	err = json.Unmarshal(info, &imap)
-	return &imap
-}
+func LoadConf(f string) (*Config, error) {
+	v := viper.New()
+	v.SetConfigType("toml")
+	v.AddConfigPath(".")
+	v.SetConfigFile(f)
 
-func CreateMysql(f string) *Database {
-	var mysql Database
-	isExist, _ := tools.IsFileExist(f)
-	if !isExist {
-		return &mysql
-	}
-	info, err := ioutil.ReadFile(f)
+	err := v.ReadInConfig()
 	if err != nil {
-		return &mysql
+		return nil, err
 	}
 
-	err = json.Unmarshal(info, &mysql)
-	return &mysql
-}
-
-func CreateQiniu(f string) *Qiniu {
-	var q Qiniu
-	isExist, _ := tools.IsFileExist(f)
-	if !isExist {
-		fmt.Println(f)
-		return &q
-	}
-	info, err := ioutil.ReadFile(f)
-	if err != nil {
-		fmt.Println("bb")
-		return &q
-	}
-
-	_ = json.Unmarshal(info, &q)
-	return &q
-}
-
-func GetMysql() map[string]string {
-	var mysql map[string]string
-	isExist, _ := tools.IsFileExist(MysqlConf)
-	if !isExist {
-		return mysql
-	}
-	info, err := ioutil.ReadFile(MysqlConf)
+	conf := &Config{}
+	err = v.Unmarshal(conf)
 	if err != nil {
-		return mysql
+		return nil, err
 	}
 
-	err = json.Unmarshal(info, &mysql)
-	return mysql
-}
-
-func GetUserInfo(uid string) map[string]string {
-	var userInfo map[string]string
-	userFile := Dir + "sess_" + uid + ".json"
-	isExist, _ := tools.IsFileExist(userFile)
-	if !isExist {
-		return userInfo
-	}
-	info, err := ioutil.ReadFile(userFile)
-	if err != nil {
-		return userInfo
-	}
+	C = conf
 
-	err = json.Unmarshal(info, &userInfo)
-	return userInfo
+	return conf, nil
 }
 
-func SetUserInfo(uid string, info map[string]string) {
-	userFile := Dir + "sess_" + uid + ".json"
-	isExist, _ := tools.IsFileExist(Dir)
-	if !isExist {
-		os.Mkdir(Dir, os.ModePerm)
-	}
-	file, _ := os.OpenFile(userFile, os.O_RDWR|os.O_CREATE, os.ModePerm)
-	str := "{\r\n"
-	for k, v := range info {
-		str += fmt.Sprintf(`"%s":"%s",`, k, v)
-	}
-	str += fmt.Sprintf(`"session_id":"%s"%s}`, uid, "\r\n")
-	file.WriteString(str)
-}
+//func GetUserInfo(uid string) map[string]string {
+//	var userInfo map[string]string
+//	userFile := DIR + "sess_" + uid + ".json"
+//	isExist, _ := tools.IsFileExist(userFile)
+//	if !isExist {
+//		return userInfo
+//	}
+//	info, err := ioutil.ReadFile(userFile)
+//	if err != nil {
+//		return userInfo
+//	}
+//
+//	err = json.Unmarshal(info, &userInfo)
+//	return userInfo
+//}
+
+//func SetUserInfo(uid string, info map[string]string) {
+//	userFile := DIR + "sess_" + uid + ".json"
+//	isExist, _ := tools.IsFileExist(DIR)
+//	if !isExist {
+//		os.Mkdir(DIR, os.ModePerm)
+//	}
+//	file, _ := os.OpenFile(userFile, os.O_RDWR|os.O_CREATE, os.ModePerm)
+//	str := "{\r\n"
+//	for k, v := range info {
+//		str += fmt.Sprintf(`"%s":"%s",`, k, v)
+//	}
+//	str += fmt.Sprintf(`"session_id":"%s"%s}`, uid, "\r\n")
+//	file.WriteString(str)
+//}

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 24 - 25
config/flytalk.sql


+ 12 - 7
config/flytalk.toml

@@ -1,15 +1,19 @@
 [basic]
-upload=""
+addr=":8083"
+daemon=false
+upload="upload"
 noticeServer=""
-lang=""
+langDir="lang"
+dataDir="config"
+resDir="static"
 
 [database]
 driver="mysql"
-host=""
+host="127.0.0.1"
 port=3306
 db="flytalk"
-username=""
-password=""
+username="flytalk"
+password="flytalk"
 
 [imap]
 server=""
@@ -24,8 +28,9 @@ zone="huanan"
 domain="http://twongpicd.shotshock.shop/"
 
 [twong]
-server="192.168.3.187"
-port="58888"
+driver="mysql"
+host="127.0.0.1"
+port="3306"
 db="twong"
 username="twong"
 password="twong"

+ 0 - 54
config/lang.go

@@ -1,54 +0,0 @@
-package config
-
-var (
-	defaultLang = NewLang("lang")
-)
-
-func SetDefaultLang(l *Lang) {
-	defaultLang = l
-}
-
-type Lang struct {
-	baseDir   string            // lang file base dir
-	lang      string            // current language
-	words     map[string]string // single lang words
-	supported map[string]int    // supported langs
-}
-
-func NewLang(dir string) *Lang {
-	return &Lang{
-		baseDir:   dir,
-		lang:      "en",
-		words:     make(map[string]string),
-		supported: map[string]int{"en": 1},
-	}
-}
-
-func (self *Lang) Register(lang string) {
-	self.supported[lang] = 1
-}
-
-func (self *Lang) ChLang(newLang string) {
-	// check
-	self.lang = newLang
-	// reload
-}
-
-func (self *Lang) Get(k string) string {
-	if r, ok := self.words[k]; ok {
-		return r
-	}
-	//log
-	return ""
-}
-
-func (self *Lang) reload() {
-}
-
-func (self *Lang) check() {
-
-}
-
-func (self *Lang) scan() {
-
-}

+ 0 - 47
config/language.go

@@ -1,47 +0,0 @@
-package config
-
-type Language struct {
-	WebCopyRight                                                             string
-	MainIntro                                                                string
-	Send                                                                     string
-	Notice, Maintech, NowAsk, LaterAsk                                       string
-	IndexSubIntro, IndexVisitors, IndexAgent, IndexDocument, IndexOnlineChat string
-}
-
-func CreateLanguage(lang string) *Language {
-	var language *Language
-
-	if lang == "en" {
-		language = &Language{
-			WebCopyRight:    "twong",
-			MainIntro:       "twong online customer chat system",
-			IndexSubIntro:   "for effectiveness, for customers",
-			IndexDocument:   "API Documents",
-			IndexVisitors:   "Visitors Here",
-			IndexAgent:      "Agents Here",
-			IndexOnlineChat: "Let’s chat. - We're online",
-			Send:            "Send",
-			Notice:          "Hello and welcome to twong - how can we help?",
-			Maintech:        "Main technical architecture",
-			NowAsk:          "Start Chat",
-			LaterAsk:        "Chat Later",
-		}
-	}
-	if lang == "cn" {
-		language = &Language{
-			WebCopyRight:    "美天旺版权所有",
-			MainIntro:       "美天旺在线客服系统",
-			IndexSubIntro:   "专注效率,服务客服",
-			IndexVisitors:   "访客入口",
-			IndexAgent:      "客服入口",
-			IndexDocument:   "文档",
-			IndexOnlineChat: "点击交流",
-			Send:            "发送",
-			Notice:          "欢迎您使用客服系统!点击测试网页版客服",
-			Maintech:        "主要技术架构",
-			NowAsk:          "现在咨询",
-			LaterAsk:        "稍后再说",
-		}
-	}
-	return language
-}

+ 5 - 6
controller/auth.go

@@ -1,7 +1,6 @@
 package controller
 
 import (
-	"git.wanbits.cc/sin/flytalk/config"
 	"git.wanbits.cc/sin/flytalk/models"
 	"git.wanbits.cc/sin/flytalk/tools"
 )
@@ -18,8 +17,8 @@ func CheckKefuPass(username string, password string) (models.User, models.User_r
 }
 
 //验证是否已经登录
-func AuthCheck(uid string) map[string]string {
-	info := config.GetUserInfo(uid)
-
-	return info
-}
+//func AuthCheck(uid string) map[string]string {
+//	info := config.GetUserInfo(uid)
+//
+//	return info
+//}

+ 1 - 1
controller/chat.go

@@ -49,7 +49,7 @@ type ClientMessage struct {
 }
 
 //定时检测客户端是否在线
-func init() {
+func Init() {
 	upgrader = websocket.Upgrader{
 		ReadBufferSize:  1024,
 		WriteBufferSize: 1024,

+ 5 - 8
controller/folder.go

@@ -24,20 +24,19 @@ func GetFolders(c *gin.Context) {
 		currentPage = 1
 	}
 
-	mailServer := config.CreateMailServer()
-
+	imap := config.C.Imap
 	var wg sync.WaitGroup
 	wg.Add(2)
 	result := make(map[string]interface{})
 	go func() {
 		defer wg.Done()
-		folders := tools.GetFolders(mailServer.Server, mailServer.Email, mailServer.Password, fid)
+		folders := tools.GetFolders(imap.Server, imap.Email, imap.Password, fid)
 		result["folders"] = folders
 		result["total"] = folders[fid]
 	}()
 	go func() {
 		defer wg.Done()
-		mails := tools.GetFolderMail(mailServer.Server, mailServer.Email, mailServer.Password, fid, currentPage, PageSize)
+		mails := tools.GetFolderMail(imap.Server, imap.Email, imap.Password, fid, currentPage, PageSize)
 		result["mails"] = mails
 	}()
 	wg.Wait()
@@ -55,11 +54,9 @@ func GetFolderList(c *gin.Context) {
 	if fid == "" {
 		fid = "INBOX"
 	}
-
-	mailServer := config.CreateMailServer()
-
+	imap := config.C.Imap
 	result := make(map[string]interface{})
-	folders := tools.GetFolders(mailServer.Server, mailServer.Email, mailServer.Password, fid)
+	folders := tools.GetFolders(imap.Server, imap.Email, imap.Password, fid)
 	result["folders"] = folders
 	result["total"] = folders[fid]
 	result["fid"] = fid

+ 3 - 1
controller/ip.go

@@ -23,6 +23,7 @@ func PostIpblack(c *gin.Context) {
 		"msg":  "添加黑名单成功!",
 	})
 }
+
 func DelIpblack(c *gin.Context) {
 	ip := c.Query("ip")
 	if ip == "" {
@@ -38,13 +39,14 @@ func DelIpblack(c *gin.Context) {
 		"msg":  "删除黑名单成功!",
 	})
 }
+
 func GetIpblacks(c *gin.Context) {
 	page, _ := strconv.Atoi(c.Query("page"))
 	if page == 0 {
 		page = 1
 	}
 	count := models.CountIps(nil, nil)
-	list := models.FindIps(nil, nil, uint(page), config.PAGE_SIZE_VISITOR)
+	list := models.FindIps(nil, nil, page, config.PAGE_SIZE_VISITOR)
 	c.JSON(200, gin.H{
 		"code": 200,
 		"msg":  "ok",

+ 6 - 0
controller/kefu.go

@@ -20,6 +20,7 @@ func GetKefuInfo(c *gin.Context) {
 		"result": info,
 	})
 }
+
 func GetKefuInfoAll(c *gin.Context) {
 	id, _ := c.Get("kefu_id")
 	userinfo := models.FindUserRole("user.avator,user.name,user.id, role.name role_name", id)
@@ -29,6 +30,7 @@ func GetKefuInfoAll(c *gin.Context) {
 		"result": userinfo,
 	})
 }
+
 func GetKefuInfoSetting(c *gin.Context) {
 	kefuId := c.Query("kefu_id")
 	user := models.FindUserById(kefuId)
@@ -38,6 +40,7 @@ func GetKefuInfoSetting(c *gin.Context) {
 		"result": user,
 	})
 }
+
 func PostKefuInfo(c *gin.Context) {
 	id := c.PostForm("id")
 	name := c.PostForm("name")
@@ -98,6 +101,7 @@ func PostKefuInfo(c *gin.Context) {
 		"result": "",
 	})
 }
+
 func GetKefuList(c *gin.Context) {
 	users := models.FindUsers()
 	c.JSON(200, gin.H{
@@ -106,6 +110,7 @@ func GetKefuList(c *gin.Context) {
 		"result": users,
 	})
 }
+
 func GetKefuListEnabled(c *gin.Context) {
 	users := models.FindUsers()
 	enabledUsers := []models.User{}
@@ -120,6 +125,7 @@ func GetKefuListEnabled(c *gin.Context) {
 		"result": enabledUsers,
 	})
 }
+
 func DeleteKefuInfo(c *gin.Context) {
 	kefuId := c.Query("id")
 	models.DeleteUserById(kefuId)

+ 12 - 13
controller/main.go

@@ -2,22 +2,20 @@ package controller
 
 import (
 	"git.wanbits.cc/sin/flytalk/models"
-	"git.wanbits.cc/sin/flytalk/tmpl"
-	"git.wanbits.cc/sin/flytalk/tools"
 	"github.com/gin-gonic/gin"
-	"net/http"
 )
 
-func ActionMain(w http.ResponseWriter, r *http.Request) {
-	sessionId := tools.GetCookie(r, "session_id")
-	info := AuthCheck(sessionId)
-	if len(info) == 0 {
-		http.Redirect(w, r, "/login", 302)
-		return
-	}
-	render := tmpl.NewRender(w)
-	render.Display("main", render)
-}
+//func ActionMain(w http.ResponseWriter, r *http.Request) {
+//	sessionId := tools.GetCookie(r, "session_id")
+//	info := AuthCheck(sessionId)
+//	if len(info) == 0 {
+//		http.Redirect(w, r, "/login", 302)
+//		return
+//	}
+//	render := tmpl.NewRender(w)
+//	render.Display("main", render)
+//}
+
 func MainCheckAuth(c *gin.Context) {
 	id, _ := c.Get("kefu_id")
 	userinfo := models.FindUserRole("user.avator,user.name,user.id, role.name role_name", id)
@@ -31,6 +29,7 @@ func MainCheckAuth(c *gin.Context) {
 		},
 	})
 }
+
 func GetStatistics(c *gin.Context) {
 	visitors := models.CountVisitors()
 	message := models.CountMessage()

+ 10 - 6
controller/message.go

@@ -117,6 +117,7 @@ func SendMessage(c *gin.Context) {
 		"result": content,
 	})
 }
+
 func SendMessageV2(c *gin.Context) {
 	fromId := c.PostForm("from_id")
 	toId := c.PostForm("to_id")
@@ -204,6 +205,7 @@ func SendMessageV2(c *gin.Context) {
 		"msg":  "ok",
 	})
 }
+
 func SendVisitorNotice(c *gin.Context) {
 	notice := c.Query("msg")
 	if notice == "" {
@@ -226,6 +228,7 @@ func SendVisitorNotice(c *gin.Context) {
 		"msg":  "ok",
 	})
 }
+
 func SendCloseMessage(c *gin.Context) {
 	visitorId := c.Query("visitor_id")
 	if visitorId == "" {
@@ -250,8 +253,9 @@ func SendCloseMessage(c *gin.Context) {
 		"msg":  "ok",
 	})
 }
+
 func UploadImg(c *gin.Context) {
-	cfg := config.CreateConfig()
+	cfg := config.C
 	f, err := c.FormFile("imgfile")
 	if err != nil {
 		c.JSON(200, gin.H{
@@ -270,7 +274,7 @@ func UploadImg(c *gin.Context) {
 			return
 		}
 		fileName := tools.Md5(fmt.Sprintf("%s%s", f.Filename, time.Now().String()))
-		fildDir := fmt.Sprintf("%s%d%s/", cfg.Upload, time.Now().Year(), time.Now().Month().String())
+		fildDir := fmt.Sprintf("%s%d%s/", cfg.Basic.Upload, time.Now().Year(), time.Now().Month().String())
 		isExist, _ := tools.IsFileExist(fildDir)
 		if !isExist {
 			os.Mkdir(fildDir, os.ModePerm)
@@ -278,9 +282,9 @@ func UploadImg(c *gin.Context) {
 		filepath := fmt.Sprintf("%s%s%s", fildDir, fileName, fileExt)
 		c.SaveUploadedFile(f, filepath)
 		// 上传到七牛
-		qn := store.NewQn(config.QiniuConfig.Access,
-			config.QiniuConfig.Secret, config.QiniuConfig.Bucket,
-			config.QiniuConfig.Zone)
+		qn := store.NewQn(cfg.Qiniu.Access,
+			cfg.Qiniu.Secret, cfg.Qiniu.Bucket,
+			cfg.Qiniu.Zone)
 		key, err := qn.Upload(filepath)
 		if err != nil {
 			c.JSON(200, gin.H{
@@ -290,7 +294,7 @@ func UploadImg(c *gin.Context) {
 			return
 		}
 		os.Remove(filepath)
-		dest := config.QiniuConfig.Domain + key
+		dest := cfg.Qiniu.Domain + key
 		c.JSON(200, gin.H{
 			"code": 200,
 			"msg":  "上传成功!",

+ 45 - 50
controller/mysql.go

@@ -1,60 +1,55 @@
 package controller
 
 import (
-	"fmt"
-	"git.wanbits.cc/sin/flytalk/config"
-	"git.wanbits.cc/sin/flytalk/database"
-	"git.wanbits.cc/sin/flytalk/tools"
 	"github.com/gin-gonic/gin"
-	"os"
 )
 
 func MysqlGetConf(c *gin.Context) {
-	mysqlInfo := config.GetMysql()
-	c.JSON(200, gin.H{
-		"code":   200,
-		"msg":    "验证成功",
-		"result": mysqlInfo,
-	})
+	//mysqlInfo := config.GetMysql()
+	//c.JSON(200, gin.H{
+	//	"code":   200,
+	//	"msg":    "验证成功",
+	//	"result": mysqlInfo,
+	//})
 }
-func MysqlSetConf(c *gin.Context) {
-
-	mysqlServer := c.PostForm("server")
-	mysqlPort := c.PostForm("port")
-	mysqlDb := c.PostForm("database")
-	mysqlUsername := c.PostForm("username")
-	mysqlPassword := c.PostForm("password")
-	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8", mysqlUsername, mysqlPassword, mysqlServer, mysqlPort, mysqlDb)
-	mysql := database.NewMysql()
-	mysql.Dsn = dsn
-	err := mysql.Ping()
-	if err != nil {
-		c.JSON(200, gin.H{
-			"code": 403,
-			"msg":  "数据库连接失败:" + err.Error(),
-		})
-		return
-	}
-	isExist, _ := tools.IsFileExist(config.Dir)
-	if !isExist {
-		os.Mkdir(config.Dir, os.ModePerm)
-	}
-	fileConfig := config.MysqlConf
-	file, _ := os.OpenFile(fileConfig, os.O_RDWR|os.O_CREATE, os.ModePerm)
 
-	format := `{
-	"Server":"%s",
-	"Port":"%s",
-	"Database":"%s",
-	"Username":"%s",
-	"Password":"%s"
-}
-`
-	data := fmt.Sprintf(format, mysqlServer, mysqlPort, mysqlDb, mysqlUsername, mysqlPassword)
-	file.WriteString(data)
-
-	c.JSON(200, gin.H{
-		"code": 200,
-		"msg":  "操作成功",
-	})
+func MysqlSetConf(c *gin.Context) {
+	//	mysqlServer := c.PostForm("server")
+	//	mysqlPort := c.PostForm("port")
+	//	mysqlDb := c.PostForm("database")
+	//	mysqlUsername := c.PostForm("username")
+	//	mysqlPassword := c.PostForm("password")
+	//	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8", mysqlUsername, mysqlPassword, mysqlServer, mysqlPort, mysqlDb)
+	//	mysql := database.NewMysql()
+	//	mysql.Dsn = dsn
+	//	err := mysql.Ping()
+	//	if err != nil {
+	//		c.JSON(200, gin.H{
+	//			"code": 403,
+	//			"msg":  "数据库连接失败:" + err.Error(),
+	//		})
+	//		return
+	//	}
+	//	isExist, _ := tools.IsFileExist(config.DIR)
+	//	if !isExist {
+	//		os.Mkdir(config.DIR, os.ModePerm)
+	//	}
+	//	fileConfig := config.MysqlConf
+	//	file, _ := os.OpenFile(fileConfig, os.O_RDWR|os.O_CREATE, os.ModePerm)
+	//
+	//	format := `{
+	//	"Server":"%s",
+	//	"Port":"%s",
+	//	"Database":"%s",
+	//	"Username":"%s",
+	//	"Password":"%s"
+	//}
+	//`
+	//	data := fmt.Sprintf(format, mysqlServer, mysqlPort, mysqlDb, mysqlUsername, mysqlPassword)
+	//	file.WriteString(data)
+	//
+	//	c.JSON(200, gin.H{
+	//		"code": 200,
+	//		"msg":  "操作成功",
+	//	})
 }

+ 4 - 0
controller/notice.go

@@ -33,6 +33,7 @@ func GetNotice(c *gin.Context) {
 		"result": result,
 	})
 }
+
 func GetNotices(c *gin.Context) {
 	kefuId, _ := c.Get("kefu_name")
 	welcomes := models.FindWelcomesByUserId(kefuId)
@@ -42,6 +43,7 @@ func GetNotices(c *gin.Context) {
 		"result": welcomes,
 	})
 }
+
 func PostNotice(c *gin.Context) {
 	kefuId, _ := c.Get("kefu_name")
 	content := c.PostForm("content")
@@ -52,6 +54,7 @@ func PostNotice(c *gin.Context) {
 		"result": "",
 	})
 }
+
 func PostNoticeSave(c *gin.Context) {
 	kefuId, _ := c.Get("kefu_name")
 	content := c.PostForm("content")
@@ -63,6 +66,7 @@ func PostNoticeSave(c *gin.Context) {
 		"result": "",
 	})
 }
+
 func DelNotice(c *gin.Context) {
 	kefuId, _ := c.Get("kefu_name")
 	id := c.Query("id")

+ 1 - 0
controller/response.go

@@ -5,6 +5,7 @@ type Response struct {
 	Msg    string      `json:"msg"`
 	result interface{} `json:"result"`
 }
+
 type ChatMessage struct {
 	Time    string `json:"time"`
 	Content string `json:"content"`

+ 1 - 0
controller/role.go

@@ -13,6 +13,7 @@ func GetRoleList(c *gin.Context) {
 		"result": roles,
 	})
 }
+
 func PostRole(c *gin.Context) {
 	roleId := c.PostForm("id")
 	method := c.PostForm("method")

+ 1 - 0
controller/setting.go

@@ -13,6 +13,7 @@ func GetConfigs(c *gin.Context) {
 		"result": configs,
 	})
 }
+
 func PostConfig(c *gin.Context) {
 	key := c.PostForm("key")
 	value := c.PostForm("value")

+ 2 - 0
controller/tcp.go

@@ -27,6 +27,7 @@ func NewTcpServer(tcpBaseServer string) {
 		//clientTcpList=append(clientTcpList,conn)
 	}
 }
+
 func PushServerTcp(str []byte) {
 	for ip, conn := range clientTcpList {
 		line := append(str, []byte("\r\n")...)
@@ -39,6 +40,7 @@ func PushServerTcp(str []byte) {
 		}
 	}
 }
+
 func DeleteOnlineTcp(c *gin.Context) {
 	ip := c.Query("ip")
 	for ipkey, conn := range clientTcpList {

+ 5 - 2
controller/visitor.go

@@ -79,7 +79,7 @@ func PostVisitorLogin(c *gin.Context) {
 	if ipcity != nil {
 		city = ipcity.CountryName + ipcity.RegionName + ipcity.CityName
 	} else {
-		city = "未识别地区"
+		city = "未地区"
 	}
 	client_ip := c.ClientIP() // c.PostForm("client_ip")
 
@@ -90,7 +90,9 @@ func PostVisitorLogin(c *gin.Context) {
 			"code": 400,
 			"msg":  err.Error(),
 		})
+		return
 	}
+
 	//log.Println(name,avator,c.ClientIP(),toId,id,refer,city,client_ip)
 	if v.Name == "" || v.Avator == "" || toId == "" || v.VisitorId == "" || city == "" || client_ip == "" {
 		c.JSON(200, gin.H{
@@ -124,6 +126,7 @@ func PostVisitorLogin(c *gin.Context) {
 		"result": visitor,
 	})
 }
+
 func GetVisitor(c *gin.Context) {
 	visitorId := c.Query("visitorId")
 	vistor := models.FindVisitorByVistorId(visitorId)
@@ -145,7 +148,7 @@ func GetVisitor(c *gin.Context) {
 func GetVisitors(c *gin.Context) {
 	page, _ := strconv.Atoi(c.Query("page"))
 	kefuId, _ := c.Get("kefu_name")
-	vistors := models.FindVisitorsByKefuId(uint(page), config.PAGE_SIZE_VISITOR, kefuId.(string))
+	vistors := models.FindVisitorsByKefuId(page, config.PAGE_SIZE_VISITOR, kefuId.(string))
 	count := models.CountVisitorsByKefuId(kefuId.(string))
 	c.JSON(200, gin.H{
 		"code": 200,

+ 0 - 27
database/mysql.go

@@ -1,27 +0,0 @@
-package database
-
-import (
-	"database/sql"
-	"fmt"
-	"git.wanbits.cc/sin/flytalk/config"
-	_ "github.com/go-sql-driver/mysql"
-)
-
-type Mysql struct {
-	SqlDB *sql.DB
-	Dsn   string
-}
-
-func NewMysql() *Mysql {
-	mysql := config.CreateMysql(config.MysqlConf)
-	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local", mysql.Username, mysql.Password, mysql.Server, mysql.Port, mysql.Db)
-	return &Mysql{
-		Dsn: dsn,
-	}
-}
-
-func (db *Mysql) Ping() error {
-	sqlDb, _ := sql.Open("mysql", db.Dsn)
-	db.SqlDB = sqlDb
-	return db.SqlDB.Ping()
-}

+ 1 - 2
docs/docs.go

@@ -10,7 +10,6 @@ import (
 	"strings"
 
 	"github.com/alecthomas/template"
-	"github.com/swaggo/swag"
 )
 
 var doc = `{
@@ -266,5 +265,5 @@ func (s *s) ReadDoc() string {
 }
 
 func init() {
-	swag.Register(swag.Name, &s{})
+	//swag.Register(swag.Name, &s{})
 }

+ 36 - 0
flytalk.toml.sample

@@ -0,0 +1,36 @@
+[basic]
+addr=":8083"
+daemon=false
+upload="upload"
+noticeServer=""
+langDir="lang"
+dataDir="config"
+resDir="static"
+
+[database]
+driver="mysql"
+host="127.0.0.1"
+port=3306
+db="flytalk"
+username="flytalk"
+password="flytalk"
+
+[imap]
+server=""
+email=""
+password=""
+
+[qiniu]
+access="SneSBtnWLdStBhCx0O_QogNkXoRlKNOiv1--XMBB"
+secret="GXMg-ENcp2UKYQWdeaf43tk_06NnMoA4OVFxdkYw"
+bucket="twongd"
+zone="huanan"
+domain="http://twongpicd.shotshock.shop/"
+
+[twong]
+driver="mysql"
+host="127.0.0.1"
+port="3306"
+db="twong"
+username="twong"
+password="twong"

+ 116 - 0
g/lang.go

@@ -0,0 +1,116 @@
+package g
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+)
+
+const (
+	DEF_LANG     = "en"
+	DEF_LANG_EXT = ".json"
+)
+
+var (
+	defaultLang *Lang
+
+	ErrLangNoExists = errors.New("language path not exists")
+)
+
+func SetDefaultLang(l *Lang) {
+	defaultLang = l
+}
+
+type Lang struct {
+	baseDir string                       // lang file base dir
+	words   map[string]map[string]string // single lang words
+}
+
+func NewLang(dir string) *Lang {
+	l := &Lang{
+		baseDir: dir,
+		words:   make(map[string]map[string]string),
+	}
+
+	l.Reload()
+
+	return l
+}
+
+func (self *Lang) Get(k string, useLang ...string) string {
+	l := DEF_LANG
+	if len(useLang) > 0 {
+		l = useLang[0]
+	}
+	kv, ok := self.words[l]
+	if !ok {
+		fmt.Println("no language:", l)
+		return ""
+	}
+	if word, ok := kv[k]; ok {
+		return word
+	}
+	fmt.Printf("no key<%v> for lang<%v>\n", k, l)
+	return ""
+}
+
+func (self *Lang) Reload() {
+	self.scan(filepath.Join(self.baseDir))
+}
+
+func (self *Lang) scan(jsDir string) {
+	self.words = make(map[string]map[string]string)
+
+	err := filepath.Walk(jsDir, func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+		if info.IsDir() {
+			self.words[info.Name()] = make(map[string]string)
+		}
+		if filepath.Ext(path) != DEF_LANG_EXT {
+			return nil
+		}
+		langDir := filepath.Base(filepath.Dir(path))
+		langKv, ok := self.words[langDir]
+		if !ok {
+			return ErrLangNoExists
+		}
+
+		fh, err := os.Open(path)
+		if err != nil {
+			return err
+		}
+		defer fh.Close()
+
+		data, err := ioutil.ReadAll(fh)
+		if err != nil {
+			return err
+		}
+		partial := map[string]string{}
+		err = json.Unmarshal(data, &partial)
+		if err != nil {
+			return err
+		}
+
+		for k, v := range partial {
+			langKv[k] = v
+		}
+
+		return nil
+	})
+
+	if err != nil {
+		fmt.Println("scan() error:", err)
+	}
+}
+
+func T(k string, useLang ...string) string {
+	if defaultLang != nil {
+		return defaultLang.Get(k, useLang...)
+	}
+	return ""
+}

+ 13 - 0
g/lang_test.go

@@ -0,0 +1,13 @@
+package g
+
+import (
+	"testing"
+)
+
+func TestLang_Get(t *testing.T) {
+	l := NewLang("../lang")
+	SetDefaultLang(l)
+
+	t.Log(T("MainIntro", "en"))
+	t.Log(T("MainIntro", "cn"))
+}

+ 15 - 4
go.mod

@@ -12,23 +12,34 @@ require (
 	github.com/emersion/go-message v0.11.2
 	github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
 	github.com/emersion/go-smtp v0.13.0
+	github.com/fsnotify/fsnotify v1.4.9 // indirect
 	github.com/gin-contrib/cors v1.3.1
 	github.com/gin-gonic/gin v1.6.3
-	github.com/go-sql-driver/mysql v1.5.0
+	github.com/go-sql-driver/mysql v1.5.0 // indirect
 	github.com/gobuffalo/packr/v2 v2.5.1
 	github.com/gorilla/websocket v1.4.2
 	github.com/ipipdotnet/ipdb-go v1.3.0
-	github.com/jinzhu/gorm v1.9.14
+	github.com/jinzhu/gorm v1.9.14 // indirect
+	github.com/magiconair/properties v1.8.5 // indirect
+	github.com/mitchellh/mapstructure v1.4.1 // indirect
+	github.com/pelletier/go-toml v1.9.0 // indirect
 	github.com/qiniu/api.v7/v7 v7.6.0
 	github.com/satori/go.uuid v1.2.0
 	github.com/sirupsen/logrus v1.4.2
+	github.com/spf13/afero v1.6.0 // indirect
+	github.com/spf13/cast v1.3.1 // indirect
 	github.com/spf13/cobra v0.0.5
+	github.com/spf13/jwalterweatherman v1.1.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
-	github.com/swaggo/gin-swagger v1.2.0
-	github.com/swaggo/swag v1.5.1
+	github.com/spf13/viper v1.7.1
+	github.com/swaggo/gin-swagger v1.2.0 // indirect
+	github.com/swaggo/swag v1.5.1 // indirect
 	golang.org/x/net v0.0.0-20201024042810-be3efd7ff127
 	golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 // indirect
 	golang.org/x/text v0.3.6
 	golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc // indirect
+	gopkg.in/ini.v1 v1.62.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
+	gorm.io/driver/mysql v1.0.5
+	gorm.io/gorm v1.21.7
 )

+ 261 - 0
go.sum

@@ -1,22 +1,53 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
 github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4=
 github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 h1:OYA+5W64v3OgClL+IrOD63t4i/RW7RqrAVl9LTZ9UqQ=
 github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394/go.mod h1:Q8n74mJTIgjX4RBBcHnJ05h//6/k6foqmgE45jTQtxg=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
 github.com/casbin/casbin/v2 v2.7.2 h1:PM/u9RGCZmlN4/cpS3FbVqCXG+H5806faG7QGwEy+lE=
 github.com/casbin/casbin/v2 v2.7.2/go.mod h1:XXtYGrs/0zlOsJMeRteEdVi/FsB0ph7KgNfjoCoJUD8=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -25,6 +56,7 @@ github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6RO
 github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 github.com/emersion/go-imap v1.0.4 h1:uiCAIHM6Z5Jwkma1zdNDWWXxSCqb+/xHBkHflD7XBro=
 github.com/emersion/go-imap v1.0.4/go.mod h1:yKASt+C3ZiDAiCSssxg9caIckWF/JG7ZQTO7GAmvicU=
 github.com/emersion/go-message v0.11.1/go.mod h1:C4jnca5HOTo4bGN9YdqNQM9sITuT3Y0K6bSUw9RklvY=
@@ -40,8 +72,11 @@ github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe h1:40SWqY0
 github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
 github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
 github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gin-contrib/cors v1.3.1 h1:doAsuITavI4IOcd0Y19U4B+O0dNWihRyX//nn4sEmgA=
 github.com/gin-contrib/cors v1.3.1/go.mod h1:jjEJ4268OPZUcU7k9Pm653S7lXUGcqMADzFA61xsmDk=
@@ -56,6 +91,10 @@ github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/
 github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
 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-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0=
 github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
 github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
@@ -77,6 +116,7 @@ github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1
 github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
 github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
 github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU=
 github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
 github.com/gobuffalo/logger v1.0.0 h1:xw9Ko9EcC5iAFprrjJ6oZco9UpzS5MQ4jAwghsLHdy4=
@@ -85,18 +125,59 @@ github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4
 github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
 github.com/gobuffalo/packr/v2 v2.5.1 h1:TFOeY2VoGamPjQLiNDT3mn//ytzk236VMO2j7iHxJR4=
 github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
 github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
 github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/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/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
+github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
+github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/ipipdotnet/ipdb-go v1.3.0 h1:FfkSkAI1do3bZ7F35ueGuF7Phur64jmikQ1C4IPl/gc=
@@ -107,18 +188,30 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
 github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
 github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
 github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
+github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
 github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/karrick/godirwalk v1.10.12 h1:BqUm+LuJcXjGv1d2mj3gBiQyrQ57a0rYoAmhvJQ7RDU=
 github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -130,10 +223,16 @@ github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgx
 github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
 github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
+github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
+github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
 github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic=
 github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/martinlindhe/base36 v1.0.0 h1:eYsumTah144C0A8P1T/AVSUk5ZoLnhfYFM3OGQxB52A=
 github.com/martinlindhe/base36 v1.0.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
@@ -142,9 +241,19 @@ github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHX
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
 github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
+github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
+github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
+github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 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=
@@ -153,35 +262,70 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLD
 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/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pelletier/go-toml v1.9.0 h1:NOd0BRdOKpPf0SxkL3HxSQOG7rNh+4kl6PHcBPFs7Q0=
+github.com/pelletier/go-toml v1.9.0/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 github.com/qiniu/api.v7/v7 v7.6.0 h1:396UGG+AWLh80pIhpPNCgEzb04t4S8CGKxqvLkiQeZI=
 github.com/qiniu/api.v7/v7 v7.6.0/go.mod h1:zg3DaqU8mVnoQSQmtC/Mr2wXTJIE7fvcup+7sMt58Q0=
+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
 github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
+github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
+github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
 github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
+github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
 github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
+github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
 github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
+github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
+github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@@ -189,10 +333,13 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
+github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/swaggo/gin-swagger v1.2.0 h1:YskZXEiv51fjOMTsXrOetAjrMDfFaXD79PEoQBOe2W0=
 github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI=
 github.com/swaggo/swag v1.5.1 h1:2Agm8I4K5qb00620mHq0VJ05/KT4FtmALPIcQR9lEZM=
 github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
 github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0=
 github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
@@ -203,40 +350,97 @@ github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2t
 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/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A=
 golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd h1:GGJVjV8waZKRHrgwvtH66z9ZGVurTD1MT0n1Bb+q4aM=
 golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+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-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/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/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20201024042810-be3efd7ff127 h1:pZPp9+iYUqwYKLjht0SDBbRCRK/9gAXDy7pz5fRDpjo=
 golang.org/x/net v0.0.0-20201024042810-be3efd7ff127/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -245,20 +449,60 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A=
 golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c h1:KfpJVdWhuRqNk4XVXzjXf2KAV4TBEP77SYdFGjeGuIE=
 golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8=
 golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -266,9 +510,26 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
 gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
 gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
+gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
+gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
+gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/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=
+gorm.io/driver/mysql v1.0.5 h1:WAAmvLK2rG0tCOqrf5XcLi2QUwugd4rcVJ/W3aoon9o=
+gorm.io/driver/mysql v1.0.5/go.mod h1:N1OIhHAIhx5SunkMGqWbGFVeh4yTNWKmMo1GOAsohLI=
+gorm.io/gorm v1.21.3/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
+gorm.io/gorm v1.21.7 h1:MuY8oejVL5l3iT7PfE3z5I4J+KW/Nu2w/uTpLe3vV1Q=
+gorm.io/gorm v1.21.7/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

+ 17 - 0
lang/cn/flytalk.json

@@ -0,0 +1,17 @@
+{
+  "title": "flytalk 在线客服系统",
+  "keywords": "flytalk, 客服, 在线客服",
+  "description": "面向沟通的系统",
+  "copyright": "©2020 周文王工作室",
+  "project_title":"极简在线客服系统",
+  "project_desc":"flytalk,轻量级易于搭建的开源客服系统",
+  "visitors_entry":"访客入口",
+  "agents_entry":"客服入口",
+  "doc_entry":"接口文档",
+  "chat_entry":"在线咨询",
+  "main_tech": "只要开源技术",
+  "send": "发送",
+  "notice": "欢迎使用flytalk客服",
+  "start_chat": "咨询",
+  "chat_later": "稍后"
+}

+ 0 - 0
lang/en/flytalk.js


+ 17 - 0
lang/en/flytalk.json

@@ -0,0 +1,17 @@
+{
+    "title": "flytalk online customer service",
+    "keywords": "flytalk, customer service",
+    "description": "For effectiveneww,for customers",
+    "copyright": "©2020 wen studio",
+    "project_title": "Simple online customer chat system",
+    "project_desc": "flytalk, lightweight and easy to deploy, open source, use with no pain",
+    "doc_entry":"API Documents",
+    "visitors_entry":"Visitors Here",
+    "agents_entry":"Agents Here",
+    "chat_entry":"Let’s chat. - We're online",
+    "main_tech": "Main technical architecture",
+    "send": "send",
+    "notice": "Hello and welcome to twong - how can we help?",
+    "start_chat": "Start chat",
+    "chat_later": "Chat later"
+}

+ 0 - 0
lang/zh/flytalk.js


+ 4 - 2
models/abouts.go

@@ -1,7 +1,7 @@
 package models
 
 type About struct {
-	ID         uint   `gorm:"primary_key" json:"id"`
+	ID         uint   `gorm:"primaryKey" json:"id"`
 	TitleCn    string `json:"title_cn"`
 	TitleEn    string `json:"title_en"`
 	KeywordsCn string `json:"keywords_cn"`
@@ -18,6 +18,7 @@ func FindAboutByPage(page interface{}) About {
 	DB.Where("page = ?", page).First(&a)
 	return a
 }
+
 func FindAboutByPageLanguage(page interface{}, lang string) About {
 	var a About
 	if lang == "" {
@@ -30,6 +31,7 @@ func FindAboutByPageLanguage(page interface{}, lang string) About {
 	}
 	return a
 }
+
 func UpdateAbout(page string, title_cn string, title_en string, keywords_cn string, keywords_en string, desc_cn string, desc_en string, css_js string, html_cn string, html_en string) {
 	c := &About{
 		TitleCn:    title_cn,
@@ -42,6 +44,6 @@ func UpdateAbout(page string, title_cn string, title_en string, keywords_cn stri
 		HtmlCn:     html_cn,
 		HtmlEn:     html_en,
 	}
-	DB.Model(c).Where("page = ?", page).Update(c)
+	DB.Model(c).Where("page = ?", page).Updates(c)
 	InitConfig()
 }

+ 7 - 4
models/configs.go

@@ -3,9 +3,9 @@ package models
 var CustomConfigs []Config
 
 type Config struct {
-	ID        uint   `gorm:"primary_key" json:"id"`
-	ConfName  string `json:"conf_name"`
-	ConfKey   string `json:"conf_key"`
+	ID        uint   `gorm:"primaryKey" json:"id"`
+	ConfName  string `gorm:"varchar(255)" json:"conf_name"`
+	ConfKey   string `gorm:"varchar(255) index:uidx_conf_key,unique" json:"conf_key"`
 	ConfValue string `json:"conf_value"`
 }
 
@@ -13,17 +13,20 @@ func UpdateConfig(key string, value string) {
 	c := &Config{
 		ConfValue: value,
 	}
-	DB.Model(c).Where("conf_key = ?", key).Update(c)
+	DB.Model(c).Where("conf_key = ?", key).Updates(c)
 	InitConfig()
 }
+
 func FindConfigs() []Config {
 	var config []Config
 	DB.Find(&config)
 	return config
 }
+
 func InitConfig() {
 	CustomConfigs = FindConfigs()
 }
+
 func FindConfig(key string) string {
 	for _, config := range CustomConfigs {
 		if key == config.ConfKey {

+ 8 - 6
models/ipblacks.go

@@ -3,9 +3,9 @@ package models
 import "time"
 
 type Ipblack struct {
-	ID       uint      `gorm:"primary_key" json:"id"`
-	IP       string    `json:"ip"`
-	KefuId   string    `json:"kefu_id"`
+	ID       uint      `gorm:"primaryKey autoIncrement" json:"id"`
+	IP       string    `gorm:"size:32" json:"ip"`
+	KefuId   string    `gorm:"size:50" json:"kefu_id"`
 	CreateAt time.Time `json:"create_at"`
 }
 
@@ -21,12 +21,14 @@ func CreateIpblack(ip string, kefuId string) uint {
 func DeleteIpblackByIp(ip string) {
 	DB.Where("ip = ?", ip).Delete(Ipblack{})
 }
+
 func FindIp(ip string) Ipblack {
 	var ipblack Ipblack
 	DB.Where("ip = ?", ip).First(&ipblack)
 	return ipblack
 }
-func FindIps(query interface{}, args []interface{}, page uint, pagesize uint) []Ipblack {
+
+func FindIps(query interface{}, args []interface{}, page int, pagesize int) []Ipblack {
 	offset := (page - 1) * pagesize
 	if offset < 0 {
 		offset = 0
@@ -41,8 +43,8 @@ func FindIps(query interface{}, args []interface{}, page uint, pagesize uint) []
 }
 
 //查询条数
-func CountIps(query interface{}, args []interface{}) uint {
-	var count uint
+func CountIps(query interface{}, args []interface{}) int64 {
+	var count int64
 	if query != nil {
 		DB.Model(&Visitor{}).Where(query, args...).Count(&count)
 	} else {

+ 17 - 17
models/messages.go

@@ -2,11 +2,11 @@ package models
 
 type Message struct {
 	Model
-	KefuId    string `json:"kefu_id"`
-	VisitorId string `json:"visitor_id"`
-	Content   string `json:"content"`
-	MesType   string `json:"mes_type"`
-	Status    string `json:"status"`
+	KefuId    string `gorm:"size:50 not null default:'' index:'idx_kefu_id'" json:"kefu_id"`
+	VisitorId string `gorm:"size:50 not null default:'' index:'idx_visitor_id'" json:"visitor_id"`
+	Content   string `gorm:"size:2048 not null default:''" json:"content"`
+	MesType   string `gorm:"default:'visitor'" json:"mes_type"`
+	Status    string `gorm:"not null default:'unread'" json:"status"`
 }
 
 func CreateMessage(kefu_id string, visitor_id string, content string, mes_type string) {
@@ -37,12 +37,12 @@ func ReadMessageByVisitorId(visitor_id string) {
 	message := &Message{
 		Status: "read",
 	}
-	DB.Model(&message).Where("visitor_id=?", visitor_id).Update(message)
+	DB.Model(&message).Where("visitor_id=?", visitor_id).Updates(message)
 }
 
 //获取未读数
-func FindUnreadMessageNumByVisitorId(visitor_id string) uint {
-	var count uint
+func FindUnreadMessageNumByVisitorId(visitor_id string) int64 {
+	var count int64
 	DB.Where("visitor_id=? and status=?", visitor_id, "unread").Count(&count)
 	return count
 }
@@ -50,20 +50,20 @@ func FindUnreadMessageNumByVisitorId(visitor_id string) uint {
 //查询最后一条消息
 func FindLastMessage(visitorIds []string) []Message {
 	var messages []Message
-	subQuery := DB.
-		Table("message").
-		Where(" visitor_id in (? )", visitorIds).
-		Order("id desc").
-		Limit(1024).
-		SubQuery()
-	DB.Raw("SELECT ANY_VALUE(visitor_id) visitor_id,ANY_VALUE(id) id,ANY_VALUE(content) content FROM ? message_alia GROUP BY visitor_id", subQuery).Scan(&messages)
+	//subQuery := DB.
+	//	Table("message").
+	//	Where(" visitor_id in (? )", visitorIds).
+	//	Order("id desc").
+	//	Limit(1024).
+	//	SubQuery()
+	//DB.Raw("SELECT ANY_VALUE(visitor_id) visitor_id,ANY_VALUE(id) id,ANY_VALUE(content) content FROM ? message_alia GROUP BY visitor_id", subQuery).Scan(&messages)
 	//DB.Select("ANY_VALUE(visitor_id) visitor_id,MAX(ANY_VALUE(id)) id,ANY_VALUE(content) content").Group("visitor_id").Find(&messages)
 	return messages
 }
 
 //查询条数
-func CountMessage() uint {
-	var count uint
+func CountMessage() int64 {
+	var count int64
 	DB.Model(&Message{}).Count(&count)
 	return count
 }

+ 14 - 15
models/models.go

@@ -3,7 +3,8 @@ package models
 import (
 	"fmt"
 	"git.wanbits.cc/sin/flytalk/config"
-	"github.com/jinzhu/gorm"
+	"gorm.io/driver/mysql"
+	"gorm.io/gorm"
 	"time"
 )
 
@@ -16,25 +17,23 @@ type Model struct {
 	DeletedAt *time.Time `sql:"index" json:"deleted_at"`
 }
 
-func init() {
-	mysql := config.CreateMysql(config.MysqlConf)
-	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local", mysql.Username, mysql.Password, mysql.Server, mysql.Port, mysql.Db)
+func InitDb() {
+	dbc := config.C.Database
+	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
+		dbc.Username, dbc.Password, dbc.Server, dbc.Port, dbc.Db)
+
 	var err error
-	DB, err = gorm.Open("mysql", dsn)
+	DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
 	if err != nil {
-		panic("数据库连接失败!")
+		panic("connect to database failed")
 	}
-	DB.SingularTable(true)
-	DB.LogMode(false)
-	//DB.SetLogger(tools.Logger())
-	DB.DB().SetMaxIdleConns(10)
-	DB.DB().SetMaxOpenConns(100)
-
-	InitConfig()
 }
+
 func Execute(sql string) {
-	DB.Exec(sql)
+	if len(sql) > 0 {
+		DB.Exec(sql)
+	}
 }
+
 func CloseDB() {
-	defer DB.Close()
 }

+ 5 - 5
models/roles.go

@@ -1,10 +1,10 @@
 package models
 
 type Role struct {
-	Id     string `json:"role_id"`
-	Name   string `json:"role_name"`
-	Method string `json:"method"`
-	Path   string `json:"path"`
+	Id     uint   `gorm:"size:11 primaryKey" json:"role_id"`
+	Name   string `gorm:"size:50 not null default:''" json:"role_name"`
+	Method string `gorm:"size:128 not null default:''" json:"method"`
+	Path   string `gorm:"size:1024 not null default:''" json:"path"`
 }
 
 func FindRoles() []Role {
@@ -23,5 +23,5 @@ func SaveRole(id string, name string, method string, path string) {
 		Name:   name,
 		Path:   path,
 	}
-	DB.Model(role).Where("id=?", id).Update(role)
+	DB.Model(role).Where("id=?", id).Updates(role)
 }

+ 12 - 11
models/users.go

@@ -1,18 +1,18 @@
 package models
 
 import (
-	_ "github.com/jinzhu/gorm/dialects/mysql"
+	_ "gorm.io/driver/mysql"
 )
 
 type User struct {
 	Model
-	Name     string `json:"name"`
-	Password string `json:"password"`
-	Nickname string `json:"nickname"`
-	Avator   string `json:"avator"`
-	RoleName string `json:"role_name" sql:"-"`
-	RoleId   string `json:"role_id" sql:"-"`
-	Enabled  uint   `json:"enabled"`
+	Name     string `gorm:"size:50 index:idx_name not null default:''" json:"name"`
+	Password string `gorm:"size:50 not null default:''" json:"password"`
+	Nickname string `gorm:"size:50 not null default:''" json:"nickname"`
+	Avator   string `gorm:"size:255 not null default:''" json:"avator"`
+	RoleName string `gorm:"-" json:"role_name"`
+	RoleId   string `gorm:"-" json:"role_id"`
+	Enabled  uint   `gorm:"size:2 not null default:1" json:"enabled"`
 }
 
 func CreateUser(name string, password string, avator string, nickname string, enabled uint) uint {
@@ -26,6 +26,7 @@ func CreateUser(name string, password string, avator string, nickname string, en
 	DB.Create(user)
 	return user.ID
 }
+
 func UpdateUser(id string, name string, password string, avator string, nickname string, enabled uint) {
 	user := &User{
 		Name:     name,
@@ -36,7 +37,7 @@ func UpdateUser(id string, name string, password string, avator string, nickname
 	if password != "" {
 		user.Password = password
 	}
-	DB.Model(&User{}).Where("id = ?", id).Update(user)
+	DB.Model(&User{}).Where("id = ?", id).Updates(user)
 }
 func FindUser(username string) User {
 	var user User
@@ -53,11 +54,11 @@ func DeleteUserById(id string) {
 }
 func FindUsers() []User {
 	var users []User
-	DB.Select("user.*,role.name role_name").Joins("left join user_role on user.id=user_role.user_id").Joins("left join role on user_role.role_id=role.id").Order("user.id desc").Find(&users)
+	DB.Select("users.*,roles.name role_name").Joins("left join user_role on users.id=user_role.user_id").Joins("left join roles on user_role.role_id=roles.id").Order("users.id desc").Find(&users)
 	return users
 }
 func FindUserRole(query interface{}, id interface{}) User {
 	var user User
-	DB.Select(query).Where("user.id = ?", id).Joins("join user_role on user.id=user_role.user_id").Joins("join role on user_role.role_id=role.id").First(&user)
+	DB.Select(query).Where("users.id = ?", id).Joins("join user_role on users.id=user_role.user_id").Joins("join roles on user_role.role_id=roles.id").First(&user)
 	return user
 }

+ 16 - 16
models/visitors.go

@@ -2,15 +2,15 @@ package models
 
 type Visitor struct {
 	Model
-	Name      string `json:"name"`
-	Avator    string `json:"avator"`
-	SourceIp  string `json:"source_ip"`
-	ToId      string `json:"to_id"`
-	VisitorId string `json:"visitor_id"`
-	Status    uint   `json:"status"`
-	Refer     string `json:"refer"`
-	City      string `json:"city"`
-	ClientIp  string `json:"client_ip"`
+	Name      string `gorm:"size:50 not null default:''" json:"name"`
+	Avator    string `gorm:"size:255 not null default:''" json:"avator"`
+	SourceIp  string `gorm:"size:32 not null default:''" json:"source_ip"`
+	ToId      string `gorm:"size:50 not null default:''" json:"to_id"`
+	VisitorId string `gorm:"size:100 not null default:'' uniqueIndex:uidx_visitor_id" json:"visitor_id"`
+	Status    uint   `gorm:"size:4 not null default:0" json:"status"`
+	Refer     string `gorm:"size:500 not null default:''" json:"refer"`
+	City      string `gorm:"size:100 not null default:''" json:"city"`
+	ClientIp  string `gorm:"size:32 not null default:''" json:"client_ip"`
 }
 
 func CreateVisitor(name string, avator string, sourceIp string, toId string, visitorId string, refer string, city string, clientIp string) {
@@ -38,7 +38,7 @@ func FindVisitorByVistorId(visitorId string) Visitor {
 	DB.Where("visitor_id = ?", visitorId).First(&v)
 	return v
 }
-func FindVisitors(page uint, pagesize uint) []Visitor {
+func FindVisitors(page int, pagesize int) []Visitor {
 	offset := (page - 1) * pagesize
 	if offset < 0 {
 		offset = 0
@@ -47,7 +47,7 @@ func FindVisitors(page uint, pagesize uint) []Visitor {
 	DB.Offset(offset).Limit(pagesize).Order("status desc, updated_at desc").Find(&visitors)
 	return visitors
 }
-func FindVisitorsByKefuId(page uint, pagesize uint, kefuId string) []Visitor {
+func FindVisitorsByKefuId(page int, pagesize int, kefuId string) []Visitor {
 	offset := (page - 1) * pagesize
 	if offset < 0 {
 		offset = 0
@@ -72,19 +72,19 @@ func UpdateVisitor(visitorId string, status uint, clientIp string, sourceIp stri
 		SourceIp: sourceIp,
 		Refer:    refer,
 	}
-	DB.Model(visitor).Where("visitor_id = ?", visitorId).Update(visitor)
+	DB.Model(visitor).Where("visitor_id = ?", visitorId).Updates(visitor)
 }
 
 //查询条数
-func CountVisitors() uint {
-	var count uint
+func CountVisitors() int64 {
+	var count int64
 	DB.Model(&Visitor{}).Count(&count)
 	return count
 }
 
 //查询条数
-func CountVisitorsByKefuId(kefuId string) uint {
-	var count uint
+func CountVisitorsByKefuId(kefuId string) int64 {
+	var count int64
 	DB.Model(&Visitor{}).Where("to_id=?", kefuId).Count(&count)
 	return count
 }

+ 6 - 7
models/welcomes.go

@@ -3,11 +3,11 @@ package models
 import "time"
 
 type Welcome struct {
-	ID        uint      `gorm:"primary_key" json:"id"`
-	UserId    string    `json:"user_id"`
-	Content   string    `json:"content"`
-	IsDefault uint      `json:"is_default"`
-	Ctime     time.Time `json:"ctime"`
+	ID        uint      `gorm:"primaryKey" json:"id"`
+	UserId    string    `gorm:"size:100 not null default:'' index:'user_id'" json:"user_id"`
+	Content   string    `gorm:"size:512 not null default:''" json:"content"`
+	IsDefault uint      `gorm:"size:3 not null default:0" json:"is_default"`
+	CreatedAt time.Time `json:"ctime"`
 }
 
 func CreateWelcome(userId string, content string) uint {
@@ -17,7 +17,6 @@ func CreateWelcome(userId string, content string) uint {
 	w := &Welcome{
 		UserId:  userId,
 		Content: content,
-		Ctime:   time.Now(),
 	}
 	DB.Create(w)
 	return w.ID
@@ -29,7 +28,7 @@ func UpdateWelcome(userId string, id string, content string) uint {
 	w := &Welcome{
 		Content: content,
 	}
-	DB.Model(w).Where("user_id = ? and id = ?", userId, id).Update(w)
+	DB.Model(w).Where("user_id = ? and id = ?", userId, id).Updates(w)
 	return w.ID
 }
 func FindWelcomeByUserId(userId interface{}) Welcome {

+ 2 - 2
static/html/chat_kf_page.html

@@ -1,9 +1,9 @@
-<html lang="cn">
+<html lang="en">
 <head>
     <meta charset="utf-8">
     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
     <meta name="description" content="">
-    <meta name="author" content="陶士涵">
+    <meta name="author" content="wen studio">
     <title>美天旺客服页</title>
     <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/element-ui@2.13.1/lib/theme-chalk/index.css">
     <script src="/static/js/functions.js?v=0.1.1"></script>

+ 2 - 2
static/html/chat_main.html

@@ -1,8 +1,8 @@
-<html lang="cn">
+<html lang="en">
 <head>
     <meta charset="utf-8">
     <meta name="description" content="">
-    <meta name="author" content="陶士涵">
+    <meta name="author" content="wen studio">
     <title>聊天界面</title>
     <link rel="stylesheet" href="/static/css/common.css">
     <link rel="stylesheet" href="/static/css/emojione.min.css">

+ 1 - 1
static/html/chat_page.html

@@ -1,4 +1,4 @@
-<html lang="cn">
+<html lang="en">
 <head>
     <meta charset="utf-8">
     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

+ 1 - 1
static/html/header.html

@@ -1,5 +1,5 @@
 {{define "header"}}
-<html lang="cn">
+<html lang="en">
 <head>
     <meta charset="utf-8">
     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

+ 136 - 3
static/html/index.html

@@ -1,4 +1,3 @@
-
 <!DOCTYPE html>
 <html lang="en">
 <head>
@@ -8,12 +7,146 @@
     <meta name="keywords" content="{{.Keywords}}" />
     <meta name="description" content="{{.Desc}}" />
 
-    {{.CssJs}}
+    <style>
+        *{
+            margin: 0;padding: 0;
+        }
+        .header{
+            height: 80px;
+            background-color: #fff;
+            color: #fff;
+            top: 0;
+            left: 0;
+            width: 100%;
+            line-height: 80px;
+            z-index: 100;
+            position: relative;
+        }
+        .container{
+            width: 1140px;
+            padding: 0;
+            margin: 0 auto;
+        }
+        .header .container{
+            height: 100%;
+            box-sizing: border-box;
+            border-bottom: 1px solid #dcdfe6;
+        }
+        .header h1{
+            margin: 0;
+            float: left;
+            font-size: 32px;
+            font-weight: 400;
+        }
+        .header a{
+            color: #519eff;
+            font-family: "Microsoft JhengHei";
+            text-decoration: none;
+        }
+        .header h1 a{
+            font-size: 30px;
+            font-weight: bold;
+        }
+        .header .navBtn{
+            float: right;
+            margin-left: 20px;
+        }
+        .banner{
+            padding-top: 20px;
+            text-align: center;
+        }
+        .banner h1{
+            font-size: 34px;
+            margin: 0;
+            line-height: 48px;
+            color: #555;
+            font-weight: 500;
+            font-family: Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,SimSun,sans-serif;
+        }
+        .banner p{
+            font-size: 18px;
+            line-height: 28px;
+            color: #888;
+            margin: 10px 0 5px;
+        }
+        .jumbotron{
+            width: 587px;
+            height: 560px;
+            margin: 30px auto;
+        }
+        .footer {
+            clear: both;
+            background-color: #f7fbfd;
+            width: 100%;
+            padding: 40px 150px;
+            box-sizing: border-box;
+        }
+        .copyright{
+            color: #6c757d;
+            text-align: center;
+            margin: 60px 0;
+        }
+        .mainTechLeft{
+            width: 300px;
+            float: left;
+        }
+        .mainTechLeft h1{
+            font-size: 34px;
+            margin: 0;
+            line-height: 48px;
+            color: #555;
+            font-weight: 500;
+            font-family: Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,SimSun,sans-serif;
+        }
+        .mainTechLeft p{
+            font-size: 18px;
+            line-height: 28px;
+            color: #888;
+            margin: 10px 0 5px;
+        }
+        .floatRight{
+            width: 700px;
+            border: 1px solid #e1e1e1;
+            padding: 4px;
+            margin-top: 35px;
+            display: block;
+            float: right;
+        }
+    </style>
 </head>
 <body>
 
+<!--content-->
+<header class="header">
+    <div class="container">
+        <h1><a href="/">flytalk</a></h1>
+        <a class="navBtn" href="/index_en">English (United States)</a>
+        <a class="navBtn" href="/index_cn">中文版 (简体)</a>
+        <a class="navBtn" href="https://github.com/taoshihan1991/go-fly" target="_blank">Github</a>
+        <a class="navBtn" href="/login">{{ .AgentsEntry }}</a>
+        <a class="navBtn" href="/docs/index.html" target="_blank">{{ .DocEntry }}</a>
+    </div>
+</header>
+<div class="banner">
+    <h1>{{ .ProjectTitle }}</h1>
+    <p>{{ .ProjectDesc }}</p>
+</div>
+<div class="jumbotron">
+    <img src="/static/images/intro1.jpg"/>
+</div>
+<div class="container">
+    <img src="/static/images/admin.png"/>
+</div>
 
-{{.Content}}
+
+<footer class="footer">
+    <div class="container">
+
+    </div>
+    <div class="copyright">
+        {{ .Copyright }}
+    </div>
+</footer>
 
 
 

+ 1 - 1
static/html/login.html

@@ -1,4 +1,4 @@
-<html lang="cn">
+<html lang="en">
 <head>
     <meta charset="utf-8">
     <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">

+ 2 - 2
static/html/main.html

@@ -1,8 +1,8 @@
-<html lang="cn">
+<html lang="en">
 <head>
     <meta charset="utf-8">
     <meta name="description" content="">
-    <meta name="author" content="陶士涵">
+    <meta name="author" content="wen studio">
     <title>美天旺即时通讯工具集</title>
     <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/element-ui@2.13.1/lib/theme-chalk/index.css">
     <link rel="stylesheet" href="/static/css/common.css">

+ 6 - 5
tmpl/chat.go

@@ -1,7 +1,7 @@
 package tmpl
 
 import (
-	"git.wanbits.cc/sin/flytalk/config"
+	"git.wanbits.cc/sin/flytalk/g"
 	"github.com/gin-gonic/gin"
 	"net/http"
 )
@@ -9,19 +9,20 @@ import (
 //咨询界面
 func PageChat(c *gin.Context) {
 	kefuId := c.Query("kefu_id")
-	lang, _ := c.Get("lang")
-	language := config.CreateLanguage(lang.(string))
+	ilang, _ := c.Get("lang")
+	lang := ilang.(string)
 	refer := c.Query("refer")
 	if refer == "" {
 		refer = c.Request.Referer()
 	}
 	c.HTML(http.StatusOK, "chat_page.html", gin.H{
 		"KEFU_ID": kefuId,
-		"SendBtn": language.Send,
-		"Lang":    lang.(string),
+		"SendBtn": g.T("send", lang),
+		"Lang":    lang,
 		"Refer":   refer,
 	})
 }
+
 func PageKfChat(c *gin.Context) {
 	kefuId := c.Query("kefu_id")
 	visitorId := c.Query("visitor_id")

+ 23 - 26
tmpl/common.go

@@ -2,10 +2,9 @@ package tmpl
 
 import (
 	"git.wanbits.cc/sin/flytalk/config"
-	"git.wanbits.cc/sin/flytalk/models"
+	"git.wanbits.cc/sin/flytalk/g"
 	"git.wanbits.cc/sin/flytalk/tools"
 	"github.com/gin-gonic/gin"
-	"html"
 	"html/template"
 	"net/http"
 )
@@ -27,14 +26,17 @@ func NewRender(rw http.ResponseWriter) *CommonHtml {
 	obj.Nav = template.HTML(nav)
 	return obj
 }
+
 func (obj *CommonHtml) SetLeft(file string) {
 	leftStr := tools.FileGetContent("html/" + file + ".html")
 	obj.Left = template.HTML(leftStr)
 }
+
 func (obj *CommonHtml) SetBottom(file string) {
 	str := tools.FileGetContent("html/" + file + ".html")
 	obj.Bottom = template.HTML(str)
 }
+
 func (obj *CommonHtml) Display(file string, data interface{}) {
 	if data == nil {
 		data = obj
@@ -50,31 +52,26 @@ func PageIndex(c *gin.Context) {
 		return
 	}
 
-	lang, _ := c.Get("lang")
-	language := config.CreateLanguage(lang.(string))
-	about := models.FindAboutByPageLanguage("index", lang.(string))
-	cssJs := html.UnescapeString(about.CssJs)
-	title := about.TitleCn
-	keywords := about.KeywordsCn
-	desc := html.UnescapeString(about.DescCn)
-	content := html.UnescapeString(about.HtmlCn)
-	if lang == "en" {
-		title = about.TitleEn
-		keywords = about.KeywordsEn
-		desc = html.UnescapeString(about.DescEn)
-		content = html.UnescapeString(about.HtmlEn)
-	}
+	ilang, _ := c.Get("lang")
+	lang := ilang.(string)
 	c.HTML(http.StatusOK, "index.html", gin.H{
-		"OnlineChat": language.IndexOnlineChat,
-		"Notice":     language.Notice,
-		"NowAsk":     language.NowAsk,
-		"LaterAsk":   language.LaterAsk,
-		"Lang":       lang,
-		"Title":      title,
-		"Keywords":   keywords,
-		"Desc":       desc,
-		"Content":    template.HTML(content),
-		"CssJs":      template.HTML(cssJs),
+		"OnlineChat":    g.T("chat_entry", lang),
+		"Notice":        g.T("notice", lang),
+		"NowAsk":        g.T("start_chat", lang),
+		"LaterAsk":      g.T("chat_later", lang),
+		"Lang":          lang,
+		"Title":         g.T("title", lang),
+		"Keywords":      g.T("keywords", lang),
+		"Desc":          g.T("description", lang),
+		"Copyright":     g.T("copyright", lang),
+		"MainTech":      g.T("main_tech", lang),
+		"DocEntry":      g.T("doc_entry", lang),
+		"VisitorsEntry": g.T("visitors_entry", lang),
+		"AgentsEntry":   g.T("agents_entry", lang),
+		"ChatEntry":     g.T("chat_entry", lang),
+		"ProjectTitle":  g.T("project_title", lang),
+		"ProjectDesc":   g.T("project_desc", lang),
+		"Version":       config.VERSION,
 	})
 }
 

+ 1 - 1
tmpl/setting.go

@@ -61,7 +61,7 @@ func PageKefuList(c *gin.Context) {
 	c.HTML(http.StatusOK, "setting_kefu_list.html", gin.H{
 		"tab_index":    "3-2",
 		"action":       "setting_kefu_list",
-		"qiniu_domain": config.QiniuConfig.Domain,
+		"qiniu_domain": config.C.Qiniu.Domain,
 	})
 }
 

+ 4 - 2
tools/ip.go

@@ -4,12 +4,14 @@ import (
 	"github.com/ipipdotnet/ipdb-go"
 )
 
+const CITY_DB_FILE = "./data/city.free.ipdb"
+
 func ParseIp(myip string) *ipdb.CityInfo {
-	db, err := ipdb.NewCity("./config/city.free.ipdb")
+	db, err := ipdb.NewCity(CITY_DB_FILE)
 	if err != nil {
 		return nil
 	}
-	db.Reload("./config/city.free.ipdb")
+	db.Reload(CITY_DB_FILE)
 	c, err := db.FindInfo(myip, "CN")
 	if err != nil {
 		return nil

+ 10 - 15
user/provider/twong/user.go

@@ -7,7 +7,8 @@ import (
 	"git.wanbits.cc/sin/flytalk/models"
 	"git.wanbits.cc/sin/flytalk/tools"
 	"git.wanbits.cc/sin/flytalk/user"
-	"github.com/jinzhu/gorm"
+	"gorm.io/driver/mysql"
+	"gorm.io/gorm"
 )
 
 /**
@@ -15,25 +16,19 @@ import (
 同时可能支持所有基于 crmeb 的应用
 */
 
-const twong_confile = config.Dir + "twong.json"
-
 var (
-	Db *gorm.DB
+	db *gorm.DB
 )
 
-func init() {
-	mysql := config.CreateMysql(twong_confile)
-	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local", mysql.Username, mysql.Password, mysql.Server, mysql.Port, mysql.Db)
+func InitDb() {
+	dbc := config.C.Database
+	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local",
+		dbc.Username, dbc.Password, dbc.Server, dbc.Port, dbc.Db)
 	var err error
-	Db, err = gorm.Open("mysql", dsn)
+	db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
 	if err != nil {
-		panic("美天旺数据库连接失败!")
+		panic("connect to twong database failed.")
 	}
-	Db.SingularTable(true)
-	Db.LogMode(false)
-	//DB.SetLogger(tools.Logger())
-	Db.DB().SetMaxIdleConns(3)
-	Db.DB().SetMaxOpenConns(30)
 }
 
 type TwUser struct {
@@ -54,7 +49,7 @@ func (self *Twong) GetVisitorInfo(key interface{}) (*models.Visitor, error) {
 		return nil, errors.New("参数错误")
 	}
 	var u TwUser
-	Db.Where("uid=?", uid).First(&u)
+	db.Where("uid=?", uid).First(&u)
 	if u.Uid != uid {
 		return nil, errors.New("用户不存在")
 	}

+ 1 - 0
ws/user.go

@@ -50,6 +50,7 @@ func NewKefuServer(c *gin.Context) {
 		}
 	}
 }
+
 func AddKefuToList(kefu *User) {
 	var newKefuConns = []*User{kefu}
 	kefuConns := KefuList[kefu.Id]

+ 1 - 0
ws/visitor.go

@@ -71,6 +71,7 @@ func NewVisitorServer(c *gin.Context) {
 		}
 	}
 }
+
 func AddVisitorToList(user *User) {
 	//用户id对应的连接
 	ClientList[user.Id] = user

+ 10 - 11
ws/ws.go

@@ -20,10 +20,12 @@ type Message struct {
 	content     []byte
 	messageType int
 }
+
 type TypeMessage struct {
 	Type interface{} `json:"type"`
 	Data interface{} `json:"data"`
 }
+
 type ClientMessage struct {
 	Name      string `json:"name"`
 	Avator    string `json:"avator"`
@@ -41,17 +43,14 @@ type ClientMessage struct {
 var ClientList = make(map[string]*User)
 var KefuList = make(map[string][]*User)
 var message = make(chan *Message)
-var upgrader = websocket.Upgrader{}
-var Mux sync.RWMutex
 
-func init() {
-	upgrader = websocket.Upgrader{
-		ReadBufferSize:  1024,
-		WriteBufferSize: 1024,
-		// 解决跨域问题
-		CheckOrigin: func(r *http.Request) bool {
-			return true
-		},
-	}
+var Mux sync.RWMutex
 
+var upgrader = websocket.Upgrader{
+	ReadBufferSize:  1024,
+	WriteBufferSize: 1024,
+	// 解决跨域问题
+	CheckOrigin: func(r *http.Request) bool {
+		return true
+	},
 }

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác