├── .gitignore ├── LICENSE ├── README.md ├── depth.gif ├── exchange_api ├── app │ ├── auth.go │ ├── depth.go │ ├── exchange.go │ └── system.go ├── application.yaml ├── cfg │ └── cfg.go ├── cmd │ ├── web_server.go │ └── ws_client.go ├── config │ ├── log.go │ └── websocket.go ├── db │ └── redis.go ├── go.mod ├── go.sum ├── main.go ├── middleware │ └── logger.go ├── model │ ├── binance.go │ ├── bitmex.go │ ├── huobi.go │ └── okex.go ├── route │ ├── api.go │ └── cors.go └── tool │ ├── binance.go │ ├── bitmex.go │ ├── huobi.go │ ├── jwt.go │ ├── okex.go │ ├── tmp_depth_ws.go │ ├── tool.go │ └── websocket.go └── exchange_vue ├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── COPYING ├── build ├── build.js ├── check-versions.js ├── dev-client.js ├── dev-server.js ├── utils.js ├── vue-loader.conf.js ├── webpack.base.conf.js ├── webpack.dev.conf.js ├── webpack.prod.conf.js └── webpack.test.conf.js ├── config ├── dev.env.js ├── index.js ├── prod.env.js └── test.env.js ├── favicon.ico ├── index.html ├── manage ├── index.html └── static │ ├── css │ └── app.85873a69abe58e3fc37a13d571ef59e2.css │ └── fonts │ └── element-icons.b02bdc1.ttf ├── package.json ├── screenshots ├── ewm.png ├── manage_home.png └── manage_shop.png ├── src ├── App.vue ├── api │ └── getData.js ├── assets │ ├── img │ │ └── avator.jpg │ └── svg │ │ ├── password.svg │ │ └── username.svg ├── components │ ├── headTop.vue │ ├── tendency.vue │ └── visitorPie.vue ├── config │ ├── env.js │ ├── fetch.js │ └── mUtils.js ├── http.js ├── main.js ├── page │ ├── depth.vue │ ├── exchange.vue │ ├── login.vue │ ├── manage.vue │ └── system.vue ├── router │ └── index.js ├── store │ └── index.js └── style │ ├── common.less │ └── mixin.less ├── static └── .gitkeep ├── test ├── e2e │ ├── custom-assertions │ │ └── elementCount.js │ ├── nightwatch.conf.js │ ├── runner.js │ └── specs │ │ └── test.js └── unit │ ├── .eslintrc │ ├── index.js │ ├── karma.conf.js │ └── specs │ └── Hello.spec.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Editor directories and files 2 | .idea 3 | .vscode 4 | *.suo 5 | *.ntvs* 6 | *.njsproj 7 | *.sln 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 tsewell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## exchange_data gin+vue 快速部署获取各大数字货币交易所行情数据 2 | ![项目展示](https://github.com/GitTsewell/exchange_data/blob/master/depth.gif) 3 | ## 项目说明 4 | ``` 5 | 1.go在服务器端挂起一个WS客户端,利用goroutine建立各大交易所(okex,火币,币安,bitmex)的ws连接,获取实时行情数据 6 | 储存在redis中,量化机器人直接在redis中读取.提供一个行情管理后台,管理币种以及ws客户端的重启,支持分布式 7 | 2.exchange_api 是go写的ws客户端和后台api接口, exchange_vue 是后台页面 8 | ``` 9 | ## 使用说明 10 | ### GO 11 | ``` 12 | golang api server 基于go.mod 如果golang版本低于1.11 请自行升级golang版本 13 | exchange_api/config/redis.go db配置 14 | cd exchange_api/cmd && go build ws_client.go 编译ws客户端 使用进程管理工具启动(比如supervisor) 15 | cd exchange_api/cmd && go build web_server.go 编译admin_api客户端 16 | ``` 17 | ### vue 18 | ``` 19 | exchange_vue/src/http.js 接口host配置 20 | cd exchange_vue 21 | yarn 22 | yarn run dev 23 | 访问 localhost:8002 24 | ``` 25 | 请愉快食用~! 26 | 27 | 28 | -------------------------------------------------------------------------------- /depth.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitTsewell/exchange_data/a2291a3082e6f67f4fd179ad1c6a11170df25ab3/depth.gif -------------------------------------------------------------------------------- /exchange_api/app/auth.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "exchange_api/db" 5 | "exchange_api/tool" 6 | "github.com/dgrijalva/jwt-go" 7 | "github.com/gin-gonic/gin" 8 | "net/http" 9 | "time" 10 | ) 11 | 12 | type account struct { 13 | Username string `form:"username" binding:"required"` 14 | Password string `form:"password" binding:"required"` 15 | } 16 | 17 | func LoginPost(c *gin.Context) { 18 | redis := db.InitRedis() 19 | defer redis.Close() 20 | 21 | var params account 22 | 23 | if err := c.ShouldBindJSON(¶ms); err != nil { 24 | c.JSON(200,gin.H{"error":err}) 25 | return 26 | } 27 | 28 | key := "admin:account:*" 29 | keys ,_ :=redis.Keys(key).Result() 30 | if len(keys) == 0 { 31 | hash,_ := tool.AesEncrypt(params.Password) 32 | _ = redis.Set("admin:account:"+params.Username,hash,0) 33 | 34 | generateToken(c,params) 35 | }else { 36 | hash,_ := redis.Get("admin:account:"+params.Username).Result() 37 | pass ,_ := tool.AesDecrypt(hash) 38 | if pass != params.Password { 39 | c.JSON(200,gin.H{ 40 | "status": -1, 41 | "msg":"账号或密码错误", 42 | }) 43 | return 44 | } 45 | 46 | generateToken(c,params) 47 | } 48 | } 49 | 50 | // 生成令牌 51 | func generateToken(c *gin.Context, account account ) { 52 | j := &tool.JWT{ 53 | []byte("tsewell"), 54 | } 55 | claims := tool.CustomClaims{ 56 | account.Username, 57 | jwt.StandardClaims{ 58 | NotBefore: int64(time.Now().Unix() - 1000), // 签名生效时间 59 | ExpiresAt: int64(time.Now().Unix() + 86400), // 过期时间 一小时 60 | Issuer: "tsewell", //签名的发行者 61 | }, 62 | } 63 | 64 | token, err := j.CreateToken(claims) 65 | 66 | if err != nil { 67 | c.JSON(http.StatusOK, gin.H{ 68 | "status": -1, 69 | "msg": err.Error(), 70 | }) 71 | return 72 | } 73 | 74 | 75 | data := LoginResult{ 76 | Username: account.Username, 77 | Token: token, 78 | } 79 | c.JSON(http.StatusOK, gin.H{ 80 | "status": 1, 81 | "msg": "登录成功!", 82 | "data": data, 83 | }) 84 | return 85 | } 86 | 87 | // LoginResult 登录结果结构 88 | type LoginResult struct { 89 | Token string `json:"token"` 90 | Username string `json:"username"` 91 | } 92 | -------------------------------------------------------------------------------- /exchange_api/app/depth.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "exchange_api/db" 5 | "exchange_api/tool" 6 | "fmt" 7 | "github.com/gin-gonic/gin" 8 | "strconv" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | type data struct { 14 | Symbols []string `form:"symbols" json:"symbols" binding:"required"` 15 | } 16 | 17 | func DepthIndex(c *gin.Context) { 18 | redis := db.InitRedis() 19 | defer redis.Close() 20 | 21 | platform := c.DefaultQuery("platform","okex") 22 | 23 | key := fmt.Sprintf("%s:depth:*",platform) 24 | keys,_ := redis.Keys(key).Result() 25 | 26 | now := time.Now().UnixNano() / 1e6 27 | 28 | list := []map[string]interface{}{} 29 | for _,v := range keys{ 30 | rst ,_ := redis.HMGet(v,"average_price","microtime").Result() 31 | // status 32 | status := 1 33 | t ,_ := strconv.ParseInt(rst[1].(string), 10, 64) 34 | if now - t >= 30000 { 35 | status = 0 36 | } 37 | // symbol 38 | spli := strings.Split(v,":") 39 | 40 | // time 41 | rst[1],_ = tool.MsToTime(rst[1].(string)) 42 | 43 | raw := map[string]interface{}{ 44 | "symbol":spli[3], 45 | "price":rst[0], 46 | "tag":spli[2], 47 | "time": rst[1], 48 | "status":status, 49 | } 50 | 51 | list = append(list,raw) 52 | } 53 | 54 | c.JSON(200,gin.H{ 55 | "data":list, 56 | "status":1, 57 | }) 58 | 59 | } 60 | 61 | func DepthEdit(c *gin.Context) { 62 | platform := c.DefaultQuery("platform","okex") 63 | 64 | redis := db.InitRedis() 65 | defer redis.Close() 66 | 67 | key1 := fmt.Sprintf("config:depth:%s:spot",platform) 68 | key2 := fmt.Sprintf("config:depth:%s:future",platform) 69 | a1,_ := redis.SMembers(key1).Result() 70 | a2,_ := redis.SMembers(key2).Result() 71 | 72 | a1 = append(a1,a2...) 73 | 74 | c.JSON(200,gin.H{ 75 | "symbols":a1, 76 | }) 77 | } 78 | 79 | func DepthUpdate(c *gin.Context) { 80 | redis := db.InitRedis() 81 | defer redis.Close() 82 | 83 | // platform 84 | platform := c.Param("platform") 85 | 86 | var data data 87 | _ = c.BindJSON(&data) 88 | 89 | key1 := fmt.Sprintf("tmp:config:depth:%s:spot",platform) 90 | key2 := fmt.Sprintf("tmp:config:depth:%s:future",platform) 91 | 92 | redis.Del(key1) 93 | redis.Del(key2) 94 | 95 | var spot []string 96 | var future []string 97 | 98 | var isSpot func(string) bool 99 | var tmpDepth func(*[]string,*[]string) 100 | switch platform { 101 | case "okex": 102 | isSpot = tool.IsSpotOfOkex 103 | tmpDepth = tool.TmpOkexDepthWs 104 | break 105 | case "huobi": 106 | isSpot = tool.IsSpotOfHuobi 107 | tmpDepth = tool.TmpHuobiDepthWs 108 | break 109 | case "bitmex": 110 | isSpot = tool.IsSpotOfBitmex 111 | tmpDepth = tool.TmpBitmexDepthWs 112 | break 113 | case "binance": 114 | isSpot = tool.IsSpotOfBinance 115 | tmpDepth = tool.TmpBinanceDepthWs 116 | break 117 | default: 118 | break 119 | } 120 | 121 | for _,i:= range data.Symbols{ 122 | if isSpot(i) { 123 | spot = append(spot,i) 124 | }else { 125 | future = append(future,i) 126 | } 127 | } 128 | 129 | redis.SAdd(key1,spot) 130 | redis.SAdd(key2,future) 131 | 132 | tmpKey := fmt.Sprintf("tmp:depth:%s",platform) 133 | redis.Del(tmpKey) 134 | 135 | c.JSON(200,gin.H{ 136 | "status":1, 137 | }) 138 | 139 | tmpDepth(&spot,&future) 140 | 141 | 142 | } 143 | 144 | func DepthCheck(c *gin.Context) { 145 | redis := db.InitRedis() 146 | defer redis.Close() 147 | 148 | platform := c.Param("platform") 149 | k1 := fmt.Sprintf("tmp:config:depth:%s:spot",platform) 150 | k2 := fmt.Sprintf("tmp:config:depth:%s:future",platform) 151 | k3 := fmt.Sprintf("tmp:depth:%s",platform) 152 | 153 | a1,_ := redis.SMembers(k1).Result() 154 | a2,_ := redis.SMembers(k2).Result() 155 | a3,_ := redis.SMembers(k3).Result() 156 | 157 | a1 = append(a1,a2...) 158 | 159 | list := []map[string]interface{}{} 160 | for _,v1 := range a1{ 161 | status := 0 162 | for _,v2 := range a3{ 163 | if v1 == v2 { 164 | status = 1 165 | } 166 | } 167 | list = append(list,map[string]interface{}{"symbol":v1,"status":status}) 168 | } 169 | 170 | c.JSON(200,gin.H{ 171 | "status":1, 172 | "data":list, 173 | }) 174 | } 175 | 176 | func DepthCommit(c *gin.Context) { 177 | redis := db.InitRedis() 178 | defer redis.Close() 179 | 180 | platform := c.Query("platform") 181 | if len(platform) == 0 { 182 | c.JSON(200,gin.H{"status":false}) 183 | return 184 | } 185 | 186 | t1 := fmt.Sprintf("tmp:config:depth:%s:spot",platform) 187 | t2 := fmt.Sprintf("tmp:config:depth:%s:future",platform) 188 | 189 | if r1,_ := redis.SMembers(t1).Result(); len(r1) > 0 { 190 | key := fmt.Sprintf("config:depth:%s:spot",platform) 191 | redis.Del(key) 192 | redis.SAdd(key,r1) 193 | } 194 | 195 | if r2,_ := redis.SMembers(t2).Result(); len(r2) > 0 { 196 | key := fmt.Sprintf("config:depth:%s:future",platform) 197 | redis.Del(key) 198 | redis.SAdd(key,r2) 199 | } 200 | 201 | c.JSON(200,gin.H{"status":true}) 202 | } 203 | -------------------------------------------------------------------------------- /exchange_api/app/exchange.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "exchange_api/db" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | var ( 8 | okex_key = "config:exchange:okex" 9 | bitmex_key = "config:exchange:bitmex" 10 | huobi_key = "config:exchange:huobi" 11 | binance_key = "config:exchange:binance" 12 | ) 13 | 14 | type exchange struct { 15 | Okex bool `form:"okex" json:"okex" binding:"required"` 16 | Bitmex bool `form:"bitmex" json:"bitmex" binding:"required"` 17 | Huobi bool `form:"huobi" json:"huobi" binding:"required"` 18 | Binance bool `form:"binance" json:"binance" binding:"required"` 19 | } 20 | 21 | func ExchangeEdit(c *gin.Context) { 22 | redis := db.InitRedis() 23 | defer redis.Close() 24 | 25 | res := exchange{ 26 | Okex:true, 27 | Bitmex:true, 28 | Huobi:true, 29 | Binance:true, 30 | } 31 | if v1,_ := redis.Get(okex_key).Result();v1 == "0" { 32 | res.Okex = false 33 | } 34 | if v2,_ := redis.Get(bitmex_key).Result(); v2 == "0" { 35 | res.Bitmex = false 36 | } 37 | if v3,_ := redis.Get(huobi_key).Result(); v3 == "0" { 38 | res.Huobi = false 39 | } 40 | if v4,_ := redis.Get(binance_key).Result(); v4 == "0" { 41 | res.Binance = false 42 | } 43 | 44 | data := map[string]interface{}{ 45 | "okex":res.Okex, 46 | "bitmex":res.Bitmex, 47 | "huobi":res.Huobi, 48 | "binance":res.Binance, 49 | } 50 | 51 | c.JSON(200,gin.H{"data":data}) 52 | } 53 | 54 | func ExchangeUpdate(c *gin.Context) { 55 | redis := db.InitRedis() 56 | defer redis.Close() 57 | 58 | var exchange exchange 59 | _ = c.ShouldBindJSON(&exchange) 60 | 61 | redis.Set(okex_key,exchange.Okex,0) 62 | redis.Set(bitmex_key,exchange.Bitmex,0) 63 | redis.Set(huobi_key,exchange.Huobi,0) 64 | redis.Set(binance_key,exchange.Binance,0) 65 | 66 | c.JSON(200,gin.H{"status":1}) 67 | } 68 | -------------------------------------------------------------------------------- /exchange_api/app/system.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "exchange_api/db" 5 | "exchange_api/tool" 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func SystemIndex(c *gin.Context) { 10 | redis := db.InitRedis() 11 | defer redis.Close() 12 | 13 | k1 := "config:system:restart_local_ws" 14 | k2 := "config:system:restart_other_ws" 15 | 16 | v1,_ := redis.Get(k1).Result() 17 | v2,_ := redis.Get(k2).Result() 18 | 19 | data := []map[string]interface{}{ 20 | {"name":"重启本机ws客户端","key":k1,"action":v1,"show":false},{"name":"重启其他服务器WS客户端","key":k2,"action":v2,"show":false}, 21 | } 22 | 23 | c.JSON(200,gin.H{ 24 | "data":data, 25 | }) 26 | } 27 | 28 | func SystemExec(c *gin.Context) { 29 | redis := db.InitRedis() 30 | defer redis.Close() 31 | 32 | key := c.Param("key") 33 | 34 | action , _ := redis.Get(key).Result() 35 | 36 | tool.SystemExec(action) 37 | 38 | c.JSON(200,gin.H{"status":1}) 39 | } 40 | 41 | func SystemUpdate(c *gin.Context) { 42 | redis := db.InitRedis() 43 | defer redis.Close() 44 | 45 | var data struct{ 46 | Key string `json:"key" binding:"required"` 47 | Action string `json:"action" binding:"required"` 48 | } 49 | 50 | _ = c.BindJSON(&data) 51 | 52 | if a,_ :=redis.Set(data.Key,data.Action,0).Result();a == "OK" { 53 | c.JSON(200,gin.H{"status":1}) 54 | }else { 55 | c.JSON(200,gin.H{"status":-1}) 56 | } 57 | } 58 | 59 | func SystemProcess(c *gin.Context) { 60 | 61 | } 62 | -------------------------------------------------------------------------------- /exchange_api/application.yaml: -------------------------------------------------------------------------------- 1 | redis : 2 | host : 127.0.0.1 3 | port : 6379 4 | auth : 5 | db : 0 6 | 7 | -------------------------------------------------------------------------------- /exchange_api/cfg/cfg.go: -------------------------------------------------------------------------------- 1 | package cfg 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "strings" 8 | 9 | "github.com/spf13/viper" 10 | ) 11 | 12 | // 初始化配置文件 13 | func Initialize(fileName string) error { 14 | splits := strings.Split(filepath.Base(fileName), ".") 15 | viper.SetConfigName(filepath.Base(splits[0])) 16 | viper.AddConfigPath(filepath.Dir(fileName)) 17 | err := viper.ReadInConfig() 18 | if err != nil { 19 | return err 20 | } 21 | 22 | // 检查必须设置的参数,是否有设置 23 | err = checkMustSetArgs() 24 | if err != nil { 25 | return err 26 | } 27 | 28 | //viper.Debug() 29 | 30 | return nil 31 | } 32 | 33 | // 检查必须设置的参数,是否有设置 34 | func checkMustSetArgs() error { 35 | return nil 36 | } 37 | 38 | func checkKey(key string) { 39 | if !viper.IsSet(key) { 40 | fmt.Printf("配置文件参数%q未设置,现在退出程序\n", key) 41 | os.Exit(1) 42 | } 43 | } 44 | 45 | func MustGetString(key string) string { 46 | checkKey(key) 47 | return viper.GetString(key) 48 | } 49 | 50 | func MustGetInt(key string) int { 51 | checkKey(key) 52 | return viper.GetInt(key) 53 | } 54 | 55 | func MustGetBool(key string) bool { 56 | checkKey(key) 57 | return viper.GetBool(key) 58 | } 59 | 60 | func GetString(key string) string { 61 | return viper.GetString(key) 62 | } 63 | 64 | func GetInt(key string) int { 65 | return viper.GetInt(key) 66 | } 67 | 68 | func GetInt64(key string) int64 { 69 | return viper.GetInt64(key) 70 | } 71 | 72 | func GetUint64(key string) uint64 { 73 | return viper.GetUint64(key) 74 | } 75 | 76 | func GetBool(key string) bool { 77 | return viper.GetBool(key) 78 | } 79 | 80 | func GetStringMap(key string) map[string]interface{} { 81 | return viper.GetStringMap(key) 82 | } 83 | -------------------------------------------------------------------------------- /exchange_api/cmd/web_server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "exchange_api/route" 4 | 5 | func main() { 6 | r := route.InitRoute() 7 | _ = r.Run() 8 | } 9 | -------------------------------------------------------------------------------- /exchange_api/cmd/ws_client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "exchange_api/db" 5 | "exchange_api/tool" 6 | "fmt" 7 | "github.com/go-redis/redis" 8 | "strings" 9 | ) 10 | 11 | func main() { 12 | redis := db.InitRedis() 13 | defer redis.Close() 14 | 15 | okexRun ,_ := redis.Get("config:exchange:okex").Result() 16 | bitmexRun ,_ := redis.Get("config:exchange:bitmex").Result() 17 | huobiRun ,_ := redis.Get("config:exchange:huobi").Result() 18 | binanceRun ,_ := redis.Get("config:exchange:binance").Result() 19 | 20 | // okex 21 | if okexRun != "0" { 22 | go okexWs(redis) 23 | } 24 | 25 | // bitmex 26 | if bitmexRun != "0" { 27 | go bitmexWs(redis) 28 | } 29 | 30 | //火币现货 31 | if huobiRun != "0" { 32 | go huobiSpotWs(redis) 33 | } 34 | 35 | // 火币期货 36 | if huobiRun != "0" { 37 | go huobiFutureWs(redis) 38 | } 39 | 40 | // binance 41 | if binanceRun != "0" { 42 | go binanceWs(redis) 43 | } 44 | 45 | for { 46 | select {} 47 | } 48 | 49 | } 50 | 51 | func okexWs(redis *redis.Client) { 52 | r1,_ := redis.SMembers("config:depth:okex:spot").Result() 53 | r2,_ := redis.SMembers("config:depth:okex:future").Result() 54 | 55 | if len(r1) > 0 || len(r2) > 0 { 56 | ws := tool.NewOkexWs() 57 | ws.OkexSetCallback(func(msg []byte) { 58 | ws.OkexDepth(msg) 59 | }) 60 | 61 | for _,v1 := range r1{ 62 | sub := fmt.Sprintf(`{"op":"subscribe","args":["spot/depth5:%s"]}`,v1) 63 | ws.OkexSubscribeDepth(sub) 64 | } 65 | 66 | for _,v2 := range r2 { 67 | sub := fmt.Sprintf(`{"op":"subscribe","args":["futures/depth5:%s"]}`,v2) 68 | ws.OkexSubscribeDepth(sub) 69 | } 70 | } 71 | } 72 | 73 | func bitmexWs(redis *redis.Client) { 74 | r,_ := redis.SMembers("config:depth:bitmex:future").Result() 75 | 76 | if len(r) > 0 { 77 | ws := tool.NewBitmexWs() 78 | ws.BitmexSetCallback(func(msg []byte) { 79 | ws.BitmexDepth(msg) 80 | }) 81 | 82 | for _,v := range r{ 83 | sub := fmt.Sprintf(`{"op": "subscribe", "args":["orderBook10:%s"]}`,v) 84 | ws.BitmexSubscribeDepth(sub) 85 | } 86 | } 87 | } 88 | 89 | func huobiSpotWs(redis *redis.Client) { 90 | r,_ := redis.SMembers("config:depth:huobi:spot").Result() 91 | 92 | if len(r) > 0 { 93 | ws := tool.NewHuobiWs("wss://api.huobi.pro/ws") 94 | ws.HuobiSetCallback(func(msg []byte) { 95 | ws.HuobiDepth(msg) 96 | }) 97 | 98 | for i,v := range r{ 99 | sub := fmt.Sprintf(`{"id":"id_%s","sub":"market.%s.depth.step0"}`,i,v) 100 | ws.HuobiSubscribeDepth(sub) 101 | } 102 | } 103 | } 104 | 105 | func huobiFutureWs(redis *redis.Client) { 106 | r,_ := redis.SMembers("config:depth:huobi:future").Result() 107 | 108 | if len(r) > 0 { 109 | ws := tool.NewHuobiWs("wss://www.hbdm.com/ws") 110 | ws.HuobiSetCallback(func(msg []byte) { 111 | ws.HuobiDepth(msg) 112 | }) 113 | 114 | for i,v := range r{ 115 | sub := fmt.Sprintf(`{"id":"id_%s","sub":"market.%s.depth.step0"}`,i,v) 116 | ws.HuobiSubscribeDepth(sub) 117 | } 118 | } 119 | } 120 | 121 | func binanceWs(redis *redis.Client) { 122 | r,_ := redis.SMembers("config:depth:binance:spot").Result() 123 | 124 | if len(r) > 0 { 125 | ws := tool.NewBinanceWs() 126 | ws.BinanceSetCallback(func(msg []byte) { 127 | ws.BinanceDepth(msg) 128 | }) 129 | 130 | split := []string{} 131 | for _,i := range r{ 132 | split = append(split,i+"@depth20") 133 | } 134 | stream := strings.Join(split,"/") 135 | keys := fmt.Sprintf("/stream?streams=%s",stream) 136 | 137 | ws.BinanceSubscribeDepth(keys) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /exchange_api/config/log.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const ( 4 | LOG_FILE_PATH = "D:\\code\\log" 5 | LOG_FILE_NAME = "depth_web.log" 6 | ) 7 | -------------------------------------------------------------------------------- /exchange_api/config/websocket.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | var ( 4 | OKEX_DEPTH_TABLE_SPOT = "spot/depth5" 5 | OKEX_DEPTH_TABLE_FUTURE = "futures/depth5" 6 | 7 | SPOT = "0" // 现货标识 8 | FUTURE = "1" // 期货标识 9 | ) 10 | -------------------------------------------------------------------------------- /exchange_api/db/redis.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "exchange_api/cfg" 5 | 6 | "github.com/go-redis/redis" 7 | ) 8 | 9 | func InitRedis() *redis.Client { 10 | redisConf := cfg.GetStringMap("redis") 11 | client := redis.NewClient(&redis.Options{ 12 | Addr: redisConf["host"].(string) + ":" + redisConf["port"].(string), 13 | Password: redisConf["auth"].(string), // no password set 14 | DB: redisConf["db"].(int), // use default DB 15 | }) 16 | 17 | return client 18 | } 19 | -------------------------------------------------------------------------------- /exchange_api/go.mod: -------------------------------------------------------------------------------- 1 | module exchange_api 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/deckarep/golang-set v1.7.1 // indirect 7 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 8 | github.com/gin-contrib/sessions v0.0.1 9 | github.com/gin-gonic/gin v1.4.0 10 | github.com/go-redis/redis v6.15.6+incompatible 11 | github.com/gorilla/websocket v1.4.2 12 | github.com/lestrrat-go/file-rotatelogs v2.2.0+incompatible 13 | github.com/lestrrat-go/strftime v1.0.0 // indirect 14 | github.com/nntaoli-project/GoEx v1.0.7 // indirect 15 | github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 16 | github.com/sirupsen/logrus v1.4.2 17 | github.com/spf13/viper v1.7.0 18 | ) 19 | -------------------------------------------------------------------------------- /exchange_api/go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 9 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 10 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= 11 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 12 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 13 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 14 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 15 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 16 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 17 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 18 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 19 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 20 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 21 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 22 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 23 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 24 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 25 | github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= 26 | github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw= 27 | github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= 28 | github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI= 29 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 30 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 31 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 32 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 33 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 34 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 35 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 36 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 37 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 38 | github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= 39 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= 40 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 41 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 42 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 43 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 44 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 45 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 46 | github.com/gin-contrib/sessions v0.0.1/go.mod h1:iziXm/6pvTtf7og1uxT499sel4h3S9DfwsrhNZ+REXM= 47 | github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g= 48 | github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= 49 | github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ= 50 | github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= 51 | github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= 52 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 53 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 54 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 55 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 56 | github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= 57 | github.com/go-redis/redis v6.15.6+incompatible h1:H9evprGPLI8+ci7fxQx6WNZHJSb7be8FqJQRhdQZ5Sg= 58 | github.com/go-redis/redis v6.15.6+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= 59 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 60 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 61 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 62 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 63 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 64 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 65 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 66 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 67 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 68 | github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= 69 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 70 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= 71 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 72 | github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= 73 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 74 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 75 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 76 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 77 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 78 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 79 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 80 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 81 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 82 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 83 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 84 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 85 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 86 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 87 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= 88 | github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= 89 | github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= 90 | github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= 91 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 92 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= 93 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 94 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 95 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 96 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 97 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= 98 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 99 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 100 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 101 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 102 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 103 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 104 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 105 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 106 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 107 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 108 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 109 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 110 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 111 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 112 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 113 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 114 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 115 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 116 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 117 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 118 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 119 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 120 | github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 121 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 122 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 123 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 124 | github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw= 125 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 126 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 127 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 128 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 129 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 130 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 131 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 132 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 133 | github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is= 134 | github.com/lestrrat-go/file-rotatelogs v2.2.0+incompatible h1:eXEwY0f2h6mcobdAxm4VRSWds4tqmlLdUqxu8ybiEEA= 135 | github.com/lestrrat-go/file-rotatelogs v2.2.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA= 136 | github.com/lestrrat-go/strftime v1.0.0 h1:wZIfTHGdu7TeGu318uLJwuQvTMt9UpRyS+XV2Rc4wo4= 137 | github.com/lestrrat-go/strftime v1.0.0/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g= 138 | github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= 139 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 140 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 141 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 142 | github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= 143 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 144 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 145 | github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc= 146 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 147 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 148 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 149 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 150 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 151 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 152 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 153 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 154 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 155 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 156 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 157 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 158 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 159 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 160 | github.com/nntaoli-project/GoEx v1.0.7/go.mod h1:ZOvrqd2u/Ll1d4N+qkSzfFsnN3cfaQwoZdaCoo0S9DI= 161 | github.com/nubo/jwt v0.0.0-20150918093313-da5b79c3bbaf/go.mod h1:LuR7jHS+7SJ6EywD7zZiO6h0vwTBSevFk5wunVt3gf4= 162 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 163 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 164 | github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= 165 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 166 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 167 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 168 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 169 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 170 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 171 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 172 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 173 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 174 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 175 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 176 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 177 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 178 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 179 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 180 | github.com/quasoft/memstore v0.0.0-20180925164028-84a050167438/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg= 181 | github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 h1:mZHayPoR0lNmnHyvtYjDeq0zlVHn9K/ZXoy17ylucdo= 182 | github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5/go.mod h1:GEXHk5HgEKCvEIIrSpFI3ozzG5xOKA2DVlEX/gGnewM= 183 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 184 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 185 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 186 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 187 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 188 | github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= 189 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 190 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 191 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 192 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 193 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 194 | github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= 195 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 196 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= 197 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 198 | github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= 199 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 200 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 201 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 202 | github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= 203 | github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= 204 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 205 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 206 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 207 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 208 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 209 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 210 | github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= 211 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 212 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 213 | github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw= 214 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= 215 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 216 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 217 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 218 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 219 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 220 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 221 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 222 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 223 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 224 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 225 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 226 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 227 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 228 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 229 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 230 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 231 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 232 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 233 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 234 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 235 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 236 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 237 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 238 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 239 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 240 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 241 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 242 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 243 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 244 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 245 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 246 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 247 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 248 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 249 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 250 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 251 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 252 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 253 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 254 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 255 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 256 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 257 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 258 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 259 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 260 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 261 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 262 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 263 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 264 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 265 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 266 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 267 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 268 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 269 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 270 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 271 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 272 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 273 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 274 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= 275 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 276 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 277 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 278 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 279 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 280 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 281 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 282 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= 283 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 284 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 285 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 286 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 287 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 288 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 289 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 290 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 291 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 292 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 293 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 294 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 295 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 296 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 297 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 298 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 299 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 300 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 301 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 302 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 303 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 304 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 305 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 306 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 307 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 308 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 309 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 310 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 311 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 312 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 313 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 314 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 315 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 316 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 317 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 318 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 319 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 320 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 321 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 322 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 323 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 324 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 325 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 326 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 327 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 328 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 329 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 330 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 331 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 332 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 333 | gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= 334 | gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= 335 | gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= 336 | gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= 337 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 338 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 339 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 340 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 341 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 342 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 343 | gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= 344 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 345 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 346 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 347 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 348 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 349 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 350 | -------------------------------------------------------------------------------- /exchange_api/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "exchange_api/cfg" 5 | "exchange_api/route" 6 | "fmt" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | // init cfg 12 | err := cfg.Initialize("./application") 13 | if err != nil { 14 | fmt.Printf("cfg err :%s", err.Error()) 15 | os.Exit(1) 16 | } 17 | 18 | r := route.InitRoute() 19 | _ = r.Run() 20 | } 21 | -------------------------------------------------------------------------------- /exchange_api/middleware/logger.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "exchange_api/config" 5 | "fmt" 6 | "github.com/gin-gonic/gin" 7 | "github.com/sirupsen/logrus" 8 | "os" 9 | "path" 10 | "time" 11 | ) 12 | 13 | // 日志记录到文件 14 | func LoggerToFile() gin.HandlerFunc { 15 | 16 | logFilePath := config.LOG_FILE_PATH 17 | logFileName := config.LOG_FILE_NAME 18 | 19 | //日志文件 20 | fileName := path.Join(logFilePath, logFileName) 21 | 22 | //判断是否存在 23 | if _,err := os.Stat(fileName);err != nil { 24 | _,_ = os.Create(fileName) 25 | } 26 | 27 | //写入文件 28 | src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend) 29 | if err != nil { 30 | fmt.Println("err", err) 31 | } 32 | 33 | //实例化 34 | logger := logrus.New() 35 | 36 | //设置输出 37 | logger.Out = src 38 | 39 | //设置日志级别 40 | logger.SetLevel(logrus.DebugLevel) 41 | 42 | //设置日志格式 43 | logger.SetFormatter(&logrus.TextFormatter{}) 44 | 45 | return func(c *gin.Context) { 46 | // 开始时间 47 | startTime := time.Now() 48 | 49 | // 处理请求 50 | c.Next() 51 | 52 | // 结束时间 53 | endTime := time.Now() 54 | 55 | // 执行时间 56 | latencyTime := endTime.Sub(startTime) 57 | 58 | // 请求方式 59 | reqMethod := c.Request.Method 60 | 61 | // 请求路由 62 | reqUri := c.Request.RequestURI 63 | 64 | // 状态码 65 | statusCode := c.Writer.Status() 66 | 67 | // 请求IP 68 | clientIP := c.ClientIP() 69 | 70 | // 日志格式 71 | logger.Infof("| %3d | %13v | %15s | %s | %s |", 72 | statusCode, 73 | latencyTime, 74 | clientIP, 75 | reqMethod, 76 | reqUri, 77 | ) 78 | } 79 | } 80 | 81 | // 日志记录到 MongoDB 82 | func LoggerToMongo() gin.HandlerFunc { 83 | return func(c *gin.Context) { 84 | 85 | } 86 | } 87 | 88 | // 日志记录到 ES 89 | func LoggerToES() gin.HandlerFunc { 90 | return func(c *gin.Context) { 91 | 92 | } 93 | } 94 | 95 | // 日志记录到 MQ 96 | func LoggerToMQ() gin.HandlerFunc { 97 | return func(c *gin.Context) { 98 | 99 | } 100 | } -------------------------------------------------------------------------------- /exchange_api/model/binance.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type BinanceDepth struct { 4 | Stream string `json:"stream"` 5 | Data struct { 6 | LastUpdateID int `json:"lastUpdateId"` 7 | Bids [][]interface{} `json:"bids"` 8 | Asks [][]interface{} `json:"asks"` 9 | } `json:"data"` 10 | } 11 | -------------------------------------------------------------------------------- /exchange_api/model/bitmex.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | type BitmexDepth struct { 6 | Table string `json:"table"` 7 | Action string `json:"action"` 8 | Data []struct { 9 | Symbol string `json:"symbol"` 10 | Asks [][]float64 `json:"asks"` 11 | Timestamp time.Time `json:"timestamp"` 12 | Bids [][]float64 `json:"bids"` 13 | } `json:"data"` 14 | } 15 | -------------------------------------------------------------------------------- /exchange_api/model/huobi.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type HuobiDepth struct { 4 | Ch string `json:"ch"` 5 | Ts int64 `json:"ts"` 6 | Tick struct { 7 | Bids [][]float64 `json:"bids"` 8 | Asks [][]float64 `json:"asks"` 9 | Version int64 `json:"version"` 10 | Ts int64 `json:"ts"` 11 | } `json:"tick"` 12 | } 13 | -------------------------------------------------------------------------------- /exchange_api/model/okex.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | type OkexDepth struct { 6 | Table string `json:"table"` 7 | Data []struct { 8 | Asks [][]interface{} `json:"asks"` 9 | Bids [][]interface{} `json:"bids"` 10 | InstrumentID string `json:"instrument_id"` 11 | Timestamp time.Time `json:"timestamp"` 12 | } `json:"data"` 13 | } 14 | -------------------------------------------------------------------------------- /exchange_api/route/api.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "exchange_api/app" 5 | "exchange_api/middleware" 6 | "exchange_api/tool" 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func InitRoute() *gin.Engine { 11 | r := gin.Default() 12 | r.Use(Cors()) // 跨域 13 | r.Use(middleware.LoggerToFile()) // 日志 14 | 15 | r.POST("/login",app.LoginPost) 16 | 17 | // auth 18 | auth := r.Group("/") 19 | auth.Use(tool.JWTAuth()) 20 | 21 | auth.GET("/depth",app.DepthIndex) 22 | auth.GET("/depth/edit",app.DepthEdit) 23 | auth.PUT("/depth/:platform",app.DepthUpdate) 24 | auth.GET("/depth/check/:platform",app.DepthCheck) 25 | auth.GET("/depth/commit",app.DepthCommit) 26 | 27 | auth.GET("/system",app.SystemIndex) 28 | auth.PUT("/system",app.SystemUpdate) 29 | auth.GET("/system/:key",app.SystemExec) 30 | 31 | auth.GET("/exchange",app.ExchangeEdit) 32 | auth.PUT("/exchange",app.ExchangeUpdate) 33 | 34 | return r 35 | } 36 | -------------------------------------------------------------------------------- /exchange_api/route/cors.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | ) 7 | 8 | func Cors() gin.HandlerFunc { 9 | return func(c *gin.Context) { 10 | method := c.Request.Method 11 | c.Header("Access-Control-Allow-Origin", "*") 12 | c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept,token") 13 | c.Header("Access-Control-Allow-Methods", "GET, OPTIONS, POST, PUT, DELETE") 14 | c.Set("content-type", "application/json") 15 | //放行所有OPTIONS方法 16 | if method == "OPTIONS" { 17 | c.JSON(http.StatusOK, "Options Request!") 18 | } 19 | 20 | c.Next() 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /exchange_api/tool/binance.go: -------------------------------------------------------------------------------- 1 | package tool 2 | 3 | import ( 4 | "encoding/json" 5 | "exchange_api/db" 6 | "exchange_api/model" 7 | "fmt" 8 | "github.com/go-redis/redis" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | type BinanceWs struct { 14 | *Wsbuilder 15 | sync.Once 16 | wsConn *WsConn 17 | redis *redis.Client 18 | 19 | depthData *model.BinanceDepth 20 | 21 | depthCallback func([]byte) 22 | 23 | } 24 | 25 | func NewBinanceWs() *BinanceWs { 26 | return &BinanceWs{} 27 | } 28 | 29 | func (baws *BinanceWs) BinanceSetCallback(f func([]byte)) { 30 | baws.depthCallback = f 31 | } 32 | 33 | func (baws *BinanceWs) BinanceConnect() { 34 | baws.Once.Do(func() { 35 | baws.wsConn = baws.Wsbuilder.Build() 36 | baws.wsConn.ReceiveMessage() 37 | }) 38 | } 39 | 40 | func (baws *BinanceWs) BinanceSubscribeDepth(path string) { 41 | baws.Wsbuilder = NewBuilder() 42 | baws.Wsbuilder.SetUrl("wss://stream.binance.com:9443"+path) // url配置 43 | baws.SetCheckStatusTime(time.Second * 30) 44 | baws.WsConfig.Handle = baws.handle 45 | baws.redis = db.InitRedis() 46 | baws.BinanceConnect() 47 | } 48 | 49 | func (baws *BinanceWs) handle(msg []byte) { 50 | baws.wsConn.UpdateActiveTime() 51 | baws.depthCallback(msg) 52 | } 53 | 54 | func (baws *BinanceWs) BinanceDepth(msg []byte) { 55 | err := json.Unmarshal(msg,&baws.depthData) 56 | if err == nil && len(baws.depthData.Data.Bids) > 0 { 57 | baws.depthToDb() 58 | } 59 | } 60 | 61 | func (baws *BinanceWs) depthToDb () { 62 | asks := ArrInterfaceToFloat64(baws.depthData.Data.Asks) 63 | bids := ArrInterfaceToFloat64(baws.depthData.Data.Bids) 64 | origin := map[string]interface{}{ 65 | "sell":asks, 66 | "buy":bids, 67 | } 68 | st ,_ := json.Marshal(origin) 69 | 70 | rst := map[string]interface{}{ 71 | "average_buy" : asks[0][0], 72 | "average_sell" : bids[0][0], 73 | "average_price" : asks[0][0], 74 | "microtime" : time.Now().UnixNano() / 1e6, 75 | "origin" : st, 76 | } 77 | 78 | symbol := string(baws.depthData.Stream[:len(baws.depthData.Stream)-8]) 79 | key := fmt.Sprintf("binance:depth:0:%s",symbol) 80 | 81 | baws.redis.HMSet(key,rst) 82 | baws.redis.Expire(key,time.Minute * 5) 83 | } 84 | 85 | func (baws *BinanceWs) BinanceDepthTmp(msg []byte) { 86 | err := json.Unmarshal(msg,&baws.depthData) 87 | if err == nil && len(baws.depthData.Data.Bids) > 0 { 88 | symbol := string(baws.depthData.Stream[:len(baws.depthData.Stream)-8]) 89 | if res ,_ := baws.redis.SIsMember("tmp:depth:binance",symbol).Result(); !res { 90 | baws.redis.SAdd("tmp:depth:binance",symbol) 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /exchange_api/tool/bitmex.go: -------------------------------------------------------------------------------- 1 | package tool 2 | 3 | import ( 4 | "encoding/json" 5 | "exchange_api/db" 6 | "exchange_api/model" 7 | "fmt" 8 | "github.com/go-redis/redis" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | type BitmexWs struct { 14 | *Wsbuilder 15 | sync.Once 16 | wsConn *WsConn 17 | redis *redis.Client 18 | 19 | depthData *model.BitmexDepth 20 | 21 | depthCallback func([]byte) 22 | 23 | } 24 | 25 | func NewBitmexWs() *BitmexWs { 26 | bitmexWs := &BitmexWs{Wsbuilder:NewBuilder()} 27 | bitmexWs.redis = db.InitRedis() 28 | bitmexWs.Wsbuilder.SetUrl("wss://www.bitmex.com/realtime") // url配置 29 | bitmexWs.Wsbuilder.SetHeartBeatData([]byte("ping")) // ping 30 | bitmexWs.Wsbuilder.SetHeartBeatTime(time.Second * 30) // 检测时间 31 | bitmexWs.Wsbuilder.SetCheckStatusTime(time.Second * 30) // 状态检测时间 32 | bitmexWs.WsConfig.Handle = bitmexWs.handle 33 | return bitmexWs 34 | } 35 | 36 | func (btws *BitmexWs) BitmexSetCallback(f func([]byte)) { 37 | btws.depthCallback = f 38 | } 39 | 40 | func (btws *BitmexWs) BitmexConnect() { 41 | btws.Once.Do(func() { 42 | btws.wsConn = btws.Wsbuilder.Build() 43 | btws.wsConn.ReceiveMessage() 44 | }) 45 | } 46 | 47 | func (btws *BitmexWs) BitmexSubscribeDepth(msg string) { 48 | btws.BitmexConnect() 49 | _ = btws.wsConn.Subscribe(msg) 50 | } 51 | 52 | func (btws *BitmexWs) handle(msg []byte) { 53 | 54 | if string(msg) == "pong" { 55 | btws.wsConn.UpdateActiveTime() 56 | } 57 | 58 | btws.depthCallback(msg) 59 | } 60 | 61 | func (btws *BitmexWs) BitmexDepth(msg []byte) { 62 | err := json.Unmarshal(msg,&btws.depthData) 63 | if err == nil && len(btws.depthData.Data) > 0 { 64 | btws.depthToDb() 65 | } 66 | } 67 | 68 | func (btws *BitmexWs) depthToDb () { 69 | origin := map[string]interface{}{ 70 | "sell":btws.depthData.Data[0].Asks, 71 | "buy":btws.depthData.Data[0].Bids, 72 | } 73 | st ,_ := json.Marshal(origin) 74 | 75 | rst := map[string]interface{}{ 76 | "average_buy" : btws.depthData.Data[0].Asks[0][0], 77 | "average_sell" : btws.depthData.Data[0].Bids[0][0], 78 | "average_price" : btws.depthData.Data[0].Asks[0][0], 79 | "microtime" : time.Now().UnixNano() / 1e6, 80 | "origin" : st, 81 | } 82 | 83 | key := fmt.Sprintf("bitmex:depth:1:%s",btws.depthData.Data[0].Symbol) 84 | 85 | btws.redis.HMSet(key,rst) 86 | btws.redis.Expire(key,time.Minute * 5) 87 | } 88 | 89 | func (btws *BitmexWs) BitmexDepthTmp(msg []byte) { 90 | err := json.Unmarshal(msg,&btws.depthData) 91 | if err == nil && len(btws.depthData.Data) > 0 { 92 | if res ,_ := btws.redis.SIsMember("tmp:depth:bitmex",btws.depthData.Data[0].Symbol).Result(); !res { 93 | btws.redis.SAdd("tmp:depth:bitmex",btws.depthData.Data[0].Symbol) 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /exchange_api/tool/huobi.go: -------------------------------------------------------------------------------- 1 | package tool 2 | 3 | import ( 4 | "encoding/json" 5 | "exchange_api/config" 6 | "exchange_api/db" 7 | "exchange_api/model" 8 | "fmt" 9 | "github.com/go-redis/redis" 10 | "strconv" 11 | "strings" 12 | "sync" 13 | "time" 14 | ) 15 | 16 | type HuobiWs struct { 17 | *Wsbuilder 18 | sync.Once 19 | wsConn *WsConn 20 | redis *redis.Client 21 | 22 | depthData *model.HuobiDepth 23 | 24 | depthCallback func([]byte) 25 | } 26 | 27 | func NewHuobiWs(url string) *HuobiWs { 28 | huobiWs := &HuobiWs{Wsbuilder:NewBuilder()} 29 | huobiWs.redis = db.InitRedis() 30 | huobiWs.Wsbuilder.SetUrl(url) // url配置 31 | huobiWs.Wsbuilder.SetCheckStatusTime(time.Second * 30) // 检测时间 32 | huobiWs.WsConfig.Handle = huobiWs.handle 33 | return huobiWs 34 | } 35 | 36 | func (hbws *HuobiWs) HuobiSetCallback(f func([]byte)) { 37 | hbws.depthCallback = f 38 | } 39 | 40 | func (hbws *HuobiWs) HuobiConnect() { 41 | hbws.Once.Do(func() { 42 | hbws.wsConn = hbws.Wsbuilder.Build() 43 | hbws.wsConn.ReceiveMessage() 44 | }) 45 | } 46 | 47 | func (hbws *HuobiWs) HuobiSubscribeDepth(msg string) { 48 | hbws.HuobiConnect() 49 | _ = hbws.wsConn.Subscribe(msg) 50 | } 51 | 52 | func (hbws *HuobiWs) handle(msg []byte) { 53 | text,err := GzipDecodeHuobi(msg) 54 | if err != nil { 55 | fmt.Println(err) 56 | } 57 | 58 | if strings.Contains(string(text),"ping") { 59 | str := strconv.FormatInt(time.Now().Unix(),10) 60 | pong := `{"pong": ` + str + `}` 61 | _ = hbws.wsConn.SendMessage([]byte(pong)) 62 | hbws.wsConn.UpdateActiveTime() 63 | } 64 | hbws.depthCallback(text) 65 | } 66 | 67 | func (hbws *HuobiWs) HuobiDepth (msg []byte) { 68 | err := json.Unmarshal(msg,&hbws.depthData) 69 | if err == nil && len(hbws.depthData.Tick.Bids) > 0 { 70 | hbws.depthToDb() 71 | } 72 | } 73 | 74 | func (hbws *HuobiWs) depthToDb () { 75 | origin := map[string]interface{}{ 76 | "sell":hbws.depthData.Tick.Asks, 77 | "buy":hbws.depthData.Tick.Bids, 78 | } 79 | st ,_ := json.Marshal(origin) 80 | 81 | rst := map[string]interface{}{ 82 | "average_buy" : hbws.depthData.Tick.Asks[0][0], 83 | "average_sell" : hbws.depthData.Tick.Bids[0][0], 84 | "average_price" : hbws.depthData.Tick.Asks[0][0], 85 | "microtime" : time.Now().UnixNano() / 1e6, 86 | "origin" : st, 87 | } 88 | 89 | var key string 90 | 91 | chs := strings.Split(hbws.depthData.Ch,".") 92 | if strings.Contains(chs[1],"_") { // 期货 93 | key = fmt.Sprintf("huobi:depth:%s:%s",config.FUTURE,chs[1]) 94 | }else { 95 | key = fmt.Sprintf("huobi:depth:%s:%s",config.SPOT,chs[1]) 96 | } 97 | 98 | hbws.redis.HMSet(key,rst) 99 | hbws.redis.Expire(key,time.Minute * 5) 100 | } 101 | 102 | func (hbws *HuobiWs) HuobiDepthTmp (msg []byte) { 103 | err := json.Unmarshal(msg,&hbws.depthData) 104 | if err == nil && len(hbws.depthData.Tick.Bids) > 0 { 105 | chs := strings.Split(hbws.depthData.Ch,".") 106 | if res,_ := hbws.redis.SIsMember("tmp:depth:huobi",chs[1]).Result(); !res { 107 | hbws.redis.SAdd("tmp:depth:huobi",chs[1]) 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /exchange_api/tool/jwt.go: -------------------------------------------------------------------------------- 1 | package tool 2 | 3 | import ( 4 | "github.com/dgrijalva/jwt-go" 5 | "github.com/gin-gonic/gin" 6 | "log" 7 | "net/http" 8 | "time" 9 | "errors" 10 | ) 11 | 12 | // JWTAuth 中间件,检查token 13 | func JWTAuth() gin.HandlerFunc { 14 | return func(c *gin.Context) { 15 | token := c.Request.Header.Get("token") 16 | if token == "" { 17 | c.JSON(http.StatusOK, gin.H{ 18 | "status": -200, 19 | "msg": "请求未携带token,无权限访问", 20 | }) 21 | c.Abort() 22 | return 23 | } 24 | 25 | j := NewJWT() 26 | // parseToken 解析token包含的信息 27 | claims, err := j.ParseToken(token) 28 | if err != nil { 29 | if err == TokenExpired { 30 | c.JSON(http.StatusOK, gin.H{ 31 | "status": -200, 32 | "msg": "授权已过期", 33 | }) 34 | c.Abort() 35 | return 36 | } 37 | c.JSON(http.StatusOK, gin.H{ 38 | "status": -200, 39 | "msg": err.Error(), 40 | }) 41 | c.Abort() 42 | return 43 | } 44 | // 继续交由下一个路由处理,并将解析出的信息传递下去 45 | c.Set("claims", claims) 46 | } 47 | } 48 | 49 | // JWT 签名结构 50 | type JWT struct { 51 | SigningKey []byte 52 | } 53 | 54 | // 一些常量 55 | var ( 56 | TokenExpired error = errors.New("Token is expired") 57 | TokenNotValidYet error = errors.New("Token not active yet") 58 | TokenMalformed error = errors.New("That's not even a token") 59 | TokenInvalid error = errors.New("Couldn't handle this token:") 60 | SignKey string = "tsewell" 61 | ) 62 | 63 | // 载荷,可以加一些自己需要的信息 64 | type CustomClaims struct { 65 | Name string `json:"name"` 66 | jwt.StandardClaims 67 | } 68 | 69 | // 新建一个jwt实例 70 | func NewJWT() *JWT { 71 | return &JWT{ 72 | []byte(GetSignKey()), 73 | } 74 | } 75 | 76 | // 获取signKey 77 | func GetSignKey() string { 78 | return SignKey 79 | } 80 | 81 | // 这是SignKey 82 | func SetSignKey(key string) string { 83 | SignKey = key 84 | return SignKey 85 | } 86 | 87 | // CreateToken 生成一个token 88 | func (j *JWT) CreateToken(claims CustomClaims) (string, error) { 89 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 90 | return token.SignedString(j.SigningKey) 91 | } 92 | 93 | // 解析Tokne 94 | func (j *JWT) ParseToken(tokenString string) (*CustomClaims, error) { 95 | token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) { 96 | return j.SigningKey, nil 97 | }) 98 | if err != nil { 99 | log.Println(err) 100 | if ve, ok := err.(*jwt.ValidationError); ok { 101 | if ve.Errors&jwt.ValidationErrorMalformed != 0 { 102 | return nil, TokenMalformed 103 | } else if ve.Errors&jwt.ValidationErrorExpired != 0 { 104 | // Token is expired 105 | return nil, TokenExpired 106 | } else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 { 107 | return nil, TokenNotValidYet 108 | } else { 109 | return nil, TokenInvalid 110 | } 111 | } 112 | } 113 | if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid { 114 | return claims, nil 115 | } 116 | return nil, TokenInvalid 117 | } 118 | 119 | // 更新token 120 | func (j *JWT) RefreshToken(tokenString string) (string, error) { 121 | jwt.TimeFunc = func() time.Time { 122 | return time.Unix(0, 0) 123 | } 124 | token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) { 125 | return j.SigningKey, nil 126 | }) 127 | if err != nil { 128 | return "", err 129 | } 130 | if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid { 131 | jwt.TimeFunc = time.Now 132 | claims.StandardClaims.ExpiresAt = time.Now().Add(1 * time.Hour).Unix() 133 | return j.CreateToken(*claims) 134 | } 135 | return "", TokenInvalid 136 | } 137 | -------------------------------------------------------------------------------- /exchange_api/tool/okex.go: -------------------------------------------------------------------------------- 1 | package tool 2 | 3 | import ( 4 | "bytes" 5 | "compress/flate" 6 | "encoding/json" 7 | "exchange_api/config" 8 | "exchange_api/db" 9 | "exchange_api/model" 10 | "fmt" 11 | "github.com/go-redis/redis" 12 | "io/ioutil" 13 | "sync" 14 | "time" 15 | ) 16 | 17 | type OkexWs struct { 18 | *Wsbuilder 19 | sync.Once 20 | wsConn *WsConn 21 | redis *redis.Client 22 | 23 | depthData *model.OkexDepth 24 | 25 | depthCallback func([]byte) 26 | 27 | } 28 | 29 | func NewOkexWs() *OkexWs { 30 | okexWs := &OkexWs{Wsbuilder:NewBuilder()} 31 | okexWs.redis = db.InitRedis() 32 | okexWs.Wsbuilder.SetUrl("wss://real.okex.com:8443/ws/v3") // url配置 33 | okexWs.Wsbuilder.SetHeartBeatData([]byte("ping")) // ping 34 | okexWs.Wsbuilder.SetHeartBeatTime(time.Second * 30) // 检测时间 35 | okexWs.Wsbuilder.SetCheckStatusTime(time.Second * 30) // 检测时间 36 | okexWs.WsConfig.Handle = okexWs.handle 37 | return okexWs 38 | } 39 | 40 | func (okws *OkexWs) OkexSetCallback(f func([]byte)) { 41 | okws.depthCallback = f 42 | } 43 | 44 | func (okws *OkexWs) OkexConnect() { 45 | okws.Once.Do(func() { 46 | okws.wsConn = okws.Wsbuilder.Build() 47 | okws.wsConn.ReceiveMessage() 48 | }) 49 | } 50 | 51 | func (okws *OkexWs) OkexSubscribeDepth(msg string) { 52 | okws.OkexConnect() 53 | _ = okws.wsConn.Subscribe(msg) 54 | } 55 | 56 | func (okws *OkexWs) handle(msg []byte) { 57 | reader :=flate.NewReader(bytes.NewBuffer(msg)) 58 | defer reader.Close() 59 | 60 | text,_ := ioutil.ReadAll(reader) 61 | 62 | if string(text) == "pong" { 63 | okws.wsConn.UpdateActiveTime() 64 | } 65 | 66 | okws.depthCallback(text) 67 | } 68 | 69 | func (okws *OkexWs) OkexDepth(msg []byte) { 70 | err := json.Unmarshal(msg,&okws.depthData) 71 | if err == nil && len(okws.depthData.Data) > 0 { 72 | okws.depthToDb() 73 | } 74 | } 75 | 76 | func (okws *OkexWs) depthToDb () { 77 | asks := ArrInterfaceToFloat64(okws.depthData.Data[0].Asks) 78 | bids := ArrInterfaceToFloat64(okws.depthData.Data[0].Bids) 79 | origin := map[string]interface{}{ 80 | "sell":asks, 81 | "buy":bids, 82 | } 83 | st ,_ := json.Marshal(origin) 84 | 85 | rst := map[string]interface{}{ 86 | "average_buy" : okws.depthData.Data[0].Asks[0][0], 87 | "average_sell" : okws.depthData.Data[0].Bids[0][0], 88 | "average_price" : okws.depthData.Data[0].Asks[0][0], 89 | "microtime" : time.Now().UnixNano() / 1e6, 90 | "origin" : st, 91 | } 92 | 93 | var key string 94 | 95 | if okws.depthData.Table == config.OKEX_DEPTH_TABLE_SPOT { 96 | key = fmt.Sprintf("okex:depth:%s:%s",config.SPOT,okws.depthData.Data[0].InstrumentID) 97 | }else { 98 | key = fmt.Sprintf("okex:depth:%s:%s",config.FUTURE,okws.depthData.Data[0].InstrumentID) 99 | } 100 | 101 | okws.redis.HMSet(key,rst) 102 | okws.redis.Expire(key,time.Minute * 5) 103 | } 104 | 105 | func (okws *OkexWs) OkexDepthTmp(msg []byte) { 106 | err := json.Unmarshal(msg,&okws.depthData) 107 | if err == nil && len(okws.depthData.Data) > 0 { 108 | if res ,_ := okws.redis.SIsMember("tmp:depth:okex",okws.depthData.Data[0].InstrumentID).Result(); !res { 109 | okws.redis.SAdd("tmp:depth:okex",okws.depthData.Data[0].InstrumentID) 110 | } 111 | 112 | } 113 | } -------------------------------------------------------------------------------- /exchange_api/tool/tmp_depth_ws.go: -------------------------------------------------------------------------------- 1 | package tool 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | func TmpOkexDepthWs(spot,future *[]string) { 11 | go func() { 12 | ws := NewOkexWs() 13 | ws.OkexSetCallback(func(msg []byte) { 14 | ws.OkexDepthTmp(msg) 15 | }) 16 | 17 | for _,i1 := range *spot{ 18 | key := fmt.Sprintf(`{"op":"subscribe","args":["spot/depth5:%s"]}`,i1) 19 | ws.OkexSubscribeDepth(key) 20 | } 21 | 22 | for _,i2 := range *future{ 23 | key := fmt.Sprintf(`{"op":"subscribe","args":["futures/depth5:%s"]}`,i2) 24 | ws.OkexSubscribeDepth(key) 25 | } 26 | 27 | t := time.After(time.Second * 30) 28 | 29 | for { 30 | select { 31 | case <-t: 32 | log.Println("okex 行情连接测试结束") 33 | return 34 | } 35 | } 36 | }() 37 | } 38 | 39 | func TmpHuobiDepthWs(spot,future *[]string) { 40 | go func() { // 火币现货 41 | ws := NewHuobiWs("wss://api.huobi.pro/ws") 42 | ws.HuobiSetCallback(func(msg []byte) { 43 | ws.HuobiDepthTmp(msg) 44 | }) 45 | 46 | for _,i:=range *spot{ 47 | key := fmt.Sprintf(`{"id":"I0TwXU3P","sub":"market.%s.depth.step0"}`,i) 48 | ws.HuobiSubscribeDepth(key) 49 | } 50 | 51 | t := time.After(time.Second * 30) 52 | 53 | for { 54 | select { 55 | case <-t: 56 | log.Println("Huobi 现货行情连接测试结束") 57 | return 58 | } 59 | } 60 | }() 61 | 62 | go func() { // 火币期货 63 | ws := NewHuobiWs("wss://www.hbdm.com/ws") 64 | ws.HuobiSetCallback(func(msg []byte) { 65 | ws.HuobiDepthTmp(msg) 66 | }) 67 | 68 | for _,i := range *future{ 69 | key := fmt.Sprintf(`{"id":"I0TwH86H","sub":"market.%s.depth.step0"}`,i) 70 | ws.HuobiSubscribeDepth(key) 71 | } 72 | 73 | t := time.After(time.Second * 30) 74 | 75 | for { 76 | select { 77 | case <-t: 78 | log.Println("Huobi 期货行情连接测试结束") 79 | return 80 | } 81 | } 82 | }() 83 | } 84 | 85 | func TmpBitmexDepthWs(spot,future *[]string) { 86 | go func() { 87 | ws := NewBitmexWs() 88 | ws.BitmexSetCallback(func(msg []byte) { 89 | ws.BitmexDepthTmp(msg) 90 | }) 91 | 92 | for _,i:=range *future{ 93 | key := fmt.Sprintf(`{"op": "subscribe", "args":["orderBook10:%s"]}`,i) 94 | ws.BitmexSubscribeDepth(key) 95 | } 96 | 97 | t := time.After(time.Second * 30) 98 | 99 | for { 100 | select { 101 | case <-t: 102 | log.Println("Bitmex 行情连接测试结束") 103 | return 104 | } 105 | } 106 | }() 107 | } 108 | 109 | func TmpBinanceDepthWs(spot,future *[]string) { 110 | go func() { 111 | ws := NewBinanceWs() 112 | ws.BinanceSetCallback(func(msg []byte) { 113 | ws.BinanceDepthTmp(msg) 114 | }) 115 | split := []string{} 116 | for _,i := range *spot{ 117 | split = append(split,i+"@depth20") 118 | } 119 | stream := strings.Join(split,"/") 120 | keys := fmt.Sprintf("/stream?streams=%s",stream) 121 | ws.BinanceSubscribeDepth(keys) 122 | 123 | t := time.After(time.Second * 30) 124 | 125 | for { 126 | select { 127 | case <-t: 128 | log.Println("Binance 行情连接测试结束") 129 | return 130 | } 131 | } 132 | }() 133 | } -------------------------------------------------------------------------------- /exchange_api/tool/tool.go: -------------------------------------------------------------------------------- 1 | package tool 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "crypto/aes" 7 | "crypto/cipher" 8 | "crypto/rand" 9 | "encoding/hex" 10 | "errors" 11 | "fmt" 12 | "io" 13 | "io/ioutil" 14 | "log" 15 | "os" 16 | "os/exec" 17 | "regexp" 18 | "strconv" 19 | "strings" 20 | "time" 21 | ) 22 | 23 | // AES 加密 24 | func AesEncrypt(plaintext string) (string, error) { 25 | key := []byte("abcdefgh12345678") 26 | block, err := aes.NewCipher(key) 27 | if err != nil { 28 | return "", err 29 | } 30 | ciphertext := make([]byte, aes.BlockSize+len(plaintext)) 31 | iv := ciphertext[:aes.BlockSize] 32 | if _, err := io.ReadFull(rand.Reader, iv); err != nil { 33 | return "", err 34 | } 35 | cipher.NewCFBEncrypter(block, iv).XORKeyStream(ciphertext[aes.BlockSize:], 36 | []byte(plaintext)) 37 | return hex.EncodeToString(ciphertext), nil 38 | } 39 | 40 | // AES 解密 41 | func AesDecrypt(d string) (string, error) { 42 | key := []byte("abcdefgh12345678") 43 | ciphertext, err := hex.DecodeString(d) 44 | if err != nil { 45 | return "", err 46 | } 47 | block, err := aes.NewCipher(key) 48 | if err != nil { 49 | return "", err 50 | } 51 | if len(ciphertext) < aes.BlockSize { 52 | return "", errors.New("ciphertext too short") 53 | } 54 | iv := ciphertext[:aes.BlockSize] 55 | ciphertext = ciphertext[aes.BlockSize:] 56 | cipher.NewCFBDecrypter(block, iv).XORKeyStream(ciphertext, ciphertext) 57 | return string(ciphertext), nil 58 | } 59 | 60 | // interface -> float64 61 | func ArrInterfaceToFloat64(arrString [][]interface{}) [][]float64 { 62 | 63 | var arrFloat [][]float64 64 | for _,i := range arrString { 65 | 66 | var tmpArr []float64 67 | for k,j := range i{ 68 | if k <= 1 { 69 | st := j.(string) 70 | tmp,_ := strconv.ParseFloat(st,64) // string 转 float64 71 | tmpArr = append(tmpArr,tmp) 72 | } 73 | } 74 | arrFloat = append(arrFloat,tmpArr) 75 | } 76 | 77 | return arrFloat 78 | } 79 | 80 | // 解压火币WS数据 81 | func GzipDecodeHuobi(in []byte) ([]byte, error) { 82 | reader, err := gzip.NewReader(bytes.NewReader(in)) 83 | if err != nil { 84 | var out []byte 85 | return out, err 86 | } 87 | defer reader.Close() 88 | 89 | return ioutil.ReadAll(reader) 90 | } 91 | 92 | func MsToTime(ms string) (time.Time, error) { 93 | msInt, err := strconv.ParseInt(ms, 10, 64) 94 | if err != nil { 95 | return time.Time{}, err 96 | } 97 | 98 | tm := time.Unix(0, msInt*int64(time.Millisecond)) 99 | 100 | return tm, nil 101 | } 102 | 103 | func IsSpotOfOkex(symbol string) bool { 104 | pattern := "\\d+" //反斜杠要转义 105 | result,_ := regexp.MatchString(pattern,symbol) 106 | return !result 107 | } 108 | 109 | func IsSpotOfHuobi(symbol string) bool { 110 | return !strings.Contains(symbol,"_") 111 | } 112 | 113 | func IsSpotOfBitmex(symbol string) bool { 114 | return false 115 | } 116 | 117 | func IsSpotOfBinance(symbol string) bool { 118 | return true 119 | } 120 | 121 | 122 | func GetCurrentPath() string { 123 | dir, err := os.Getwd() 124 | if err != nil { 125 | log.Fatal(err) 126 | } 127 | return strings.Replace(dir, "\\", "/", -1) 128 | } 129 | 130 | func SystemExec(s string) { 131 | cmd := exec.Command("/bin/bash", "-c", s) 132 | var out bytes.Buffer 133 | var stderr bytes.Buffer 134 | cmd.Stdout = &out 135 | cmd.Stderr = &stderr 136 | err := cmd.Run() 137 | if err != nil { 138 | log.Println(fmt.Sprint(err) + ": " + stderr.String()) 139 | return 140 | } 141 | log.Println("Result: " + out.String()) 142 | } 143 | -------------------------------------------------------------------------------- /exchange_api/tool/websocket.go: -------------------------------------------------------------------------------- 1 | package tool 2 | 3 | import ( 4 | "github.com/gorilla/websocket" 5 | "log" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | type WsConfig struct { 11 | url string 12 | HeartBeatTime time.Duration // 心跳检测间隔时间 13 | heartBeatData []byte // 心跳数据 14 | CheckStatusTime time.Duration // 检测连接状态间隔时间 15 | ReconnectIntervalTime time.Duration // 定时重连时间间隔 16 | 17 | Handle func([]byte) 18 | } 19 | 20 | type WsConn struct { 21 | *websocket.Conn 22 | sync.Mutex 23 | WsConfig 24 | 25 | ActiveTime time.Time 26 | ActiveTimeL sync.Mutex 27 | 28 | subs []string 29 | } 30 | 31 | type Wsbuilder struct { 32 | WsConfig *WsConfig 33 | } 34 | 35 | func NewBuilder() *Wsbuilder { 36 | return &Wsbuilder{&WsConfig{}} 37 | } 38 | 39 | func (b *Wsbuilder ) SetUrl(url string) { 40 | b.WsConfig.url = url 41 | } 42 | 43 | func (b *Wsbuilder) SetHeartBeatData(data []byte) { 44 | b.WsConfig.heartBeatData = data 45 | } 46 | 47 | func (b *Wsbuilder) SetHeartBeatTime(time time.Duration) { 48 | b.WsConfig.HeartBeatTime = time 49 | } 50 | 51 | func (b *Wsbuilder) SetCheckStatusTime(time time.Duration) { 52 | b.WsConfig.CheckStatusTime = time 53 | } 54 | func (b *Wsbuilder) Build() *WsConn { 55 | WsConn := &WsConn{WsConfig:*b.WsConfig} 56 | return WsConn.NewWs() 57 | } 58 | 59 | func (ws *WsConn) NewWs() *WsConn { 60 | ws.Lock() 61 | defer ws.Unlock() 62 | 63 | if err := ws.connect();err != nil { 64 | log.Println(ws.url, "ws connect error ", err) 65 | } 66 | 67 | ws.ActiveTime = time.Now() 68 | ws.heartBeat() 69 | ws.checkStatusTimer() 70 | 71 | return ws 72 | } 73 | 74 | func (ws *WsConn) checkStatusTimer() { 75 | checkStatusTimer := time.NewTicker(ws.CheckStatusTime) 76 | 77 | go func() { 78 | for { 79 | select { 80 | case <-checkStatusTimer.C: 81 | if time.Now().Sub(ws.ActiveTime) > ws.CheckStatusTime *2 { 82 | ws.ReConnect() 83 | } 84 | } 85 | } 86 | }() 87 | } 88 | 89 | func (ws *WsConn) heartBeat() { 90 | if ws.HeartBeatTime == 0 { 91 | return 92 | } 93 | 94 | wsHeart := time.NewTicker(ws.HeartBeatTime) 95 | 96 | go func() { 97 | for { 98 | select { 99 | case <-wsHeart.C: 100 | _ = ws.SendMessage(ws.heartBeatData) 101 | ws.UpdateActiveTime() 102 | } 103 | } 104 | }() 105 | } 106 | 107 | func (ws *WsConn) UpdateActiveTime() { 108 | ws.ActiveTimeL.Lock() 109 | defer ws.ActiveTimeL.Unlock() 110 | 111 | ws.ActiveTime = time.Now() 112 | } 113 | 114 | func (ws *WsConn) connect() error { 115 | log.Printf("connecting to %s", ws.url) 116 | conn ,_,err := websocket.DefaultDialer.Dial(ws.url,nil) 117 | if err != nil { 118 | return err 119 | } 120 | ws.Conn = conn 121 | 122 | return nil 123 | } 124 | 125 | func (ws *WsConn) ReConnect() { 126 | ws.Lock() 127 | defer ws.Unlock() 128 | 129 | log.Println("close ws error :", ws.Close()) 130 | time.Sleep(time.Second) 131 | 132 | if err := ws.connect(); err != nil { 133 | log.Println(ws.url, "ws connect error ", err) 134 | return 135 | } 136 | 137 | for _,sub := range ws.subs { 138 | log.Println("subscribe:", sub) 139 | _ = ws.SendMessage([]byte(sub)) 140 | } 141 | 142 | } 143 | 144 | func (ws *WsConn) Subscribe(subEvent string) error { 145 | log.Println("Subscribe:", subEvent) 146 | 147 | err := ws.SendMessage([]byte(subEvent)) 148 | if err != nil { 149 | return err 150 | } 151 | ws.subs = append(ws.subs, subEvent) 152 | return nil 153 | } 154 | 155 | func (ws *WsConn) SendMessage(msg []byte) error { 156 | ws.Lock() 157 | defer ws.Unlock() 158 | 159 | err := ws.WriteMessage(websocket.TextMessage,msg) 160 | if err != nil { 161 | return err 162 | } 163 | 164 | return nil 165 | } 166 | 167 | func (ws *WsConn) ReceiveMessage() { 168 | go func() { 169 | for { 170 | _,message ,err := ws.ReadMessage() 171 | 172 | if err != nil { 173 | log.Println("websocket消息读取失败 : ",err) 174 | } 175 | ws.Handle([]byte(message)) 176 | } 177 | }() 178 | } -------------------------------------------------------------------------------- /exchange_vue/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { "modules": false }], 4 | "stage-2" 5 | ], 6 | "plugins": ["transform-runtime"], 7 | "comments": false, 8 | "env": { 9 | "test": { 10 | "presets": ["env", "stage-2"], 11 | "plugins": [ "istanbul" ] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /exchange_vue/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /exchange_vue/.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | src/* 4 | -------------------------------------------------------------------------------- /exchange_vue/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | sourceType: 'module' 6 | }, 7 | env: { 8 | browser: true, 9 | node: true 10 | }, 11 | extends: 'eslint:recommended', 12 | // required to lint *.vue files 13 | plugins: [ 14 | 'html' 15 | ], 16 | // check if imports actually resolve 17 | 'settings': { 18 | 'import/resolver': { 19 | 'webpack': { 20 | 'config': 'build/webpack.base.conf.js' 21 | } 22 | } 23 | }, 24 | // add your custom rules here 25 | 'rules': { 26 | // allow paren-less arrow functions 27 | 'arrow-parens': 0, 28 | // allow async-await 29 | 'generator-star-spacing': 0, 30 | // allow debugger during development 31 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 32 | "indent": ["error", "tab"] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /exchange_vue/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | test/unit/coverage 8 | test/e2e/reports 9 | selenium-debug.log 10 | package-lock.json 11 | .DS_Store 12 | manage/ -------------------------------------------------------------------------------- /exchange_vue/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserlist" field in package.json 6 | "autoprefixer": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /exchange_vue/COPYING: -------------------------------------------------------------------------------- 1 | 2 | GNU GENERAL PUBLIC LICENSE 3 | Version 2, June 1991 4 | 5 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 6 | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 7 | Everyone is permitted to copy and distribute verbatim copies 8 | of this license document, but changing it is not allowed. 9 | 10 | Preamble 11 | 12 | The licenses for most software are designed to take away your 13 | freedom to share and change it. By contrast, the GNU General Public 14 | License is intended to guarantee your freedom to share and change free 15 | software--to make sure the software is free for all its users. This 16 | General Public License applies to most of the Free Software 17 | Foundation's software and to any other program whose authors commit to 18 | using it. (Some other Free Software Foundation software is covered by 19 | the GNU Library General Public License instead.) You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | this service if you wish), that you receive source code or can get it 26 | if you want it, that you can change the software or use pieces of it 27 | in new free programs; and that you know you can do these things. 28 | 29 | To protect your rights, we need to make restrictions that forbid 30 | anyone to deny you these rights or to ask you to surrender the rights. 31 | These restrictions translate to certain responsibilities for you if you 32 | distribute copies of the software, or if you modify it. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must give the recipients all the rights that 36 | you have. You must make sure that they, too, receive or can get the 37 | source code. And you must show them these terms so they know their 38 | rights. 39 | 40 | We protect your rights with two steps: (1) copyright the software, and 41 | (2) offer you this license which gives you legal permission to copy, 42 | distribute and/or modify the software. 43 | 44 | Also, for each author's protection and ours, we want to make certain 45 | that everyone understands that there is no warranty for this free 46 | software. If the software is modified by someone else and passed on, we 47 | want its recipients to know that what they have is not the original, so 48 | that any problems introduced by others will not reflect on the original 49 | authors' reputations. 50 | 51 | Finally, any free program is threatened constantly by software 52 | patents. We wish to avoid the danger that redistributors of a free 53 | program will individually obtain patent licenses, in effect making the 54 | program proprietary. To prevent this, we have made it clear that any 55 | patent must be licensed for everyone's free use or not licensed at all. 56 | 57 | The precise terms and conditions for copying, distribution and 58 | modification follow. 59 | 60 | GNU GENERAL PUBLIC LICENSE 61 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 62 | 63 | 0. This License applies to any program or other work which contains 64 | a notice placed by the copyright holder saying it may be distributed 65 | under the terms of this General Public License. The "Program", below, 66 | refers to any such program or work, and a "work based on the Program" 67 | means either the Program or any derivative work under copyright law: 68 | that is to say, a work containing the Program or a portion of it, 69 | either verbatim or with modifications and/or translated into another 70 | language. (Hereinafter, translation is included without limitation in 71 | the term "modification".) Each licensee is addressed as "you". 72 | 73 | Activities other than copying, distribution and modification are not 74 | covered by this License; they are outside its scope. The act of 75 | running the Program is not restricted, and the output from the Program 76 | is covered only if its contents constitute a work based on the 77 | Program (independent of having been made by running the Program). 78 | Whether that is true depends on what the Program does. 79 | 80 | 1. You may copy and distribute verbatim copies of the Program's 81 | source code as you receive it, in any medium, provided that you 82 | conspicuously and appropriately publish on each copy an appropriate 83 | copyright notice and disclaimer of warranty; keep intact all the 84 | notices that refer to this License and to the absence of any warranty; 85 | and give any other recipients of the Program a copy of this License 86 | along with the Program. 87 | 88 | You may charge a fee for the physical act of transferring a copy, and 89 | you may at your option offer warranty protection in exchange for a fee. 90 | 91 | 2. You may modify your copy or copies of the Program or any portion 92 | of it, thus forming a work based on the Program, and copy and 93 | distribute such modifications or work under the terms of Section 1 94 | above, provided that you also meet all of these conditions: 95 | 96 | a) You must cause the modified files to carry prominent notices 97 | stating that you changed the files and the date of any change. 98 | 99 | b) You must cause any work that you distribute or publish, that in 100 | whole or in part contains or is derived from the Program or any 101 | part thereof, to be licensed as a whole at no charge to all third 102 | parties under the terms of this License. 103 | 104 | c) If the modified program normally reads commands interactively 105 | when run, you must cause it, when started running for such 106 | interactive use in the most ordinary way, to print or display an 107 | announcement including an appropriate copyright notice and a 108 | notice that there is no warranty (or else, saying that you provide 109 | a warranty) and that users may redistribute the program under 110 | these conditions, and telling the user how to view a copy of this 111 | License. (Exception: if the Program itself is interactive but 112 | does not normally print such an announcement, your work based on 113 | the Program is not required to print an announcement.) 114 | 115 | These requirements apply to the modified work as a whole. If 116 | identifiable sections of that work are not derived from the Program, 117 | and can be reasonably considered independent and separate works in 118 | themselves, then this License, and its terms, do not apply to those 119 | sections when you distribute them as separate works. But when you 120 | distribute the same sections as part of a whole which is a work based 121 | on the Program, the distribution of the whole must be on the terms of 122 | this License, whose permissions for other licensees extend to the 123 | entire whole, and thus to each and every part regardless of who wrote it. 124 | 125 | Thus, it is not the intent of this section to claim rights or contest 126 | your rights to work written entirely by you; rather, the intent is to 127 | exercise the right to control the distribution of derivative or 128 | collective works based on the Program. 129 | 130 | In addition, mere aggregation of another work not based on the Program 131 | with the Program (or with a work based on the Program) on a volume of 132 | a storage or distribution medium does not bring the other work under 133 | the scope of this License. 134 | 135 | 3. You may copy and distribute the Program (or a work based on it, 136 | under Section 2) in object code or executable form under the terms of 137 | Sections 1 and 2 above provided that you also do one of the following: 138 | 139 | a) Accompany it with the complete corresponding machine-readable 140 | source code, which must be distributed under the terms of Sections 141 | 1 and 2 above on a medium customarily used for software interchange; or, 142 | 143 | b) Accompany it with a written offer, valid for at least three 144 | years, to give any third party, for a charge no more than your 145 | cost of physically performing source distribution, a complete 146 | machine-readable copy of the corresponding source code, to be 147 | distributed under the terms of Sections 1 and 2 above on a medium 148 | customarily used for software interchange; or, 149 | 150 | c) Accompany it with the information you received as to the offer 151 | to distribute corresponding source code. (This alternative is 152 | allowed only for noncommercial distribution and only if you 153 | received the program in object code or executable form with such 154 | an offer, in accord with Subsection b above.) 155 | 156 | The source code for a work means the preferred form of the work for 157 | making modifications to it. For an executable work, complete source 158 | code means all the source code for all modules it contains, plus any 159 | associated interface definition files, plus the scripts used to 160 | control compilation and installation of the executable. However, as a 161 | special exception, the source code distributed need not include 162 | anything that is normally distributed (in either source or binary 163 | form) with the major components (compiler, kernel, and so on) of the 164 | operating system on which the executable runs, unless that component 165 | itself accompanies the executable. 166 | 167 | If distribution of executable or object code is made by offering 168 | access to copy from a designated place, then offering equivalent 169 | access to copy the source code from the same place counts as 170 | distribution of the source code, even though third parties are not 171 | compelled to copy the source along with the object code. 172 | 173 | 4. You may not copy, modify, sublicense, or distribute the Program 174 | except as expressly provided under this License. Any attempt 175 | otherwise to copy, modify, sublicense or distribute the Program is 176 | void, and will automatically terminate your rights under this License. 177 | However, parties who have received copies, or rights, from you under 178 | this License will not have their licenses terminated so long as such 179 | parties remain in full compliance. 180 | 181 | 5. You are not required to accept this License, since you have not 182 | signed it. However, nothing else grants you permission to modify or 183 | distribute the Program or its derivative works. These actions are 184 | prohibited by law if you do not accept this License. Therefore, by 185 | modifying or distributing the Program (or any work based on the 186 | Program), you indicate your acceptance of this License to do so, and 187 | all its terms and conditions for copying, distributing or modifying 188 | the Program or works based on it. 189 | 190 | 6. Each time you redistribute the Program (or any work based on the 191 | Program), the recipient automatically receives a license from the 192 | original licensor to copy, distribute or modify the Program subject to 193 | these terms and conditions. You may not impose any further 194 | restrictions on the recipients' exercise of the rights granted herein. 195 | You are not responsible for enforcing compliance by third parties to 196 | this License. 197 | 198 | 7. If, as a consequence of a court judgment or allegation of patent 199 | infringement or for any other reason (not limited to patent issues), 200 | conditions are imposed on you (whether by court order, agreement or 201 | otherwise) that contradict the conditions of this License, they do not 202 | excuse you from the conditions of this License. If you cannot 203 | distribute so as to satisfy simultaneously your obligations under this 204 | License and any other pertinent obligations, then as a consequence you 205 | may not distribute the Program at all. For example, if a patent 206 | license would not permit royalty-free redistribution of the Program by 207 | all those who receive copies directly or indirectly through you, then 208 | the only way you could satisfy both it and this License would be to 209 | refrain entirely from distribution of the Program. 210 | 211 | If any portion of this section is held invalid or unenforceable under 212 | any particular circumstance, the balance of the section is intended to 213 | apply and the section as a whole is intended to apply in other 214 | circumstances. 215 | 216 | It is not the purpose of this section to induce you to infringe any 217 | patents or other property right claims or to contest validity of any 218 | such claims; this section has the sole purpose of protecting the 219 | integrity of the free software distribution system, which is 220 | implemented by public license practices. Many people have made 221 | generous contributions to the wide range of software distributed 222 | through that system in reliance on consistent application of that 223 | system; it is up to the author/donor to decide if he or she is willing 224 | to distribute software through any other system and a licensee cannot 225 | impose that choice. 226 | 227 | This section is intended to make thoroughly clear what is believed to 228 | be a consequence of the rest of this License. 229 | 230 | 8. If the distribution and/or use of the Program is restricted in 231 | certain countries either by patents or by copyrighted interfaces, the 232 | original copyright holder who places the Program under this License 233 | may add an explicit geographical distribution limitation excluding 234 | those countries, so that distribution is permitted only in or among 235 | countries not thus excluded. In such case, this License incorporates 236 | the limitation as if written in the body of this License. 237 | 238 | 9. The Free Software Foundation may publish revised and/or new versions 239 | of the General Public License from time to time. Such new versions will 240 | be similar in spirit to the present version, but may differ in detail to 241 | address new problems or concerns. 242 | 243 | Each version is given a distinguishing version number. If the Program 244 | specifies a version number of this License which applies to it and "any 245 | later version", you have the option of following the terms and conditions 246 | either of that version or of any later version published by the Free 247 | Software Foundation. If the Program does not specify a version number of 248 | this License, you may choose any version ever published by the Free Software 249 | Foundation. 250 | 251 | 10. If you wish to incorporate parts of the Program into other free 252 | programs whose distribution conditions are different, write to the author 253 | to ask for permission. For software which is copyrighted by the Free 254 | Software Foundation, write to the Free Software Foundation; we sometimes 255 | make exceptions for this. Our decision will be guided by the two goals 256 | of preserving the free status of all derivatives of our free software and 257 | of promoting the sharing and reuse of software generally. 258 | 259 | NO WARRANTY 260 | 261 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 262 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 263 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 264 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 265 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 266 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 267 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 268 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 269 | REPAIR OR CORRECTION. 270 | 271 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 272 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 273 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 274 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 275 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 276 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 277 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 278 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 279 | POSSIBILITY OF SUCH DAMAGES. 280 | 281 | END OF TERMS AND CONDITIONS 282 | 283 | How to Apply These Terms to Your New Programs 284 | 285 | If you develop a new program, and you want it to be of the greatest 286 | possible use to the public, the best way to achieve this is to make it 287 | free software which everyone can redistribute and change under these terms. 288 | 289 | To do so, attach the following notices to the program. It is safest 290 | to attach them to the start of each source file to most effectively 291 | convey the exclusion of warranty; and each file should have at least 292 | the "copyright" line and a pointer to where the full notice is found. 293 | 294 | 295 | Copyright (C) 296 | 297 | This program is free software; you can redistribute it and/or modify 298 | it under the terms of the GNU General Public License as published by 299 | the Free Software Foundation; either version 2 of the License, or 300 | (at your option) any later version. 301 | 302 | This program is distributed in the hope that it will be useful, 303 | but WITHOUT ANY WARRANTY; without even the implied warranty of 304 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 305 | GNU General Public License for more details. 306 | 307 | You should have received a copy of the GNU General Public License 308 | along with this program; if not, write to the Free Software 309 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 310 | 311 | 312 | Also add information on how to contact you by electronic and paper mail. 313 | 314 | If the program is interactive, make it output a short notice like this 315 | when it starts in an interactive mode: 316 | 317 | Gnomovision version 69, Copyright (C) year name of author 318 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 319 | This is free software, and you are welcome to redistribute it 320 | under certain conditions; type `show c' for details. 321 | 322 | The hypothetical commands `show w' and `show c' should show the appropriate 323 | parts of the General Public License. Of course, the commands you use may 324 | be called something other than `show w' and `show c'; they could even be 325 | mouse-clicks or menu items--whatever suits your program. 326 | 327 | You should also get your employer (if you work as a programmer) or your 328 | school, if any, to sign a "copyright disclaimer" for the program, if 329 | necessary. Here is a sample; alter the names: 330 | 331 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 332 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 333 | 334 | , 1 April 1989 335 | Ty Coon, President of Vice 336 | 337 | This General Public License does not permit incorporating your program into 338 | proprietary programs. If your program is a subroutine library, you may 339 | consider it more useful to permit linking proprietary applications with the 340 | library. If this is what you want to do, use the GNU Library General 341 | Public License instead of this License. -------------------------------------------------------------------------------- /exchange_vue/build/build.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | process.env.NODE_ENV = 'production' 4 | 5 | var ora = require('ora') 6 | var rm = require('rimraf') 7 | var path = require('path') 8 | var chalk = require('chalk') 9 | var webpack = require('webpack') 10 | var config = require('../config') 11 | var webpackConfig = require('./webpack.prod.conf') 12 | 13 | var spinner = ora('building for production...') 14 | spinner.start() 15 | 16 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 17 | if (err) throw err 18 | webpack(webpackConfig, function (err, stats) { 19 | spinner.stop() 20 | if (err) throw err 21 | process.stdout.write(stats.toString({ 22 | colors: true, 23 | modules: false, 24 | children: false, 25 | chunks: false, 26 | chunkModules: false 27 | }) + '\n\n') 28 | 29 | console.log(chalk.cyan(' Build complete.\n')) 30 | console.log(chalk.yellow( 31 | ' Tip: built files are meant to be served over an HTTP server.\n' + 32 | ' Opening index.html over file:// won\'t work.\n' 33 | )) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /exchange_vue/build/check-versions.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk') 2 | var semver = require('semver') 3 | var packageConfig = require('../package.json') 4 | var shell = require('shelljs') 5 | function exec (cmd) { 6 | return require('child_process').execSync(cmd).toString().trim() 7 | } 8 | 9 | var versionRequirements = [ 10 | { 11 | name: 'node', 12 | currentVersion: semver.clean(process.version), 13 | versionRequirement: packageConfig.engines.node 14 | }, 15 | ] 16 | 17 | if (shell.which('npm')) { 18 | versionRequirements.push({ 19 | name: 'npm', 20 | currentVersion: exec('npm --version'), 21 | versionRequirement: packageConfig.engines.npm 22 | }) 23 | } 24 | 25 | module.exports = function () { 26 | var warnings = [] 27 | for (var i = 0; i < versionRequirements.length; i++) { 28 | var mod = versionRequirements[i] 29 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 30 | warnings.push(mod.name + ': ' + 31 | chalk.red(mod.currentVersion) + ' should be ' + 32 | chalk.green(mod.versionRequirement) 33 | ) 34 | } 35 | } 36 | 37 | if (warnings.length) { 38 | console.log('') 39 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 40 | console.log() 41 | for (var i = 0; i < warnings.length; i++) { 42 | var warning = warnings[i] 43 | console.log(' ' + warning) 44 | } 45 | console.log() 46 | process.exit(1) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /exchange_vue/build/dev-client.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | require('eventsource-polyfill') 3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 4 | 5 | hotClient.subscribe(function (event) { 6 | if (event.action === 'reload') { 7 | window.location.reload() 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /exchange_vue/build/dev-server.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | var config = require('../config') 4 | if (!process.env.NODE_ENV) { 5 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) 6 | } 7 | 8 | var opn = require('opn') 9 | var path = require('path') 10 | var express = require('express') 11 | var webpack = require('webpack') 12 | var proxyMiddleware = require('http-proxy-middleware') 13 | var webpackConfig = process.env.NODE_ENV === 'testing' 14 | ? require('./webpack.prod.conf') 15 | : require('./webpack.dev.conf') 16 | 17 | // default port where dev server listens for incoming traffic 18 | var port = process.env.PORT || config.dev.port 19 | // automatically open browser, if not set will be false 20 | var autoOpenBrowser = !!config.dev.autoOpenBrowser 21 | // Define HTTP proxies to your custom API backend 22 | // https://github.com/chimurai/http-proxy-middleware 23 | var proxyTable = config.dev.proxyTable 24 | 25 | var app = express() 26 | var compiler = webpack(webpackConfig) 27 | 28 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 29 | publicPath: webpackConfig.output.publicPath, 30 | quiet: true 31 | }) 32 | 33 | var hotMiddleware = require('webpack-hot-middleware')(compiler, { 34 | log: () => {} 35 | }) 36 | // force page reload when html-webpack-plugin template changes 37 | compiler.plugin('compilation', function (compilation) { 38 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 39 | hotMiddleware.publish({ action: 'reload' }) 40 | cb() 41 | }) 42 | }) 43 | 44 | // proxy api requests 45 | // Object.keys(proxyTable).forEach(function (context) { 46 | // var options = proxyTable[context] 47 | // if (typeof options === 'string') { 48 | // options = { target: options } 49 | // } 50 | // app.use(proxyMiddleware(options.filter || context, options)) 51 | // }) 52 | 53 | var context = config.dev.context 54 | 55 | switch(process.env.NODE_ENV){ 56 | case 'local': var proxypath = 'http://localhost:8001'; break; 57 | case 'online': var proxypath = 'http://elm.cangdu.org'; break; 58 | } 59 | var options = { 60 | target: proxypath, 61 | changeOrigin: true, 62 | } 63 | if (context.length) { 64 | app.use(proxyMiddleware(context, options)) 65 | } 66 | // handle fallback for HTML5 history API 67 | app.use(require('connect-history-api-fallback')()) 68 | 69 | // serve webpack bundle output 70 | app.use(devMiddleware) 71 | 72 | // enable hot-reload and state-preserving 73 | // compilation error display 74 | app.use(hotMiddleware) 75 | 76 | // serve pure static assets 77 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 78 | app.use(staticPath, express.static('./static')) 79 | 80 | var uri = 'http://localhost:' + port 81 | 82 | var _resolve 83 | var readyPromise = new Promise(resolve => { 84 | _resolve = resolve 85 | }) 86 | 87 | console.log('> Starting dev server...') 88 | devMiddleware.waitUntilValid(() => { 89 | console.log('> Listening at ' + uri + '\n') 90 | // when env is testing, don't need open it 91 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { 92 | opn(uri) 93 | } 94 | _resolve() 95 | }) 96 | 97 | var server = app.listen(port) 98 | 99 | module.exports = { 100 | ready: readyPromise, 101 | close: () => { 102 | server.close() 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /exchange_vue/build/utils.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 4 | 5 | exports.assetsPath = function (_path) { 6 | var assetsSubDirectory = process.env.NODE_ENV === 'production' 7 | ? config.build.assetsSubDirectory 8 | : config.dev.assetsSubDirectory 9 | return path.posix.join(assetsSubDirectory, _path) 10 | } 11 | 12 | exports.cssLoaders = function (options) { 13 | options = options || {} 14 | 15 | var cssLoader = { 16 | loader: 'css-loader', 17 | options: { 18 | minimize: process.env.NODE_ENV === 'production', 19 | sourceMap: options.sourceMap 20 | } 21 | } 22 | 23 | // generate loader string to be used with extract text plugin 24 | function generateLoaders (loader, loaderOptions) { 25 | var loaders = [cssLoader] 26 | if (loader) { 27 | loaders.push({ 28 | loader: loader + '-loader', 29 | options: Object.assign({}, loaderOptions, { 30 | sourceMap: options.sourceMap 31 | }) 32 | }) 33 | } 34 | 35 | // Extract CSS when that option is specified 36 | // (which is the case during production build) 37 | if (options.extract) { 38 | return ExtractTextPlugin.extract({ 39 | use: loaders, 40 | fallback: 'vue-style-loader' 41 | }) 42 | } else { 43 | return ['vue-style-loader'].concat(loaders) 44 | } 45 | } 46 | 47 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 48 | return { 49 | css: generateLoaders(), 50 | postcss: generateLoaders(), 51 | less: generateLoaders('less'), 52 | sass: generateLoaders('sass', { indentedSyntax: true }), 53 | scss: generateLoaders('sass'), 54 | stylus: generateLoaders('stylus'), 55 | styl: generateLoaders('stylus') 56 | } 57 | } 58 | 59 | // Generate loaders for standalone style files (outside of .vue) 60 | exports.styleLoaders = function (options) { 61 | var output = [] 62 | var loaders = exports.cssLoaders(options) 63 | for (var extension in loaders) { 64 | var loader = loaders[extension] 65 | output.push({ 66 | test: new RegExp('\\.' + extension + '$'), 67 | use: loader 68 | }) 69 | } 70 | return output 71 | } 72 | -------------------------------------------------------------------------------- /exchange_vue/build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var config = require('../config') 3 | var isProduction = process.env.NODE_ENV === 'production' 4 | 5 | module.exports = { 6 | loaders: utils.cssLoaders({ 7 | sourceMap: isProduction 8 | ? config.build.productionSourceMap 9 | : config.dev.cssSourceMap, 10 | extract: isProduction 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /exchange_vue/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var config = require('../config') 4 | var vueLoaderConfig = require('./vue-loader.conf') 5 | 6 | function resolve (dir) { 7 | return path.join(__dirname, '..', dir) 8 | } 9 | 10 | module.exports = { 11 | entry: { 12 | app: './src/main.js' 13 | }, 14 | output: { 15 | path: config.build.assetsRoot, 16 | filename: '[name].js', 17 | publicPath: process.env.NODE_ENV === 'production' 18 | ? config.build.assetsPublicPath 19 | : config.dev.assetsPublicPath 20 | }, 21 | resolve: { 22 | extensions: ['.js', '.vue', '.json'], 23 | alias: { 24 | 'vue$': 'vue/dist/vue.esm.js', 25 | '@': resolve('src') 26 | } 27 | }, 28 | module: { 29 | rules: [ 30 | { 31 | test: /\.(js|vue)$/, 32 | loader: 'eslint-loader', 33 | enforce: 'pre', 34 | include: [resolve('src'), resolve('test')], 35 | options: { 36 | formatter: require('eslint-friendly-formatter') 37 | } 38 | }, 39 | { 40 | test: /\.vue$/, 41 | loader: 'vue-loader', 42 | options: vueLoaderConfig 43 | }, 44 | { 45 | test: /\.js$/, 46 | loader: 'babel-loader', 47 | include: [resolve('src'), resolve('test')] 48 | }, 49 | { 50 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 51 | loader: 'url-loader', 52 | options: { 53 | limit: 10000, 54 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 55 | } 56 | }, 57 | { 58 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 59 | loader: 'url-loader', 60 | options: { 61 | limit: 10000, 62 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 63 | } 64 | } 65 | ] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /exchange_vue/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var webpack = require('webpack') 3 | var config = require('../config') 4 | var merge = require('webpack-merge') 5 | var baseWebpackConfig = require('./webpack.base.conf') 6 | var HtmlWebpackPlugin = require('html-webpack-plugin') 7 | var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 8 | 9 | // add hot-reload related code to entry chunks 10 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 11 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) 12 | }) 13 | 14 | module.exports = merge(baseWebpackConfig, { 15 | module: { 16 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 17 | }, 18 | // cheap-module-eval-source-map is faster for development 19 | devtool: '#cheap-module-eval-source-map', 20 | plugins: [ 21 | new webpack.DefinePlugin({ 22 | 'process.env': config.dev.env 23 | }), 24 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 25 | new webpack.HotModuleReplacementPlugin(), 26 | new webpack.NoEmitOnErrorsPlugin(), 27 | // https://github.com/ampedandwired/html-webpack-plugin 28 | new HtmlWebpackPlugin({ 29 | filename: 'index.html', 30 | template: 'index.html', 31 | inject: true 32 | }), 33 | new FriendlyErrorsPlugin() 34 | ] 35 | }) 36 | -------------------------------------------------------------------------------- /exchange_vue/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var webpack = require('webpack') 4 | var config = require('../config') 5 | var merge = require('webpack-merge') 6 | var baseWebpackConfig = require('./webpack.base.conf') 7 | var CopyWebpackPlugin = require('copy-webpack-plugin') 8 | var HtmlWebpackPlugin = require('html-webpack-plugin') 9 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 10 | var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 11 | 12 | var env = process.env.NODE_ENV === 'testing' 13 | ? require('../config/test.env') 14 | : config.build.env 15 | 16 | var webpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ 19 | sourceMap: config.build.productionSourceMap, 20 | extract: true 21 | }) 22 | }, 23 | devtool: config.build.productionSourceMap ? '#source-map' : false, 24 | output: { 25 | path: config.build.assetsRoot, 26 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 27 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 28 | }, 29 | plugins: [ 30 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 31 | new webpack.DefinePlugin({ 32 | 'process.env': env 33 | }), 34 | new webpack.optimize.UglifyJsPlugin({ 35 | compress: { 36 | warnings: false 37 | }, 38 | sourceMap: true 39 | }), 40 | // extract css into its own file 41 | new ExtractTextPlugin({ 42 | filename: utils.assetsPath('css/[name].[contenthash].css') 43 | }), 44 | // Compress extracted CSS. We are using this plugin so that possible 45 | // duplicated CSS from different components can be deduped. 46 | new OptimizeCSSPlugin({ 47 | cssProcessorOptions: { 48 | safe: true 49 | } 50 | }), 51 | // generate dist index.html with correct asset hash for caching. 52 | // you can customize output by editing /index.html 53 | // see https://github.com/ampedandwired/html-webpack-plugin 54 | new HtmlWebpackPlugin({ 55 | filename: process.env.NODE_ENV === 'testing' 56 | ? 'index.html' 57 | : config.build.index, 58 | template: 'index.html', 59 | inject: true, 60 | minify: { 61 | removeComments: true, 62 | collapseWhitespace: true, 63 | removeAttributeQuotes: true 64 | // more options: 65 | // https://github.com/kangax/html-minifier#options-quick-reference 66 | }, 67 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 68 | chunksSortMode: 'dependency' 69 | }), 70 | // split vendor js into its own file 71 | new webpack.optimize.CommonsChunkPlugin({ 72 | name: 'vendor', 73 | minChunks: function (module, count) { 74 | // any required modules inside node_modules are extracted to vendor 75 | return ( 76 | module.resource && 77 | /\.js$/.test(module.resource) && 78 | module.resource.indexOf( 79 | path.join(__dirname, '../node_modules') 80 | ) === 0 81 | ) 82 | } 83 | }), 84 | // extract webpack runtime and module manifest to its own file in order to 85 | // prevent vendor hash from being updated whenever app bundle is updated 86 | new webpack.optimize.CommonsChunkPlugin({ 87 | name: 'manifest', 88 | chunks: ['vendor'] 89 | }), 90 | // copy custom static assets 91 | new CopyWebpackPlugin([ 92 | { 93 | from: path.resolve(__dirname, '../static'), 94 | to: config.build.assetsSubDirectory, 95 | ignore: ['.*'] 96 | } 97 | ]) 98 | ] 99 | }) 100 | 101 | if (config.build.productionGzip) { 102 | var CompressionWebpackPlugin = require('compression-webpack-plugin') 103 | 104 | webpackConfig.plugins.push( 105 | new CompressionWebpackPlugin({ 106 | asset: '[path].gz[query]', 107 | algorithm: 'gzip', 108 | test: new RegExp( 109 | '\\.(' + 110 | config.build.productionGzipExtensions.join('|') + 111 | ')$' 112 | ), 113 | threshold: 10240, 114 | minRatio: 0.8 115 | }) 116 | ) 117 | } 118 | 119 | if (config.build.bundleAnalyzerReport) { 120 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 121 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 122 | } 123 | 124 | module.exports = webpackConfig 125 | -------------------------------------------------------------------------------- /exchange_vue/build/webpack.test.conf.js: -------------------------------------------------------------------------------- 1 | // This is the webpack config used for unit tests. 2 | 3 | var utils = require('./utils') 4 | var webpack = require('webpack') 5 | var merge = require('webpack-merge') 6 | var baseConfig = require('./webpack.base.conf') 7 | 8 | var webpackConfig = merge(baseConfig, { 9 | // use inline sourcemap for karma-sourcemap-loader 10 | module: { 11 | rules: utils.styleLoaders() 12 | }, 13 | devtool: '#inline-source-map', 14 | resolveLoader: { 15 | alias: { 16 | // necessary to to make lang="scss" work in test when using vue-loader's ?inject option 17 | // see discussion at https://github.com/vuejs/vue-loader/issues/724 18 | 'scss-loader': 'sass-loader' 19 | } 20 | }, 21 | plugins: [ 22 | new webpack.DefinePlugin({ 23 | 'process.env': require('../config/test.env') 24 | }) 25 | ] 26 | }) 27 | 28 | // no need for app entry during tests 29 | delete webpackConfig.entry 30 | 31 | module.exports = webpackConfig 32 | -------------------------------------------------------------------------------- /exchange_vue/config/dev.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var prodEnv = require('./prod.env') 3 | 4 | module.exports = merge(prodEnv, { 5 | NODE_ENV: '"development"' 6 | }) 7 | -------------------------------------------------------------------------------- /exchange_vue/config/index.js: -------------------------------------------------------------------------------- 1 | // see http://vuejs-templates.github.io/webpack for documentation. 2 | var path = require('path') 3 | 4 | module.exports = { 5 | build: { 6 | env: require('./prod.env'), 7 | index: path.resolve(__dirname, '../manage/index.html'), 8 | assetsRoot: path.resolve(__dirname, '../manage'), 9 | assetsSubDirectory: 'static', 10 | assetsPublicPath: '/manage/', 11 | productionSourceMap: false, 12 | // Gzip off by default as many popular static hosts such as 13 | // Surge or Netlify already gzip all static assets for you. 14 | // Before setting to `true`, make sure to: 15 | // npm install --save-dev compression-webpack-plugin 16 | productionGzip: false, 17 | productionGzipExtensions: ['js', 'css'], 18 | // Run the build command with an extra argument to 19 | // View the bundle analyzer report after build finishes: 20 | // `npm run build --report` 21 | // Set to `true` or `false` to always turn it on or off 22 | bundleAnalyzerReport: process.env.npm_config_report 23 | }, 24 | dev: { 25 | env: require('./dev.env'), 26 | port: 8002, 27 | autoOpenBrowser: true, 28 | assetsSubDirectory: 'static', 29 | assetsPublicPath: '/', 30 | proxyTable: {}, 31 | context: [ //代理路径 32 | '/shopping', 33 | '/ugc', 34 | '/v1', 35 | '/v2', 36 | '/v3', 37 | '/v4', 38 | '/bos', 39 | '/member', 40 | '/promotion', 41 | '/eus', 42 | '/payapi', 43 | '/admin', 44 | '/statis', 45 | '/img', 46 | ], 47 | // CSS Sourcemaps off by default because relative paths are "buggy" 48 | // with this option, according to the CSS-Loader README 49 | // (https://github.com/webpack/css-loader#sourcemaps) 50 | // In our experience, they generally work as expected, 51 | // just be aware of this issue when enabling this option. 52 | cssSourceMap: false 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /exchange_vue/config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /exchange_vue/config/test.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var devEnv = require('./dev.env') 3 | 4 | module.exports = merge(devEnv, { 5 | NODE_ENV: '"testing"' 6 | }) 7 | -------------------------------------------------------------------------------- /exchange_vue/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitTsewell/exchange_data/a2291a3082e6f67f4fd179ad1c6a11170df25ab3/exchange_vue/favicon.ico -------------------------------------------------------------------------------- /exchange_vue/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | back-manage 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /exchange_vue/manage/index.html: -------------------------------------------------------------------------------- 1 | back-manage
-------------------------------------------------------------------------------- /exchange_vue/manage/static/fonts/element-icons.b02bdc1.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitTsewell/exchange_data/a2291a3082e6f67f4fd179ad1c6a11170df25ab3/exchange_vue/manage/static/fonts/element-icons.b02bdc1.ttf -------------------------------------------------------------------------------- /exchange_vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue2-manage", 3 | "version": "1.0.1", 4 | "description": "vue2-manage", 5 | "author": "cangdu <1264889788@qq.com>", 6 | "license": "GPL", 7 | "private": true, 8 | "scripts": { 9 | "dev": "cross-env NODE_ENV=online node build/dev-server.js", 10 | "local": "cross-env NODE_ENV=local node build/dev-server.js", 11 | "build": "node build/build.js", 12 | "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run", 13 | "e2e": "node test/e2e/runner.js", 14 | "test": "npm run unit && npm run e2e", 15 | "lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs" 16 | }, 17 | "dependencies": { 18 | "axios": "^0.19.0", 19 | "echarts": "^3.5.4", 20 | "element-ui": "^1.2.9", 21 | "time-formater": "^1.0.1", 22 | "vue": "^2.2.6", 23 | "vue-quill-editor": "^2.2.1", 24 | "vue-router": "^2.3.1", 25 | "vue-simplemde": "^0.3.8", 26 | "vuex": "^2.3.1" 27 | }, 28 | "devDependencies": { 29 | "autoprefixer": "^6.7.2", 30 | "babel-core": "^6.22.1", 31 | "babel-eslint": "^7.1.1", 32 | "babel-loader": "^6.2.10", 33 | "babel-plugin-component": "^0.9.1", 34 | "babel-plugin-istanbul": "^4.1.1", 35 | "babel-plugin-transform-runtime": "^6.22.0", 36 | "babel-polyfill": "^6.23.0", 37 | "babel-preset-env": "^1.3.2", 38 | "babel-preset-stage-2": "^6.22.0", 39 | "babel-register": "^6.22.0", 40 | "babel-runtime": "^6.23.0", 41 | "chai": "^3.5.0", 42 | "chalk": "^1.1.3", 43 | "chromedriver": "^2.27.2", 44 | "connect-history-api-fallback": "^1.3.0", 45 | "copy-webpack-plugin": "^4.0.1", 46 | "cross-env": "^4.0.0", 47 | "cross-spawn": "^5.0.1", 48 | "css-loader": "^0.28.0", 49 | "eslint": "^4.18.2", 50 | "eslint-config-standard": "^6.2.1", 51 | "eslint-friendly-formatter": "^2.0.7", 52 | "eslint-loader": "^1.7.1", 53 | "eslint-plugin-html": "^2.0.0", 54 | "eslint-plugin-promise": "^3.4.0", 55 | "eslint-plugin-standard": "^2.0.1", 56 | "eventsource-polyfill": "^0.9.6", 57 | "express": "^4.14.1", 58 | "extract-text-webpack-plugin": "^2.0.0", 59 | "file-loader": "^0.11.1", 60 | "friendly-errors-webpack-plugin": "^1.1.3", 61 | "html-webpack-plugin": "^2.28.0", 62 | "http-proxy-middleware": "^0.17.3", 63 | "inject-loader": "^3.0.0", 64 | "karma": "^1.4.1", 65 | "karma-coverage": "^1.1.1", 66 | "karma-mocha": "^1.3.0", 67 | "karma-phantomjs-launcher": "^1.0.2", 68 | "karma-phantomjs-shim": "^1.4.0", 69 | "karma-sinon-chai": "^1.3.1", 70 | "karma-sourcemap-loader": "^0.3.7", 71 | "karma-spec-reporter": "0.0.30", 72 | "karma-webpack": "^2.0.2", 73 | "less": "^2.7.2", 74 | "less-loader": "^4.0.3", 75 | "lolex": "^1.5.2", 76 | "mocha": "^3.2.0", 77 | "nightwatch": "^0.9.12", 78 | "opn": "^4.0.2", 79 | "optimize-css-assets-webpack-plugin": "^1.3.0", 80 | "ora": "^1.2.0", 81 | "phantomjs-prebuilt": "^2.1.14", 82 | "rimraf": "^2.6.0", 83 | "selenium-server": "^3.0.1", 84 | "semver": "^5.3.0", 85 | "shelljs": "^0.7.6", 86 | "sinon": "^2.1.0", 87 | "sinon-chai": "^2.8.0", 88 | "url-loader": "^0.5.8", 89 | "vue-loader": "^11.3.4", 90 | "vue-style-loader": "^2.0.5", 91 | "vue-template-compiler": "^2.2.6", 92 | "webpack": "^2.3.3", 93 | "webpack-bundle-analyzer": "^3.3.2", 94 | "webpack-dev-middleware": "^1.10.0", 95 | "webpack-hot-middleware": "^2.18.0", 96 | "webpack-merge": "^4.1.0" 97 | }, 98 | "engines": { 99 | "node": ">= 4.0.0", 100 | "npm": ">= 3.0.0" 101 | }, 102 | "browserslist": [ 103 | "> 1%", 104 | "last 2 versions", 105 | "not ie <= 8" 106 | ] 107 | } 108 | -------------------------------------------------------------------------------- /exchange_vue/screenshots/ewm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitTsewell/exchange_data/a2291a3082e6f67f4fd179ad1c6a11170df25ab3/exchange_vue/screenshots/ewm.png -------------------------------------------------------------------------------- /exchange_vue/screenshots/manage_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitTsewell/exchange_data/a2291a3082e6f67f4fd179ad1c6a11170df25ab3/exchange_vue/screenshots/manage_home.png -------------------------------------------------------------------------------- /exchange_vue/screenshots/manage_shop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitTsewell/exchange_data/a2291a3082e6f67f4fd179ad1c6a11170df25ab3/exchange_vue/screenshots/manage_shop.png -------------------------------------------------------------------------------- /exchange_vue/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /exchange_vue/src/api/getData.js: -------------------------------------------------------------------------------- 1 | import fetch from '@/config/fetch' 2 | 3 | /** 4 | * 登陆 5 | */ 6 | 7 | export const login = data => fetch('/admin/login', data, 'POST'); 8 | 9 | /** 10 | * 退出 11 | */ 12 | 13 | export const signout = () => fetch('/admin/signout'); 14 | 15 | /** 16 | * 获取用户信息 17 | */ 18 | 19 | export const getAdminInfo = () => fetch('/admin/info'); 20 | 21 | /** 22 | * api请求量 23 | */ 24 | 25 | export const apiCount = date => fetch('/statis/api/' + date + '/count'); 26 | 27 | /** 28 | * 所有api请求量 29 | */ 30 | 31 | export const apiAllCount = () => fetch('/statis/api/count'); 32 | 33 | 34 | /** 35 | * 所有api请求信息 36 | */ 37 | 38 | export const apiAllRecord = () => fetch('/statis/api/all'); 39 | 40 | /** 41 | * 用户注册量 42 | */ 43 | 44 | export const userCount = date => fetch('/statis/user/' + date + '/count'); 45 | 46 | /** 47 | * 某一天订单数量 48 | */ 49 | 50 | export const orderCount = date => fetch('/statis/order/' + date + '/count'); 51 | 52 | 53 | /** 54 | * 某一天管理员注册量 55 | */ 56 | 57 | export const adminDayCount = date => fetch('/statis/admin/' + date + '/count'); 58 | 59 | /** 60 | * 管理员列表 61 | */ 62 | 63 | export const adminList = data => fetch('/admin/all', data); 64 | 65 | /** 66 | * 管理员数量 67 | */ 68 | 69 | export const adminCount = () => fetch('/admin/count'); 70 | 71 | /** 72 | * 获取定位城市 73 | */ 74 | 75 | export const cityGuess = () => fetch('/v1/cities', { 76 | type: 'guess' 77 | }); 78 | 79 | /** 80 | * 添加商铺 81 | */ 82 | 83 | export const addShop = data => fetch('/shopping/addShop', data, 'POST'); 84 | 85 | /** 86 | * 获取搜索地址 87 | */ 88 | 89 | export const searchplace = (cityid, value) => fetch('/v1/pois', { 90 | type: 'search', 91 | city_id: cityid, 92 | keyword: value 93 | }); 94 | 95 | /** 96 | * 获取当前店铺食品种类 97 | */ 98 | 99 | export const getCategory = restaurant_id => fetch('/shopping/getcategory/' + restaurant_id); 100 | 101 | /** 102 | * 添加食品种类 103 | */ 104 | 105 | export const addCategory = data => fetch('/shopping/addcategory', data, 'POST'); 106 | 107 | 108 | /** 109 | * 添加食品 110 | */ 111 | 112 | export const addFood = data => fetch('/shopping/addfood', data, 'POST'); 113 | 114 | 115 | /** 116 | * category 种类列表 117 | */ 118 | 119 | export const foodCategory = (latitude, longitude) => fetch('/shopping/v2/restaurant/category'); 120 | 121 | /** 122 | * 获取餐馆列表 123 | */ 124 | 125 | export const getResturants = data => fetch('/shopping/restaurants', data); 126 | 127 | /** 128 | * 获取餐馆详细信息 129 | */ 130 | 131 | export const getResturantDetail = restaurant_id => fetch('/shopping/restaurant/' + restaurant_id); 132 | 133 | /** 134 | * 获取餐馆数量 135 | */ 136 | 137 | export const getResturantsCount = () => fetch('/shopping/restaurants/count'); 138 | 139 | /** 140 | * 更新餐馆信息 141 | */ 142 | 143 | export const updateResturant = data => fetch('/shopping/updateshop', data, 'POST'); 144 | 145 | /** 146 | * 删除餐馆 147 | */ 148 | 149 | export const deleteResturant = restaurant_id => fetch('/shopping/restaurant/' + restaurant_id, {}, 'DELETE'); 150 | 151 | /** 152 | * 获取食品列表 153 | */ 154 | 155 | export const getFoods = data => fetch('/shopping/v2/foods', data); 156 | 157 | /** 158 | * 获取食品数量 159 | */ 160 | 161 | export const getFoodsCount = data => fetch('/shopping/v2/foods/count', data); 162 | 163 | 164 | /** 165 | * 获取menu列表 166 | */ 167 | 168 | export const getMenu = data => fetch('/shopping/v2/menu', data); 169 | 170 | /** 171 | * 获取menu详情 172 | */ 173 | 174 | export const getMenuById = category_id => fetch('/shopping/v2/menu/' + category_id); 175 | 176 | /** 177 | * 更新食品信息 178 | */ 179 | 180 | export const updateFood = data => fetch('/shopping/v2/updatefood', data, 'POST'); 181 | 182 | /** 183 | * 删除食品 184 | */ 185 | 186 | export const deleteFood = food_id => fetch('/shopping/v2/food/' + food_id, {}, 'DELETE'); 187 | 188 | /** 189 | * 获取用户列表 190 | */ 191 | 192 | export const getUserList = data => fetch('/v1/users/list', data); 193 | 194 | /** 195 | * 获取用户数量 196 | */ 197 | 198 | export const getUserCount = data => fetch('/v1/users/count', data); 199 | 200 | /** 201 | * 获取订单列表 202 | */ 203 | 204 | export const getOrderList = data => fetch('/bos/orders', data); 205 | 206 | /** 207 | * 获取订单数量 208 | */ 209 | 210 | export const getOrderCount = data => fetch('/bos/orders/count', data); 211 | 212 | /** 213 | * 获取用户信息 214 | */ 215 | 216 | export const getUserInfo = user_id => fetch('/v1/user/' + user_id); 217 | 218 | /** 219 | * 获取地址信息 220 | */ 221 | 222 | export const getAddressById = address_id => fetch('/v1/addresse/' + address_id); 223 | 224 | /** 225 | * 获取用户分布信息 226 | */ 227 | 228 | export const getUserCity = () => fetch('/v1/user/city/count'); 229 | -------------------------------------------------------------------------------- /exchange_vue/src/assets/img/avator.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitTsewell/exchange_data/a2291a3082e6f67f4fd179ad1c6a11170df25ab3/exchange_vue/src/assets/img/avator.jpg -------------------------------------------------------------------------------- /exchange_vue/src/assets/svg/password.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exchange_vue/src/assets/svg/username.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exchange_vue/src/components/headTop.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 60 | 61 | 80 | -------------------------------------------------------------------------------- /exchange_vue/src/components/tendency.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 138 | 139 | 146 | -------------------------------------------------------------------------------- /exchange_vue/src/components/visitorPie.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 71 | 72 | 80 | -------------------------------------------------------------------------------- /exchange_vue/src/config/env.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 配置编译环境和线上环境之间的切换 3 | * 4 | * baseUrl: 域名地址 5 | * routerMode: 路由模式 6 | * baseImgPath: 图片存放地址 7 | * 8 | */ 9 | let baseUrl = ''; 10 | let routerMode = 'hash'; 11 | let baseImgPath; 12 | 13 | if (process.env.NODE_ENV == 'development') { 14 | baseUrl = ''; 15 | baseImgPath = '/img/'; 16 | }else{ 17 | baseUrl = '//elm.cangdu.org'; 18 | baseImgPath = '//elm.cangdu.org/img/'; 19 | } 20 | 21 | export { 22 | baseUrl, 23 | routerMode, 24 | baseImgPath 25 | } -------------------------------------------------------------------------------- /exchange_vue/src/config/fetch.js: -------------------------------------------------------------------------------- 1 | import { baseUrl } from './env' 2 | 3 | export default async(url = '', data = {}, type = 'GET', method = 'fetch') => { 4 | type = type.toUpperCase(); 5 | url = baseUrl + url; 6 | 7 | if (type == 'GET') { 8 | let dataStr = ''; //数据拼接字符串 9 | Object.keys(data).forEach(key => { 10 | dataStr += key + '=' + data[key] + '&'; 11 | }) 12 | 13 | if (dataStr !== '') { 14 | dataStr = dataStr.substr(0, dataStr.lastIndexOf('&')); 15 | url = url + '?' + dataStr; 16 | } 17 | } 18 | 19 | if (window.fetch && method == 'fetch') { 20 | let requestConfig = { 21 | credentials: 'include', 22 | method: type, 23 | headers: { 24 | 'Accept': 'application/json', 25 | 'Content-Type': 'application/json' 26 | }, 27 | mode: "cors", 28 | cache: "force-cache" 29 | } 30 | 31 | if (type == 'POST') { 32 | Object.defineProperty(requestConfig, 'body', { 33 | value: JSON.stringify(data) 34 | }) 35 | } 36 | 37 | try { 38 | const response = await fetch(url, requestConfig); 39 | const responseJson = await response.json(); 40 | return responseJson 41 | } catch (error) { 42 | throw new Error(error) 43 | } 44 | } else { 45 | return new Promise((resolve, reject) => { 46 | let requestObj; 47 | if (window.XMLHttpRequest) { 48 | requestObj = new XMLHttpRequest(); 49 | } else { 50 | requestObj = new ActiveXObject; 51 | } 52 | 53 | let sendData = ''; 54 | if (type == 'POST') { 55 | sendData = JSON.stringify(data); 56 | } 57 | 58 | requestObj.open(type, url, true); 59 | requestObj.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); 60 | requestObj.send(sendData); 61 | 62 | requestObj.onreadystatechange = () => { 63 | if (requestObj.readyState == 4) { 64 | if (requestObj.status == 200) { 65 | let obj = requestObj.response 66 | if (typeof obj !== 'object') { 67 | obj = JSON.parse(obj); 68 | } 69 | resolve(obj) 70 | } else { 71 | reject(requestObj) 72 | } 73 | } 74 | } 75 | }) 76 | } 77 | } -------------------------------------------------------------------------------- /exchange_vue/src/config/mUtils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 存储localStorage 3 | */ 4 | export const setStore = (name, content) => { 5 | if (!name) return; 6 | if (typeof content !== 'string') { 7 | content = JSON.stringify(content); 8 | } 9 | window.localStorage.setItem(name, content); 10 | } 11 | 12 | /** 13 | * 获取localStorage 14 | */ 15 | export const getStore = name => { 16 | if (!name) return; 17 | return window.localStorage.getItem(name); 18 | } 19 | 20 | /** 21 | * 删除localStorage 22 | */ 23 | export const removeStore = name => { 24 | if (!name) return; 25 | window.localStorage.removeItem(name); 26 | } 27 | 28 | /** 29 | * 获取style样式 30 | */ 31 | export const getStyle = (element, attr, NumberMode = 'int') => { 32 | let target; 33 | // scrollTop 获取方式不同,没有它不属于style,而且只有document.body才能用 34 | if (attr === 'scrollTop') { 35 | target = element.scrollTop; 36 | }else if(element.currentStyle){ 37 | target = element.currentStyle[attr]; 38 | }else{ 39 | target = document.defaultView.getComputedStyle(element,null)[attr]; 40 | } 41 | //在获取 opactiy 时需要获取小数 parseFloat 42 | return NumberMode == 'float'? parseFloat(target) : parseInt(target); 43 | } 44 | 45 | /** 46 | * 页面到达底部,加载更多 47 | */ 48 | export const loadMore = (element, callback) => { 49 | let windowHeight = window.screen.height; 50 | let height; 51 | let setTop; 52 | let paddingBottom; 53 | let marginBottom; 54 | let requestFram; 55 | let oldScrollTop; 56 | 57 | document.body.addEventListener('scroll',() => { 58 | loadMore(); 59 | }, false) 60 | //运动开始时获取元素 高度 和 offseTop, pading, margin 61 | element.addEventListener('touchstart',() => { 62 | height = element.offsetHeight; 63 | setTop = element.offsetTop; 64 | paddingBottom = getStyle(element,'paddingBottom'); 65 | marginBottom = getStyle(element,'marginBottom'); 66 | },{passive: true}) 67 | 68 | //运动过程中保持监听 scrollTop 的值判断是否到达底部 69 | element.addEventListener('touchmove',() => { 70 | loadMore(); 71 | },{passive: true}) 72 | 73 | //运动结束时判断是否有惯性运动,惯性运动结束判断是非到达底部 74 | element.addEventListener('touchend',() => { 75 | oldScrollTop = document.body.scrollTop; 76 | moveEnd(); 77 | },{passive: true}) 78 | 79 | const moveEnd = () => { 80 | requestFram = requestAnimationFrame(() => { 81 | if (document.body.scrollTop != oldScrollTop) { 82 | oldScrollTop = document.body.scrollTop; 83 | loadMore(); 84 | moveEnd(); 85 | }else{ 86 | cancelAnimationFrame(requestFram); 87 | //为了防止鼠标抬起时已经渲染好数据从而导致重获取数据,应该重新获取dom高度 88 | height = element.offsetHeight; 89 | loadMore(); 90 | } 91 | }) 92 | } 93 | 94 | const loadMore = () => { 95 | if (document.body.scrollTop + windowHeight >= height + setTop + paddingBottom + marginBottom) { 96 | callback(); 97 | } 98 | } 99 | } 100 | 101 | /** 102 | * 显示返回顶部按钮,开始、结束、运动 三个过程中调用函数判断是否达到目标点 103 | */ 104 | export const showBack = callback => { 105 | let requestFram; 106 | let oldScrollTop; 107 | 108 | document.addEventListener('scroll',() => { 109 | showBackFun(); 110 | }, false) 111 | document.addEventListener('touchstart',() => { 112 | showBackFun(); 113 | },{passive: true}) 114 | 115 | document.addEventListener('touchmove',() => { 116 | showBackFun(); 117 | },{passive: true}) 118 | 119 | document.addEventListener('touchend',() => { 120 | oldScrollTop = document.body.scrollTop; 121 | moveEnd(); 122 | },{passive: true}) 123 | 124 | const moveEnd = () => { 125 | requestFram = requestAnimationFrame(() => { 126 | if (document.body.scrollTop != oldScrollTop) { 127 | oldScrollTop = document.body.scrollTop; 128 | moveEnd(); 129 | }else{ 130 | cancelAnimationFrame(requestFram); 131 | } 132 | showBackFun(); 133 | }) 134 | } 135 | 136 | //判断是否达到目标点 137 | const showBackFun = () => { 138 | if (document.body.scrollTop > 500) { 139 | callback(true); 140 | }else{ 141 | callback(false); 142 | } 143 | } 144 | } 145 | 146 | 147 | /** 148 | * 运动效果 149 | * @param {HTMLElement} element 运动对象,必选 150 | * @param {JSON} target 属性:目标值,必选 151 | * @param {number} duration 运动时间,可选 152 | * @param {string} mode 运动模式,可选 153 | * @param {function} callback 可选,回调函数,链式动画 154 | */ 155 | export const animate = (element, target, duration = 400, mode = 'ease-out', callback) => { 156 | clearInterval(element.timer); 157 | 158 | //判断不同参数的情况 159 | if (duration instanceof Function) { 160 | callback = duration; 161 | duration = 400; 162 | }else if(duration instanceof String){ 163 | mode = duration; 164 | duration = 400; 165 | } 166 | 167 | //判断不同参数的情况 168 | if (mode instanceof Function) { 169 | callback = mode; 170 | mode = 'ease-out'; 171 | } 172 | 173 | //获取dom样式 174 | const attrStyle = attr => { 175 | if (attr === "opacity") { 176 | return Math.round(getStyle(element, attr, 'float') * 100); 177 | } else { 178 | return getStyle(element, attr); 179 | } 180 | } 181 | //根字体大小,需要从此将 rem 改成 px 进行运算 182 | const rootSize = parseFloat(document.documentElement.style.fontSize); 183 | 184 | const unit = {}; 185 | const initState = {}; 186 | 187 | //获取目标属性单位和初始样式值 188 | Object.keys(target).forEach(attr => { 189 | if (/[^\d^\.]+/gi.test(target[attr])) { 190 | unit[attr] = target[attr].match(/[^\d^\.]+/gi)[0] || 'px'; 191 | }else{ 192 | unit[attr] = 'px'; 193 | } 194 | initState[attr] = attrStyle(attr); 195 | }); 196 | 197 | //去掉传入的后缀单位 198 | Object.keys(target).forEach(attr => { 199 | if (unit[attr] == 'rem') { 200 | target[attr] = Math.ceil(parseInt(target[attr])*rootSize); 201 | }else{ 202 | target[attr] = parseInt(target[attr]); 203 | } 204 | }); 205 | 206 | 207 | let flag = true; //假设所有运动到达终点 208 | const remberSpeed = {};//记录上一个速度值,在ease-in模式下需要用到 209 | element.timer = setInterval(() => { 210 | Object.keys(target).forEach(attr => { 211 | let iSpeed = 0; //步长 212 | let status = false; //是否仍需运动 213 | let iCurrent = attrStyle(attr) || 0; //当前元素属性址 214 | let speedBase = 0; //目标点需要减去的基础值,三种运动状态的值都不同 215 | let intervalTime; //将目标值分为多少步执行,数值越大,步长越小,运动时间越长 216 | switch(mode){ 217 | case 'ease-out': 218 | speedBase = iCurrent; 219 | intervalTime = duration*5/400; 220 | break; 221 | case 'linear': 222 | speedBase = initState[attr]; 223 | intervalTime = duration*20/400; 224 | break; 225 | case 'ease-in': 226 | let oldspeed = remberSpeed[attr] || 0; 227 | iSpeed = oldspeed + (target[attr] - initState[attr])/duration; 228 | remberSpeed[attr] = iSpeed 229 | break; 230 | default: 231 | speedBase = iCurrent; 232 | intervalTime = duration*5/400; 233 | } 234 | if (mode !== 'ease-in') { 235 | iSpeed = (target[attr] - speedBase) / intervalTime; 236 | iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed); 237 | } 238 | //判断是否达步长之内的误差距离,如果到达说明到达目标点 239 | switch(mode){ 240 | case 'ease-out': 241 | status = iCurrent != target[attr]; 242 | break; 243 | case 'linear': 244 | status = Math.abs(Math.abs(iCurrent) - Math.abs(target[attr])) > Math.abs(iSpeed); 245 | break; 246 | case 'ease-in': 247 | status = Math.abs(Math.abs(iCurrent) - Math.abs(target[attr])) > Math.abs(iSpeed); 248 | break; 249 | default: 250 | status = iCurrent != target[attr]; 251 | } 252 | 253 | if (status) { 254 | flag = false; 255 | //opacity 和 scrollTop 需要特殊处理 256 | if (attr === "opacity") { 257 | element.style.filter = "alpha(opacity:" + (iCurrent + iSpeed) + ")"; 258 | element.style.opacity = (iCurrent + iSpeed) / 100; 259 | } else if (attr === 'scrollTop') { 260 | element.scrollTop = iCurrent + iSpeed; 261 | }else{ 262 | element.style[attr] = iCurrent + iSpeed + 'px'; 263 | } 264 | } else { 265 | flag = true; 266 | } 267 | 268 | if (flag) { 269 | clearInterval(element.timer); 270 | if (callback) { 271 | callback(); 272 | } 273 | } 274 | }) 275 | }, 20); 276 | } 277 | -------------------------------------------------------------------------------- /exchange_vue/src/http.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import router from './router'; 3 | 4 | // axios 配置 5 | axios.defaults.timeout = 8000; 6 | axios.defaults.baseURL = 'http://127.0.0.1:8080'; 7 | 8 | // http request 拦截器 9 | axios.interceptors.request.use( 10 | config => { 11 | if (localStorage.token) { //判断token是否存在 12 | config.headers.token = localStorage.token; //将token设置成请求头 13 | } 14 | return config; 15 | }, 16 | err => { 17 | return Promise.reject(err); 18 | } 19 | ); 20 | 21 | // http response 拦截器 22 | axios.interceptors.response.use( 23 | response => { 24 | if (response.data.status === 1545154) { 25 | router.replace('/'); 26 | console.log("token过期"); 27 | } 28 | return response; 29 | }, 30 | error => { 31 | return Promise.reject(error); 32 | } 33 | ); 34 | export default axios; 35 | -------------------------------------------------------------------------------- /exchange_vue/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App' 3 | import router from './router' 4 | import store from './store/' 5 | import ElementUI from 'element-ui' 6 | import 'element-ui/lib/theme-default/index.css' 7 | import http from './http'; //此处问http文件的路径 8 | 9 | Vue.prototype.$http = http; 10 | 11 | Vue.config.productionTip = false; 12 | 13 | Vue.use(ElementUI); 14 | 15 | new Vue({ 16 | el: '#app', 17 | router, 18 | store, 19 | template: '', 20 | components: { App } 21 | }); 22 | -------------------------------------------------------------------------------- /exchange_vue/src/page/depth.vue: -------------------------------------------------------------------------------- 1 | 97 | 98 | 220 | 221 | 227 | -------------------------------------------------------------------------------- /exchange_vue/src/page/exchange.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 72 | 73 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /exchange_vue/src/page/login.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 96 | 97 | 136 | -------------------------------------------------------------------------------- /exchange_vue/src/page/manage.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 29 | 30 | 31 | 37 | -------------------------------------------------------------------------------- /exchange_vue/src/page/system.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 67 | 68 | 74 | -------------------------------------------------------------------------------- /exchange_vue/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | Vue.use(Router) 5 | 6 | const login = r => require.ensure([], () => r(require('@/page/login')), 'login'); 7 | const manage = r => require.ensure([], () => r(require('@/page/manage')), 'manage'); 8 | const depth = r => require.ensure([], () => r(require('@/page/depth')), 'depth'); 9 | const system = r => require.ensure([], () => r(require('@/page/system')), 'system'); 10 | const exchange = r => require.ensure([], () => r(require('@/page/exchange')), 'exchange'); 11 | 12 | const routes = [ 13 | { 14 | path: '/', 15 | component: login 16 | }, 17 | { 18 | path: '/manage', 19 | component: manage, 20 | name: '', 21 | children: [{ 22 | path: '/depth', 23 | component: depth, 24 | meta: { 25 | requireAuth: '行情深度', // 该路由项需要权限校验 26 | } 27 | },{ 28 | path: '/system', 29 | component: system, 30 | meta: { 31 | requireAuth: '系统设置', // 该路由项需要权限校验 32 | } 33 | },{ 34 | path: '/exchange', 35 | component: exchange, 36 | meta: { 37 | requireAuth: '交易所状态', // 该路由项需要权限校验 38 | } 39 | }] 40 | } 41 | ] 42 | 43 | export default new Router({ 44 | routes, 45 | strict: process.env.NODE_ENV !== 'production', 46 | }) 47 | -------------------------------------------------------------------------------- /exchange_vue/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import {getAdminInfo} from '@/api/getData' 4 | 5 | Vue.use(Vuex) 6 | 7 | const state = { 8 | adminInfo: { 9 | avatar: 'default.jpg' 10 | }, 11 | } 12 | 13 | const mutations = { 14 | saveAdminInfo(state, adminInfo){ 15 | state.adminInfo = adminInfo; 16 | } 17 | } 18 | 19 | const actions = { 20 | async getAdminData({commit}){ 21 | try{ 22 | const res = await getAdminInfo() 23 | if (res.status == 1) { 24 | commit('saveAdminInfo', res.data); 25 | }else{ 26 | throw new Error(res.type) 27 | } 28 | }catch(err){ 29 | // console.log(err.message) 30 | } 31 | } 32 | } 33 | 34 | export default new Vuex.Store({ 35 | state, 36 | actions, 37 | mutations, 38 | }) 39 | -------------------------------------------------------------------------------- /exchange_vue/src/style/common.less: -------------------------------------------------------------------------------- 1 | body, div, span, header, footer, nav, section, aside, article, ul, dl, dt, dd, li, a, p, h1, h2, h3, h4,h5, h6, i, b, textarea, button, input, select, figure, figcaption { 2 | padding: 0; 3 | margin: 0; 4 | list-style: none; 5 | font-style: normal; 6 | text-decoration: none; 7 | border: none; 8 | font-family: "Microsoft Yahei",sans-serif; 9 | -webkit-tap-highlight-color:transparent; 10 | -webkit-font-smoothing: antialiased; 11 | &:focus { 12 | outline: none; 13 | } 14 | } 15 | 16 | /*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/ 17 | ::-webkit-scrollbar 18 | { 19 | width: 0px; 20 | height: 0px; 21 | background-color: #F5F5F5; 22 | } 23 | 24 | /*定义滚动条轨道 内阴影+圆角*/ 25 | ::-webkit-scrollbar-track 26 | { 27 | -webkit-box-shadow: inset 0 0 1px rgba(0,0,0,0); 28 | border-radius: 10px; 29 | background-color: #F5F5F5; 30 | } 31 | 32 | /*定义滑块 内阴影+圆角*/ 33 | ::-webkit-scrollbar-thumb 34 | { 35 | border-radius: 10px; 36 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); 37 | background-color: #555; 38 | } 39 | 40 | input[type="button"], input[type="submit"], input[type="search"], input[type="reset"] { 41 | -webkit-appearance: none; 42 | } 43 | 44 | textarea { -webkit-appearance: none;} 45 | 46 | html,body{ 47 | height: 100%; 48 | width: 100%; 49 | // background-color: #F5F5F5; 50 | } 51 | 52 | .fillcontain{ 53 | height: 100%; 54 | width: 100%; 55 | } 56 | .clear:after{ 57 | content: ''; 58 | display: block; 59 | clear: both; 60 | } 61 | 62 | .clear{ 63 | zoom:1; 64 | } 65 | 66 | .back_img{ 67 | background-repeat: no-repeat; 68 | background-size: 100% 100%; 69 | } 70 | 71 | .margin{ 72 | margin: 0 auto; 73 | } 74 | 75 | .left{ 76 | float: left; 77 | } 78 | 79 | .right{ 80 | float: right; 81 | } 82 | 83 | .hide{ 84 | display: none; 85 | } 86 | 87 | .show{ 88 | display: block; 89 | } 90 | 91 | .ellipsis{ 92 | overflow: hidden; 93 | text-overflow: ellipsis; 94 | white-space: nowrap; 95 | } 96 | -------------------------------------------------------------------------------- /exchange_vue/src/style/mixin.less: -------------------------------------------------------------------------------- 1 | @blue: #3190e8; 2 | @bc: #e4e4e4; 3 | @fc:#fff; 4 | 5 | // 背景图片地址和大小 6 | .bis(@url) { 7 | background-image: url(@url); 8 | background-repeat: no-repeat; 9 | background-size: 100% 100%; 10 | } 11 | 12 | //定位全屏 13 | .allcover{ 14 | position:absolute; 15 | top:0; 16 | right:0; 17 | } 18 | 19 | //transform上下左右居中 20 | .ctt { 21 | position: absolute; 22 | top: 50%; 23 | left: 50%; 24 | transform: translate(-50%, -50%); 25 | } 26 | //定位上下左右居中 27 | .ctp(@width, @height) { 28 | position: absolute; 29 | top: 50%; 30 | left: 50%; 31 | margin-top: -@height/2; 32 | margin-left: -@width/2; 33 | } 34 | 35 | //定位上下居中 36 | .tb { 37 | position: absolute; 38 | top: 50%; 39 | transform: translateY(-50%); 40 | } 41 | 42 | //定位左右居中 43 | .lr { 44 | position: absolute; 45 | left: 50%; 46 | transform: translateX(-50%); 47 | } 48 | 49 | //宽高 50 | .wh(@width, @height){ 51 | width: @width; 52 | height: @height; 53 | } 54 | 55 | //字体大小、行高、字体 56 | .ft(@size, @line-height) { 57 | font-size: @size; 58 | line-height:@line-height; 59 | } 60 | 61 | //字体大小,颜色 62 | .sc(@size, @color){ 63 | font-size: @size; 64 | color: @color; 65 | } 66 | 67 | //flex 布局和 子元素 对其方式 68 | .fj(@type: space-between){ 69 | display: flex; 70 | justify-content: @type; 71 | 72 | } -------------------------------------------------------------------------------- /exchange_vue/static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitTsewell/exchange_data/a2291a3082e6f67f4fd179ad1c6a11170df25ab3/exchange_vue/static/.gitkeep -------------------------------------------------------------------------------- /exchange_vue/test/e2e/custom-assertions/elementCount.js: -------------------------------------------------------------------------------- 1 | // A custom Nightwatch assertion. 2 | // the name of the method is the filename. 3 | // can be used in tests like this: 4 | // 5 | // browser.assert.elementCount(selector, count) 6 | // 7 | // for how to write custom assertions see 8 | // http://nightwatchjs.org/guide#writing-custom-assertions 9 | exports.assertion = function (selector, count) { 10 | this.message = 'Testing if element <' + selector + '> has count: ' + count 11 | this.expected = count 12 | this.pass = function (val) { 13 | return val === this.expected 14 | } 15 | this.value = function (res) { 16 | return res.value 17 | } 18 | this.command = function (cb) { 19 | var self = this 20 | return this.api.execute(function (selector) { 21 | return document.querySelectorAll(selector).length 22 | }, [selector], function (res) { 23 | cb.call(self, res) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /exchange_vue/test/e2e/nightwatch.conf.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | var config = require('../../config') 3 | 4 | // http://nightwatchjs.org/gettingstarted#settings-file 5 | module.exports = { 6 | src_folders: ['test/e2e/specs'], 7 | output_folder: 'test/e2e/reports', 8 | custom_assertions_path: ['test/e2e/custom-assertions'], 9 | 10 | selenium: { 11 | start_process: true, 12 | server_path: require('selenium-server').path, 13 | host: '127.0.0.1', 14 | port: 4444, 15 | cli_args: { 16 | 'webdriver.chrome.driver': require('chromedriver').path 17 | } 18 | }, 19 | 20 | test_settings: { 21 | default: { 22 | selenium_port: 4444, 23 | selenium_host: 'localhost', 24 | silent: true, 25 | globals: { 26 | devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port) 27 | } 28 | }, 29 | 30 | chrome: { 31 | desiredCapabilities: { 32 | browserName: 'chrome', 33 | javascriptEnabled: true, 34 | acceptSslCerts: true 35 | } 36 | }, 37 | 38 | firefox: { 39 | desiredCapabilities: { 40 | browserName: 'firefox', 41 | javascriptEnabled: true, 42 | acceptSslCerts: true 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /exchange_vue/test/e2e/runner.js: -------------------------------------------------------------------------------- 1 | // 1. start the dev server using production config 2 | process.env.NODE_ENV = 'testing' 3 | var server = require('../../build/dev-server.js') 4 | 5 | server.ready.then(() => { 6 | // 2. run the nightwatch test suite against it 7 | // to run in additional browsers: 8 | // 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings" 9 | // 2. add it to the --env flag below 10 | // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox` 11 | // For more information on Nightwatch's config file, see 12 | // http://nightwatchjs.org/guide#settings-file 13 | var opts = process.argv.slice(2) 14 | if (opts.indexOf('--config') === -1) { 15 | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js']) 16 | } 17 | if (opts.indexOf('--env') === -1) { 18 | opts = opts.concat(['--env', 'chrome']) 19 | } 20 | 21 | var spawn = require('cross-spawn') 22 | var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' }) 23 | 24 | runner.on('exit', function (code) { 25 | server.close() 26 | process.exit(code) 27 | }) 28 | 29 | runner.on('error', function (err) { 30 | server.close() 31 | throw err 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /exchange_vue/test/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // For authoring Nightwatch tests, see 2 | // http://nightwatchjs.org/guide#usage 3 | 4 | module.exports = { 5 | 'default e2e tests': function (browser) { 6 | // automatically uses dev Server port from /config.index.js 7 | // default: http://localhost:8080 8 | // see nightwatch.conf.js 9 | const devServer = browser.globals.devServerURL 10 | 11 | browser 12 | .url(devServer) 13 | .waitForElementVisible('#app', 5000) 14 | .assert.elementPresent('.hello') 15 | .assert.containsText('h1', 'Welcome to Your Vue.js App') 16 | .assert.elementCount('img', 1) 17 | .end() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /exchange_vue/test/unit/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "globals": { 6 | "expect": true, 7 | "sinon": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /exchange_vue/test/unit/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | Vue.config.productionTip = false 4 | 5 | // require all test files (files that ends with .spec.js) 6 | const testsContext = require.context('./specs', true, /\.spec$/) 7 | testsContext.keys().forEach(testsContext) 8 | 9 | // require all src files except main.js for coverage. 10 | // you can also change this to match only the subset of files that 11 | // you want coverage for. 12 | const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/) 13 | srcContext.keys().forEach(srcContext) 14 | -------------------------------------------------------------------------------- /exchange_vue/test/unit/karma.conf.js: -------------------------------------------------------------------------------- 1 | // This is a karma config file. For more details see 2 | // http://karma-runner.github.io/0.13/config/configuration-file.html 3 | // we are also using it with karma-webpack 4 | // https://github.com/webpack/karma-webpack 5 | 6 | var webpackConfig = require('../../build/webpack.test.conf') 7 | 8 | module.exports = function (config) { 9 | config.set({ 10 | // to run in additional browsers: 11 | // 1. install corresponding karma launcher 12 | // http://karma-runner.github.io/0.13/config/browsers.html 13 | // 2. add it to the `browsers` array below. 14 | browsers: ['PhantomJS'], 15 | frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'], 16 | reporters: ['spec', 'coverage'], 17 | files: ['./index.js'], 18 | preprocessors: { 19 | './index.js': ['webpack', 'sourcemap'] 20 | }, 21 | webpack: webpackConfig, 22 | webpackMiddleware: { 23 | noInfo: true 24 | }, 25 | coverageReporter: { 26 | dir: './coverage', 27 | reporters: [ 28 | { type: 'lcov', subdir: '.' }, 29 | { type: 'text-summary' } 30 | ] 31 | } 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /exchange_vue/test/unit/specs/Hello.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Hello from '@/components/Hello' 3 | 4 | describe('Hello.vue', () => { 5 | it('should render correct contents', () => { 6 | const Constructor = Vue.extend(Hello) 7 | const vm = new Constructor().$mount() 8 | expect(vm.$el.querySelector('.hello h1').textContent) 9 | .to.equal('Welcome to Your Vue.js App') 10 | }) 11 | }) 12 | --------------------------------------------------------------------------------